From 7b7e3bb62e7666c7b288827c07a2dd16b14fa742 Mon Sep 17 00:00:00 2001 From: Riza Marhaban Date: Fri, 7 Nov 2025 10:41:03 +0800 Subject: [PATCH] Initial Updatess for O2DES.NET Version 4.0 --- .editorconfig | 263 + .gitattributes | 62 +- .gitignore | 124 +- .vs/ProjectSettings.json | 3 - .vs/VSWorkspaceState.json | 6 - .vs/slnx.sqlite | Bin 630784 -> 0 bytes MathNet.Numerics/ArrayExtensions.cs | 80 + MathNet.Numerics/Combinatorics.cs | 440 + MathNet.Numerics/Compatibility.cs | 158 + MathNet.Numerics/Complex32.cs | 1526 +++ MathNet.Numerics/ComplexExtensions.cs | 765 ++ MathNet.Numerics/Constants.cs | 467 + MathNet.Numerics/Control.cs | 342 + MathNet.Numerics/Differentiate.cs | 207 + .../FiniteDifferenceCoefficients.cs | 144 + .../Differentiation/NumericalDerivative.cs | 467 + .../Differentiation/NumericalHessian.cs | 118 + .../Differentiation/NumericalJacobian.cs | 170 + MathNet.Numerics/Distance.cs | 580 ++ MathNet.Numerics/Distributions/Bernoulli.cs | 485 + MathNet.Numerics/Distributions/Beta.cs | 764 ++ MathNet.Numerics/Distributions/BetaScaled.cs | 624 ++ MathNet.Numerics/Distributions/Binomial.cs | 554 ++ MathNet.Numerics/Distributions/Burr.cs | 438 + MathNet.Numerics/Distributions/Categorical.cs | 839 ++ MathNet.Numerics/Distributions/Cauchy.cs | 480 + MathNet.Numerics/Distributions/Chi.cs | 476 + MathNet.Numerics/Distributions/ChiSquared.cs | 510 + .../Distributions/ContinuousUniform.cs | 485 + .../Distributions/ConwayMaxwellPoisson.cs | 679 ++ MathNet.Numerics/Distributions/Dirichlet.cs | 357 + .../Distributions/DiscreteUniform.cs | 444 + MathNet.Numerics/Distributions/Erlang.cs | 548 ++ MathNet.Numerics/Distributions/Exponential.cs | 458 + .../Distributions/FisherSnedecor.cs | 512 + MathNet.Numerics/Distributions/Gamma.cs | 680 ++ MathNet.Numerics/Distributions/Geometric.cs | 451 + .../Distributions/Hypergeometric.cs | 494 + .../Distributions/IContinuousDistribution.cs | 85 + .../Distributions/IDiscreteDistribution.cs | 85 + .../Distributions/IDistribution.cs | 43 + .../Distributions/IUnivariateDistribution.cs | 75 + .../Distributions/InverseGamma.cs | 459 + .../Distributions/InverseGaussian.cs | 458 + .../Distributions/InverseWishart.cs | 249 + MathNet.Numerics/Distributions/Laplace.cs | 454 + MathNet.Numerics/Distributions/LogNormal.cs | 555 ++ .../Distributions/MatrixNormal.cs | 278 + MathNet.Numerics/Distributions/Multinomial.cs | 391 + .../Distributions/NegativeBinomial.cs | 444 + MathNet.Numerics/Distributions/Normal.cs | 622 ++ MathNet.Numerics/Distributions/NormalGamma.cs | 414 + MathNet.Numerics/Distributions/Pareto.cs | 491 + MathNet.Numerics/Distributions/Poisson.cs | 529 ++ MathNet.Numerics/Distributions/Rayleigh.cs | 450 + MathNet.Numerics/Distributions/Stable.cs | 649 ++ MathNet.Numerics/Distributions/StudentT.cs | 622 ++ MathNet.Numerics/Distributions/Triangular.cs | 573 ++ .../Distributions/TruncatedPareto.cs | 475 + MathNet.Numerics/Distributions/Weibull.cs | 573 ++ MathNet.Numerics/Distributions/Wishart.cs | 291 + MathNet.Numerics/Distributions/Zipf.cs | 496 + MathNet.Numerics/Euclid.cs | 707 ++ MathNet.Numerics/ExcelFunctions.cs | 164 + MathNet.Numerics/Exceptions.cs | 164 + .../Financial/AbsoluteReturnMeasures.cs | 92 + .../Financial/AbsoluteRiskMeasures.cs | 124 + MathNet.Numerics/FindMinimum.cs | 179 + MathNet.Numerics/FindRoots.cs | 195 + MathNet.Numerics/Fit.cs | 395 + MathNet.Numerics/Generate.cs | 1132 +++ MathNet.Numerics/GlobalizationHelper.cs | 270 + MathNet.Numerics/GoodnessOfFit.cs | 177 + .../IntegralTransforms/Fourier.cs | 931 ++ .../IntegralTransforms/FourierOptions.cs | 73 + .../IntegralTransforms/Hartley.Naive.cs | 69 + .../IntegralTransforms/Hartley.cs | 108 + .../IntegralTransforms/HartleyOptions.cs | 58 + MathNet.Numerics/Integrate.cs | 451 + .../DoubleExponentialTransformation.cs | 591 ++ .../Integration/GaussKronrodRule.cs | 436 + .../Integration/GaussLegendreRule.cs | 283 + .../GaussRule/GaussKronrodPoint.cs | 618 ++ .../GaussRule/GaussKronrodPointFactory.cs | 33 + .../GaussRule/GaussLegendrePoint.cs | 186 + .../GaussRule/GaussLegendrePointFactory.cs | 107 + .../Integration/GaussRule/GaussPoint.cs | 59 + .../Integration/GaussRule/GaussPointPair.cs | 39 + .../Integration/NewtonCotesTrapeziumRule.cs | 426 + MathNet.Numerics/Integration/SimpsonRule.cs | 100 + MathNet.Numerics/Interpolate.cs | 266 + MathNet.Numerics/Interpolation/Barycentric.cs | 369 + .../BulirschStoerRationalInterpolation.cs | 217 + MathNet.Numerics/Interpolation/CubicSpline.cs | 534 ++ .../Interpolation/IInterpolation.cs | 80 + .../Interpolation/LinearSpline.cs | 213 + MathNet.Numerics/Interpolation/LogLinear.cs | 181 + .../NevillePolynomialInterpolation.cs | 226 + .../Interpolation/QuadraticSpline.cs | 168 + .../Interpolation/SplineBoundaryCondition.cs | 56 + .../Interpolation/StepInterpolation.cs | 198 + .../Interpolation/TransformedInterpolation.cs | 184 + MathNet.Numerics/LinearAlgebra/Builder.cs | 1656 ++++ .../LinearAlgebra/Complex/DenseMatrix.cs | 1343 +++ .../LinearAlgebra/Complex/DenseVector.cs | 825 ++ .../LinearAlgebra/Complex/DiagonalMatrix.cs | 1039 ++ .../Complex/Factorization/Cholesky.cs | 86 + .../Complex/Factorization/DenseCholesky.cs | 189 + .../Complex/Factorization/DenseEvd.cs | 945 ++ .../Complex/Factorization/DenseGramSchmidt.cs | 200 + .../Complex/Factorization/DenseLU.cs | 196 + .../Complex/Factorization/DenseQR.cs | 171 + .../Complex/Factorization/DenseSvd.cs | 163 + .../Complex/Factorization/Evd.cs | 122 + .../Complex/Factorization/GramSchmidt.cs | 98 + .../LinearAlgebra/Complex/Factorization/LU.cs | 76 + .../LinearAlgebra/Complex/Factorization/QR.cs | 103 + .../Complex/Factorization/Svd.cs | 124 + .../Complex/Factorization/UserCholesky.cs | 276 + .../Complex/Factorization/UserEvd.cs | 939 ++ .../Complex/Factorization/UserGramSchmidt.cs | 233 + .../Complex/Factorization/UserLU.cs | 308 + .../Complex/Factorization/UserQR.cs | 351 + .../Complex/Factorization/UserSvd.cs | 919 ++ .../LinearAlgebra/Complex/Matrix.cs | 871 ++ .../LinearAlgebra/Complex/Solvers/BiCgStab.cs | 275 + .../Complex/Solvers/CompositeSolver.cs | 154 + .../Complex/Solvers/DiagonalPreconditioner.cs | 108 + .../LinearAlgebra/Complex/Solvers/GpBiCg.cs | 374 + .../Complex/Solvers/ILU0Preconditioner.cs | 223 + .../Complex/Solvers/ILUTPPreconditioner.cs | 865 ++ .../Complex/Solvers/MILU0Preconditioner.cs | 263 + .../Complex/Solvers/MlkBiCgStab.cs | 539 ++ .../LinearAlgebra/Complex/Solvers/TFQMR.cs | 274 + .../LinearAlgebra/Complex/SparseMatrix.cs | 1562 ++++ .../LinearAlgebra/Complex/SparseVector.cs | 958 ++ .../LinearAlgebra/Complex/Vector.cs | 627 ++ .../LinearAlgebra/Complex32/DenseMatrix.cs | 1345 +++ .../LinearAlgebra/Complex32/DenseVector.cs | 825 ++ .../LinearAlgebra/Complex32/DiagonalMatrix.cs | 1038 ++ .../Complex32/Factorization/Cholesky.cs | 86 + .../Complex32/Factorization/DenseCholesky.cs | 189 + .../Complex32/Factorization/DenseEvd.cs | 947 ++ .../Factorization/DenseGramSchmidt.cs | 200 + .../Complex32/Factorization/DenseLU.cs | 196 + .../Complex32/Factorization/DenseQR.cs | 171 + .../Complex32/Factorization/DenseSvd.cs | 163 + .../Complex32/Factorization/Evd.cs | 126 + .../Complex32/Factorization/GramSchmidt.cs | 98 + .../Complex32/Factorization/LU.cs | 76 + .../Complex32/Factorization/QR.cs | 103 + .../Complex32/Factorization/Svd.cs | 124 + .../Complex32/Factorization/UserCholesky.cs | 276 + .../Complex32/Factorization/UserEvd.cs | 944 ++ .../Factorization/UserGramSchmidt.cs | 233 + .../Complex32/Factorization/UserLU.cs | 308 + .../Complex32/Factorization/UserQR.cs | 351 + .../Complex32/Factorization/UserSvd.cs | 919 ++ .../LinearAlgebra/Complex32/Matrix.cs | 871 ++ .../Complex32/Solvers/BiCgStab.cs | 273 + .../Complex32/Solvers/CompositeSolver.cs | 152 + .../Solvers/DiagonalPreconditioner.cs | 108 + .../LinearAlgebra/Complex32/Solvers/GpBiCg.cs | 377 + .../Complex32/Solvers/ILU0Preconditioner.cs | 223 + .../Complex32/Solvers/ILUTPPreconditioner.cs | 871 ++ .../Complex32/Solvers/MILU0Preconditioner.cs | 263 + .../Complex32/Solvers/MlkBiCgStab.cs | 542 ++ .../LinearAlgebra/Complex32/Solvers/TFQMR.cs | 277 + .../LinearAlgebra/Complex32/SparseMatrix.cs | 1560 ++++ .../LinearAlgebra/Complex32/SparseVector.cs | 958 ++ .../LinearAlgebra/Complex32/Vector.cs | 627 ++ .../LinearAlgebra/CreateMatrix.cs | 952 ++ .../LinearAlgebra/CreateVector.cs | 312 + .../LinearAlgebra/Double/DenseMatrix.cs | 1262 +++ .../LinearAlgebra/Double/DenseVector.cs | 874 ++ .../LinearAlgebra/Double/DiagonalMatrix.cs | 966 ++ .../Double/Factorization/Cholesky.cs | 86 + .../Double/Factorization/DenseCholesky.cs | 187 + .../Double/Factorization/DenseEvd.cs | 1238 +++ .../Double/Factorization/DenseGramSchmidt.cs | 198 + .../Double/Factorization/DenseLU.cs | 194 + .../Double/Factorization/DenseQR.cs | 169 + .../Double/Factorization/DenseSvd.cs | 161 + .../LinearAlgebra/Double/Factorization/Evd.cs | 126 + .../Double/Factorization/GramSchmidt.cs | 96 + .../LinearAlgebra/Double/Factorization/LU.cs | 74 + .../LinearAlgebra/Double/Factorization/QR.cs | 101 + .../LinearAlgebra/Double/Factorization/Svd.cs | 122 + .../Double/Factorization/UserCholesky.cs | 273 + .../Double/Factorization/UserEvd.cs | 1212 +++ .../Double/Factorization/UserGramSchmidt.cs | 226 + .../Double/Factorization/UserLU.cs | 306 + .../Double/Factorization/UserQR.cs | 350 + .../Double/Factorization/UserSvd.cs | 903 ++ .../LinearAlgebra/Double/Matrix.cs | 814 ++ .../LinearAlgebra/Double/Solvers/BiCgStab.cs | 273 + .../Double/Solvers/CompositeSolver.cs | 152 + .../Double/Solvers/DiagonalPreconditioner.cs | 106 + .../LinearAlgebra/Double/Solvers/GpBiCg.cs | 383 + .../Double/Solvers/ILU0Preconditioner.cs | 221 + .../Double/Solvers/ILUTPPreconditioner.cs | 869 ++ .../Double/Solvers/MILU0Preconditioner.cs | 261 + .../Double/Solvers/MlkBiCgStab.cs | 542 ++ .../LinearAlgebra/Double/Solvers/TFQMR.cs | 277 + .../LinearAlgebra/Double/SparseMatrix.cs | 1576 ++++ .../LinearAlgebra/Double/SparseVector.cs | 974 ++ .../LinearAlgebra/Double/Vector.cs | 648 ++ .../LinearAlgebra/Factorization/Cholesky.cs | 110 + .../LinearAlgebra/Factorization/Evd.cs | 139 + .../Factorization/GramSchmidt.cs | 49 + .../LinearAlgebra/Factorization/ISolver.cs | 67 + .../LinearAlgebra/Factorization/LU.cs | 150 + .../LinearAlgebra/Factorization/QR.cs | 141 + .../LinearAlgebra/Factorization/Svd.cs | 183 + .../LinearAlgebra/Matrix.Arithmetic.cs | 2423 +++++ MathNet.Numerics/LinearAlgebra/Matrix.BCL.cs | 418 + .../LinearAlgebra/Matrix.Operators.cs | 478 + .../LinearAlgebra/Matrix.Solve.cs | 353 + MathNet.Numerics/LinearAlgebra/Matrix.cs | 1888 ++++ .../LinearAlgebra/MatrixExtensions.cs | 115 + MathNet.Numerics/LinearAlgebra/Options.cs | 82 + .../LinearAlgebra/Single/DenseMatrix.cs | 1262 +++ .../LinearAlgebra/Single/DenseVector.cs | 865 ++ .../LinearAlgebra/Single/DiagonalMatrix.cs | 966 ++ .../Single/Factorization/Cholesky.cs | 86 + .../Single/Factorization/DenseCholesky.cs | 187 + .../Single/Factorization/DenseEvd.cs | 1238 +++ .../Single/Factorization/DenseGramSchmidt.cs | 198 + .../Single/Factorization/DenseLU.cs | 194 + .../Single/Factorization/DenseQR.cs | 169 + .../Single/Factorization/DenseSvd.cs | 161 + .../LinearAlgebra/Single/Factorization/Evd.cs | 124 + .../Single/Factorization/GramSchmidt.cs | 96 + .../LinearAlgebra/Single/Factorization/LU.cs | 74 + .../LinearAlgebra/Single/Factorization/QR.cs | 101 + .../LinearAlgebra/Single/Factorization/Svd.cs | 122 + .../Single/Factorization/UserCholesky.cs | 273 + .../Single/Factorization/UserEvd.cs | 1214 +++ .../Single/Factorization/UserGramSchmidt.cs | 226 + .../Single/Factorization/UserLU.cs | 306 + .../Single/Factorization/UserQR.cs | 350 + .../Single/Factorization/UserSvd.cs | 903 ++ .../LinearAlgebra/Single/Matrix.cs | 814 ++ .../LinearAlgebra/Single/Solvers/BiCgStab.cs | 273 + .../Single/Solvers/CompositeSolver.cs | 152 + .../Single/Solvers/DiagonalPreconditioner.cs | 106 + .../LinearAlgebra/Single/Solvers/GpBiCg.cs | 377 + .../Single/Solvers/ILU0Preconditioner.cs | 221 + .../Single/Solvers/ILUTPPreconditioner.cs | 869 ++ .../Single/Solvers/MILU0Preconditioner.cs | 261 + .../Single/Solvers/MlkBiCgStab.cs | 545 ++ .../LinearAlgebra/Single/Solvers/TFQMR.cs | 277 + .../LinearAlgebra/Single/SparseMatrix.cs | 1583 ++++ .../LinearAlgebra/Single/SparseVector.cs | 975 ++ .../LinearAlgebra/Single/Vector.cs | 648 ++ .../Solvers/CancellationStopCriterion.cs | 110 + .../Solvers/DelegateStopCriterion.cs | 93 + .../Solvers/DivergenceStopCriterion.cs | 253 + .../Solvers/FailureStopCriterion.cs | 120 + .../Solvers/IIterationStopCriterion.cs | 69 + .../LinearAlgebra/Solvers/IIterativeSolver.cs | 50 + .../Solvers/IIterativeSolverSetup.cs | 72 + .../LinearAlgebra/Solvers/IPreconditioner.cs | 66 + .../Solvers/IterationCountStopCriterion.cs | 159 + .../LinearAlgebra/Solvers/IterationStatus.cs | 43 + .../LinearAlgebra/Solvers/Iterator.cs | 176 + .../Solvers/ResidualStopCriterion.cs | 242 + .../LinearAlgebra/Solvers/SolverSetup.cs | 135 + .../Solvers/UnitPreconditioner.cs | 91 + .../Storage/DenseColumnMajorMatrixStorage.cs | 1075 +++ .../Storage/DenseVectorStorage.cs | 615 ++ .../Storage/DiagonalMatrixStorage.cs | 1226 +++ .../Storage/MatrixStorage.Validation.cs | 226 + .../LinearAlgebra/Storage/MatrixStorage.cs | 1008 ++ .../SparseCompressedRowMatrixStorage.cs | 2378 +++++ .../Storage/SparseVectorStorage.cs | 1244 +++ .../Storage/VectorStorage.Validation.cs | 186 + .../LinearAlgebra/Storage/VectorStorage.cs | 618 ++ .../LinearAlgebra/Vector.Arithmetic.cs | 1769 ++++ MathNet.Numerics/LinearAlgebra/Vector.BCL.cs | 442 + .../LinearAlgebra/Vector.Operators.cs | 443 + MathNet.Numerics/LinearAlgebra/Vector.cs | 545 ++ .../LinearAlgebra/VectorExtensions.cs | 115 + .../LinearRegression/MultipleRegression.cs | 388 + MathNet.Numerics/LinearRegression/Options.cs | 37 + .../LinearRegression/SimpleRegression.cs | 145 + MathNet.Numerics/LinearRegression/Util.cs | 50 + .../LinearRegression/WeightedRegression.cs | 130 + MathNet.Numerics/MathNet.Numerics.csproj | 44 + MathNet.Numerics/OdeSolvers/AdamsBashforth.cs | 171 + MathNet.Numerics/OdeSolvers/RungeKutta.cs | 160 + .../Optimization/BfgsBMinimizer.cs | 312 + .../Optimization/BfgsMinimizer.cs | 141 + .../Optimization/BfgsMinimizerBase.cs | 86 + MathNet.Numerics/Optimization/BfgsSolver.cs | 109 + .../ConjugateGradientMinimizer.cs | 152 + MathNet.Numerics/Optimization/Exceptions.cs | 79 + .../Optimization/ExitCondition.cs | 46 + .../Optimization/GoldenSectionMinimizer.cs | 148 + .../Optimization/IObjectiveFunction.cs | 76 + .../Optimization/IObjectiveModel.cs | 74 + .../Optimization/IUnconstrainedMinimizer.cs | 37 + .../LevenbergMarquardtMinimizer.cs | 234 + .../LimitedMemoryBfgsMinimizer.cs | 182 + .../LineSearch/LineSearchResult.cs | 41 + .../LineSearch/StrongWolfeLineSearch.cs | 51 + .../LineSearch/WeakWolfeLineSearch.cs | 96 + .../LineSearch/WolfeLineSearch.cs | 156 + .../Optimization/MinimizationResult.cs | 47 + .../MinimizationWithLineSearchResult.cs | 43 + .../Optimization/MinimizerBase.cs | 119 + .../Optimization/NelderMeadSimplex.cs | 423 + .../Optimization/NewtonMinimizer.cs | 153 + .../NonlinearMinimizationResult.cs | 77 + .../Optimization/NonlinearMinimizerBase.cs | 312 + .../Optimization/ObjectiveFunction.cs | 224 + ...wardDifferenceGradientObjectiveFunction.cs | 172 + .../GradientHessianObjectiveFunction.cs | 86 + .../GradientObjectiveFunction.cs | 88 + .../HessianObjectiveFunction.cs | 88 + .../LazyObjectiveFunction.cs | 144 + .../LazyObjectiveFunctionBase.cs | 150 + .../NonlinearObjectiveFunction.cs | 450 + .../ObjectiveFunctionBase.cs | 70 + .../ScalarObjectiveFunction.cs | 113 + .../ScalarValueObjectiveFunction.cs | 79 + .../ValueObjectiveFunction.cs | 88 + .../QuadraticGradientProjectionSearch.cs | 127 + .../Optimization/ScalarMinimizationResult.cs | 45 + .../TrustRegion/ITrustRegionSubProblem.cs | 11 + .../Subproblems/DogLegSubproblem.cs | 44 + .../Subproblems/NewtonCGSubproblem.cs | 65 + .../TrustRegion/Subproblems/Util.cs | 35 + .../TrustRegion/TrustRegionDogLegMinimizer.cs | 11 + .../TrustRegion/TrustRegionMinimizerBase.cs | 245 + .../TrustRegionNewtonCGMinimizer.cs | 11 + .../TrustRegion/TrustRegionSubProblem.cs | 16 + MathNet.Numerics/Permutation.cs | 192 + MathNet.Numerics/Polynomial.cs | 1072 +++ MathNet.Numerics/Precision.Comparison.cs | 686 ++ MathNet.Numerics/Precision.Equality.cs | 1046 +++ MathNet.Numerics/Precision.cs | 792 ++ MathNet.Numerics/Properties/AssemblyInfo.cs | 51 + .../Properties/Resources.Designer.cs | 1073 +++ MathNet.Numerics/Properties/Resources.resx | 454 + .../Providers/Common/Cuda/CudaProvider.cs | 143 + .../Common/Cuda/CudaProviderCapabilities.cs | 53 + .../Common/Cuda/SafeNativeMethods.cs | 379 + .../Providers/Common/Mkl/MklProvider.cs | 340 + .../Common/Mkl/MklProviderCapabilities.cs | 63 + .../Common/Mkl/MklProviderPrecision.cs | 65 + .../Providers/Common/Mkl/SafeNativeMethods.cs | 437 + .../Providers/Common/NativeProviderLoader.cs | 256 + .../Common/OpenBlas/OpenBlasProvider.cs | 166 + .../OpenBlas/OpenBlasProviderCapabilities.cs | 55 + .../Common/OpenBlas/SafeNativeMethods.cs | 305 + .../FourierTransformControl.cs | 193 + .../IFourierTransformProvider.cs | 75 + ...nagedFourierTransformProvider.Bluestein.cs | 278 + .../ManagedFourierTransformProvider.Radix2.cs | 259 + .../ManagedFourierTransformProvider.cs | 397 + .../Mkl/MklFourierTransformProvider.cs | 371 + .../Acml/AcmlLinearAlgebraProvider.Complex.cs | 1093 +++ .../AcmlLinearAlgebraProvider.Complex32.cs | 1096 +++ .../Acml/AcmlLinearAlgebraProvider.Double.cs | 1097 +++ .../Acml/AcmlLinearAlgebraProvider.Single.cs | 1096 +++ .../LinearAlgebra/Acml/SafeNativeMethods.cs | 263 + .../Cuda/CudaLinearAlgebraProvider.Complex.cs | 577 ++ .../CudaLinearAlgebraProvider.Complex32.cs | 576 ++ .../Cuda/CudaLinearAlgebraProvider.Double.cs | 576 ++ .../Cuda/CudaLinearAlgebraProvider.Single.cs | 576 ++ .../Cuda/CudaLinearAlgebraProvider.cs | 194 + .../LinearAlgebra/ILinearAlgebraProvider.cs | 449 + .../LinearAlgebra/LinearAlgebraControl.cs | 253 + .../ManagedLinearAlgebraProvider.Complex.cs | 2567 +++++ .../ManagedLinearAlgebraProvider.Complex32.cs | 2568 +++++ .../ManagedLinearAlgebraProvider.Double.cs | 2588 +++++ .../ManagedLinearAlgebraProvider.Single.cs | 2590 +++++ .../Managed/ManagedLinearAlgebraProvider.cs | 103 + ...dReferenceLinearAlgebraProvider.Complex.cs | 2822 ++++++ ...eferenceLinearAlgebraProvider.Complex32.cs | 2824 ++++++ ...edReferenceLinearAlgebraProvider.Double.cs | 2732 ++++++ ...edReferenceLinearAlgebraProvider.Single.cs | 2734 ++++++ .../ManagedReferenceLinearAlgebraProvider.cs | 103 + .../Mkl/MklLinearAlgebraProvider.Complex.cs | 1205 +++ .../Mkl/MklLinearAlgebraProvider.Complex32.cs | 1200 +++ .../Mkl/MklLinearAlgebraProvider.Double.cs | 1205 +++ .../Mkl/MklLinearAlgebraProvider.Single.cs | 1200 +++ .../Mkl/MklLinearAlgebraProvider.cs | 134 + .../OpenBlasLinearAlgebraProvider.Complex.cs | 1025 ++ ...OpenBlasLinearAlgebraProvider.Complex32.cs | 1020 ++ .../OpenBlasLinearAlgebraProvider.Double.cs | 1025 ++ .../OpenBlasLinearAlgebraProvider.Single.cs | 1020 ++ .../OpenBlas/OpenBlasLinearAlgebraProvider.cs | 121 + MathNet.Numerics/Random/CryptoRandomSource.cs | 177 + MathNet.Numerics/Random/Mcg31m1.cs | 164 + MathNet.Numerics/Random/Mcg59.cs | 165 + MathNet.Numerics/Random/MersenneTwister.cs | 468 + MathNet.Numerics/Random/Mrg32k3a.cs | 254 + MathNet.Numerics/Random/Palf.cs | 363 + MathNet.Numerics/Random/RandomExtensions.cs | 312 + MathNet.Numerics/Random/RandomSeed.cs | 44 + MathNet.Numerics/Random/RandomSource.cs | 604 ++ MathNet.Numerics/Random/SystemRandomSource.cs | 233 + MathNet.Numerics/Random/WH1982.cs | 192 + MathNet.Numerics/Random/WH2006.cs | 200 + MathNet.Numerics/Random/Xorshift.cs | 383 + MathNet.Numerics/Random/Xoshiro256StarStar.cs | 375 + MathNet.Numerics/RootFinding/Bisection.cs | 150 + MathNet.Numerics/RootFinding/Brent.cs | 206 + MathNet.Numerics/RootFinding/Broyden.cs | 166 + MathNet.Numerics/RootFinding/Cubic.cs | 138 + MathNet.Numerics/RootFinding/NewtonRaphson.cs | 115 + .../RootFinding/RobustNewtonRaphson.cs | 183 + MathNet.Numerics/RootFinding/Secant.cs | 107 + .../RootFinding/ZeroCrossingBracketing.cs | 174 + MathNet.Numerics/Settings.StyleCop | 405 + MathNet.Numerics/Sorting.cs | 890 ++ MathNet.Numerics/SpecialFunctions/Airy.cs | 185 + .../SpecialFunctions/Amos/Amos.cs | 1026 ++ .../SpecialFunctions/Amos/AmosHelper.cs | 8320 +++++++++++++++++ MathNet.Numerics/SpecialFunctions/Bessel.cs | 201 + MathNet.Numerics/SpecialFunctions/Beta.cs | 194 + MathNet.Numerics/SpecialFunctions/Erf.cs | 677 ++ MathNet.Numerics/SpecialFunctions/Evaluate.cs | 262 + .../SpecialFunctions/ExponentialIntegral.cs | 145 + .../SpecialFunctions/Factorial.cs | 197 + MathNet.Numerics/SpecialFunctions/Gamma.cs | 656 ++ MathNet.Numerics/SpecialFunctions/Hankel.cs | 57 + MathNet.Numerics/SpecialFunctions/Harmonic.cs | 72 + MathNet.Numerics/SpecialFunctions/Kelvin.cs | 263 + MathNet.Numerics/SpecialFunctions/Logistic.cs | 73 + .../SpecialFunctions/ModifiedBessel.cs | 292 + .../SpecialFunctions/ModifiedStruve.cs | 552 ++ .../SpecialFunctions/SphericalBessel.cs | 129 + .../SpecialFunctions/Stability.cs | 166 + .../SpecialFunctions/TestFunctions.cs | 173 + .../Statistics/ArrayStatistics.Complex.cs | 163 + .../Statistics/ArrayStatistics.Int32.cs | 277 + .../Statistics/ArrayStatistics.Single.cs | 841 ++ .../Statistics/ArrayStatistics.cs | 851 ++ MathNet.Numerics/Statistics/Correlation.cs | 365 + .../Statistics/DescriptiveStatistics.cs | 396 + MathNet.Numerics/Statistics/Histogram.cs | 519 + MathNet.Numerics/Statistics/KernelDensity.cs | 133 + MathNet.Numerics/Statistics/MCMC/HybridMC.cs | 292 + .../Statistics/MCMC/HybridMCGeneric.cs | 329 + .../Statistics/MCMC/MCMCDiagnostics.cs | 87 + .../Statistics/MCMC/MCMCSampler.cs | 159 + .../MCMC/MetropolisHastingsSampler.cs | 163 + .../Statistics/MCMC/MetropolisSampler.cs | 150 + .../Statistics/MCMC/RejectionSampler.cs | 104 + .../Statistics/MCMC/UnivariateHybridMC.cs | 195 + .../Statistics/MCMC/UnivariateSliceSampler.cs | 199 + .../Statistics/MovingStatistics.cs | 360 + .../Statistics/QuantileDefinition.cs | 43 + MathNet.Numerics/Statistics/RankDefinition.cs | 49 + .../Statistics/RunningStatistics.cs | 272 + .../SortedArrayStatistics.Single.cs | 478 + .../Statistics/SortedArrayStatistics.cs | 517 + MathNet.Numerics/Statistics/Statistics.cs | 1541 +++ .../Statistics/StreamingStatistics.cs | 828 ++ MathNet.Numerics/Threading/CommonParallel.cs | 362 + MathNet.Numerics/Trigonometry.cs | 836 ++ MathNet.Numerics/Window.cs | 444 + MathNet.Numerics/paket.references | 6 + O2DESNet.UnitTests/HourCounter_Tests.cs | 208 +- O2DESNet.UnitTests/O2DESNet.UnitTests.csproj | 36 +- O2DESNet.UnitTests/PatternGenerator_Tests.cs | 422 +- O2DESNet.UnitTests/PhaseTracker_Tests.cs | 99 +- .../PmPathTests/ControlPoint.cs | 16 + .../PmPathTests/ControlPointId.cs | 125 + O2DESNet.UnitTests/PmPathTests/PmPath.cs | 38 + .../PmPathTests/PmPathTraffic_Tests.cs | 437 + O2DESNet.UnitTests/PmPathTests/Vehicle.cs | 60 + .../PmPathTests/VehicleEventArgs.cs | 21 + .../Categorical/UniformTests.cs | 45 +- .../Continuous/BetaTests.cs | 96 +- .../Continuous/ExponentialTests.cs | 46 +- .../Continuous/GammaTests.cs | 88 +- .../Continuous/LogNormalTests.cs | 87 +- .../Continuous/NormalTests.cs | 83 +- .../Continuous/TriangularTests.cs | 43 +- .../Continuous/UniformTests.cs | 81 +- .../Discrete/PoissonTests.cs | 40 +- .../Discrete/UniformTests.cs | 78 +- .../RandomVariableTests/PrintResult.cs | 33 +- .../RandomVariableTests/RunningStat.cs | 91 +- O2DESNet.UnitTests/Simulator_Tests.cs | 99 +- O2DESNet.UnitTests/TandemQueue.cs | 27 - O2DESNet.UnitTests/TandemQueue_Tests.cs | 128 + O2DESNet.UnitTests/Version_3.cs | 108 - O2DESNet.UnitTests/Version_3_Tests.cs | 225 + O2DESNet/O2DESNet.nuspec => O2DESNet.nuspec | 34 +- O2DESNet.sln | 26 +- O2DESNet/Assets.cs | 9 +- O2DESNet/Demos/IMMnQueue.cs | 55 +- O2DESNet/Demos/LoadEntity.cs | 8 + O2DESNet/Demos/MMnQueue_Atomic.cs | 141 +- O2DESNet/Demos/MMnQueue_Modular.cs | 125 +- O2DESNet/Demos/TandemQueue.cs | 172 +- O2DESNet/Distributions/Beta.cs | 111 +- O2DESNet/Distributions/Empirical.cs | 31 +- O2DESNet/Distributions/Exponential.cs | 35 +- O2DESNet/Distributions/Gamma.cs | 59 +- O2DESNet/Distributions/LogNormal.cs | 99 +- O2DESNet/Distributions/Normal.cs | 99 +- O2DESNet/Distributions/Poisson.cs | 19 +- O2DESNet/Distributions/Triangular.cs | 43 +- O2DESNet/Distributions/Uniform.cs | 30 +- O2DESNet/Event.cs | 44 - O2DESNet/HourCounter.cs | 401 - O2DESNet/HourCounters/HourCounter.cs | 450 + .../HourCounters/HourCounterHistoryPoint.cs | 19 + O2DESNet/HourCounters/IHourCounter.cs | 12 + O2DESNet/HourCounters/IReadOnlyHourCounter.cs | 41 + O2DESNet/HourCounters/ReadOnlyHourCounter.cs | 41 + O2DESNet/ISandbox.cs | 24 + O2DESNet/Internals/Event.cs | 44 + O2DESNet/Internals/EventComparer.cs | 22 + O2DESNet/Internals/EventIndexer.cs | 17 + O2DESNet/Internals/FutureEventList.cs | 29 + O2DESNet/O2DESNet.csproj | 57 +- O2DESNet/PhaseTracker.cs | 124 +- O2DESNet/Pointer.cs | 67 +- .../RandomVariables/Categorical/Uniform.cs | 86 +- O2DESNet/RandomVariables/Continuous/Beta.cs | 304 +- .../RandomVariables/Continuous/Exponential.cs | 131 +- O2DESNet/RandomVariables/Continuous/Gamma.cs | 239 +- .../RandomVariables/Continuous/LogNormal.cs | 230 +- O2DESNet/RandomVariables/Continuous/Normal.cs | 139 +- .../RandomVariables/Continuous/Triangular.cs | 204 +- .../RandomVariables/Continuous/Uniform.cs | 133 +- O2DESNet/RandomVariables/Discrete/Poisson.cs | 125 +- O2DESNet/RandomVariables/Discrete/Uniform.cs | 246 +- O2DESNet/RandomVariables/IRandomVariable.cs | 61 +- O2DESNet/ReadOnlyExtensions.cs | 45 - O2DESNet/Sandbox.cs | 451 +- O2DESNet/SimulationBuilder.cs | 119 + O2DESNet/Standard/Entity.Generic.cs | 39 + O2DESNet/Standard/Entity.cs | 29 + O2DESNet/Standard/EntityId.cs | 154 + O2DESNet/Standard/Generator.cs | 188 +- O2DESNet/Standard/IEntity.cs | 15 + O2DESNet/Standard/IGenerator.cs | 43 +- O2DESNet/Standard/ILoad.cs | 8 - O2DESNet/Standard/IQueue.cs | 33 +- O2DESNet/Standard/IServer.cs | 47 +- O2DESNet/Standard/Load.cs | 10 - O2DESNet/Standard/PatternGenerator.cs | 488 +- O2DESNet/Standard/Queue.cs | 179 +- O2DESNet/Standard/Server.cs | 256 +- README.md | 29 +- 553 files changed, 215280 insertions(+), 3640 deletions(-) create mode 100644 .editorconfig delete mode 100644 .vs/ProjectSettings.json delete mode 100644 .vs/VSWorkspaceState.json delete mode 100644 .vs/slnx.sqlite create mode 100644 MathNet.Numerics/ArrayExtensions.cs create mode 100644 MathNet.Numerics/Combinatorics.cs create mode 100644 MathNet.Numerics/Compatibility.cs create mode 100644 MathNet.Numerics/Complex32.cs create mode 100644 MathNet.Numerics/ComplexExtensions.cs create mode 100644 MathNet.Numerics/Constants.cs create mode 100644 MathNet.Numerics/Control.cs create mode 100644 MathNet.Numerics/Differentiate.cs create mode 100644 MathNet.Numerics/Differentiation/FiniteDifferenceCoefficients.cs create mode 100644 MathNet.Numerics/Differentiation/NumericalDerivative.cs create mode 100644 MathNet.Numerics/Differentiation/NumericalHessian.cs create mode 100644 MathNet.Numerics/Differentiation/NumericalJacobian.cs create mode 100644 MathNet.Numerics/Distance.cs create mode 100644 MathNet.Numerics/Distributions/Bernoulli.cs create mode 100644 MathNet.Numerics/Distributions/Beta.cs create mode 100644 MathNet.Numerics/Distributions/BetaScaled.cs create mode 100644 MathNet.Numerics/Distributions/Binomial.cs create mode 100644 MathNet.Numerics/Distributions/Burr.cs create mode 100644 MathNet.Numerics/Distributions/Categorical.cs create mode 100644 MathNet.Numerics/Distributions/Cauchy.cs create mode 100644 MathNet.Numerics/Distributions/Chi.cs create mode 100644 MathNet.Numerics/Distributions/ChiSquared.cs create mode 100644 MathNet.Numerics/Distributions/ContinuousUniform.cs create mode 100644 MathNet.Numerics/Distributions/ConwayMaxwellPoisson.cs create mode 100644 MathNet.Numerics/Distributions/Dirichlet.cs create mode 100644 MathNet.Numerics/Distributions/DiscreteUniform.cs create mode 100644 MathNet.Numerics/Distributions/Erlang.cs create mode 100644 MathNet.Numerics/Distributions/Exponential.cs create mode 100644 MathNet.Numerics/Distributions/FisherSnedecor.cs create mode 100644 MathNet.Numerics/Distributions/Gamma.cs create mode 100644 MathNet.Numerics/Distributions/Geometric.cs create mode 100644 MathNet.Numerics/Distributions/Hypergeometric.cs create mode 100644 MathNet.Numerics/Distributions/IContinuousDistribution.cs create mode 100644 MathNet.Numerics/Distributions/IDiscreteDistribution.cs create mode 100644 MathNet.Numerics/Distributions/IDistribution.cs create mode 100644 MathNet.Numerics/Distributions/IUnivariateDistribution.cs create mode 100644 MathNet.Numerics/Distributions/InverseGamma.cs create mode 100644 MathNet.Numerics/Distributions/InverseGaussian.cs create mode 100644 MathNet.Numerics/Distributions/InverseWishart.cs create mode 100644 MathNet.Numerics/Distributions/Laplace.cs create mode 100644 MathNet.Numerics/Distributions/LogNormal.cs create mode 100644 MathNet.Numerics/Distributions/MatrixNormal.cs create mode 100644 MathNet.Numerics/Distributions/Multinomial.cs create mode 100644 MathNet.Numerics/Distributions/NegativeBinomial.cs create mode 100644 MathNet.Numerics/Distributions/Normal.cs create mode 100644 MathNet.Numerics/Distributions/NormalGamma.cs create mode 100644 MathNet.Numerics/Distributions/Pareto.cs create mode 100644 MathNet.Numerics/Distributions/Poisson.cs create mode 100644 MathNet.Numerics/Distributions/Rayleigh.cs create mode 100644 MathNet.Numerics/Distributions/Stable.cs create mode 100644 MathNet.Numerics/Distributions/StudentT.cs create mode 100644 MathNet.Numerics/Distributions/Triangular.cs create mode 100644 MathNet.Numerics/Distributions/TruncatedPareto.cs create mode 100644 MathNet.Numerics/Distributions/Weibull.cs create mode 100644 MathNet.Numerics/Distributions/Wishart.cs create mode 100644 MathNet.Numerics/Distributions/Zipf.cs create mode 100644 MathNet.Numerics/Euclid.cs create mode 100644 MathNet.Numerics/ExcelFunctions.cs create mode 100644 MathNet.Numerics/Exceptions.cs create mode 100644 MathNet.Numerics/Financial/AbsoluteReturnMeasures.cs create mode 100644 MathNet.Numerics/Financial/AbsoluteRiskMeasures.cs create mode 100644 MathNet.Numerics/FindMinimum.cs create mode 100644 MathNet.Numerics/FindRoots.cs create mode 100644 MathNet.Numerics/Fit.cs create mode 100644 MathNet.Numerics/Generate.cs create mode 100644 MathNet.Numerics/GlobalizationHelper.cs create mode 100644 MathNet.Numerics/GoodnessOfFit.cs create mode 100644 MathNet.Numerics/IntegralTransforms/Fourier.cs create mode 100644 MathNet.Numerics/IntegralTransforms/FourierOptions.cs create mode 100644 MathNet.Numerics/IntegralTransforms/Hartley.Naive.cs create mode 100644 MathNet.Numerics/IntegralTransforms/Hartley.cs create mode 100644 MathNet.Numerics/IntegralTransforms/HartleyOptions.cs create mode 100644 MathNet.Numerics/Integrate.cs create mode 100644 MathNet.Numerics/Integration/DoubleExponentialTransformation.cs create mode 100644 MathNet.Numerics/Integration/GaussKronrodRule.cs create mode 100644 MathNet.Numerics/Integration/GaussLegendreRule.cs create mode 100644 MathNet.Numerics/Integration/GaussRule/GaussKronrodPoint.cs create mode 100644 MathNet.Numerics/Integration/GaussRule/GaussKronrodPointFactory.cs create mode 100644 MathNet.Numerics/Integration/GaussRule/GaussLegendrePoint.cs create mode 100644 MathNet.Numerics/Integration/GaussRule/GaussLegendrePointFactory.cs create mode 100644 MathNet.Numerics/Integration/GaussRule/GaussPoint.cs create mode 100644 MathNet.Numerics/Integration/GaussRule/GaussPointPair.cs create mode 100644 MathNet.Numerics/Integration/NewtonCotesTrapeziumRule.cs create mode 100644 MathNet.Numerics/Integration/SimpsonRule.cs create mode 100644 MathNet.Numerics/Interpolate.cs create mode 100644 MathNet.Numerics/Interpolation/Barycentric.cs create mode 100644 MathNet.Numerics/Interpolation/BulirschStoerRationalInterpolation.cs create mode 100644 MathNet.Numerics/Interpolation/CubicSpline.cs create mode 100644 MathNet.Numerics/Interpolation/IInterpolation.cs create mode 100644 MathNet.Numerics/Interpolation/LinearSpline.cs create mode 100644 MathNet.Numerics/Interpolation/LogLinear.cs create mode 100644 MathNet.Numerics/Interpolation/NevillePolynomialInterpolation.cs create mode 100644 MathNet.Numerics/Interpolation/QuadraticSpline.cs create mode 100644 MathNet.Numerics/Interpolation/SplineBoundaryCondition.cs create mode 100644 MathNet.Numerics/Interpolation/StepInterpolation.cs create mode 100644 MathNet.Numerics/Interpolation/TransformedInterpolation.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Builder.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/DenseMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/DenseVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/DiagonalMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/Cholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseCholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseEvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseGramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseLU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseQR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseSvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/Evd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/LU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/QR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/Svd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserCholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserGramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserLU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserQR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserSvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Matrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/BiCgStab.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/CompositeSolver.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/DiagonalPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/GpBiCg.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/ILU0Preconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/ILUTPPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/MILU0Preconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/MlkBiCgStab.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Solvers/TFQMR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/SparseMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/SparseVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex/Vector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/DenseMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/DenseVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/DiagonalMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Cholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseCholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseEvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseGramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseLU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseQR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseSvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Evd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/GramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/LU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/QR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Svd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserCholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserGramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserLU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserQR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserSvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Matrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/BiCgStab.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/CompositeSolver.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/DiagonalPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/GpBiCg.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/ILU0Preconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/ILUTPPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/MILU0Preconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/MlkBiCgStab.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Solvers/TFQMR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/SparseMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/SparseVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Complex32/Vector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/CreateMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/CreateVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/DenseMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/DenseVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/DiagonalMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/Cholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseCholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseEvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseGramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseLU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseQR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseSvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/Evd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/LU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/QR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/Svd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/UserCholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/UserGramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/UserLU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/UserQR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Factorization/UserSvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Matrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/BiCgStab.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/CompositeSolver.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/DiagonalPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/GpBiCg.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/ILU0Preconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/ILUTPPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/MILU0Preconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/MlkBiCgStab.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Solvers/TFQMR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/SparseMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/SparseVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Double/Vector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Factorization/Cholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Factorization/Evd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Factorization/GramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Factorization/ISolver.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Factorization/LU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Factorization/QR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Factorization/Svd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Matrix.Arithmetic.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Matrix.BCL.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Matrix.Operators.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Matrix.Solve.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Matrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/MatrixExtensions.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Options.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/DenseMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/DenseVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/DiagonalMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/Cholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseCholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseEvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseGramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseLU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseQR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseSvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/Evd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/GramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/LU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/QR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/Svd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/UserCholesky.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/UserGramSchmidt.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/UserLU.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/UserQR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Factorization/UserSvd.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Matrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/BiCgStab.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/CompositeSolver.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/DiagonalPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/GpBiCg.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/ILU0Preconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/ILUTPPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/MILU0Preconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/MlkBiCgStab.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Solvers/TFQMR.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/SparseMatrix.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/SparseVector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Single/Vector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/CancellationStopCriterion.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/DelegateStopCriterion.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/DivergenceStopCriterion.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/FailureStopCriterion.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/IIterationStopCriterion.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/IIterativeSolver.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/IIterativeSolverSetup.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/IPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/IterationCountStopCriterion.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/IterationStatus.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/Iterator.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/ResidualStopCriterion.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/SolverSetup.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Solvers/UnitPreconditioner.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/DenseColumnMajorMatrixStorage.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/DenseVectorStorage.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/DiagonalMatrixStorage.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/MatrixStorage.Validation.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/MatrixStorage.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/SparseCompressedRowMatrixStorage.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/SparseVectorStorage.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/VectorStorage.Validation.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Storage/VectorStorage.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Vector.Arithmetic.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Vector.BCL.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Vector.Operators.cs create mode 100644 MathNet.Numerics/LinearAlgebra/Vector.cs create mode 100644 MathNet.Numerics/LinearAlgebra/VectorExtensions.cs create mode 100644 MathNet.Numerics/LinearRegression/MultipleRegression.cs create mode 100644 MathNet.Numerics/LinearRegression/Options.cs create mode 100644 MathNet.Numerics/LinearRegression/SimpleRegression.cs create mode 100644 MathNet.Numerics/LinearRegression/Util.cs create mode 100644 MathNet.Numerics/LinearRegression/WeightedRegression.cs create mode 100644 MathNet.Numerics/MathNet.Numerics.csproj create mode 100644 MathNet.Numerics/OdeSolvers/AdamsBashforth.cs create mode 100644 MathNet.Numerics/OdeSolvers/RungeKutta.cs create mode 100644 MathNet.Numerics/Optimization/BfgsBMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/BfgsMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/BfgsMinimizerBase.cs create mode 100644 MathNet.Numerics/Optimization/BfgsSolver.cs create mode 100644 MathNet.Numerics/Optimization/ConjugateGradientMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/Exceptions.cs create mode 100644 MathNet.Numerics/Optimization/ExitCondition.cs create mode 100644 MathNet.Numerics/Optimization/GoldenSectionMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/IObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/IObjectiveModel.cs create mode 100644 MathNet.Numerics/Optimization/IUnconstrainedMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/LevenbergMarquardtMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/LimitedMemoryBfgsMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/LineSearch/LineSearchResult.cs create mode 100644 MathNet.Numerics/Optimization/LineSearch/StrongWolfeLineSearch.cs create mode 100644 MathNet.Numerics/Optimization/LineSearch/WeakWolfeLineSearch.cs create mode 100644 MathNet.Numerics/Optimization/LineSearch/WolfeLineSearch.cs create mode 100644 MathNet.Numerics/Optimization/MinimizationResult.cs create mode 100644 MathNet.Numerics/Optimization/MinimizationWithLineSearchResult.cs create mode 100644 MathNet.Numerics/Optimization/MinimizerBase.cs create mode 100644 MathNet.Numerics/Optimization/NelderMeadSimplex.cs create mode 100644 MathNet.Numerics/Optimization/NewtonMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/NonlinearMinimizationResult.cs create mode 100644 MathNet.Numerics/Optimization/NonlinearMinimizerBase.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/ForwardDifferenceGradientObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/GradientHessianObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/GradientObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/HessianObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunctionBase.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/NonlinearObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/ObjectiveFunctionBase.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/ScalarObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/ScalarValueObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/ObjectiveFunctions/ValueObjectiveFunction.cs create mode 100644 MathNet.Numerics/Optimization/QuadraticGradientProjectionSearch.cs create mode 100644 MathNet.Numerics/Optimization/ScalarMinimizationResult.cs create mode 100644 MathNet.Numerics/Optimization/TrustRegion/ITrustRegionSubProblem.cs create mode 100644 MathNet.Numerics/Optimization/TrustRegion/Subproblems/DogLegSubproblem.cs create mode 100644 MathNet.Numerics/Optimization/TrustRegion/Subproblems/NewtonCGSubproblem.cs create mode 100644 MathNet.Numerics/Optimization/TrustRegion/Subproblems/Util.cs create mode 100644 MathNet.Numerics/Optimization/TrustRegion/TrustRegionDogLegMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/TrustRegion/TrustRegionMinimizerBase.cs create mode 100644 MathNet.Numerics/Optimization/TrustRegion/TrustRegionNewtonCGMinimizer.cs create mode 100644 MathNet.Numerics/Optimization/TrustRegion/TrustRegionSubProblem.cs create mode 100644 MathNet.Numerics/Permutation.cs create mode 100644 MathNet.Numerics/Polynomial.cs create mode 100644 MathNet.Numerics/Precision.Comparison.cs create mode 100644 MathNet.Numerics/Precision.Equality.cs create mode 100644 MathNet.Numerics/Precision.cs create mode 100644 MathNet.Numerics/Properties/AssemblyInfo.cs create mode 100644 MathNet.Numerics/Properties/Resources.Designer.cs create mode 100644 MathNet.Numerics/Properties/Resources.resx create mode 100644 MathNet.Numerics/Providers/Common/Cuda/CudaProvider.cs create mode 100644 MathNet.Numerics/Providers/Common/Cuda/CudaProviderCapabilities.cs create mode 100644 MathNet.Numerics/Providers/Common/Cuda/SafeNativeMethods.cs create mode 100644 MathNet.Numerics/Providers/Common/Mkl/MklProvider.cs create mode 100644 MathNet.Numerics/Providers/Common/Mkl/MklProviderCapabilities.cs create mode 100644 MathNet.Numerics/Providers/Common/Mkl/MklProviderPrecision.cs create mode 100644 MathNet.Numerics/Providers/Common/Mkl/SafeNativeMethods.cs create mode 100644 MathNet.Numerics/Providers/Common/NativeProviderLoader.cs create mode 100644 MathNet.Numerics/Providers/Common/OpenBlas/OpenBlasProvider.cs create mode 100644 MathNet.Numerics/Providers/Common/OpenBlas/OpenBlasProviderCapabilities.cs create mode 100644 MathNet.Numerics/Providers/Common/OpenBlas/SafeNativeMethods.cs create mode 100644 MathNet.Numerics/Providers/FourierTransform/FourierTransformControl.cs create mode 100644 MathNet.Numerics/Providers/FourierTransform/IFourierTransformProvider.cs create mode 100644 MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.Bluestein.cs create mode 100644 MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.Radix2.cs create mode 100644 MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.cs create mode 100644 MathNet.Numerics/Providers/FourierTransform/Mkl/MklFourierTransformProvider.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Complex.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Complex32.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Double.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Single.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Acml/SafeNativeMethods.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Complex.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Complex32.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Double.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Single.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/ILinearAlgebraProvider.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/LinearAlgebraControl.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Complex.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Complex32.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Double.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Single.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Complex.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Complex32.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Double.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Single.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Complex.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Complex32.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Double.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Single.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Complex.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Complex32.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Double.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Single.cs create mode 100644 MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.cs create mode 100644 MathNet.Numerics/Random/CryptoRandomSource.cs create mode 100644 MathNet.Numerics/Random/Mcg31m1.cs create mode 100644 MathNet.Numerics/Random/Mcg59.cs create mode 100644 MathNet.Numerics/Random/MersenneTwister.cs create mode 100644 MathNet.Numerics/Random/Mrg32k3a.cs create mode 100644 MathNet.Numerics/Random/Palf.cs create mode 100644 MathNet.Numerics/Random/RandomExtensions.cs create mode 100644 MathNet.Numerics/Random/RandomSeed.cs create mode 100644 MathNet.Numerics/Random/RandomSource.cs create mode 100644 MathNet.Numerics/Random/SystemRandomSource.cs create mode 100644 MathNet.Numerics/Random/WH1982.cs create mode 100644 MathNet.Numerics/Random/WH2006.cs create mode 100644 MathNet.Numerics/Random/Xorshift.cs create mode 100644 MathNet.Numerics/Random/Xoshiro256StarStar.cs create mode 100644 MathNet.Numerics/RootFinding/Bisection.cs create mode 100644 MathNet.Numerics/RootFinding/Brent.cs create mode 100644 MathNet.Numerics/RootFinding/Broyden.cs create mode 100644 MathNet.Numerics/RootFinding/Cubic.cs create mode 100644 MathNet.Numerics/RootFinding/NewtonRaphson.cs create mode 100644 MathNet.Numerics/RootFinding/RobustNewtonRaphson.cs create mode 100644 MathNet.Numerics/RootFinding/Secant.cs create mode 100644 MathNet.Numerics/RootFinding/ZeroCrossingBracketing.cs create mode 100644 MathNet.Numerics/Settings.StyleCop create mode 100644 MathNet.Numerics/Sorting.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Airy.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Amos/Amos.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Amos/AmosHelper.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Bessel.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Beta.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Erf.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Evaluate.cs create mode 100644 MathNet.Numerics/SpecialFunctions/ExponentialIntegral.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Factorial.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Gamma.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Hankel.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Harmonic.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Kelvin.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Logistic.cs create mode 100644 MathNet.Numerics/SpecialFunctions/ModifiedBessel.cs create mode 100644 MathNet.Numerics/SpecialFunctions/ModifiedStruve.cs create mode 100644 MathNet.Numerics/SpecialFunctions/SphericalBessel.cs create mode 100644 MathNet.Numerics/SpecialFunctions/Stability.cs create mode 100644 MathNet.Numerics/SpecialFunctions/TestFunctions.cs create mode 100644 MathNet.Numerics/Statistics/ArrayStatistics.Complex.cs create mode 100644 MathNet.Numerics/Statistics/ArrayStatistics.Int32.cs create mode 100644 MathNet.Numerics/Statistics/ArrayStatistics.Single.cs create mode 100644 MathNet.Numerics/Statistics/ArrayStatistics.cs create mode 100644 MathNet.Numerics/Statistics/Correlation.cs create mode 100644 MathNet.Numerics/Statistics/DescriptiveStatistics.cs create mode 100644 MathNet.Numerics/Statistics/Histogram.cs create mode 100644 MathNet.Numerics/Statistics/KernelDensity.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/HybridMC.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/HybridMCGeneric.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/MCMCDiagnostics.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/MCMCSampler.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/MetropolisHastingsSampler.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/MetropolisSampler.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/RejectionSampler.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/UnivariateHybridMC.cs create mode 100644 MathNet.Numerics/Statistics/MCMC/UnivariateSliceSampler.cs create mode 100644 MathNet.Numerics/Statistics/MovingStatistics.cs create mode 100644 MathNet.Numerics/Statistics/QuantileDefinition.cs create mode 100644 MathNet.Numerics/Statistics/RankDefinition.cs create mode 100644 MathNet.Numerics/Statistics/RunningStatistics.cs create mode 100644 MathNet.Numerics/Statistics/SortedArrayStatistics.Single.cs create mode 100644 MathNet.Numerics/Statistics/SortedArrayStatistics.cs create mode 100644 MathNet.Numerics/Statistics/Statistics.cs create mode 100644 MathNet.Numerics/Statistics/StreamingStatistics.cs create mode 100644 MathNet.Numerics/Threading/CommonParallel.cs create mode 100644 MathNet.Numerics/Trigonometry.cs create mode 100644 MathNet.Numerics/Window.cs create mode 100644 MathNet.Numerics/paket.references create mode 100644 O2DESNet.UnitTests/PmPathTests/ControlPoint.cs create mode 100644 O2DESNet.UnitTests/PmPathTests/ControlPointId.cs create mode 100644 O2DESNet.UnitTests/PmPathTests/PmPath.cs create mode 100644 O2DESNet.UnitTests/PmPathTests/PmPathTraffic_Tests.cs create mode 100644 O2DESNet.UnitTests/PmPathTests/Vehicle.cs create mode 100644 O2DESNet.UnitTests/PmPathTests/VehicleEventArgs.cs delete mode 100644 O2DESNet.UnitTests/TandemQueue.cs create mode 100644 O2DESNet.UnitTests/TandemQueue_Tests.cs delete mode 100644 O2DESNet.UnitTests/Version_3.cs create mode 100644 O2DESNet.UnitTests/Version_3_Tests.cs rename O2DESNet/O2DESNet.nuspec => O2DESNet.nuspec (97%) create mode 100644 O2DESNet/Demos/LoadEntity.cs delete mode 100644 O2DESNet/Event.cs delete mode 100644 O2DESNet/HourCounter.cs create mode 100644 O2DESNet/HourCounters/HourCounter.cs create mode 100644 O2DESNet/HourCounters/HourCounterHistoryPoint.cs create mode 100644 O2DESNet/HourCounters/IHourCounter.cs create mode 100644 O2DESNet/HourCounters/IReadOnlyHourCounter.cs create mode 100644 O2DESNet/HourCounters/ReadOnlyHourCounter.cs create mode 100644 O2DESNet/ISandbox.cs create mode 100644 O2DESNet/Internals/Event.cs create mode 100644 O2DESNet/Internals/EventComparer.cs create mode 100644 O2DESNet/Internals/EventIndexer.cs create mode 100644 O2DESNet/Internals/FutureEventList.cs delete mode 100644 O2DESNet/ReadOnlyExtensions.cs create mode 100644 O2DESNet/SimulationBuilder.cs create mode 100644 O2DESNet/Standard/Entity.Generic.cs create mode 100644 O2DESNet/Standard/Entity.cs create mode 100644 O2DESNet/Standard/EntityId.cs create mode 100644 O2DESNet/Standard/IEntity.cs delete mode 100644 O2DESNet/Standard/ILoad.cs delete mode 100644 O2DESNet/Standard/Load.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0bc6c81 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,263 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = true +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = false +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = false:error +dotnet_style_allow_statement_immediately_after_block_experimental = false:error + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = when_on_single_line:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = when_on_single_line:silent +csharp_style_expression_bodied_operators = when_on_single_line:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = when_multiline:silent +csharp_prefer_simple_using_statement = true:silent +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_prefer_method_group_conversion = false:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:error +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:error +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:error +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:error +csharp_style_allow_embedded_statements_on_same_line_experimental = false:error + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_or_internal_field_should_be__fieldname.severity = suggestion +dotnet_naming_rule.private_or_internal_field_should_be__fieldname.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be__fieldname.style = _fieldname + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private +dotnet_naming_symbols.private_or_internal_field.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style._fieldname.required_prefix = _ +dotnet_naming_style._fieldname.required_suffix = +dotnet_naming_style._fieldname.word_separator = +dotnet_naming_style._fieldname.capitalization = camel_case +csharp_prefer_system_threading_lock = true:suggestion + +[*.{cs,vb}] +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = false:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index a078412..1ff0c42 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,15 @@ ############################################################################### * text=auto +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + ############################################################################### # Set the merge driver for project and solution files # @@ -11,29 +20,8 @@ # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just comment the entries below and -# uncomment the group further below +# intervention with every merge. To do so, just uncomment the entries below ############################################################################### - -*.sln text eol=crlf -*.csproj text eol=crlf -*.vbproj text eol=crlf -*.vcxproj text eol=crlf -*.vcproj text eol=crlf -*.dbproj text eol=crlf -*.fsproj text eol=crlf -*.lsproj text eol=crlf -*.wixproj text eol=crlf -*.modelproj text eol=crlf -*.sqlproj text eol=crlf -*.wmaproj text eol=crlf - -*.xproj text eol=crlf -*.props text eol=crlf -*.filters text eol=crlf -*.vcxitems text eol=crlf - - #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary @@ -47,7 +35,29 @@ #*.sqlproj merge=binary #*.wwaproj merge=binary -#*.xproj merge=binary -#*.props merge=binary -#*.filters merge=binary -#*.vcxitems merge=binary +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index c3f28f0..de3f6b2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files +*.rsuser *.suo *.user *.userosscache @@ -11,8 +12,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs -#Exclude bak -**/*.bak + +# Mono auto generated files +mono_crash.* # Build results [Dd]ebug/ @@ -21,43 +23,63 @@ [Rr]eleases/ x64/ x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ +[Oo]ut/ [Ll]og/ +[Ll]ogs/ -# Visual Studio 2015 cache/options directory +# Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ +# Visual Studio 2017 auto generated files +Generated\ Files/ + # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUNIT +# NUnit *.VisualState.xml TestResult.xml +nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c +# Benchmark Results +BenchmarkDotNet.Artifacts/ + # .NET Core project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio *_i.c *_p.c -*_i.h +*_h.h *.ilk *.meta *.obj +*.iobj *.pch *.pdb +*.ipdb *.pgc *.pgd *.rsp @@ -67,6 +89,7 @@ artifacts/ *.tlh *.tmp *.tmp_proj +*_wpftmp.csproj *.log *.vspscc *.vssscc @@ -95,6 +118,9 @@ ipch/ *.vspx *.sap +# Visual Studio Trace Files +*.e2e + # TFS 2012 Local Workspace $tf/ @@ -106,15 +132,21 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JustCode is a .NET coding add-in -.JustCode - # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + # Visual Studio code coverage results *.coverage *.coveragexml @@ -150,7 +182,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj @@ -162,12 +194,14 @@ PublishScripts/ # NuGet Packages *.nupkg +# NuGet Symbol Packages +*.snupkg # The packages folder can be ignored because of Package Restore -**/packages/* +**/[Pp]ackages/* # except build/, which is used as an MSBuild target. -!**/packages/build/ +!**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config +#!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets @@ -185,12 +219,15 @@ AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt +*.appx +*.appxbundle +*.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache -!*.[Cc]ache/ +!?*.[Cc]ache/ # Others ClientBin/ @@ -199,10 +236,14 @@ ClientBin/ *.dbmdl *.dbproj.schemaview *.jfm -# *.pfx +*.pfx *.publishsettings orleans.codegen.cs +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ @@ -217,6 +258,8 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak # SQL Server files *.mdf @@ -227,6 +270,10 @@ UpgradeLog*.htm *.rdl.data *.bim.layout *.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ @@ -238,9 +285,6 @@ FakesAssemblies/ .ntvs_analysis.dat node_modules/ -# Typescript v1 declaration files -typings/ - # Visual Studio 6 build log *.plg @@ -265,12 +309,8 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ +# CodeRush personal settings +.cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ @@ -280,6 +320,9 @@ __pycache__/ # tools/** # !tools/packages.config +# Tabs Studio +*.tss + # Telerik's JustMock configuration file *.jmconfig @@ -289,4 +332,35 @@ __pycache__/ *.odx.cs *.xsd.cs -Src/SgkMobileApp/\.mfractor/ +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# Msi Files +*.msi diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json deleted file mode 100644 index f8b4888..0000000 --- a/.vs/ProjectSettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "CurrentProjectSetting": null -} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json deleted file mode 100644 index 6b61141..0000000 --- a/.vs/VSWorkspaceState.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "ExpandedNodes": [ - "" - ], - "PreviewInSolutionExplorer": false -} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index aeaa3e843294df72521cbf51a43547530da7b80c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 630784 zcmeFa31Az=)i^F`cXh3#mF?Jy<2Y8F7{@rWt;1JtN0wtbzTzVxB#x1#wPRw-#I|^S*f8YO; z1A04e-h1=r?ObnWc4ly2_vAe99-o<;j?BCLI;T#r*X?q3LVH33?*4&rPusvj_ukM!ciZq#U$_^<_Jn$eKr8rvxVw9^D5ZaH z=J;fcpHp&6H12^=M`$3_8w?G)4@rcf`26JjiIGh*0yt}kFjNa(R(q&B1o?;D2iw|1 zQbNQyz5Ck+f}L#xn|zIpHL@}qn@KDk3sX}k_ANxFCdViFST+G_TT6U<;r*v=A>~Gj zZHq?vL}GGmatiYyn@mflDP0+KD&t_eA3Twm=ci@!Xi;b;x5`e#{q0yzL_4XXvYW7b zCK|!&HZXJk@_I`|!I`Or>3Alc-CA>Uz18Fzgo@H9R1`teXe1uz=Yo;wQGPU2bu9_2 zt}xo@EE433qoswCl$qX0&E>-uq1s$EB2*hKmo{uwR~v0Kq%Kt(ZIrU$=TL3LSC~!b ze@hWj6@$|*AyOq(W=bm9ssbw!%EeVegc#nw;hn^O5P<5jPg` zp>Pj0htTL?*{e01T=g~jMOG+#QnDy-6n`WHQOqK~*5TgpzG1oRkZ5Ekxo}A_Vz!`@ zkT67HO?x57?&s$cP_Z%^??t`ICL;oElu^`N9IR-g6`ZH7an0o?XrXCKR;8OZ zg+aAxG3tV)jaC#bZPcqQV%a)csfI7pQ!o1p(oKY7%n?qYrX%j%;mq+!cO)_2H6~6=KMs{?hBCRlW%`p9WQfYo&rilD6s2j9U}$ym6R`?9xzveK%TeuL3fTdO}P z6t%>8Fy`S$6a0A#d>lqW>L8^mXqw1Paj4&$fwSghr{3f$E7LDd2r@+lL|{^HT)^X2p_j9+|kFd@5t^Yk|E1z=S0(lGM zEs(cB-U4|GB3{6J)i_m4M6bW`O zOismAREf$h?nA_@5h!sowsMw;gQ&_rUwdenoHYF*ZmXY&DTsKH5EaZKAc_=8YENAi3ah{gHZD$$N9zrf!5e~ zlc#CS8}oR*u~tuOfR7@1tkvJx(i(v{KhH-SntV}@H_+tw1e#kKJQ061=HZ(fy?jG! zV{;P^=rO-9+SJnQ@wIrPo}eR|n?R=+2nQ`a zQLisH?r)6od~2&G*6Q>6qKz?6Yja}+BmrflxzX?OjW@P5jg60uH#9eUV&l=~##V2u zrzI8v#m53s&sZ$b=y@0ho_6^zQ4P_(X|6N(TNR$RRa&Md^|ebok9=kW*U_{cmT z>zf;%jp0K>Q+-3A-UrIMy{%jPzAb@3iJsO~y9QxxV>%*Ee|qcub&Yl|r899IdhRdW zQ`~#FFL85R2Y2ZIQm>QGYu*BR3*;@3w?N(kc?;w%khehI0(lGMEs(cB-U7K=pvXwo z7}7f@+9UH3<2A)5%45)OZe`xFilIDufmX5Mkmv5uts+=q9}G&kj4Pz58a?>?!uC|M z>=fT0x$XJF4S;$Q|Xr z%zcncaevG;*Lk|U1@aciTOev;_qpkud zv3=&}K*nB45!WH3t{h?{737G@vyD1`7U4?fl7mKF8PIgkL}E&O=~+hIdccP>(_4Lj zXcEpy(6ygv64pvvJ8aZdqck~#%CyRch?%oSt{o(-Lvxdn_{73g1oLi_^1gb2=+cYF z$xJC@%JvbV-7^!JktO{k*Sq=oh?1$iFWWoqlv1ngB?j&3i3@oj?U{**8p$;4dyG0S zWL2hVo1dAUj6y0hNm(~hEiBO}BVBvZhGrvd0*%Kab1_CrytIqFkwL(Z&0GLHa@58! zDQg3KB-R(7IuW{Ho{!_l)d?lls!pTM1(MtO>6tX|>OI7QZHWXwucF`BL8R^4o*&kDY6z_^Le`ZFqmYJj{qkeEir#$RDRcrI`O#m z@HciX-nuDB>V&*8Si31#u7VqO8+DB!xN~M(r-`}9v@)t@hf&uCQG+Mq(W7%S@yQGMn7DsZ zl;TySw!1YuDM(b3X%ds8Hf$x?$5v&GkHI(H^GADTNb69qNTx-ttTL(`o43#faBRR7 z-`@l2%8{ilq`0Ls=~0O6#HMY}9#Rd6CB^(HsVeI;s2~ZM!rf@pAqBjdj&MgGc`aB3 zH_ItKq+3)guZ)^NX5hVeGm{Bu{ghFzGSXe{ ziA+zcyP-9t8wy=8I}?Y49ws$iTPf*3LesO8bCXeZq+?8%#9W-!(+ywZCxoE_BUv6I z&n<(JB^Fp2?ayVEE2W*Po zLYb_~PHYf54i;BNthM0?1p3WMs0fo|3wRt5NELGfwOBAhTYM4@w3{J06|J|yN*ZWr zL-7kE=}QfD2kNpw2dsm#9BOOt3Dr%tfHvz|y zO~j#Fm15TF!Omg~3~L3G@iL#4Ez|ie8+F_txSw#3Tc5OUw?4vcaZGR?$B<)>vW;^VY-Gb=-Z9mmI(1Zs0CwcUazK8FM^h>2!R_(ZU_J zyn$P=-fjJm<#jA=eYZ7iX|&oLHI|LmXRXgVF8I&<`~RD-lh04y0(lGMEs(cB-U4|G z{C6$jq^k7FImdGfVOyqpo^iC4YSt@9(DFRWk#cIMe%blN5j#}@dojg1#KShKTrW-I z9dc3r3`Q6TpUuMdO>su=po3bkPtOUSh0^4Czyl~vn)};dKvnBi^L@jZEv0icge0nY zy+M>GPuC3~gL3|EA8I1b+4X1Iqz^M7&8aEo=6XvgZyGPp$Mv9iMzZ^?Zup? z=i0i^-dX3@!WPN}D{bK5ccS+4JlY=Q$U1k{f%?cEmHgsD?1FBC!t>2CN7jz-Rvzuk z{s6oroLLC2rW*AM??#rq+i`*6&SmGn+HCjeYPGTAWXdjxpdN~sUZOZwb8bCCNb2^*gd81+;ry9bmG;=p+6j8Oxe4ra! z1$39nO~pnC%_>T#vLbJQxFPLaOeG+9D&}9-L%3dSM%?hcK`EgE5QVC_m35fi^n6OW zU{HAuWi3`!Y2Ks^n*#M*$r`NN%K4E}&Y|0@oD*3Mh%Mqgh)b;aFt;5|lt9FKxpo4g z7!Xdy9LFkA6>(mp2u-JKiGigMaq=4`^A`mw9I3HRu3Kjl=P6+GeT}mAf*1$tCCx|J zv9(mqLD-@c?5d$ye}(@TE!-h)nhy$S9EBn@a4+wH4?|Se4 zBDxyrm1F3=!$Njds3Cz&JapkNBHSi9kRX*+r1P%P8JW`+%A*{X$+h`L=Il*EDXKP!8LTFUOA3W%irzTXLuRC zPnD~b-iOj=8-@K4+IZ0u_w9n%0)^}A+v%p;pyXw4UJyCFJAk7u8v*F*-L4Vm-6klW z%_pzl3bY#)N!M=yL=m`LihU!k!mCR=(ViBd3FswfR^^*}njvnrCJvPHG`Z+{sGS-6 zK^kp0>&{jvusHyTu);YOv%J|4cyERYjXt5UW-*n~t5Mt7z?snZ)H=#ZGhM#FvX?VB85UK z0lkN`XW~AJ^?+%V8ZOQap=#B%9#;V<__kc$(t~2w38qN<^R{wgc-f=3treOA$)~rK z35|~0tGBHYS`ejQZ!2~DT{o;(diKm}K<$Q!MPXA8ad(1~+o^Ntr9BCpkUPa(9qQo_G?n~) z#xAtoDvzJBL5VBWD|~*26{>AEub;9y=n9=a?f1KAuTJ*f(=C^|fUZ*DMbCd#no=n9 zMmt@erebV#nNBb65}?g=kxuabIeI-j$U6cIg*0pgQ2YOQ{y)vZ`v2Ft>$yYlkpIhD zAa8-Z1@aciTOe zU%-THx!k9qW`k=bhO2YAt3l1iAX_dsC8*e}T$PS?{6Po4KgH|Awe}0`6ZRo{o4wAy)^4@E zX#1t@5%%tt{{62nPWcSvEs(cB-U4|G6a zOUhv*FMR5tH`a-$nU`k@;M7P&IYrdin+kBduO5zF3(^AL*`B}Ybksi)<0P2Q`l|u+ zTg21}m~Xt`Jfk2$1cfL{IJ4~2U-JRcR|O3WO4O$i)h*S3;8yi>NgU6dcEks9psM&fY~-mxOx%hYR4~T}1UMP;lZdqDm#y@Vy0aye^{p zC6wvWf_8%H5mDaH{;B|u%tfkh33dH*1#mVlqQVmD*eeBX1XZL!?II|LgbJApb`n&f zgj!cr0H@lbEV}}=ouKxJsD{6-F4#s;PO->szVk={oKTC5CPt_l`qf|lJl_9T>-d+B zdx4)oD8A`8rw!m{Z*bKixXA}HxuWLknC;-pi zpdWr~+kKAT>ukHch;M7a-`(|qFWO#*&pSLEqciTr#~_ct&Y18U6)aBltUd7=J^D@b}2s_`CZc{vJLHe-9nN--G+{_pD+3Jurm7`v>uNXaIkQ z_u+4OKmHE(;qO2%{;uu8-+kTqTecT}*L2};Zy0~OI`Ow}5B`>R;BS8je|y^Tw>yZx z;obOK)P}!~UHDrlwki`PC7AEP~8O1RT@>vgA;v9iOUvfY2 zmxh2_;7=?}^D;|VA*T+`*?>3;S|<{*jN?pacd`%IYjKtqtlI_`9YrSi_64{w4Q}K}tB_5Mh8V=v z6m}$gF*^~JIN=(poMZ`a?c#;Lc zT?=<9A@@*ZZi1ihfE&m7^Wj1^O^|gOK^vz+8?g+DfORqu&#e$o3r#b;uZ4!>KA@|D zJH8-Yld|1P*9=8tW_D~Y=}mS6i)RuTfCVp%gVTbBCa3uUtet7LY?V_>4RxZ-xjVTR z1g;+;0*7YiBU6}UVr5ZjIuNqP*_rGDA=ObLq@9n>@zZd-59oA?>Zu|HQ6(TMtPmBB z=PXJ^3ZjagUCB-mRl_T?Ca0G}_GDTRwhDyp(F&7O%t4q;3&NbvEy)fLRys^H6Z)Ij z%0dVYFx#Mq2?2A(A;OGz2j|e3%48sdg_g7(L`W?u#$gd!Sp$g*1Xv4qC4(TKagSJX zvy`?Kg_#}Zl8WSR)B$d0gN&FYatYcUMs`QC4JfMjkwo_J^U#9L&djNLA^E5%xcf>) zVJY113M6-75~7v|B6HJxY))j9_?f|w8 zJw#oF@T{DQcuQfGvnjb9m@DA=MzjXLjX`KC)3s1RUMI)0g&QlHlG_xFop5&+bU=OS zZd%DG$Fha)VqbDAFqVZ#ic*xXJv7+M&#P=B#<7JJwN1$_iqx=32=cS$P>y8_OI=OL zRt00*)Re%NJ%4g6Te!MBkZb`)=^aXiaM=XWHf*7*bZ4>|*r4p-p9xC_-VZBWrk#~n zMPXqIZN-hrCX%oqVRSMcgU&I#!cwG(DP(Jc$wuI-73=oslw_mj)7H>{6l?MJWB{a8 z^%4Cc0l57uGNr1e$~UVd0Dj9FPtp(k8yd+7TS1yF_l2gyilh&7yIgH7WY|E`3lwEy z2^ISAxpb2uoTers=jy} zJgBgBs*r`TZzyO@)`5(Q9@&a3rVd6V@g~MzS?^1FF#T4nkb09!cJ}lQc6Zt4z3DRP)m$+(@H9+mc%_CA4G_r+k0WDWpo7|+3+!j@}vI1EhL9zzBy|gu1 z4b;`51BMsR!v(#3OjSM!lPV5)ZDlpdGZb3&oex(rYqSz@K+CSFN>%}F5u9_1Ie;O! zh7vG9XDzKsZdAxrS2d!CfCE}fNo{fi&`LHOjj9|gLQIfQ49siSCo55M4XH>%v|0zd z5h~Ampjo!^*lD&Z=?01dIHDHw5mt2e;#sQU7|ZNooM$8}fUXSg+!W|Yq>vu?@l~%k z!f(aZgYa8+bp`ycT{;54B}=eHNLQR1g5T2AYWQ81JYWD|;GTkC-5TzXa0kFW+y}XL za#z5L{!#8Q*8^VxG;wEgYd9<1_V=vghmMCF_rR9`H#*+rxXdx_IOOO6|97p!Xn)TB zL;HjFyX^0`UuVC}9=9L1ciDH@YweYGhh1m;lkFF_$88VV?y=o&yV3SW+r_p?+kX8M zwl3Qao5$w171@mJ-`Qu`pRkXz53nC&Z)UG#kF!y>pWVgQv+G!g^&i&XS$|@E#QG)c zC#^SIud`leJ!;)&-D2HfwOO9GJP9`v+za;%+-|wWa=qnp%dBO@(q-9c3Baud6_x@E zWB$AOC+6>(zhM5j`6lz5%!}rPnKvIWhs|5fb>{WvRc4dv&!(qL-!VO4`jqJdrnj4x zOc$A^O{1p0re@Oylgap7;}gcO7(Z#e$@m82#m4iDBgUP^O-7fIHR_lbm}i-vFyCXo z#(a+X2y+9o#9YkGFk{Rx6K1wE^^A-D7yV26yYzkZC+S=18|XLDDf%+{I6X~A=wUic zZ=>t!N}8h?>P70e)T7kbsV`6;r*5Q{s0*ocr~zsRbtY9p84Z6m{L1i%;XcEM4R1DF zX}G{}%rIi;GXxFIhV=$k|APJx`d{OV#6JgBVQA;aBMVdW1B3LOk>U&o=~W2MqKk?t zk3l=ir~Z@``_ru0Z?aUW0mnkav-G6m`KX;L7w|ZMqD{&W^#vPMW)Qs}>N;iMjYuaA zk*NDisCo>6Q!q3WoSKOq!{&l|u868J2>ygTMy7tB1^1&YxUX^4dcrICEYu%0;h$*2 z-;ia+ua?&V&IR_4!p)twG~M zQg3on8vrR^XivkPO=?8YOcepe1oZ(cIKE~4;9Z}#Zn>A(E9YFPJX1mpMHFl@XdjE zdPGP(J)uRt8_QJ2j1l#Ya%#JT&Y1k6-j44=WD+N(sCQz5LdQ+rT0m79l#`OwKQk~d zXJ8(|cP?=W^QF`)nW%qdqP|x~wHo9;S#^#ty<>&AR?J134{sKucW1$!nFUvgx4$4E2&~ouKPfp);^ek&lEY7p>7P*^sqZ6veq+I-(5N z6#?oO%D}^zmgHzrzg2~vP=&s%3f+wjpX~He_eeouc7pnyI`{*1@GDp@6|=C^dl0dg z=VwLdllmK`6&8=Ir_aLXH!M|HW!3)@mUc1*ramk_2irU7?E=3zz^6WeEn^s*D(W9t zZiT@iJ(GSsC_hrSV2P8vCY;iunG6*5R3_@{m__*%etH9zc;y@)^<5KHUO_;oAt4u^;tb6s&IW&De+`YU%`*4{%FSD<9pF74M}(SjRKyX-{@^RT|wkH#d1a zG9~1>Jv(}HcJxMkeahGJk{0oIE#et1;+JSYXU>%1oeWWG9r*h2zNHpK7j)%gnoebqfpfZ6ZL?El4lmE z&!8sqY43Cowm#XXovG_F2OU$Bv$N1Wm!K#?UCD)}KAwsCC>BbAiuzFc@qL)4d{jHV z6Xl97CcR%7a4G`Sua$w@G53lYcj_%zaoYJ=9CZvSo|{oAdFUjR3u6X~`f(=eAyl%R zA6uA!li7KC3@f9wG(-Ja75bSf^etqRmlUYCq1vL2s5|69`~>w@flwM%(xZZuqcH6c z{T_V;oABUKK6*?ZchWQtNU&wth7ITLh4FEIPI%XYlNa*z9&8}+Vh(x>*4wt@6GB@I z-noz7tHF5bE;OKmhHlUzHlaPFq-;}5Sk(juCxuUr>EnI`IEXej0E9rbJm=1xKCB$I@yFWNt6ztjFv`>po(+TUS+ zll^M@<@Ss13owf?VL!)y&^}<_Yj3x2w>R1A?VIfD?Q85sb{p(D_?PW(w&!fWu{~}3 zk;!MOHElGNn@UWa$!yXaUo!s1_&eh-jXyR%ZhV-V=Ek{E_&Q-9*Tn_7ZCoRK(NN8~ zxl*o>V>#OPsO?*}uiEan-EF(W_F?v&?Dg#H*(=NsnjbLVWB!!+cJl|!H=5sWzRtV^ zGY1!$_nEuQLGw1#A5G7geg?Z4z6XB%+4LaYO^4_mbTi#R*U**J*T9Sa6ZI_hbMEil zpSj=K-fw%i?X9*q*pjx_*-qHzY{zUd>$BFMTc5Ok-}-Iq*Q{T#eir67K4QJa`X1}s zVDCiAdeVBKHDR5y^425P1J-_P*t*-g)f%vRtY=s&tgEdBR;!hQn;BoQ{K4{T%TFyo zusmY?+pI8evb! zi`GA}_p+ac8J^qNo6Pg(Y4f;w)O?n?5oTek&2Dq4xzNm-Y12PVe>J_ubgk(s(`BX$ zOy`--HAPK_O~a;M(;m|*do#xEK_XZoh;E2jHQpD}&H^dZyxOz$$@Y5b`1R^xk( z?=Zf}c(w6zn5S7V#*GukbBqU#1IE3^cH?$qld&G=ZPpvt7>kTHBg6cQ`5W^b^Bd-A z=10t<%(s}YGWRogGj}i_X5P=dn|Ukq1}4e8jyb{1F~^t~Gs5g=`j}3pjoHHZnaxZU zvyO2wPR7C*=zq}9)4!*GMgN3;0_J)iqQ6AnOMjaF7=0Uk6a7y5diwSB74#)A>oY?i zr9Ml2lKKdB3-uoAZFCvEinh~6>J^y%d6N1*^=+NbNa+nD%n$*C1Pl>?NQJL5K+x&`H1^1V@+-0zw3|69C_+Vcc#4+7Rq!b`h|XfE@&ECtw=^ zTM5`gKq~<)1T-T!%rp_uNI-x9KLI`hyaY55P)|S|0UiQ26HrUQnFQ1ju!(?b0?r_y zihzv-Y#^YLfb|5p38)}o9RcM8tR3k#za!wc1UyT? zZwPpXfL{~vD+K%LUlQ;O0)9@w(+Gy>pAqm=0)B$PNxg@FcN6d~0&XDSodmptfVUIy zHUi#Cz*`7-GXd8V@FoJTBjAk)hUgy?@Du@067VAeen`L%2zY{k#|e0hfbSFVC;{Ii z;1L48i(r_3n1JsP@NELVMZh-+_yz$F5%6^c<@AFDe2svw67UrQzKmdy{t^LSB;Wx8 zzCghJ2nOi;2>3h!_Y&|q0`5Vumi{aOcN6d#0`5YvkNz|PcM|X^0zOH=9SF+kPZ02N z0zO8-M+vx{fR7+pLw}fn4-xP|0&XMVRsueNpqIXdfcF#dJ_2qg;3fjzOTdi;2<4@h z7RpO6EtHpDS|~5Qv`}7pX`#II(n5LZrG@g+OAFRE?OurU9?bMx@e)i zbkRb2>7w6&>2=Z95^xOxuP5MY0+t9!5s)O{Dgv$~;0gjRCt#6)lLWkufXfKDlz>YJ zxR`*82)K}d69im9!1)9mCt!hqc>)py%n@)N0kZ_m5D+I|nt&++juCJ!0h0tAC18Sp zaRPV(Vgy797$YD;z&Qkr5^#io5dsbqaEO4j2{=f=Sp*zF&`0klV3>d*0tN{fAYdN> z{RH$8&`UrM0o?@bC7_FdFaeze>>;3ofDi%g1Oy4#ji8inBVZQ+I|2)gMS0yYs)O~4rh zR1vU|fDHsxA_&v#32+loLBKi!$_ZFYKp6pR2q-0BHG(4AML-Dw#RRM(pooA%0tyf~ zXeR+20S*G}1lR~*39u4iA;3(4i2x%33;{F&6afYT^aSVxK)pi1zXLi_XPZofZr1EECIhE;28pbO~9`R z_$2|qAmHZ&JWasQ2>2-hKOx}91UyB+lLY*TfFBa@0|K5P;Bf*TBjEc4JW9a#2zZ2m z?-KAZ0pB6u+XQ@zfNv7;4FVn_;OhiDNWj+!_$mQkA>hjde2IWBA}FLDAm9rG+)u!L z1bm)=dkOd)0rwE_Spx1R;4=i=MZl*CxRZcS5%5U_cIplSK0(083HTTRA0^;+0zN{( zhY9!)0UspbHUe%X-~$BQLcse8cpm{bBiKXT1OS|Up~iL5m*FN;hI<%jkeo!MZz*i>Y^8hoe({WQG~z844> zZZnkXPNKLR|4y!6#GOBS-QotAi7*H2>JDrEgYA4cVe7`-=?V9y3tF0L4);t(=VlT! zTu7Xu&eiQaA6LYwhr%}jHkKlf`dmR zbF+0~q_{d=2g|4v{CtAmvuL-VYP_yi&&YcZ(^oYfmX>zXD7ZLs7Ve!L;HP-lNOo~s z{DeE$KTL%dZK9;=)08C4hRSmMi>x4LyGo<99@$g(E?Pw{W$IckbxJ3#i@320-iztK z@LqZ%ao9^Ali$Qi%OY;k0&Sqc{3CC~^dVG?f3&&wPoQIuyX_f4YxLFNUf}=%W1C@r<+wik0gqz`BGlk$i z)`h2W19Jbu*wkd=C^kpXbfqn1TEzW!pb#{~uYrZcZ9shs^Ro-{c)M{Tt%`XOxATE2 z)N82A-_8xKK`o&RP% zu!du*ayw#J#BGa^()s`9l(M%YxD^8m}pB^0S& zvWUAZA@%9kBy}{u*yoenpR_IF#!aXJ>}#XI!Ynooum@J%ZY9LGaqc4S5e0kfd9~`u zL!&zARf15wg@V2ikHN-UaeyNwc9L4e{icxGiC3E%2+zGELruiiC_K+kOKKcn#NDi* z#pr9Mh4}Kh#dYN6=hS_^>anN%N)8KM`C+($t3lbvWB)F`|C3u6@#8}%k2zJxSFMsX z940-L@)e4$pqvK))N4e=PC=4xIn7P$CjxgzuP z{9Ih#)*`3pTU;e@9$Jo5u>nYCI5%d($y$zt1#LaIC7$!br^=%QE2HRv$MWB zU)uh|uyc##IyMC#&&nQ{JIw#@ce(4j_J5YIOTuNcAWhwTLs_jCtx-8Z*+|MHdUkBse^+SG=D}8zcJA1 zuAFd~TzV^5p*c6X0I@volz>AJS78Dm82(=A`+(T`0nStv&A)=hS+8~(Dz zhQRrlKM{_NxWm0ep*^7icmF`Rr)}V%dvEBVyKQ)=FWd`B^n`kc+`WD9|8RHrX16G> ze{SaZWDGnP1-nG#9td@W2132T(4hN}L`YH_*(4)?vxW#m1^as4?V;`vWGC1*7;I|~ zZ59)PeNlWo7KzVe{!W=%+6Z!bS`?-$=0Z$(xW66kC?=ePPO7(}(g{qY%Dpym$iEi# z@74!hlM(k&=q&Qa+`+!?Zp>b9A7oHAD;YP9xO?}v4Fo&e1~&N`8*7ADybMEdCO*$! zFn<~xxM@6Hwy^Zrtu-er?Iza&kN#v2?6Bc47(6jOHZui-8R3(=nfNGqj0hMIWG{TIo?SKjDO5{y zuBZ@_jc8s(&6Q5pKGpP~jSeTkRu@5FVakeFvO+fZqvKQXJq6kZkpqS&fD14%tm>ip&f_8Ly zGEM1{TidM0(Y6l%nFgbKCMp~+GB9)g@_I`|!I`Or>3Aj`*5>tAlWPzvN}o_s1m&ZV zIDBI$Ot6h=a}$nfNmzA-(MD&HAXgkM?J}mieApsXo2y3BeXBx8trV-e+GwK{)kYho zEciK88>uH1#GFWgBbV7B|4)jDsvkS;F77p}&6-|9Q^jyUIeaBNJv-vYK6)tJ109*z zlhm3`uKF7NA}f?VNnw;Xia!#9Sk)rF*5TgpzTptmL80rAXk;e2a7i)ZFakOW2}2at zv=?%$=!0Z5-ivyZ$pvTET)tf>g_1BLUL7*4OQ1SZQU0WeS6S{Pc9NfTbJ_sIfR$TQ ziBryNF3i|kv=~jUkVkrB+JZmYAHk)iQEZ;Iai*-VPfMkgAiqg15q9|5`q#}cRuvS` zGJt(yM8GIV1X>b7XEGwtMj1uT#S*8DR&buS#x<9ppoOL_S(R?u6fKE1Ek<3iw9$&9 zrHy)(MJ!uKd#bXv+$%_To9Kp!J#aXInvS@4hZQc07>D0Ri66K@AD(VKq_W)}>S!D8 z9&$&?ismS;$Oui1D7>R@AQaxyD>y??`6~7rBLbCahBCRlW%`p9 zWQfYo&%@UeiqbSlFtob(iC6`lT{v~k{R?14@v2u%B2tin$ z>Pycxk(GC#scemSTfGhbagVoYycvWywRpz(#()R#d~+l)-rU&OR4LX~S(0L6D=jlU ze|4JDQRz$U)=RFpU8@%?Q2YLB!2*GppZB+ndHmy`P{8L4c*a`647}ePjd~;gh`-T0 z2%}~^)kE-`Hc>HAt2nqz3JUTrAtU3H z6XLmqC`EjWIEexYz{h6N_6l_z#Hv`aQ$W217U>0|5FkW9O%;zKVJ1*R7LG@!7Givu zoHPf*=I0i8Sz$p^KTOubqU5wBO_W3W2Bf$|L0_qu)Wm%zxn)3P(&(nlf&{7mB-;Ez*7}XqPL`V={jyAuisl6f45eWL4n;QKq7ysO9ep=UjmH59~C2}im>9QhLDQ%TV z7rFmQnkwgyk2qcz}ZYw-C!4J|>R-`@~uY;W~tuP{=_ z@}`O#YnmuV*BJv3(&St8VRC}5fH5_B?46kCiI|$ zR_+3G>f*9@Z$gKY)-g-RB~Yjfz$p^W=$ceXE4roAOB{6of8mn;YjwC>HtdCUFcZVa zS_0#-#)v1t^IlIN(9-H@jm2UfZ!8vQ8Xs?M9{08in@3ev)F{z=&1I6v>Q^gLH3&KP zno9+NTVWN@#56XJH8)2CUQcUtBk1V&M?uHNaZe-P;%yxFHu@TyeBlJl)Z$4`!udZ7 zvtltKBR4e;$1ifn2nNyVC_IuChR7#jSujX9jWvunG(#C==aeVd)aqLyPx|+YCG5n1B~S4MSToNxAKn%6 zp?h2|bXA#!uIE}>Y6q&E=2zFW$n5}>h1j(eX$HbNozwh3ZU$oL=ZN(aJm`jNVOO_q z{k1f%{~MXxblka)udqL|95vqqf&5?I0(lGMEs(ds|0@=_%2{A4_IS*%KX81KKOd)R zaH>LBgoBweZCIRv-X9JfaBJh-k%ZlPAUxCw+kJw=1B2oHp-h^#L2>&PPB;z1_NL&F z@c9+l+eOy?!ZCN-pc^Mj)!5DM<(3?fUfg#|NF+g<-RPmJqUVpgI|llCGEyZYDpU&= zMEiQvL84_kj3oVaS=3XLbPU#=zPcGA1obbz*j;_N+6|yz`k)M7k|tYeUXnk}t3>Ml zG)=g-7q+)`!G^XBOCV*(AnZR&Q$uF7%Qx_rwk!3)g-XfnF#T`0N^f_t?##^^y%Tc5 zuv=GBHWwv9)#Y@{d`D0~J7FJQ_QK*04+<;kCu^N1*TEY7$w+z&hp>j&4z5C$?H9t@ zdfGflRis{HQb#T)0{lp_Axlk;%TH;HWa5#{hj7p}Qgg*DCoH2cm1$NyQI9kqh6>aR zNa{#PJ#%Lya=pssq_lKd<@AE3wBq>Mtq+RR3G%K4Wv=7}Sfx7Pj0)R1iT1EqDItJU zvBJ6RE=}R>C^i$GdJ_TG^^CF5*<}Ulz^Lull z4I_Wc+DM)%!6pJbvi#oMIP6qFYe@Th<*q=x5%qu1-drn;a5?*wj(Zf&{lA#&=QcWC zbll|Vg!BF89c%4hwok#y{_oKJ^#8Sgjz=Ar<~BIDI}O2>mPixd+8Fh;@Qp2=Ktn@| zr*#~jVgaw$-x!Jdo5rKY<2s$~7dg_xR~5FqbEKUI+G}#8O#tmVInvGnZ6HV5^MKZz zBke4F5%Oe?v@mJRej-QOIM7~^BQ1Ol%AS=Y?G(^%&XM*Q(CTueJy)l*KAIyfe5_~v zV2-p$f%f7YX(xcTKS$bepxu}wEq=xIVve*ip#5f!v{9hFDM#8dpq6sj9 z&jQ+ebEJjO5>40TNV^|sV>!~|+E`1Dv~b^z$(AE6+?iwiX^yl5KznD7wEKWIl_PCG z&>qf_7QPZSHsna#3$%2Ov^{Vt&=Wb*b_4D0Inu%>u*~anr0oLQp&V&pk%ZZlBQ1RS zNWYvT?H-_gI7iwJpuHtW+7QrQkRxq7(01oY8w6T+jc7a5wh?IW&XG0%wAbWFi&wXwlOrvB)2t8VNDCW2^yVCC8^Eu4 zGDq5ap#4OSv~{(#e%KIf3Pc;4{88A2%==(RM^m)L6Y({U!Tu9|ti?YTXdI75jMos_ zT$YiIuRjgBSD%L5B|@9a(z7uo&^mLSGn^WE{7idj6>HE6V zpuLRH=CUi@xG2)*uz%ZllF;Uqw@dBJyLDmRdvtJP-C<@k{RjHna6{cy^cd}?eh26I zT~EcRZPY66C){1!8@MU1lUw8XgX61?8yrU*4G!A=nEeCxut;SBrzaF)O`a2EZXWvhiYf608a`G~n3PRxIgsoz8y?>0^wH!+VJzGt}F zuvPyubBq3S`U~_SeWC6tP)VoTo;(0sF8m>KsiJg?f#SFbX)9A*mN-YC(3ji~{H5ac zljJfnnMHZW4)7-yrg;kkR~0lQhk?lzBzG_hmuF??5u$*_3oJvY$I<||GYqH{B z-1$srcd`%IYbOZ1=Aw|5#b}5@Tuos|vKO-xQHjgp%4$)TKna32z^&CiAV|8kTDm@F zWrZXv5K!T)O5zbb(rxpK>!uWstg{eqrN^^wBs%e;Gq{W|p?GAS1x#%cPosfvDDk#V zqxy0>b%=F3nVRGppsudgQV-9;5hid?>?GdY3<1{3f#gz!WHDLssx@_pbvhVtay3xb z*AvNNyttbW1ZE&ur=8iEbOF!y?JAy(tKLM`3_Rd1WaTA~jSSJJV+zRnDewt;5 z_qEWF+y``3%|f~+WxJIw)QU#Y?ATn=o5UTGo=H-fw0C``*|ODfEj84MGUx8(UJ$r` zga{Pw;S_EbT3J+@4uq_6b|!JNX?2tcY3HMJ{4^h*#|xrXmZKsCQ6(TMtPlkoN^=#Z zA_Y;!&aPx9h^pZgS(DRSSuG)ZGA#&O1;X}dg~=)AAWWtOVNU0kWCsW<74Nnd`WwZS zz}em%p#f$a^e`b{t~f-pg&&pX(3r|(AcBRKv>il9Eh)y~XYDIzL!tr!*1}!MAP8vO zBbMAO8m}nK>@b&9BzL0@%iTEcFtR(6Z9oBcxMQ^x?r?{54dIME_=ZiEE+1*d&(*1} zXR{P;cLkEWFbPphyw(=4ug%IUMOq4*iUY}=3O4Ef+N^9+q@}Q~cuR5zux;oe>M9h= z%BhIA6jnK#lG}l~LcCoa--d8~Y*tn|mMz>^(Ujb#V3e=R%gQLnvW4zqUveujmW7DA zQk1VfG}sG=)+yAh6XV#zirS{+7DZ}UBm}~&smZZyVX3Pr*{Wca@6yW3D95setIGq) z7GRX#p;QR7@=1|wp{sOfvKiQ*?BJhJcH~?7vhrftktr-pp{=+v*+dc+B)}D2F}P?k zk(F7BG%@?T`k&CMYjyMipIj&Hx#rc@o?#i z9@&cUIX!$Fz6?KDnZ(1z@0InwB%TFb-HMHm-lUS9o!?-0mu*hsvCU;<9AXl$VN{dX ziCBw$ZE;-^k5n$-N{l02F0G-L&=$MPU7N%skEOfo$u*}Ma)PuN>?N+6B%Vj?Y9!eg zvY;Ura6roy)+X`5U`cYDaJ!v`m>^k$-Co+7#3OyHMF&iEv9yLs6$iYwvYI3w!z*be zTp6t45pY1uuBl4mLApg=k^>lmYbXH&bk@?EB%XCE$rP%QhEl)*t)-+ki6_%aIUS9v z94o;fbp#2;z`S;S5|5E})sTvm#fEkxR31Frb=k^er`f6`p2u3?C;12~I(u>pVqG}K zGJ6>38A&`bwag>d77{7vM7-S3yoV}Tm&5~3g(NL{`2>53(!Phq{r}r^*Xg(~a%oLSSL~s!N(v=uu0Y!kx6~ z8l1^V2!L~a$<7oy((6S(xKqCWb%k`(L?))M{EXHVj?l}*ZbZCfeFb)r0(jR}HmA@F zm-H5H^z^O3N(g|nY<+7A9dLE3-W6C$s$Sq-1F537tx9zzRv}g7EM31nh3<8Q*x<^? zeXfwMOauI@tGZHxYZ4`8S9?S03SzXRY+s3McM9E-szb6c`TEtB1<7P=Uoi+lw@ zl`Mjnv5T~w9(vJyMjI%O196jsyBsBhaj>)nDSITFGK*+4k z)ty2&rJh%6sJN5*lnN?n**@0gPodMYp^Y@X%L4D@J+7$XJFv(-aae$gs9kTZs z*=;HGSxUunI|%25O(`0y$;ABJ@N1{%?v$>9tSxQ%xZcCxZ@+#RaWz8w{Tq=6R z&T4vga&9sjnM$jfU4c?l>DClFFED|k$)%DZyBK>Z!#1bTdnpw?H5h@wy~^3C;bi{# zNFAZD^f#>YrqF#U5{HMUV1sB&zk6MC3OyGowH0kFq$ZlT-?7e>^Elf{+Noxw-8JF0)3m*@kLIr^acsG@7O`%I8rJS_~)}}1=!aZwBLMe1* zHbltSM{(==DbiO^vP@4&c`$`8&H7P=9HD8<0y zd2b5cAxv<8LVc z?O|R0DRi&YA{CANDRM|eD(KlBt7~ftU9j3PX%aN~Sy^C@2|5S#$8zc%dKfmGLVwI7 zmSAlm&?FX?bg9!4vP^fmb7u9#rdrqBaaOH;h+Q%F-# zf^KGKLkfMw3bHz4l5M!06mvIKa%Ku0MWHAtJTl_isQK*O>|hE##zwJj49!RrsO^)2 z;~kkhA+tzPp$q2ucmn1=!BNUWZwXho?o6SVx&ENogd`-^m1MQaRF-h1e`gB)&sQ&D z9Y{dO^wmpPvwLR>SLt58gc6W|>vykSLLuY=SN2}L1V_!z6t4EYdI{D|TT{3O=oJSm z(z{F-gB9Lt*j$SKe^~c~j{7{f$eqJAaz@8fjt@I7adbOe_Mh5sw#V(wcAM>Iw!3Vv zx19s)`~PAegS*?0v%Ty(>z}L-S>I`mS{toa%Y&A;z?{Hl^Iy&PnU`QzV1wx;(|1j` z!L98+lfn2U;~R}<8_Su;;70W*Q%}D_e}{e>JxH&ET?P+Oucr=DwT9ms-fjpQ^!m@} zXY}Qu(y9Nb7HCw+%o_zO)5=+w-AhLnHb!XfolVB1T@~3!7{zY zYx|b4fOy2}r)^(PB}+@s_7=H9OITnwD7szIhs;5k+$#4Ly7nw#v8fVWo)sEpsdpHa za;T(bdkb7Um$3Mhiw>vM@}5c^iH_~HuiCdHEGy5PW(O`Ql-W-A+FbjVaM`(DB~#J( zo=UyUF1(j@ZC}ELXwP0v7cS~?3X#3w;Vt1JbxxgluaT`^S_RZ)*fkjR8B@H2k$7xu z<^p+=w@deza{m%8URSh<9C%;{=$3X1WgaPp?b1D0ylDxSw2N@^7Se%QAZJ9P2b_iO z(mlPpeyKpAlDL&48##XC1j2vl@-5*CcKJ>sd~|9?`1~n@MT#&nUAiBv-nE3w+O;^F z4jM@-W}T4ZDSj@4R}+t#e&5}+AqF$S+bo5{e#8cB^KzxAcA~p$~{9ZDFV{C-(A0C1sTMPSs-w|l8-CSC|WB_`oaDs|( zBW4CM;NUgBwYYOh50ri!d&658o#V;ETgS{CEDyvIPA)V+qE<*?Ny}}^D(zV{XIdzOEB518y?~@cEXtv9}x+-;U4tkV>9+N0` zJINpE7iD$Bl3cOBWV+Tk>QYBfhoRK5DK!BMMa?7yWpgBp$M%TnS`DP*xskenl+TT{ z1W04Kkro4KG&j;!jzDS*NGrrm!os<2szv zQs~^4ItN&)aV!8Va)YF58)ZgSG_V&G1yZ95Mdijb^Cinsz~(IUrH%kwc^gSp>NT@) zVV@2?G%#`N>rx}YB$XyjPb%vucBM?0qwvhsVPGg0+YwchBa;hl4%20~H>3^$v1B`K zgCNsOEdtYJD>x%{Hn0@n*cfc5XcA;%WatHGO{hH{XhY$xI)#|?ghzQxR&;X^hMY0N(}?+25}-M zrba%Q2g7@v?mGrb$34gWf_sAdCU+nA3GPPj8txMAD0ep3&H1@1ZVgw&**Kl! z&yF7(_BcM{c$4FpBj~8Kzhr;dew+PL`=Gtv?zR`(Ew+E!{$%^5!D;)x?JKrV+dg1> ztL;kLf{nKwwDs7w+G=g@DmY*$dei%pE z-wj-BJ!U=I8n(7rtF6UW%JQ=11#)xsq zxW~A~KpAU|mBv!TTBE~AG0!niG2dkFXFkllm${soXLx2m?9pgps+dB?O20(^hJJ$n z8vSwlM*0o(rSuGam=4ohXbFKH2f*CL+LB7W(hDh$#t&p35277j5;y{HU)P9~g~i%iq3@P4F$H1<*(=;)iYh(H=4EaTBW zF|9CJZl(Pus=&}0WOky~sx89Q0}@K!`AK~SGa^qGGNY;>{S$TY5q0oA>R?Y9)hdaC zb9SP~A`^TcxgdhN-b%R)dm_`*5&HI3Fh!~vx6=#wMmnY@XJ`2sU4mH*T`)TnhduR^ zktzCC)LU=@#Eq77)WK*Tc~1a-^Rz84m6PBixG;5!>fQ6c&1oj zA_4k2YzM`r$d`VmUr{{2Pw_mYcy2~R$<7QthbEUl62K)*+L(c&ew>MVh{zGA0qHlZ zgJW2Jp=E^j@c_>sV+MuH3E`fZ`2103*hq1PcIG?+X4g?Wv$Ww-(|qX~s#y^|+7}-M z=VP=FtacC+N;m4vS$3)%!qdW`X4Icmp`WQj-@@V~v_;IK{7C;G{rD}+lKdeM^)`G1 zVo{~;kOT1()LS=DVT^%0pZVBmx?31k7+?q+MR^oE+7>&Gdw7|!kcKodKwqQ|j-uJ6 z;jJ)bL|-A~K^)i8SE@osil`bO=cC8uglHNo2y8i_zbHL?UV8Yf^l+#2@GP7QY>_0|MKpv*zT-YZ&qv#z9Y?dkks?p zkU!6c{7xM;DzOVyZ1lk7{LxYP&;%Nop&7Y&j*|Mfo#Y{3`KRrvX8DJU&atw#!qy;U(MvzQGFk|Uw`k25eFCf!yO}Ii6UZ)9{Yr<9^@l1%1xa~9ax@5Xqyv#<4DUSH&KJjlL{rSZyZOzSFA{gjbPun&e&p;or2U!@{tjBmk>G% zD?0E=F!fi=3z>{$==3ukPd`)d$EuN@45u%&8rJ@Q?0pG*RMqwW%v)yW&6_0$#`7D{WW8KK?njK3l7;)>dTm+2-3kHoNtG>tC(Eft~&DTJN&nV7=P< zX_!HuupYKHTWetkoo8Lloq##C)$%vXZ!9ms4Eo!aZ&l~;rbnDB>cejpz8+L z<*t*i!>+BaQdhFe?EJO!apw)rOPxc`-Ojbn9OoP-yI|$vLiZEyFMv%0 z)^V0&pQFi9>BxcA2e-o{{!x5I{Gs>=*CO5_UMF5Eo-G~}cf&eFmAFitVgIN7b^A~4 z&v4gpHT-H=kGR`@v;A6FkEpX(fS!Yw>$cAl-WOgMo)hjDt{1Kpg2FNLugwoKy!%z5 zM`#na2sJ{9kR_xGiGpZ*A66q?<(}al;%?`z<}T!paoyYwnEw}YX`GXNnSGMIpZzL( z1v}0jW&P|Hwt~%JGuQ-HVBTZiW?q3cfJc~Hn9nhnFeA)SriaM7anj5jV|AY;`7`I;3Lp6f_a^2-IXBAQkFO}=uf3CJu{3aZF|<5013!(@ zEp>rL(hldB;`CDgKp8a7lmwNlAp1LA=mlNqVO{7>DZk2zSoYgWkVdz^*9TwJ2fvGr zO=HZ1{XE{-;P)R=mb(6qZK;+FdF<`+^YuzqeWs~ zTGHSnZm~a&y!AM~2USg$%aB-NZQ@?V)}}RW!9F9s0<}Nkz9%>BWEm}l+^=Q)p^S%Q z+)2n@g?U#ej}C2!djm^OdX~p+Qh(=i^(dHhpPwE2DX2fWFW|d}t)>B33Fp2g2e8z+ z!`eWLHjtblJ(bPuu$N=v(otmgI(%gzl{D@m ztnLlH(!26^^7;Cp2@AiW7ki0g>b^&T(M>b)mEUD2pQsOWf5b|yd}_I$=|WFSiQ)=} zG|oxW2c5bg_g8)Jci1SkhNaklMcjEW;?A?!?iH&2FdFf*??>MHcjT?-qI@tbqTDYw z9s|rZzHKwU%^2S1{$zanHRIc_7~g)z`1a#CXi!HSu=N%??Z-VFtj5-Hpa7+F`kuT3ku~O54Ca9*f8u~r9r$U2KUmX%AQB&NVsNQXtpjiLmOhh zqYFu+B}D@EYTdIhU`Ww-<}TNTvUQ<_?tLbgJpQ`d!(#8Fgxq(Sio+~K@pag7;8ZJ@ z-w4kWHT(DS2y#P|iVv1?O`gW?gD}j8nZF!)U6jac=doqlFHPH-xKK#*Fun%$|0_&) znA{J-`TrW%KV3Jux?IW5mz<}a8^NmIosQ#AvaTwwmN4{KaGA$5 zi%y|lH(2SBG-8$Uu_2}M5bQ{qPc6tlg?inJsPat4kWPLky3EHCi%y|-x00NqrEW;3 zJhVb`M`stG5`j=MvjlmRCIz_5ZAhs;g&a0#m7YRnZ>4lX!#6|H&|{!o3y1bm?R(h{ zDxF3!VTDCGpeQ^TvWSQUmL1KNVA28=z$Keln1Kn|=weu1<_cfYDb)Q|%1&8aF_b}h zC@?N_LE@@YsQ0ZTt=srCq}IiN*R~Wmpc1&kK^+I;Jj0NKE(UPO1d9%+67EIonvi|{ zhMge`eH1Li)P&qql45ujRrE0Hq1s9=8xM7v=X!Eap>p_~DbNU>Wv5V0JW)RPA&qkl zWt5&kMd5$~wZSJ%VfwRY)fj4pD@CdsoEfI2iy^p7Zvh8X7oR?dH^5dE>XC!3Dyid8 zQqY;z(Uz(<0!Z*{phxxc>C)d1R+Lb~+#?@msfGEkVb<{hQ~=Yh2`k1>yBsXFNwr8L zn<1U%IWz>*jWbt_p_2JD$-h1`e+;$Fl`c8hn=W2kQ~S(k&FuUXPC zq|`hI>d$$K#w5*ixqoikPmB#om50z_ny$>w9YghVrQS<5HKabSDH(9KI5YrC5AbvMX$k-R(UA8P9#>3p?Z2&GYAi=rQz%1hUDRqMAzZOwPUEXE_M09CSrltkWWM` zsI3PRO2$yHUHb3!e8*@$@%`hCVO;;KnH zCO1tyD2T>|%g0bNU&&bbn9fKZb!=CcxPA_cL3lKYbNi7UoXyFXd(6O`T}W|=6FMAy2+jbo_kpCubw3hymqvI&nYy4FCGM9u#+ z*)T__#m8h6iYB_M6W5I4DS$4g%UTJn@H~Nzm73hdWu!(GT`Lkx#_*iM9JvDx`}!G4qkILIdsdF& z$%9l`g7=s=a;1yt{ftsqB8je|IjH}C+S*}q|JMBjupJiOaFHwEI^sG5d;x56 zt#K`PWxLW{2`<6;p7U+zE6yKTzv_I{d6)Bg=VzS(>rv~UZ1J{ySSh&Oc@(S)>~(H) zu6LHhzJ025Cag64!SSNwDaS*OJFOQvzUH{Xdb#65$9c9!$C+S%aI0gb!|Rx3d(9!( zo)O;>ei}X)KyT8 zVU^>;;Y8BHF{P+P5i(b176q`Q0NZ_-DjR0qQ%+AqDqH%@EJ)7}V3**bvqWGqp*=K4 zizJitm_IK+fW1Ik1?4fsIJ&faW^Q6>Kx*=9iAvg$f-?hBX2J<|5BbcT`9%S2^Ops%)h5&VJ$QYza}q9{a$n3`>?sUjGX=?{cSaQjMQ@=j?uaYO_$Gj(27U<>fbrL$A4 zMyx1IB4db|Df4mzn}JIz9_oiOri4Hz#8?7&AZ8}d$_i`(nj|zd05sc3izJ*vzAa|X zo8=2MK>RthTF6SpA5lJZSIkUG%?i|OxZ{1$P7UWS-xf3Hrj`flAby72ok$NlB;B6~ znn9=s9Rt~#C}O53uQ;#~=#%RxH&wDWax`M)xtKX)_R_!xNmA*=E7nhBAB8Z}k&qEs z57FoN4(p#>El@Fu?#OPKU@n^CXrWA(O=)!V{V-Xhf7 zdI#A@amPJ0Ibz=q3;#SU`~+@Q>gOEnTL$-DHn{f~)^BCJ$4lCbN{BnHea2poYDPv@ z-Z*KS9|sDOdkFSk)PBGQpmZUt!*8tAY(`2=sJ(xme{cpuXdEU{hbZ?c!- z{|ul8e;Ux#zWkpf+$&8|cvn z&X8o5j1qH?X?{Pd`TdCI_k)_>_h^3Ksrh}I=J(B--#2J}OGOfhz1S2f)rb&kRe-QjrFafxH2 z!!AB8ULv-NOYCpjZ?q5Cb3h0FI?#7dfRh84*f!f{TVJ$Z1UdjYmVa6vwOj#b<+Cg% z{t@siSjk(!tKdc4M$XFK${q$m{=wV<+U(Vg!~BBzYv#k|<)*)zZtR266caI>1wHq! zuI>)lHyGSs*S98^3IneUdDTzTFx6ofye~-;SQ_ENs1L#s1Kw6w-s)fq4*ld6KV?8W zAr~d0*y>Cw2qpuI8SlY^hII7nxhuq~r!s z8zWKnpCmnw;4f224~3Rhab-c&&q$N4UNrP7x^2-2LoqBZD>7FHQCVZbR+xLivbJ<6 zO=b{{UK>Z?Te1@hgQ&itW|q$VMx(B#$3jbnr#y(79P(7ls_6ux(Q2XyE%Q7pgQ(;I z^pfacyjm&}-Skd*nN-l4C?el7J9$eG6+nt*w~K~K)zL&TjBMpw5*JnmQCVb>Y~)vp zH<4VrNWdgv;i@3&lfYZSX^~ZXvNM`M+Bjm%tiXs;ltC_7wW1xv5w!|kE22sC6 zd8rYza(%%nv-6gjiKRhQFG;1f2&=&gfSyu~AhyhyRUDL*Olak!%6V&AJcw&otL2(eZPUQK!Rq3Uo6`Jx~Y!MTe1X1TCv5dMENxCRxE!e4` zR>>4Pl@^Ow6qNKxt;%SP_oy9_L;}6) z4r^*o5LHGJYpCk!4fuq`hhgbaaffAAeh_s;=HOm8)Yfp@UdjuX(oigX?cnDv4x&=G+{eN!nNMF4s(@Z0CQR*a*bh?1^4 zgo>W78U@JbxYmuMl890QYF(d5fUt-nzdbRfWE}NHl4PUf(qKWmh!&m)IRcO-q^uc7 zRS~be!KpHr3MhI`^znd^C#8BERY(@eP8hMblKKujY#yDDE*h|z6}Ni)7_iBZyJvhp zVRkf;fJIzf_4rYZB-rszLK0mxVB-KuB>fVNHTVf7K_;QX72`_AQOQJkFHt*brEV$R zG{4;*w_+UCOq9x{cEiz&QjG$_3(n=^sBw}kyJ=Fo!RXXV3?R0}EgMHg6RRVbvoo%292H4Yg%3i`$w&6{eK4?Dlj5|=lQ zS|TvHz&~r0FS|@3ecHkLn$tIq+96=ikMd#snU;ub+t`Ghaooh6!iRdBSUQgTw@LEk zFX~iDJ-wz*mzo+<){o=%t*?V}q%34d*vD4BPaz&*Q3bAPeOmcAZt14V+y83Z$d{%* z3fZ_9nyTVR|Nlyp`yuy;yA1sQ-3zw;e9kwWH^Cl#zT;1hTO0$93Wr5}M!XEhi^=v^ z?Oz7ld`pG5ggb@fLbYH4FMyZX_SiD5uUWrtJp<^`cSyeU@B zlsJ~A)yXB}xc>~^pQL(28!WMCDTcr|#9|U27uaP(Bf(8+ z#dj4L)QiOd9t%yGp2Bh5k4}^)BvHm4p;szJ5SkKz4mYM1I=up&+V_U&NOh@c=B)g2 z+@AKxlNdac9ZN`jE;h~Zl#k=)v{LybA#kZO7O5IZY>Jy(F^=2RNwPMNyf!@nHLd2* z;-=8#NNF6$P3x>7W#q3v`8pY9;iqNACec?sj$7I@WVIZlPAXK0kR?edn29vmlS;;M zTRW*yUZ)A)5RGirGI^6NA#WUavr}bH9%=$H^3Y_NnLCb~*Hh?%n{beUo7bSICbbQv z)kLOKtI7bw3~C@2e(UUn{BhjOj+X}+hBaD4idCB5I?Gi&j+@$va*LEILd+zk7(f{Z zl(@aEP)4cMniQ#8^IILx!g1W#_Q-Pp<9e+jlqxpA)dqth+|~xQEUAPfNsMc@hFFay z@>{L(h2yx>ourW!qLBdJEk9p2GXw%8JX!aW@(k~GA!Q^H0! zjO$hdeOd!`ETJe-3M>?%5gZhbuL9a>m02STgPoNqx%?rHS!Q(JQ7Nx2OW_@t18uzg zA&XvKG9i}LmK4bA3QdYJ%StAc%V~=%C3n0GY1dLY(c>s(L=U%M`78&TX_e0wRxB7V z#XQPu+0pX|hxO22KsIJ_o+D-XcnJzRnO3V%%DJ3pP0AlH24bb{$j_r>Q+_%R^I0_A ze9mLV72|~{u&hxWvmSt^w)|o!ny`6kO3`=$kjKl_D0(r4XtAI+r=+YH&)1|Ivo?fi z<)Yf`f+m{>rceX;q8OtXMLhgJR2bgqUJWCSH|3Icfgys4>Xm?r0cE9B``jxW@EVL31zIL?trX<=)`(FBrx z-PWfktQb$zkcUkXqmV1p#QJ#&YsTjTIrwsu#)uj>w4ryQ_t`5;o~?-rZsukuuNcQ& zZ6zDHRI2fG5=9P*1>}h^fWy7)M0qr?U$coqEky$21h8)Nl<8IW0TWpN|Ec?VI0Nt) zSOWOA`!@GC+}DC1fX}!uagT#%z~k;gI0NuE*W0e&x_;?;$@QG;N!NGb9Kg3+H@m*( z`jYEP*QZ<;x<*~+x{iW{zB61Mu2!(qSMOTuS`F6v@?1VwCfMvtaJgMJu-x}g=bxRw z2m5_LbN(1S2t4lmj`MEkH^G+Qbf%kM3}w*1WU zW6RU-gYGVO8=Ps_>|XD#a<2eChuLtpVZM8gd!}1-b6_>_`;JE(_k+EITO3~(FBd-{ zo)$-JkAZ!IZ-YI=Z`iK2eZlq_&~q5K1;FCLX7KD#Wm{o=+xlDUFRd?GpX1uOo#4%I zBUi(%Y({U`VD9M?EL2mTN)a*R37a~yN@I}SKH!RNuN z_LuEHvOi^i)c%0|F8i%;cHztR&)Y8p9|))H!}ha4)1t@jx9^6N44c3oLZ!XTUSQ9% zFSMuHXWM7k?Xqu#w}jsazYty&o)w-D9v1Eu?htMgzA9WTTp@f?xIj25oFkkGz7o2H zy+Vu7B-9COgjJ4Rj%|(x$2wTuS?` zEEaP_FL-xIf|D6e(PDq!{*L{R;yvQ+;*H=#@+#Y)?Vzp8)@IvoE3{?X7J-GyIkuTL z(Z*T-W&NA=Q`QTuqt5!5`5nvM)+fOV<$cy~S#P#} z&H5$lm6mT>Zm?Wu`J&~smP;)`%lWVhH3VK8_F48=c38GpHdv}H<(4ALGO$3IW|?b= zw>T_3|8M^9;KAW_{uTZw{15r>gT~7J{GI$Q;LYJ0{&V21@*;kWKaW4g_wxt%PJS1x zE;jJ%_zHeGpU*D^9}g*fA|J;KJj1=q{e^pzdkt2*UI2@a-vgVE_kh*M8@aD=S8ezQoo;H3XhT8ZI1nUuM_*#N>1Zy#c-TWF1?dEF;stKwH zDhVnOD*4p}s|Z#SloPBVC?f#t(nwHBP(n~_EoJt%@2$m6K z6J!xACGZg}A@C9`CRjwUkYE8pCP4;4Izbx2e1cSh6oO=ec?3xWa}iqjIRvu_5(yFr zJOp4l6Jy2`%p{mW5J%u905>8S(@EeU5DDxA0)Y)-2X7^?5by+0hwvFoz#wer%>*V1 zxc3SEP4F**_Xyr4_$R?X2;L$1JHg)w{z~u{fo+j>>7;55vLGW{gIQH`dpCh=E;0l7v2|i2k z8G_3QK27i`f=?2Bg5XkuO9(Eu{>^kn6ZbO=HE};Bc$wfQ1TPW1Nbmx|j|rY9_z}Sm z37#W(mf#tLt=!WDKOlID;QItm5i;6{QQ2);pZJ;Ge>>jYmT_$t9y2(BZz*8WGcy`K9rhU&R%2);ycHNjN`UnKYf zfmC1WIjO$Xb5eb&=cM{l&q?*Ao|EcJJtx(ddQPe@_1ve$hfG(j=cM|wo|Ed!dQPe@ z>p7{utmmZqvYxvb6I#z*L~tR&1q7!Ff&}9PrwGOfMhQ+5j1Zhp5Fi*PIFI05f^!H? z5S&eLoZu{iV+2PD&LlWOFhnp&FhI~xaG2l_K_5Xc!9ju^f&&C+5OfplC)h{OMc^ms zBbPQpB7#DK0)l*kJc3+;9D-#8*#ubxO9^}gOAt14UV_C0iwG7HEFj1v z$RJ2ZsO8cK<`bk6q!1(%%p*u5m`gB+U^YTLmq?I6;31er5Kl0ZUo;f_Di1PVhH^zY_d~ z;LikqBKRZ09|+zic#Ggog5MMTj^GV~*9m@0@Ed~H2!2iQD}q-EULp7;!7m7YPVh5= zpAx)G@DqZU2wo(3f#Amk&lCKJ;D-dy5j;!q48hX`KOlID;QItm5ex65K;@H^H|F?jpF8;9CTD5Zq4iO@iA9Zbfji zw-DS+a1+6e1UC?TgW!6CuM>QY;Hv~*A-ImCq({@=V1 z#xgWVop=>sqM7vn_9}sXo^LcGpW3WAG?!J^3^a4%sOtxzd9S>ZU^M@o{EYg8uOevI z3=CEjVyq$ z@g8$QHghXLRyK1Bz`|_iW`NXe<|csIE7>CP9#h6jwh&<6O11#Nvx?1!L4#`*n+ITB z#pYsXP{!t9%rbTv#w=sAF=iQ?1>h=UmjYPJSRckLWPS*tdkdN80PZMco&~t6ka-5+ ztA)(d09O|>KLEI*ka-H=lZDLp0WK(Ho&-2q$UFgXP9gI>fHMo3#{mu%GLHdt7c!3m z>@8#-0ca^?z6;P)$UF>CSI9gBu%?jt4#28H=0SjxLgoR0+(PDl04THj0HDn71%NWU z2f$Ov+zsF=WWEhxEoAPp9b_AP%qYM*A9E6*!pDpNEcY?z1LXUd0KifoGYqi6$D9X{ z;$zMQNc1u10L1y2699sbIU9iSF~@BOnRk86Spa|WF~$tL`j`%YMjx{mV55&|2dMEe zdjM8?nKtnrrr68u2FUR;y8ygirWGLF%d`L_d6}I6v%Jg>0H>GP4q)*z%}~$Y_cGf6 z-tjV90saVQUjg3mGK~PQ!jU$Bm%YqpfFFSy2!N-+3NXN_a2ym;H z*#L08mst<+WiL|;@Odw@4&X8`vlifDFS7>Vl$WUi81^#N0B3=3Hh=*yQwh)mu73dh zUS>7GZZES6pxMi;1lZ(d$^mM<%nE=?FH;6k=4F-x6nL3ZfGjUl0*l+tDw)47Q^ey$rUa zXT6LM;0Z6Y1mIyW;{~|a%Pa=C!^>P zf=?5Cir|w3pCGuD;1Yt12`(bIkl+G>(*!|+ae`9>V+5lFCkaLf&L;>E3=^D3a4x|) z1Sbg2COA%T7Qr!sqXcIX93dDY7)02^4-oVd940tK&_~cqaFC#f-~hoH1lu@2|5V&60{TSA!sAmO|Xlg6=4_OLa>uy2f=oNW`b=9hKs+GU+3M0UL!HRCW1zS zEd-khHW4%+e7vL!%eFJHBwF}50ylw+z)9dB5DDxA0)Y+T<0V~K=Y0iBV!BJ7hD*W! z`4!p^XBl-8OQjR60%6hp53Xx{l#8)g1po63VgF;R#i;*ZV0zi){;vC!yTR>pJ?=W? zssams4>`|rE_b})_$>I&Gl>t0=ZodAgZsGsJlKVf6J8QNB?z|D*7vNpTbEdVYPrm^ z!eZec;9(<%I}5zqbL@B617MuwZRSSi9A-O{ZhqE$wt1=PWp+KgiY;Kh;Nkvn%)|B9g|;KM{kEO9jkcAxSvHIH9qSv`m#t4(@34Nwdc?ZhT4$}c z7F)BeY1VkF33m4%vwRD7d_Q3sgWcXkmJZ7{ORX?0_|cMIT@Y2V(`40fv~fhfl2!c- z)!7%C7tnbxBcBS{AsS)VVozNV^|KXzlF1{A?Wf#fIxyveM1X0;LEltXsCFDsf z^h>O=@L54r?DqL6R+xF2u%KAQ@hgL<&Yjp!B_{dKh)h1aDP(#^eveX2p-uEu1yQei z*;Xo?h=`M6)@FkuLEY{t6-k>#Yzj)6-EwuTQe09io~jMf4l`7f9YG`1*gdO)sQm4d zRl>qZCT3b=h3udP_>{6!1De!9RQ>kIXO+>xx5Lo9!miW}>njH~mw*_hd>ZVVr2JGg<=HP0`XNK|yHTIN5tY0M}Jbncuo9vGU;j^+8lz&p3#xuwWBCWdCZa{P@ZP z?qO@N9J1Rs>?NtRW};&{-=0)rljbH=ZuL|LQ9V9cwiFE-%WJ?voYA|EEOaHZP+8#_ z7K3GwqmJP$%8_KRtX6Tm7SmVgr1CTcenMrQ=Xh{ANYgl+EH@kU6rebgGxiKODJdjd z%dzy`|dMt0Rl$$8PFE#NsAYVw=W;0ZN;Wc!!fouoBxeo)DN)o37-*TnC;I zfYZ~G`gpjKFC1~wSyxiDds}y>6a=Ni0kWNsFeTP-I(jU$Hpi_A;-LVad>$vvkcFDl zaQd)#me!`sQ;cs2*|xIm$7O=w-Cf3QtGRX#yE7)7@7t!dthAf6^r>;Op7@P!+mhNqg4t&Idc z=6f50c&s2_K7*?_Tok)>4B`UE)P;3HJa&*9QrDhzKC8luMC4mjlFNg55J7oupwm5J z9@Qv7pPalVi02TLnm<`#!s3aoNehaCcpgD%vJ*)mKNnl)E~pOTX#}sV(64fiUA+2e zV(Xj*l|ej{0Ifi3d1#J7GK^qs<3xVnx>#cC?8KEpJemM@A5OYJ5U21;zM8>#W4(K|G6~ zG-B1VL{!n$7-DPutdby}L;#ZyQsF5U9iq}I5yaM+i8%jXWEwVs5Br z7rM8({^9y8Xz_bNQ~v?b$nSKPJDztmh?m&^Wgij#1(x$ow)1R^^=xarB?vmy1Kcaz zX7)a|0KEIn0d|xB8=f7&?$2xrWSC55TUB;;YuJDA&Wg_0=1YT|`HPR16nR_gx;y&& z2Ku@N=WpvC059hA8wZCvyZc)6a&k*sfuPGjFworx2IpJr8Y_podpcXIhWgPdY-?qA zZ-_d(s&RjN|DkO8p=?Gt8|)x~@zVirZNMp@XnvE=%v;s+RL9b-k+-rt1`hT2ow3E= z<8L4EpIy;=Y<|_It!z!eA#++ZMNZZJy+iv{KKTJr;#01ZC?tn*>bU{COr=ddoJyb4 zuswjE6?iY!|H6BzYwrbTP@U?V7#0HfodRk2|AI7fEm5k)uqA*WIJ0T^+{p;j+SxbQ z>mO_#=seJxpPicxb+omy^T2?n8rIh|Wy7sjIfbw%^+Vw2prNa)r@Pl*)!wn+A1W4i z06&vZEHge5Nx;_XAQ*V4?L?o{UETiv+TJcdG^-AcNX`I$XF(+BS|)DKAGmPBR*4J+ z!VmRFCJ?;G?An8ez@Ng%99x1X|UJk3<%HPnG^Ia1a0vD-Oc3 zY^8a31#mnAa;*B0<-it>sVWH#GXWg*KuTwR$SE23Bk0x~D&ra8j=ia`uO}NmREGw* z;{hBvfe_svnGl-PPXutp1*sqWNK(h*mwR){{jei|BQ|IO;zuLE&>`#^{GDO_)`0vr z&IsT*5Xxiibj4AJM%nOHsY3r3SkZ@iJA3?{@&HFkY?uw;z!6eAHr>>K`Q$%jD2dz~ z)eia(DqMLoLKIN^sMcFypZ#;hA<@Y~a z7C^JoAkw6o81vOae!m0jb2Ik7+Y8>e(lY#JYHM=A*M~b$=WD-Kr(4@_vv`F8ox-Ws* z!_vTPDRM)!$eK+nh@KNjl%h9BiypQi1SI)^1c_u7+!ZkPuG6vP+oRksQlFu z&hG5#(M-u;M#k(6%%lX|rUlUxkmf{_Vz17gka5=D75iryihm>qCQu@*?JL8+(N13kT*Q+(fKh5dgx z@m^tn%ltFb%kInFwUeK||1ZZJKIcLP-|^P0^97U1%9r{Jit>v)OS5u{I=iw8_7-(! zm6YU^W)&2b`1f`eZI4(3xCE&YekyHW&o%#O!lD%2^T_8|FUS2`g-cl$Ae|~O9M{aw5dwyYVV_(nE zAgrBK_4N+IG>-~EE|!7HV{3=HJ2fS7Jg*=>r@EkcO;%1#etuR#Nq%8gNnvS4R!u=w zX+cR$4TWAQIHh`pvZ=(t)(mBz8zP1Lf(^$b{)ww5$*oA8 zemuXrB&RYT8q=D}{K_n72Q^t0Ybx@yimG!8))Z9b6&Dre$FBa9YxyBrKW-BL|2B!q z)wWPwk(*R_lL%G057Ib7Tr{7-h)-5TgUPEbsm!a$&8jG@$jK^LQ(BN!k&~C7l~Yoc zm!F?gP*`1>XWU?v57T^U?x>OcKhq+VHjZB``jE&U8EEvA%0txG5H%xmI&%M%7h(|J zKZS9S%NT*APFEu6K48)pl;WzNz)5}Lj6ZME2PY(>!G}wVp|1cFBpmT+(j~3=EKMzO zlwlW)hc;bk!h!JCT$9OX<^7!{1znwm?O6qWe{NPmK}l&=X=i6=R&Hl!K~Yy%X>nI> zsq~@Nm139>^%tD0uv|1qhRE&h`B|lTrR`b%g1nBs`Ryegd3hZ&`z?NX>7UZUMk!ZZrM;*zsI8{g z9BJf3Ur?eXt$kAIDKG2#so>Aao$f$SgmvmA}d|pg`Cn4EQqsBuWM2J z0jLX|7qZk1z+^nm_JO;BPTWRT+UG&qU4>eB0{8!yn|@?+-|0T&UgCPqb))N$E6@2C z=dI3uX94K{-|0B%Snm+Thv5W3qnKp>nf+R@NLMKQQ}~u}mQZN>Gg!y-+fuF1TQ9KI zSl+SRWC49r{!u=_ujKv#7SK*`%h`9q65beF56&9@$lS#AGb!d<%+-+Sf9kL2q!U#6 zs)pT?LIil*ZU?iw?LCUCWYp?!=#so5D}5oj3#2&RgU3`;-5TeCQ5lFC>1k)2bU<2%V>ujCBy%(SN9NPlD z_~iKOX~c~9q~#|C;F;7cso)!U43GXEGF%C(PTGLDkc>O4FIM}~Vv3a^&P+XN1rAYt zi)RR8$w>>wAZ?mRx8RCL%5DDr-5s#4t9a!Qd>tu8Cwa^pX-~3p3{|3VO8L3qYmX~B z$xVlFPh81KRwL}zL!Hv%ZPX-{2!d}{!itl)ItB`Cs5Qh^wfA(?b@#wGGAeN>is0Lk zu=*q}ErFJwM7{+~ogVSirdCu2+DL+LJ4j&CNC3KrL?6;*4ll7#6v5Y=uzciPAeVKf z*osbhPc3TlY7D`*EunJc93Y-UI@By!_w73nJ?BcX)f|B(C$w%3kpsn9b>2nz;_<$m#HtY-kk09zRL<1) zrO%izy?UgdxJXrMf=(ig3&n<9Ey<`JISg#PEtD&Lyr`MdN8^2q6IYEK0yZ8}1ZJD| zcgL#Ucq*{HM=oKA3W6-GtsLnC4oZ`i{KqJfqROF*#rqb{Up~?cOqAXWy^Fw9?ckxP z4AdB+FMZDHk%K_2biVRtZtVU?8%gw~%qkh_0T#+)_1Tw{Q8dyGgtF%V7W~FXl@f|j^2GyjL|;PE@{#>O zm`JM0Y~vwty*?qS6if7Zk_t!mN#Yt0RKj~gIb~mbLe@wZ(vhd;i3S0_xS1ps2SOV{Jx>t^LXru^r1<_fA;s67O zyJTc9W}95?z)O-;Xrogrub21P=jDvF18Fk3R%8_!qte=ZwuEIPdw?V>OFEUxhDQw# zJAD>$?MNF?c+qhHxEXKm?;i9w^})){KwnSfhaWcr>R>?ApRJE5co{CGY-BfzivuU* z)ZTy4-?=ri;c9ruQ3S8~y_E71bWE0tn(jcYMhX{M_IQpWsBwV)$8oDhT7f+cYL}?eQ(BL|X zq5$=?K#dN{6zV2WlSXx@7-=g}An#A56po;qvP2wTL*BRe2S6`2d}tC%Sb7e0-%qI+ zX$HCs><54jMo$sW+#ACOehOV?sZ>OuldgOnh|tRwg|uQz(*G?Mv3U+@m}*ClK%f}(-D(XsbmtMU zre7ys1n2ziLcsQnZH@I+nAQKlvXDOw7R@u*eaubf-K@aACP;mcXy|j@jYaoXSzbdeXgg^X0 zDm-X$_4{QdbK@)&1ZpS?piT52EPr){S6eGUQ%Bkk$gVjGnh|)uM5{N4iag;JEF+hTw7n=(A=ou0qwSR z4V*9YpV`-cp!G;Wc20JFPCgtX3$>wUc+%C=cc#?5LxrZMIy^Ukh6dns23l7C!QxR` zcctzO+eFgs37`u;P=*DgpN7Ist4y}HAM6PgfHi;?37`Nbb)@A3w>^CU=tap2p#MJ* zr7lW#VgK?&I8Uh7j#h)JTGiL-uYe_iV*}mVPlWb%sO-1^Iv<4hbNoLluyN(VuNG{z z!Z{GoJV!T%P;eiO=DAvcVI6r?o%67<4JoCEim@VqRue${p^u~(ry;(<=q5Z9WdStW z@IU(wstz5>-d44-d+$JQUJmRP!|ZgRUDFNB4xsf25dXh&mY0rk$@N^`a}U)IA%F%d zK(_zN2~zzLv*E=7G(Gx&ZAs|`ue@`;yu)9_; zu>Ws*otbT7?qa^iT+WO$XEA4h7pD~PNoEI+WPf2_XP;*uVsB=zVJ~CH*|XU`b}ze? zUB`lfaO=;l&w>ZGJFVASzra1pEw)~4Jz?#!ws7}yKjgk_U1?ovook(G6~Ip4-z{%} zFTR&8&sn}_xzF--%cq&ymQ$AF;GJ!crOr~tR5DqXr4|qWF8?R~xBN5wL;N@SFY}+` zNBN`t8GI{W&#&fl`E=gH3*cw%P4K7pEAUqM7`mVe&c$^^@!_k*Y)6?aMU#f)(ke=ertQwc7S=DD`a`sI#(HZCYjPfm7xd zH=9Pcuy+|SH@n!>P}uaQ`dC`(V$=0u=1cmpd3RX2WDZ!HY&x?K*8B#cev^R>^nS@6 zj|T(Y`c+)^n3GLrl!aUNXcAi*LECt205sazGaYOa#Oxjf>ox3t49E-p=3BIZo3()( zw1IQAffL%m25n%GHo#~Crq{KBm*j#S^jGcQCzX`>jL2InB5%dx%R$lorZ0tlelqm) zQh`l0OA*dhf1RcN%F+nIZjDi1O*w1_GX=w6>=uL9Q>S}98)vimOyjZMj{W_8z1?T| zJ7ECd0roxE4OsQ?0lQu+UTL)WCCIj^uW_&+UZ&~qy3oZUI|rWC9PJ)7{{p$eZ#e(b zZ+;5vDX#P$9`XGyd0JB&L z!`p$d=_is1%6=i!>ts{S7`^dOd+$JI0=rBit_EEWJjT=5(ca@XeM@Skuz$%MvaudB zZhDqVEe4v4W}%zf-cpGNJlX5-Y*W{w*=*z69;E{?e;uoxZi$&`l5!@m@-sW6f~BQb zW=I=2LmSv@(D^Vo$Fn|jc#kB#G>IR$PyqEZ*!BZw2=b2<<%cN$Fh8LDxJmhOh4SNK z`G@HuD;p2#>^pM?yFgp(`pu^!?wmc3Ez`zPYNpih6vnX`Oykf19$>=PV}3aN#$6Ia zia~Apk5Z>dcl@2^A7ZJ<{SBL-2{2L>m7g$oYM+d21N*cAyFp8nYM$B=F2m^ZCL=5L zNJ=0B-=iEDU{mk~$)PIfQug)rA7kg@+m~-{^!M%?+|P2zui4+z(Ra|#yqC!4VlTU< zc1;6y&!Dz^u#fqBq*%;fF*!vn<}U^@Ued*Q5o73iGcQPqQ_syjZV&-nHMyA`3@?HC za|D<2tlPY!8{5?Ox{YwgY{1{&hjjx&OuF(TP5F^5|1dwM{CHIPagXxjD&@x)l^@`E z!3{={ISI^H5?q2QZ{P1?)6DYM3X(izko7i$d*;Y{=Ccg$tuwfn9(m97zQMiM4DQ`# zaBp}f>s7KK@2?KcVzbP+P6Zt=wgu21?Cz_>){8HB>mk#vy4QK5E;OnO?bd}%+K^fI zSvRlMJxkMuO#jw}eyt0g=U|h~O642W2HLa%seQ;J12cylV9E)~2QaqzvdAWxKlN82W&H#zlWb(rNt2?+?PHQ z<|j0*mN}^njA#R0+Q1fVpjI2G(FRI10rMN$z{@yt*arIie)Fi*8?<+{_jVjpMBxyK?Pq^IUw7@|{EN{c@6B zl3cj|EDsqDhu>JO($P>s;?I<+(c}R{GxoN?UK+8H3?o>Nlw0O;S)x!KdQm2k)P#ub z*)oZAi#Z>wH!zSw3#FHFs8g<|P}?Y#-{ANGj|uePtAihSSk?4>X;cB{Di30^O+)>? z&E13hxAe7lnjeX{b2nzD2~NJ&_I66` zp|J3`u?9BvVZWRa`k92qCyhVl)`D$>$&yAUeS_^i7{q~+>19a>yhRF1%+f?Q->e_G zN)>QD_Q=wGNjG|<8m-#j(eFP93ZYW84Oo=Q;L`jVUFei9)Taye=tA(}(QxtWLhZWH zN?oW-7n-dLd32$fx{wR?|MN}PncQRULf0>0ro6%Vs`K;C3a9A!k>fJQR)jaaV zQ5Ex5EeL2KWV04Vve2x1L+bsLVI%T83scTI%|r5y!*eE;yk66Px~WV0{`}4YNSvEg z;vvob50do|7sz>jO2cVXFZcFQ#y9!f4;UQ)h?++|0dUNlz2Y=#ohQl$f2F1~33;V> z;?CSz%TJ@$xkt8HT079tI|*uxChpAfRG&tD^He{m@PIvVs2Kf&*dI)SgBn-dxoqyb zAnKhb9hTWZ_1fEabaG52;9&3^c)6>FJ;bXDs&(H%oa2Vo>?v@M%0t|_ zB;OxA0qGUXM^&t0skBL}H&aN3x9Yv~0wk z3(^h;k3*_e&`u3gH9YqdweI4VwYy_LJsD(hdPv+kKdmo#7Nl7*oER(3Np=ablazXd zok@`5u~;dFTJQ%b*$@+PXF}SxAZp!v&!sHtkMv9-5j~-}b4Jq2AZp>ykPX?_hLm%m zB%*ZMN*UF5iaVW2tAeO`pVUtNJmhDS7ip)MusVoJ_gS+0jPP&7#H@A-GjoHeZNF?; zn1>W44-q_^JFQ|}5Ebiv-PExvvyF9->mGloN#HmO*H#Czd`A%u8jH0DzhtvJ)0oQs6nq} zc>)rCPd#KAwdSXjxgwP1b&zFLu2-^L5i`pb*%iW`wUA|0r&qE( z0SUjSCb2k(s`E2s5jDM-QR_k5r_i3E``S~LR1!p;`8kc$hUigj5hZGVPetO!AnMF# z$z5k%UGHXGpKhz`>m2IQd{m7@2#+l6$@8oaqCUM(9vZ1JDsVW`5mThFxWb-v&&nXG z+Na6|q7JxYvQc9RduDl71SJi7I$LN}ddHabN+e;A(~}>RRP1Z$tDttBvFP{M!C?j} z)=$Y{#vYrvBZ$iMzH^}yWXQ*iW_QdW#qckT#u<)=P~dl&zLHQCL{)naek5exQ)7r+ zuAJ=;?tt+^vE+^j*o4N1n)D{m*N|M;Wx5a?kD+qD;vOv&Z#uk%T_?c11}g0*(y2PD zerOu1)=|n+iZ1Nx1qT;gv3_JbQZ-N17~W;D(uDq+`dPeTxu_%qHVp5!D> zf1G?mmKq{q*GA8gU=zHfTE+JbEV}FkW8a-^(l|_VGS@~Fx{>6Bsl?_Z>?-!){J+Tb z1(W+;_c^cv@C(;xT$Rp$J8yEH;auT#I)37~#<9<_K>WRUo!BY*>~F&wK#M(5ctSW| z$cMB4x7m){R@+S0`>g@%Qp@X>Yb@;+5B~%|z^8H#a=SR5y^viAr~bddtTw-H{;YYm z>CceZr2n>zE`Va0BYT+At_a{^RNOuo$lg?^oZY~Yl5Wci5Ae$?Z_1(FvW4mEM>FB= z%{YPM7BgEBIzcieeiefvQ??*u>u3f@m^5r9Zt|j&TIHw2QYHqznJM+7>DZXgl)emj zN?y*#v^eu+>4{CFX~1yN31X-_jW`9KIy$~AZGOw>e8_*+Fe|kMRym9`AWhgcBiV@y zBAiq#OP;rBG!+{fHC}do-`;8P)li9LN%Pi^rU1)i`J}X5&V#~SHN1cd6LlH-yE|Z^tKQ$eZ~xvt+_%Q;Oo6{V zCCZM~S?WhMh!Qpu9EZ}fiShqTPLCfqj zSB=gBUghOs2Q|aQiN?}dW)rJMkdBOhKi zcsUA>us2fT@Dz?ZfHY}CtTz%)&6L>1@=X;>!7BUgXaKoy?I2_R@2K z&spawJc$N!l4^-oRwoM2x=blQ1-gZ^mYqBfbe^i{?^-6JH=N`2EIo;)Z<0!5zF{I_ zazxR060O^K!9G1C5wbxb&S&@rskU>>f}4%-NdW&<05C0)v=B(F!oZg-h^YehMor zO%f+5gfyWPipmmj{=d|;-{ii*-Q>2oZg%Z(nVnZUS2$jFT9_-43fL z!gWG}?RDGvwguKlt$r(Ox!SUd|0RDupT+$c?EGc0kHhJJWab8@(fp?QTjs;2*MPD7 zcYJg+40;vkzF_v^BXsVY2+n;+H$n1>?`D1SvE1>h#Ti;^!6g}ab}O&l=`s_Gn^0-@ zRE;*kJCmLJvJuQClVBydY#*%$F7jGssOwRsjAY}ihcH{NXgY!bj?atNEt(W5jTh_k{9= zaSfyEAw7?>xPg=(AOW#FBe{9BR$Dft2V0ppU9R9&*WX{6CzhuJ=XD00b3Yj8T(LY2 zIIoT5T=BtVt^k?m1LrjcoIjY%6(DnJa^q+XaGnFpa#GhQeOE)9S5vDTL@bu4q&ALL zM{o`84^4w>g;<`P+A>-NT+wCPnWwabpn4|RUAE>@7ONL6lpXqBET@k^3y7foL9qP~wU&m+}a8DafrM9Y8u<6!+ zC4~?pu{>^R$LMmyR6b}v)KnlZ?xjtmrI1R}un=nzZjxUh#r$x&Gog7D&$j0ulevCa z-@S7C%*IhXaQ>mLX_ec=&7(z74DrJPz0$}h^Y%rYXg%gGqVfbU`Dx0`%Gza*}3SqIwk)ailh;`)fd0*LCDeFg<0b^x% zMj88sNfeW%GWRR%PuVq^4K-$&vaJ!3{uJ0o&s*i+0wcw&6n&cv;wJ}DC-HPtYSbTp=kRp7X7w-L+_N~Z%< z>Fd&)Mwh^wRQ4Nnv8Eukj*c(ekk~ltmBf_?c2gF2O?NN2WY!B?oxWjoF$g;Yl{jD` zJ*6#SEkkKfxGH1Y=pv92J_5u}UWJ@OuC+X6YSO-LQf19vhu-Klr-E2F{R$~2S zw11|b$p20&GykdJrVp)?S`7yVOjgTeXS?B@94^7>j*=@&OFFZmLdEeElgV<`#IxOS zc2W5&-B(3Fa$aLHmkuwYmKn~%a1ARwQnYl!;}J8VVndwC#6PXckFr`7R>Q*Sqot#e zixuk@1Lq?+D3JG3BUig7%M@3?DykEJ-$GdZQo46(No$&7O0+^n74YPq#V%c5VKD4o zCM#w|f5(2XkfZBsDpsaL5`DO+kW9V9MmQriO{sMWARm#bC{F_ZZrBDIj;CHi8|gzx zhp#A0G?~~NplcuYPzpMokZC5N)znFS|EFPK?cZRQ)Ojd4bLfx4sRun*YAUvvRnD_+L zL8iv|V~?mGdqjofl(cdDu}9Qi#WAAnhKsyJ&6oe8xt{yjBP#gKFKAoxoB z*dyx4PJ*Et$S(dqn5Sc^TA|7XeBncm2f{H&Kn-YSn|b;^p&WkEp7{Q@JSQGDi5=BdXLPfzxEIJ60?i z^oT0Y&cXSosQ@mpUu;?kS0-G|aBYWcCtP5r-_!zED_pzb+6z}3TzlYZhpP*&4!Anu z0?YWOeQ@=_h5P?tPS$h=TnFGf2p1TiH}%4WX&!tKh1J3(QrTD&eYvs|GG`q-a_L z*IKyN!L=T)2DmoBh1T*K;i`kH9l1KY4A&)aeFm;e z;rb+8pMvYta9sx1a=6Oi`Yc?R!*vB*FtL{2T_GTrQ4U_{`;CDvHPj4?dWkw$CoOzprX z^u)fm#~=D7R$2J0F+8^6^GOTB;kJpx41WFqcyV~^Cgmq(haKnS1Qso4u!Bya^;f(efV^$EhSh52Iyh4YkW1rDh?C-$IlL(Ow zc?i{TP__g+tt0E<}tK*wLnq3 z)SkYcqKH~jp?bGx>lh21Gn6eT%^~b5GS*NE)f+v>#~9#St8CT9K6X9Dq+&A@s*B-$ z;`tKgea1W|9;^IAghKUVV2Vdh6sDnb=u?zFL?~1zdUlMS3+1#(*|Mwe)5tSL#yVP| z+Tm#)#X~Ap-LiU6^u|23gsKHlw#LINQ|i^Kp*fqt-kmxA1Z`kNsz6P+1%x|XlJe7P zy9b!<09}wF%3L@p##D1P$&E3Lw%Rt+37lsNvrO)D-455KuDQ-@oJ$-xIV!~`#8xrc z{)+tqd##-nZVvehlmfWbkh8VeUR|Jy*?T{{Q~(|MFnvW7+?&mjGHP(x+Ig?+|8zS_#+K|tim73@S_NRm>+{5S@`2o_>qY} z?tvdk_~R=0F%y4$5q=2xLot~Rd|lhkR{~hKn~hCo^TnnW`co;)OOa-(nKu3*)2y&~ zlQyFbv|(wO)8XD?J-?5}j5hzW$*CGy*V@$P-!Zuq6-wqswEL=tZnUg4Em1?Gmam)U zsiEBv3em;1uxJN5LMoRJ#lp7b2c{&|!Zrk4V9F`Bg>CC~rYB{MIWQFx#lp6A z*pw$n3Ry>De&eOKOLa8`#SVx1|WWVo^*} zD#itOnx0MBayk)s_zp{RL)C&=xP8ayyp-0c+IZY)`hHsDX|(W>g|=%zFt8DzvuCW1 z0HbpaClPjGQnARvvhu3= zgQwk)T-|UyB?s26!5l+dB$;f;b`nWW!~&9CnR3Qy7bIIXEGYxRo2O)zEK*{VHY^;2 z8WJm?ee|>w659;toTMT(j!v`wNn015m|Y0==lh7~Xo>8!&|FI~yXFu!v-Bx|2IS!sIT&;_YazB+|T9%2ExErmCX z%XZo~Os3vHU_G9I2_#)od+4+clCFh=n93VAwlc3@+=^MuF&IFS#VO~U#&y4DSs6IG zVf(39Y2%c&d_aJr6rBGrf-QabK{w+XbMek0$L}4D;+|Ww1v|CODVLprSyO9x%aK}?##WiBqy!?eI@q1-+T9W z&)v^G%R)8-ITuopb`*kh>+|>uT-}#>Wqu(AVbt)2J$f2n09yd@3rDm9<#Vro39(== z;KG99Bp;iEO@KC!E|LIk(;q=7mnX&8`9T>x3%&~Qd-RJ42TO)h z_?Y>90B+Ui5D3@*P9Tpto500b?u+XG@v#<&S&ECbPL`3|Qm+7UC`ESgNG zIplFLB>@}A>Z7`bA4>&xc|SIy%@pU#eF_2QhxX`6{E(+nxXZ;zE`Avdoq3kvyY&QO z!N|i|!*Ph0L_f1nv_nf&6As&49G^E#sG$dt6=Vb9U_($z=$>*FQyB4bv36^ z1M4(IZafX)8FHzlmbY4-gmoHa7%I3|y^I;ug3(;=4 zHY4Kn42%Rwymn5T)i5BPJvguFG_Et$du^J1IG4@m;ilP=JE_tq8G+(*1rKue&duUk zn~ub6UYnp9b8Qk4eS)N zn{*oD3I_DnHK9hQ76%_bs{M%7=rn2;jBha4U4x!YiSd2G803<+=NZzZk249uE%7Q7 z;X4vYH>kmh1yVHn2X8Si4K@xLWVMa-eUI z{n6G3Qu$&$-8xYyCR5oMoJ0mC-68D8!qNxWh=yvz`4#|iG3+W z!e4@1@7n^6ZwL=SJilO#7W#N78hl%D8)T}_*Z+0>aNRG2zu&Ud{I%u-k)Nj80yucR z9Y;6J#^OIR81dWmiT#DDP_*0$O-4+x(#;nknbOj7WJKCVS*wjh4_Qqvc3V-t_V9>} z!8)BQ49$YR-_fAwwV9~niPfV?`v~KE_SwbvRPh)b(Orxq|4=gl6Ef9X?%%8$Sm z?{a+i#)7}onCy-{+$KV;rGXdS<=z-xz9*EXt^kb^RAH(hJIL*fKDg3VpufBY)*99^ zwr%0N7Cy5%mN9d#V@Dlo$Zj|OcCv-B1n0IJ%aYH8ek3;&YA*miVdpB5bnfTQW%KDJ z=eEui-)J%g=Xe&{!4}{;LhTTM+buVe8`S{WoLUGqV*pNc7hHbiEU)X8l^1VFH>C}P z+CTuM+tz@RH8M=4HRsC{)!BmDK&U%~6sr}V`sl=bM1zeN)z>=qq9NFte~ywO4^KYLVrL)R`Aur^c^YldF-My}*?n_2p{cKc;?Rwt zI1K0NAvp3bt}!X2UgvrTmaxsy>{UWFvEeTp?yG-y{f@eq)@=yCr1n2*wVF@Vw1-|A{6X;1V6Zx0{ZMsx zbzRj*s|r;URXYR!68JdytixTS@Xvnx#@ltma#{l1$djIvhsVf(U2{_#10nNKhmFBD zcAE`57AJlvlbWXvcyKVipIu?oOL)fzmw5@#?z!n5yyshrckprR3f}P@|10Y*qu2#}gk28n^nRhdIz@UWXR3weUB;)`MKJ+I!Aj97b1_fq`2BBi<%+Xr zbG2J~(!}q7sTO*L8bX!|-ClT=3)|Vod0eLtyZ7tcDq;aua9$n`GP zcIh&|f{w2U71sc)Uv<}R|&h2w~ z{VN<|e5?g-=hx7v*<9O3t86)NE5C@|w)WC8$@%K&$a;1WeIq7Bp+&*n{7!ms?S-Xc zU9gqiRqJhQPm^w1m^vK{Qt2e_bC*swu$yeANm|n*O!v51Zc6)Z6%*4L@ke)_Os%uC;2%`hi-m4iobCV%x$HtdMCu4l z_ztQ9RN0la0a|gzl%*=*Tp9?CIcBOY)|Lw$`CMnm*+7C##s>G5Ys|*97xx&rn0^J9 z35{|F{TCG(*Uhb*B7l+|ru8E@2vQqFPNtVsGcp(GCVLggn1%>!y>zNt_@z1$;pUy} zw%u{*>a>?+4>|E`ga&+ru06lXyluHT`Gy`PG&UTRKUJ~(>*!0+lN}C?txw{85?Cua zZ`B|)(3yA++{)lu?VcwP0cPAz#AGf$JDWsFG{b;5DsCyqr)G`0B zQCqO_EO_?;a_3bciT#5wH?#B=`>JJ)8J#q;yAgR~!z9j^X`va-NTrQh7N#1z{ z!R>A^IB66#_*Cm|>^t$T!E0#8C>E&$!j zJk>q@$pkrB?y+6e?VHn>i99-X^t45B2lG$|jh#~pG@=5}P!9#)$z0PteS~EtOXMT< zu|P9~udjQ1T_k+I_LpnEQ!^C$KtfM7+ z7{SUGK`Y+xN;YV`6NJbXLbH&zeAEZK~43d-I=ZRGfH+c%CK~= zii|C)o<)LL6!{r$qAJ1r*k;^Cb}+{0SBdd*g$yg7nDuRBJ7c|ll~}__cAkPpAaXtI zwcuACH^pj)D1T%PvkubAsDT~cs&s*63Y7D7d9|?+DPSpG`xeHz@+LxUq_N5Hrfsp7 zSCgBR`LolA*fAYAf40Z@P1|CZh+LimH+_t7rnLp+HnuPIgu3ZtL|xkHE)x})LmVy3;aC;e9(c{wWlr*{4PwZNWrb$vaF zj=t{1KwF2_)!Ek5mpITC@9Ldt>*>*E4s_4-^v)*YG_`jc!V7`xi*}W!9o#`17{cd4 z>@saAH<#aWu=Qd3J3ML6WD^iND>Y|k;oQ+)%;e*A?f%flf4|zWe#@s zcXaND|LLOt!~SN!mC5D{$#iOFpZybr=E_2x1nOCT5cII~3kCZbez3>>cT$@Lwg)HA z4GoVDo*O+mIy5ml2$x9{lS8LQhR%&R|K2s-VZV4WHFpslgW)1+X)*37JV_(uIqHD_ zK1jLHFe~=^)E5@?SaX?!y`5e5&)EeCk&}8%b9^ydEF8?vJQA}a`FZ2-xnlX*pUOdu z-Mm&f7~3&?Fh=7^%;n{+yP8Zb4&|KmZ_M6y>~PJ8Y_KGk6f1F&ED**}v1)0*xbWPlrg0 zh1mljl!cN^!#LY?i2)R zz#y9-BQ*LRobfTGp@d~3K)`f+oSZruvVI*FN$6_b55U4c~{NRA*mD?+#3ui*hsb zLfZl4=n8g$J!AgY#T?9g*$n)?!75XLW(HS{#i;X_%EY|`cb21c#>4fK zm(G5@lF(pzz~VE-bQ-e!x+Axqet9ju2blurQdq;aR8gHjqpv z+d7lUp1#@Hf&SUf0sOA3YVI$-qVMH+;|==)@I?E+x-S?RJu-F7xf5{yU4B>KSeBN# zD{w4=)~FO8Zte**Sn2R?-RdOff*p%J`}PEt zCc!n?5g-XBuJZeVN^Bt{JO7N@ZUDc_gRj%`|AyK(23oY{zid7e`AFno({DGmH-4;f zxZ(F32J7EmzqRhgb;0n9!NUKIWmN+!@(`kNJmS!pdtJ~( zX1N;k6eESvI98C9lseiBy)=XY(z(;u}@~S;a zkj$`FM041+W@X)K?jS4|g$_n~YuS#acMoP5F~0z3+X+io!6GxPg0KX=Z9Gl_#t3Vt zJP4@HYpej_I9&tIZLG6W$vTW-MuATM%coE#CdgIe2rB`Tbu7D*mJ}{XG5T10z(H;e zNSH>`JROB!vJRt{5uy%0t1lIkkn7}b&I}}E^spvcn;A-IxP6<^&4fbj>Q-MU3bxwx z{J%Lc6KI)j{+;HQh~D&fO}iUsAot%L^^es3Ufn%mt@gvU)iw8rel+;$;QH#?I0-?}E1r~3;l+O>5@WfGlSVs}%NlM7Q9WZF8m|~p#vrlk0`bP(AiW0nNx2|Xwv7ED98ttMI_$-lpIG_i zC9vu)VL$um5saVTZfP#gFDqoofB^aeV?_>TZut~yWc!J+hBNMA^O1Z$<$1~ zv#SH_|EMh|*dkhOu-V{I{&%kG*z>=#s&ExiZ}4FMQYYr??jP>!H20=6MD8_sSbqqk zQ28jbY%d?i;rnEZDzJ9o<)ts+y_|zzSI2K=C?J!r1=HS1I>}hGm#HOUJ=nE&yO@VeGO~_CK^A}7-@K{ zp{o9|`bgci@PCJ&gzNyXtPR&(4ShQ_9sFEyu=+jKcUQeO@SpJ6Kmd;aJkgNU@qgJp zs%+Iw9|%_(n#~5!Ep$WX1az1p*(j* zwFb)`M4prpjqpa$t}fRA2r}E_f!japT#Lbz2CdDNj7-?Vh{4hoVs3?s^UQS%XA+O^ zmbT3&w_LY$K7QAXin4(i^ zTYcT*)e}wdn!)mmVH~m5pDk#k^OcyN?3xS~V^Uvz#W&9K6+#9+Vl=SUSe2_(zQ|iq z0)qw%D~So9)<*iS>j%~biv|lQNv79i7|=ez8^6UsUs9F^uwwm}y)m>FdWnF5g9eKk zsY?yVWm7Wus%)il2E^dAAxcQ7Z*Zhhc7+8bK?A*TSd!q?lc-7GlB}sHVGWx!LTnTb zu5gbsr*KxxJw_0txxqoTzRu1aZJD22ZfCoUY6QY5(HCf%sdpGvh=YhNzBnI8xVw!2 zz5e zp$x|aU?=u2okcXHp#hytEx*S-I*VlJ`IxWU8l&5{z@QJy)#gqtKM1n*1D;>sp_5Z&zpa4 zO2CxBc$m&{)LV4dy3q2xMV%feN#=Yy2=l_aP;c){e}AHh0?q%kymuOzpWsap6oZ`-qk(uvijF8b@JrX+fj@wDa*yd`?S(ukN)# zpypbjs(z~Nsop>!JfQXTb@wMBPDNjGwykHTFWGkBK*s>}BzwANItTjsXOGa( zrSQEx`#$~^zK#Ea2o<)=uK71`Jcb475nBF0ymO$lqkFckvv0N^KHhhrZAR%9e{XM}G^s8oBB-CFm7nYQj(sANx9S5Mo_0EkxW z?o1>)gnzn>FGb()-l@M-PUuU zySME?@4!&oXwUFK&w=62;f~QG*X$RdK}vn8t?KfI;m+=W9nltOfQd{Zr8&H&G4cpR z5e9mk7-vPDr|p;dGnW@M{R;0H_!X<4w+^3~azBIOU9X;s&tsp(SIka9KLWs`P_XMc zch>U2#xjX?F{zDZ@ai9mE98orMHs(yCLJ$84(ECEYeShO%8Z3up@?e^yy>e>OOvmF z%fX#~lKyd3!R;%)8kI2XW7o!X(&tUNL$YUC(lFmQ1!s0Do4twPlTWApD%2M(T`G4? z#@l4Q<7yw=UXAt*V}I{z>**L7ZX4?D8EWf-xnyYMK*z|@;nDKlA1ut~v(7P(|2D_F z&2d$S!s&W*#n`jl6~oET!fVjLWEGK zM>BvB>;#_UafR$`(B_P?!+pevtzGOM`>-M6VxM;`gn3@FesG_%te?%e_e4g@T z38hc$bPLuqXRZe8M|UWo^u>5KdTMH7V)E#!#00|4R!kfO#dSC05XXu}1aj>7o*n-G zHNlBM%j;Xl>Yu3J4}ZP+=cWX1N?`dCc>U3;Br%~_a+|GEf zZhR1qltraat(-}vj|6c#`DO2Px06O`KNiP5r0yp0)EV}$OEv6ah0e92yB(&=D5D>x z03N2$r3eGPm8p1QyF$UH$Q~6w1G+*@tzmSXaT6OYy~gm0#7%tZv6rL80W1}bfnEPs zmkLX<@$6VADjgE6&SWhUtF3C0?cFyQ2_8oL<{~kZUMdqC*Y4sKy4JI2i9pI~3xsqn ztr4xbJYWI*Zf5-DmeLz0@)fuyeR&-&HuRo!xNg<%Nh6J43Ty~01RBpZdUUOsx9UCst-v2Ljy`2mSII;4{`bIvc%L@Vn`k?r z^&Wr=kB$Rv1GDhAWKU;jcW*q=-8Y+HR#G%xGz|_-FB9S7@O&zrOvH0ZW&mYbA~E|Z zxT;&=_qXQ#%`!j2JKX4EJY9sOn#l*?zj$p&uiEH6GwDR%w5e$VrYS1*v8#Z&u4HQ< z@mgih-RO`u6{kkF%j2vjcAK>Ly~?b^=z%uXOKy>6J(UN|uq9bIeu4!VyVf_mg&(3a zlY{6?HnmHymt|sJgHt`Ln}PF*uw?ha!KEejoP_cC+iF?lI}VlkbOt+Y7`@G=+Q=QM z{$M?wm05uw9&Ke~=h?Ic;* z!p(*nN#)!{J01uNzK#Z@jaEeOF<=W$3v!||NkKdSf41;I$kuG3tLV8kwTpmxwpy{` zZh|8ZuR?Z|&Z=e?!HznK?pagEXizx0(z!=5x72`!0yCj(Zs}C^TsG?wLs_U$Fm}>{ zWsO0s)=Bj2ni|w09_Dpb%iXSm3JI%n9X-<3N)MxJ*wg@xV7Z*?P+OPMS(JM1YLIMOUhS-K9OW9z0txG9+HX)wVRSm1>LknTKcz#{Eo}J#+Kj$t zz+~6*ghN-1a&n>1g(5LKp;I(L^fd!QEsBJ=YaKP^@g?MVE+j-dDIMTXpnuua9s;hl zN&|wc41EdkXTrie`=XuE{ytiMAP|aDd(8pm4q_hm$`SEk$`R_o>F!SSE1T*kV3e&S z4w%JhCMwzq^4(p&QgK6sTX;@X0V1Gre45P$<@Dg5B!1>ENZH)K@SWny^mR(;HGN>i@qvuo!6R zYW{`De?_#W&q4gZV-4@F|47}J!yl@BTh0H}kkCJeP6VG0p057OYOU&9Rk^_T;Dzh@ za}6V=!q#7Uj-)#{x-#V#;t9_j?Rq2>qPbqsro4&~Q&sl0=CoJ3csid*6@D25sKQ=e zddk5xPxPiU=h?(M9tewGV3`e})>klIDhM6ijph0;oqLC9@jdpb3L6&7h(RbR_v@YAVn z@&`5Z;~ssL^|M5wB!p2;Vaowl`Eo7mIxc&Pc0UuAk7wyY)A}2vO}1l@)2Y*>O_l}^ zRC*ko#?l%OO-Xwer9cB=dze*(3&~pQx)zK@3fqzGvK-HkPuxEN+Xw1%B3N&IAwn+q z#~0%hiChZuje^u3#~7qA7fDZr&eGf+)%iqN{Q3KNWpGj3g^@?6CJ4Cq$aNiSPvFqC z9*^gbXj+CTS+%5_mBb4|vSz#K8!#m4)Cogc`8Qn57qat`%GC;BA-@&aIawQyMQg{w z*hG(m(K z1;&N<+Q9yOP3mg3b&NZ!$KYVs;n;^qS^QU6)EDwNe&;df>(rDkt;(Ms1AF>ZJncU9 zTRMn*=|)xNjk4moPPJJqh6z2fzMC*i?9{OQ{LLmMKId80^l9rdKw<67`H5V>*soKg z%EuiqF(3JtYbhbK70~ZF<$gEDh@CnszfPFFhB{Qz9N@WmEt!HlG&>ZId$OsF7uMD8 z9vd+n?9`Ba_mHJ0#!1k+1;f8ijmqD-Oj}zGbne6MN-6k^&N4_LH5^N=7}Is?ko?u9 zA1k_F^R%Q7BZ?BTvG6>I@q#tIt;g7|Q-{e`*_oV;V(W{x^xPbUFd<26sGqn!edI-T2AI?uLi!vvvPacOm?@ z;Y+puU7M}>T+KjePxTM0vsJ%W)d8=p%pbCUl12-edYDLGARH;u<3f^}6Eh=8PZ%;^ z{iWnfLV&Qb#tev~A}Ku9 z>%fwL_bA?)d7m8xZ&LwwIi&Lt#ViJ0G1}SD(YBD&7E{{gzP66euCBfwu!>J!0%Le< z6KSOZvqBrjAYq#Ie=Z#D5b!!oz&N(dO0if?D5}V56xFC9eYrw4o8V`hzdxVNSnhp% zzvG^b%OP)sm^fRFvX8}_|DpnU1Xs@ur%)b;<*x>ZFKUTPN3*%pi&~DR)|`S^1o#)X zFoZCnBxd93JhMi+YaB_PAp2zdGuOJD(;h9tefh~$Ld)c}p#)7K9|zwHH?<0234)C$ zk)Xomlqr2)_GuRFz|G4aBcx+4<>?csAKV6kx#b4`2zd4{O@KnwUA*Z8#?Q?nP29v3I&Fl)`w46&^q$dFnA(nqi02PwEIt=R9R1%J6W&w1wp1(GD^>_+G zqChc6;SOP}pv}Wio~@R(&xS|m;+N@*^oryf&X7~1Sl4b{TDNEt5Z2HKu=e#@k10u~ zy2&2dHsp@zeC?YkEP0PiJ^*38tVx)=$Dyn@VUAK2y~xC+)B-zHpiT)7O00D~y{ZgQ z$kCi(NfL z*Z>nt^E27Br)M~y2un6c$r2_og_~K04nY-asZ1&laZud#9nl<*Di%%8|C<9}3ABti zXCl8FxxFdYc(LJ&4Sn_Rs{8M{Q{i`o8*3k_`GcC)(659Bg1;HOrTSRapH!WM7oO#R za%o(9?vQWGp(1qgoTnN2k3un?Otl_I$738|;Y(QPK6!1GXE}AZr01cKL`Ey*VY8kD zcZe>grN};lL+1|Zi*H|*1piT3ZYP{W;s0UP$5L4J^F(?t&DR<@4^7U_Pb_GO)NCpN z6ATR#HwGp^IN$mSBwzj&A>BdxO?uKCoTh){i!uEHygKOq2Ruu#F552hW*nEDc_2Vx zDnoCrX*-nMTDu<#`Awc18Xg_AoFOMBhfa+Qog1NU^XA{XrkS*>=8$2V{8qO7mfHJN z$nn%vXJ1DzmD0j2QrIGI>2NAbi*dIo`%PF}XMacMAp8$nk>oY()VrkD1753>+M|V>krRWF#no+H)17XT6~ID*U0|4s%}4H+kYFcBkP+PWB2KVPc48{mYMs*x5(ID=f}oFNZM=UP89qcHAEVr+jmLKN z^unS10`G_r1EHiZ%Dp4xOS>eqzuo`B6{nj{tJB4IB1Q)q^`UIN@=kn?9FH)}v>rR`@zDbF84s>^PM-L$T)(4UOxV$-|O$AKvaX&6c z+#fCE+!+OL52P49j}$M+Pq(EMJ*N!cAt3u!Im$>l_CO=O>?QSNPIlseD{s4ae&OpD z>yQ~BoG%&9wZz?5uJ2pI+F8?L#}u~5336Jtb``4Tre6UpZC(8bI(p#JkhVxe z%ih35)iZ&XFSnejdZy*=Esr<$j!X!v)C9`K6|iw!3lTI;`8|LOWS))(r>AeO+t z)_tn(=j(EH$Lj8Eexmuy;eQE#BK&jVh45(j_S%20eY*DbwVB%C+RZipRP(W#pQ%aL z4AtBk`c~+pp`Q*t5;`2(82o1N!@*YvF9r_-Tq5bmEK6z-bJhVq1+AR<5l83g-L!qDx4g6E$(G5MJ4% zH@&54sp(YH&c^RIezx(K8ZS4VY~0@PZw-Ig@TP`h!~G3+)qkh{6ZJn^pQ%4me=B$t ze7NqX>SpT>)-{K}8h(HHC&DibcZF+f|E%`cYW3O&YWLRsu;xoOZ>@Q(=2Xp&(7%N~ z9r}gp*H_P14^?lf`exPdR=ui9gVTQ`@Hf2&==mS(T5`B|{Ob5?g*bzOUs1rmQvv%9 z1?<}uuy0eqzEuJH%L>@HC}7{Lfc+%}>@Ug>_zUvT|AopRstP$TFM$W-p;PkENqOjm zLMM2W0``px*k4e<{=5SA4GP$wQ^5YL0`~O^*w-mwe?|fOS_SM+D_~!vfPJ+B_NNrE zuTsFiQUUu41?*2MU|+6)eVGFGr3%=eP{97U0`|ugus^DReTf40#R}M`6tIQ@mTUQC zu0sNBhg!0`@(a=&_!0SO{YiP~6Y|h&^3ccSp;zUhkI6%qMi}KK-JhUJW z&C5e`^3X@+p$qcRtUNR$51p5XrsbiRRFj7$<)I0A=!`rx zE)RW?Jak$f`a){|e|zAiftHEp_c!l~ye1N88gG0zc>lklVR!vI>+9-%yzW-GvEN$z zikffMTnK$Ov^Usa{od+bRlDJ(mHk6DlUg*NI4tkIIz0h4`voi9`4}YkE`S}m<_Xa) zgb7V195(f6xbhgOL2d)Ow1NAX;!HY~zX*2bv)~qI#xQnUbogV0tX3qb3-zedas|kN z4q;JU9N`CQS2nSf2WP+NLUHEGJchIdbDa>@zEeJciTlf?aT80Q3u|q2!ltMqK_s(B zJ`sSsuyRcxOCn3ESzpzq^H@x~S1tVcN_3%(jq8dN!Mzr%!|> z+pd?7K88i#A@AG+12I`llmrXFAB6=vWAa{G!Xgzy0-L_zlc|E&o!a$Cs(7;P3YI^v zu=l{^G29mcJZB&XCO3#?J;2@i(-qJ{mZov()yqh*P2QuiJR$E>ApzPK^NZ+vwNu)m z@MJtQ=W9yrcpxmXKiZ8`*2W@Md8d4yB0fJ(#^*eaoQ?;=VzE0$!HBpO0?j6J;78kr zS?*hT`cLj3_*em1?UDD^#VoZcnu?^_*8Wag0a`bUv+yS$Y@rv)<} z4GAn_Z9i5bj+~kokds1;G0usH?-{)Up`r45=&$ZMNQ4qH93PuxgtACzn|zG6;q1av zE;V=2vm#hO3yZW6tsi?wD>PYe8~o2e>*0hm$SJ`t+2+oo}Quovh8#(1(dagO=k4` zzd3L$(2{HZD0Hp=R=?o2+$^hgB6Es@;uM87~wObQ{x!8oN&iVY$c7 zlVRB=nzp#vN(NZ;P3C@trCyQdyoa!qW!>`LRCF6u;Ql@Fbb1E7sN5p;0SNgF!nE4c z*UA3Vh5zYi|I2>sXTLGusVHF?%N~}GH8PY;!vBZR#Mw+d4Ko<}O`99Jb7n4h&$2H0 zlCVuw-Ws8`6;pdC(r4HJ|g3a#^!uu7|PYOG3tj%l6h&iw;O51!CkIcWvnK z(W?kc&DLv#rH9X{;+$4+f6Lt$r+pAYDcpD=kd1^Ta@$U%V@Gm50tC%+SKa*(uvQSOf%XhNUsbXpqEQ zD(P7R$^eC;v&Q%W*@UNd8MP$9brOOmFLU|}qiNEeeD z)q3(JFF%%nO^62JA~T64`n#Ks#0x^A=IN3&5|%t|8Ic*waWf+`x-pgMY$7a;nnIv= zh}DyYZQ1Nn#YIC5tW2zitjFD5H(4*+yO}N%!}6IWyBp94P?<=Cu*_SOzcj=lBS)I534ZEw|C|VfX^5_UfTm6a^=B7f;_4e*-lsrG=&U5tie3x zR%+iea3M!)BD+8u*#On0*JZB5qaiLwui)hC*|hlpghU3NOLhC+y@O7jH~lw0w;s^H z?A_bGH(G=Q=j?BL7eKYZ{tBs**=j~=n{$F2Caf%k zQbA6Qq#ijbtR9|5<{j7FVf`^eHj8FQ-9qZPh@>Osc#5(Qd1V>dFd-?bvoNKKvT7h< zE-UFsl%D%*Us?HMgtWM*==pzB;M0MY7c_sQc~9gWk&R8H@hc7AtpEG^sk--v|0#T1 z?Wvky34JQ~cfoztxvHNB=(F?>IZaspv1{aE*_D}H_sJzyn7A0vEwEnVnH`q<6=B<0 zbJ|)F04qEyosNdS*LQJsa&aE(uw^)A%AdU5e<5M{(@yKVWjhjAW>}gR-t|G}bxxZpLPNOuR)h;l z4yUzvW^gc?)Mn$wbYbwA2AMt!pk5u1!)_iT)X`t4TG-q+SV5=hf}I~6#94KkM6p-R z>C*f1ij>DPun*O87#dlzw$4|DHHUh6mK~^#?~ho;ZG`1DJE3q_!7eF$8WE)~TPXw! ztHBx&DUK4Bk1Rpr*Wb_ZyG}tW(RIEkr0yqdtjIaS5{;eEr4tT{yOUA}La>nVvPTJT z9+swTMVbeRI;AYzJS8s*Yb$J?I|$21c1S+T+gK*UhcYx2HGo@!u&+gLL~jvFNYOih zQO?bT; zab&8gm|mQxz;kEic6=bp*hF~>O};^n7UH=%2#$;rxJ2&31!AAB$+ii#RVYH#cfRzw z>+gB^Y^c4ny1NKVou;6T;d)?evB-~P6GeLOiI<*K@95e1O1vNxi?zpXAuONTA^GF1 zsj&9ab2pF(RhiOyvJ<;6>LRP*T@d!)Glv!Qc{XrJ|&NZ9B}+>Altc6 zw#kkkOZi*Ap@c9YAxouCZX>P8YEXVp1UqZITePkl{O)cAAR!}q$mapO%^=-7$Tp;Q zR-T%DW6WnT=^JJdUPzd;r04%z18)j6|4roMk=;!nXc}mIWy22}X6ipzzqRhC!rux{ z*1o#trJ?VIjt6&Cld7*)4FGrz|By|DB`@lgw+NjmF3`LeT9Rpzsm+yV82TZEk{Z3D z4W3VB=zko!<>6+BB{x!-U|C0Fd9&eg11n3?nrtT$7q$7gho{5~LfX~@dk0~OkH+M+ z%0A`Nvz9DJ2evZ3t>h>UlN0jkbbROy#~0!=sWg}mc-y)HxFSJ)2Vr@PhK)hliI43@ z>}wsqfe>w?e1Sq@E4iJrasb9+52W(NczU8xOs2B1vtr%TXNzT%b#1Tq5SA6`Jn5B{ z(nPkHOK8*&VmuBPK}jl}{87mNRfJrG<`{ObSyHbEC1(wZ4TPmjQb^uMO+vGaT5c3f zRI)ip^#F-*e6~bFn2?mUmgynFk|p&Ujj|$S9nH?>>61O$7b0Y{lAX)SXMZG>iN(%k z^XVn{uhr$OJv-RVxw_IJVy0@6HaDgcpvHW#M`bt7`O_ZTM?Zv63e)skp!zwLg=rsh z?7$wFwzYY19OQYQhOS~?5J_3osze4AExQ}EJYlxHXX9m6a-=})4SVJIZM1Dq>3BO zFnK=GY?r^-hFeB9E&2Nvl{^*_;4KV`2vTVyEG5){{FMSMH&lzsl|gjxajcl~=t})D zLQ+?c4vlv7j1G6Sb$4_gZR_kD>25o6wC6}$N5^nS&(Z#Y(W6I(S<1b2gr$-?B=2$L zTp#6DQ71}B+M3qDt#c6Db5vd#;^ZX1L-HaexHXf5$Zb&m@ag2{*yy;2sQ@IwXcrkk zMhe@?q4eeWQXXc8Q*c_(r4q1;7iVWvSH?1w9}ZSGJBb&BOwHNkadH58?k3WU725@I zYY#Ug*CS!sd{aV)W}5V)E!Wn|vP-3@uW=V_{wgeUcqh&Tx061s?wC?1rZa(A0^7QP zEg2-(^|ABDSwnyol8YvAp3*myUSxOLxI2aRuL}bxRN`HMQcmJ_(YL^ zv}8-+P=x7skRFuHSwmWH!9Fg3S63N-W2D1sP*yxN{@-}@(}B?Ap);YogWnH+Citdc zA$UA^C&d4I8nOaFMu3(xEq6D6zxgxGZ)z?yA8)=h^6kjek=I4ik^8_};2TXJYIIasUC0OUhPs8i zk-E*{e++*#{F?B^@bkj!YQJ9lf!deVzNogRwyx$cYJR=ushS6C_SXbLUk<&!`gPUm z>ieoUR(+%DLshSY*#8HrngY+rsvs010`|8Rupd;wen0{H zTMF3sD`0<90sB4$>~APw->ZOqj{^4B6|nDC!2X&7_E+is|Hi|9jO}0QfBW18Ogp5chi3UfKEiY=+;_ zy48ceYA+}J#wd15)Lux*d5H#UFP5VBy47CJ+WHB{t0r^eZ;W(G)m|my0cR1GCU}*c zMVvx#3ys z%B|5)So+`C3Bu9}tJn%Vx%rs6cpnSpx01I7)@1?OA2==vkWD#T z;mza$yfaX^vg8)Z{N5Mt|LEP1@0wB$DU=KI3ZM}Mnh8rXtbS#Q_S}`OEWIEhBm07P z7nww9b;?I$;wGQjT;BhNy!5${hj}~RY2J<}g}39|$pjK@lh4mQJfEBz18bjxcBN2y zk?(!F5+wJKb4aq6oR_`Jg9tRVStm8HYz1?Td?`^sL`AZey@6PwRpcy}Xp1aalcjmr z>7L2FPlY6zSrgvP?j&cBL_0YvOTsLCLdz|t655!@K%?ZbkjB-%fu50(fg^owM-KG$ zw)G769%&mIIndj7ps%~PZ**Yzz=08Nn-L_Zk!Cl6P4~KxOa=xI4|GcjYRNd(V~>id z%S>@@4s3+d@x`ns1`h_MwVnyfx8`^DCEEY=AUTOlj_FIX4a+SV9*obYO{1#3<<{tK z;<8X7&y`$9t(8+~B#L5KPfnm1rsQ3W9CbUT6$`nziZq08Jd1=vwvzi%C<@li)buq6 zLGi$P{q*bzHNS&@fUWrH3~mD4y`utHC<=3fP0#;z?%KLuyineTL9;FtbZM00dx1s+gtMz z`~~27DCA&Cz~*w9uu!?%<$D7Yw`kDO-zyTJQdlA^Chku8X!X3FrA0d)NR>^3gvGqw zBJX1a%UekxME0AoNM~dvzj*y*9Z6#sR4{*rszTs;&9WUfgU5MBqyQzPYVT7wl1s>G zh-{Y~oYN()nVhBPm?FkW@Tw<|Ag_KRABG4w$rDTSGud=rLNali5%;Dl)+J#FV3rS$5_&j=%vyhGa~ zlWis%a_g551mP}pJex^f^5)?2g;e6SpZnk*GP%`|B=TbAtTjgnT%Jpx3u(ZV**bh?YCi-M*?*<4hc=|%MqbE-WJqgIX%-Af zhnAV+%D_BpSr0D}O2}?aG%$;ueKd7a2|1^mWywS5JuyB1Zw~xMpe51#$>zHvZ;tG1 zdZ=-!;rHslUw^Ic8+8wb|1>;U`&7+$Yhs~X@IQj->ffo}Ty+^7o zH#-|^+MztZgBi*tE~W~QEde}^&5-lyC2-?(4-nP2g?+-_6>im83|a-fkY~cNDpAIv z%9?q+U?Icdljpi{M%<>Gv1r?5x2>=vOn|B?n{$eFC~r#7Bh&I=AwkDJsZD3$Xdfm6 zvJ_$KlTSg)J(Cl#^W)*BD*2L-vNc`S>nt>_!j@Zto-ag5W}lx%KobJ_k!WZDtm#Ibg`rhA`hf$ubre079Ztvd`EhN222#zq zYbC}BYhX`4O}ZI@c8hEa&~W^hb1+w))8a{b-$OMg9!~xk2|nF=EiT$|`9xpPs)gZ; z5QP$YKTUTdjt}XUoBVr1-qvuwU1vFAkI5@Nrg`K5tBsi#J;e1diCa<@DkNt$@->~M zY~3fX4&rh7&cxv!CarmLg?Szd30Tr;)LEKVg^t|u1)_lP9f>HX7lMqH?XeOUcLs*Gu;R*(dxT3R+s@vp!s7JKL(ad6r z;>`M4$Z78sq>rFUm6CgLZW`5Ds??pjv|5U{OzDx-@jzHU-40W$H^?!Z%4_M1@oeUOo&b#wp~~~N}9#``Nq|bwpfuojytB^ z(mN(bd>9PD;C$hpL|l)A^}?Zgg|I}hyX7-~LiB!CKxtobJrWk&*}>Fny9mn!s}POC zu@$6qbzy2D37a$As4d3Rkf5dDnb9R)5b_*>z!x~0HWQXVc1&KaRK8qybCt&m=`kU0 zAuPA-iDhx4iwBL51>0Uh@CXMvKd_J;72Hk#|L)#6`Z-q}llPmbI;wyjRltrYV22g3 zM-;F_3fTJ;u!j|}&sV@6lE()AQvrK39t89Me~!h2sCu4!8&?e~U=J!_2NbXe6tMjY z*ggepuL8D50o$#B?NY#YDquSlu^24LT?*K(3fMapuy-h6Z&$!>QNV6iz}}{Sy;T9bNddc2 z0eg!Ac7pEW|8EG41zL_bf4aFX@|vcvH{IX(riQ<%f2Qso z;ZN0mr1svLw}svpstVo*pmKj+oYz@?h|ZtL%If=QOlH*Qvs;kTSi&XGg}RJbE|zJ< zKhwh(wZtVzoPb}ZiBL~v@vnn6#{L^Vq4!yE=}&Dy@($FQoVF0pY38TgPw>UjMQ~Mc z{O-No@%ftN?o;~O32lj~!X1xzKxoG=V5b1)D%^N!e1vLz98ae*V4F@83ruBl+8o?D zF#AXc!n{faYnN0!oq7!IC%M%tTtYdJEco*TXz$VxSYPwSm|rvf9h~o5*_)^q((zqe zu3)<0_}$`u&YwBtxz1^rk0#Ahj@iw424ZL1En@!e__oOtLDr=k-eQVS+^&M%DRVnh zD($@ac~m?e(pdtMJ-T!pTnC4eF7JLO(cPJ=H@@bmh&8etzhTe_1iC8FuCp{H7l?Fv z1J+AHFoj$;J(iix=H^+7Ac@%hmERIp+PYfI>UZIN@u)r_Kh@9}A2R-SFye$rA+?n> z_Hw(e$6~abJ)v*K8t;?8DWrl^;ziL-BZewD87w4f1`t2x)hjeM{P;aeF z*9R3z_`f3~ip#0Ye9~DlsIK#$P)}zC^=^F&R&c@?mu~5N9&Bg8RwbJTo3A`{dY-gn zLW_gtU+LwTRU7dO!m3^MynHm5oo6K;T)o-BQzq{i`>DX=?X=()^fmsF{s~$0vICF`%->)3ey5z+W2l{=}0{9dx>k>5;}205lm!f3zuP2 z=`hfB{Izvra#LHAv0^^wME~6#E!{lLGURL%xq}>)e?)hve1g; zuIlTN+irP-H*W9a7Y@SpNLco8It2$n>|;IpIxK!dew>;U2b*pz6K$vUw0kT_ydb7p z%)vga;QTn{P0#1ybJYi{q5!_0KV(4fM?qbZceQaHB@2BW0 zUxuTFDN9A~(#@P7r)8&Ge(>gf+nV1*shldpg#^uwTWc#!$f>yuFOh8#_)*%i*PEVz zxCv&;B%9U|Jf8CeTnVR^gIe)=Yr*=2wXnfsKFOG5Xahb~t+Q+(Loc}|E431(j@3Z! z(J97B$jKzx+R6^}B_DH$OG>8v170aP^)pAon0_n+Mp-G-irSXFqd5L076YLi!d!fP zF_r?xW5Hiqkkho?Nf-4OC2}zZSNfevEnl#ym^d89cj(JCM>o za!j`IEIzQmE-^3lJQNmw-@VhGIfi`=ybrRuB^rQj54PSey&d`W%I^f&cR>e{gJ=(z zv$;zpB>fPTnYzE|CAvwEBGFy)ZWm&L&L@?LS?I0~sH&IP7QGFL?U9dVATTXe%Do5S;A$DKz`0A!jf67?^#x@sq}^trEc+9 zYx|+FY{cQrAJTUrgH9qHKLlUqmwZAJuICawHtKgH4+XzNEMkEjmj_udt(JvmDz3;k+m44q}{85L5>Gex0SDKW3be z?U#I3r^k^>8Ij9Eg~ZHGV;-R$wRiM4JUE*q@<*1quaN^$CWCVcie)=6uky{$(y%?Q zJ8ykNAH_a=_^IGB@8hG*i5OJD%Ox(IWuHCJdyg{P0lGf4=)$H4T) ziz+7|dSk(PXLkwl&_#BiClcM7XK4qUwC9b$^2Fx$wT(ChOVRDp@Tjx9+vv4kO zvr1#TxL0f;OejZd?bdJAhmn}d2kO07uQAWw%6LW*e&TMs`93gyD8Mx z*3!L`>zPHzy^FFjoW6x~_4}s+`Vo{&>Lt=Ho7S{U)nHUWX31C*2IH|WBR#jyYVS%& z=jt$Y`V@7?Gg)BYyyob)j=x?#p2`&HbW3kC>G9TyyUOMx>u6*y8t*W#BcUpn@jzpsdAaXHskvwPr}J+2R7ADAGQW#>My*aEqOvcP-c zE;2WectN7IqkcE+uDT59_hDT|$((Xs>>(mpjIB!eASKxIkqA<=-*=IJNI#6^&+F3h z3(F}y1+|cRMWQ;+BOG7K`Smity>QJmq*s4FR!$-934~uSrjz6G+@)c-T#KhNF3}C; zxj7rPb#za`3NH#PYTXb<^g~E|kVuCXVdBtmfH>kYxk(B@LPBPk8?sJ+9uiUr3F;`z zbz>o9x0PY7=gPq*>e!CtFAT`bXmWVV?l-N%&~=Q=vXFSh(inz+9oHn zEIziogJ$%DScSv7beuIOcWb#gm(6;DZ3$sQ>SY!HhzSG{)Z?)jtwq{vX)!2akOFj= zhj~;V;2jA9pUX9xvnu9NSiZtel(4?Oi>Vc)A8knJQuWk!{QxpP`=k408S}nluEX}! zYjq&^AgGKN*23;^et+X1=35|}hhu+IZfbVGu6Ikwcm6(S2)}{mDA;EKw4PdjX>B!Y zx?;zc(r>c~jcWIx5c_;XI|)7iZw!z?%XstE$d@7yG<~LNuwi}Of7YE3e>^-=`;)cx zHLnbPFZlB6H&@?YwGMzQ{f9K_EEAhToS=zIDG2qour!rPT#RSH*d4dA?w;w75%xxN z0n=-BmPKvIsF58sraH_#IE&6YVw{kcJv6{S0nEa>yUaphWbpfKM|+zk_(aF|LBW{j z*%r)?n#JKxw?=2F*ZSr2)r>-oj z$zvf4v-YG*XBpb!@=@BRX))e5@i;ZH4Z2%8^6v?0pPrqi-RTLPWo%QpZo|Sy(rGq& zy#_1T||1`Fd0y7ijQERc=|+GwiWK{!TGf5Nd_kNQ841i5w=Zd zIoZaE^xo5KL$+J7G)qH;LM}A}lewE^CA5&a+1K=2be4}z)1_}IJ=FNo-P|o(O!K9l z1uAa}sVf|GXdb;-%%x#7!_!mefM_Xah^a?g?x}lpmLIO|N2Sj>bY=&|T$$z6`BYdv zb5d_@wg1L<$G!&u{>;y9QfjwE4-nRexj`2``u?9DDo!sW%%a6EC*Y57Q3xpF1o1!O6}_h%Kfk1OexNM>QzAPBqN|XkQt$g*9lJq{7cwqjvo`)+kP-V_vd>SLWtb)FQZ#mw8Vp z2TJ*7{68!H-%rcOO{jW}0`}Dk*q>6szDfc6N(JmI6tF+3fPJ|F_GJp#mnvY7D`0QN zgJf|rpKI|Te?p;+e_R3kV+z{AL@LjkKRV2J|uM-;G6Dqx>bz+O|p zKCXbhs(^h=0lTDty`q4st~QoxQYU{5MwPbgsTSHOs#yT7;U99p&N50rO!CrK zp%C#3LL8tm05=?ggwk*ec`TPLE{OCfaxV#$C|>gr&?8ez-*|2gT-2$L7Al60>nt(# zL-H11OdF_H8M$|^vo5D~meBeFk-j&vZgAmRiDulR8M`VsjqF>}_3<;}$L86mvs~C0 z>GfQ%JMN2w<|r11irF}-vy|e!FIlfBW-5*HQg$IEgba6uX8lY% zD5qGQZD&=&4Aa3MI({$rDQdHZ6Z&M9b!*EE*71R92a125#jkT>Kmy!!%Pj8*7}C;c zuM1!m=0KKdR7cvN30SfqmQmu;X^7Nlf7|gLyy@U{%>IdU(Jq}OU~ePRhQRzYK^)^?J6N7@QUF(pl2>J@T;^@p}T1xt~eUs-`r{`@61Pq$qM!XSwb3L_Wwcv#ng3pUJ{xN<#-w zV-hOKp=2^eBMijTIRB5$!mN=`!4&15OZe)k@&TbhEn@`oeEo&EUA#o3-J2PamT1pHZej+Z)L{q_IRs6o3>cQv30m zeDJaA3svXfQRP2+z0T4^jv3Mp;ZEV4E2cHrg_*mdlE9GVvBFBqxc5Ae$`|A5)`>zf znaajuT^*eRu^5$j5fW7{#bT3Me7=?XS3_73!#Ybv zIUyf$*I7YUidl#$6OYfH#0x^PNH{&MfK+hl*`l+|m)*vI?6`H-jA>CkUeP{))vW6H z1Rla1u4Azn6OY+v7CZZ*9kkQfquqgyI)9HAP%ni2#3<^QaW=U2lHIbxVczmuj{957*Q$r0rEfr^qD1f4izv})LpsJcwXw&&Scwc-%n-pwT>7==uryz!7f_nT zr=(rrr1Y9)1-qOtJf~%xSCz4pI=rlmm-B_^u#8`*=TOFZ`7lK?8t<|qUd9KW!xBED zKZ+984C&+&bcqn^_RES^<;~}?OxNoRDAQp>I)aAtgot;YA{|W`pqj+iaG|FQJUOIi zQ7FgAyzE{XgD)f@16s)fUy^jIEL12MzTb2PdB_j?IQ?Stz_*cP! z>X$(VfOded^&b*4SayeQ`M~?mgBSI{$`(D5MtlH5S#@@GU^~_tEX{+0i;=UKiQ@cx zJXh{8&j3Ommg0t-Hd?Sp9o7}*3HyjTQA{K>4Se z8xbTsPNdV_xX8vAwQ((4O$hq;XppH7ZfP)@VYK!^>C9nHs2y zS~&+$NNtT&a!0SgIXMP@sAi7oUaO5pltlaTCXlnmlvbvF@UgJg_MpAhXkhIjpLoF8 zY1mRWnFSRDxUiKOwjQ7avwMwtWHzyyQwwwtXJ*Lf>SuAfC?7Az1#QTgIB%YAZDBzUA z*jxrYWXDs`TQYNF5JQv3wzpJkWfasEUKCc&o-wu=A!M!)NTHND&+<}HpU&vI(L#>a zO1RYsBE=JA&$4<23fHt_NmBM@`bH^OhG6ye5HzXbmMLFhvhV>{CKxfQkf1`;L}w$z zC38Y!Q@&Vo4p3!6cNzgCG`74ZJsMA?PiOde!8N!HuM(_~o;@mV)t^Lu!^>MoX?!eD zqvLe?V7?@Fvb|JzfD+6u>Q5lEHvPhK21BX$zq6s~Qz3`7x!2=cD>|z_u7mq{Rn^oU z`HXw?R(xT>yxeqm3imT%HSJE&r$3J6D@^LecFRKZR;o4BOqoN?ROyjD6c;KD$N z6jnPs^GGbl-o*3K-3H4Ua>xKX2RUQTRC)o1y76ATm%i7Ar8bh7z1zH!GY|%wU;K=5^^SA?u12^kk4AOFIU(sTcVj z^(bVHX942i2j5(vs&5PBI(j9MF2cElk>K(E;o=;Q+xbK0D7b2{geh(EQP)lt!AK7K zX&jH+?y=~7Dx`Nv@4*)2GYKp_ey9XN3*e?G`1nbHqxlr z*5|ZDF_#BLle_10M%L4c3`m1<7gFe#Pce8LG!1;2&O;v=&E>K=&ne6oA|zus*SfZ|s%0BWp3Xpl~`3X{)&LJWchAMw$ZAc%MHj1Vc?rDKV#1%-@{- zg|r!)Q6&9D`bbI7QVx4W7OK!awtOK%!N{r(ahblwxD5#(l0Th$*a)nzL?|I`2-b$7 zDUnvIaVyg5mQNh(6e87QfRV3Tiar1#nekL6HD8>Er4j6lwG7O@s2Yjp=p}{5(#Y2u zn>fc?WS1W&1&HxElg)dUWFLT#qrDLBHZ~%YDS4k*XF;%mp&@~35ZSSed3pw2yI6dp zH-ucxrH5=VZb7Ptw04A(GYd-A7VWS6WDL5rnka=k$BZ(J;RM)0IV+-vvy4ir1KjE8~dT>_7 z>6&X=VLMs?!!ma>mB)$&z>b{!lSs73=wfX~PRn*BLE%HfylN6JRHE$sIP$JHI+1t3 zd|dETVA6sc^_)2e!xZe6t1m>z&Dt^Deec zw%BQF=Hn{{9*K{{^N^#qaM3M_@>n4~Y@BO(_tb#lQO-&^--rJ-4ZMV6mm7Y6xm|zM`FjwF4@*)|1LsDT|k5duCUNnnCiLKDHllG>q>HZgsQ%JYx@v z_pA|@?NAmWKk?$c<{pie@y|AqT8#ZA=$yvxFz!Y94anyNVE46Vm>e`V%X*E*{V_s0 zIWE3qF&-j^&ayQA|EcQN1wxO9&V=p`en0q`;G2Sl;PK#{)!(juy5-9)Z*O_Lfcg-rT%1nYu$J2K2`UIx`n!ty3OH#41YBIn()Q&^T5jB z>$M-KeOc{`YJ0%f;4f-^z2>Q!2W$4%1VUd9y}kN%)#>W{sy9}Bqv}IdudGT|9jIyw zJR_@!P>6;2H3jTf6|jG!fPF>*`_~HCzf!>dr2_UZ6tI7;fc-NC?4K%NU#ozHZ_6rKw&nsX*r-1#e0`@Zs*gsUj zep&(h2MX9vDPTXTfc=C5_TviJrxmcjuYmoS0`~V5upd>xenbKLVFm2(DqugPfc+f> z>~AYzKd6BHfCBcn6tM4C!2YHJ_I(Q2-%!B5R{{GT1?;aYVBf8P{WS&buPR{QrGWhv z1?)Q&u>zpiYiL2ZQQE0a!8>_ESG%&?VJE1 zzqMtTk00W%r7f#81JdKuz}2aQ-HAER=c=q#SE5hwp|~V)zDI76xV=q_nMN& zLP{&p6z%R_1|fzQ(qNBei+!iT655`XH>4U#Oyifwx zwa_|a3>Cc!(Ouc4%SothzQCIUMn=MO-6ui`DLHM>SVCF_8#K__n_`2jD5u#CPay7= zTt&E$B~3%#jW>O*#xa!Kn1Y{}HXARd3*5UW4=ymwFp7oBcT?o{K?1Ch-{9W8?a{sM zd-o#8M&l?~LBwk!E z+oN#$S_F##G=qaRgIW=15-e#w-gdL!N&-YFyeK4YhEZ*8n^(mO($j$y_{>hsY!# zL^fgA3ea*~^+FGR@D65D3u`ADXdNJpu57)EfZxtWwc7t$y%s3XQ9GnQE}GkiWBk$5 z#;{Hfab`E3h$k*$gfM#kzb){-K=ap{k4HWni8lRABasvoI)YxpbS+iQOs zA^=m2opA4xglUl z-C3v^4C=hW5+{x63O0i7$n7w4L6bmhQF@++oJg)H?{4h%RVT?m4W3JA0Gse=G@fU$ zR7?Hx83HZ3=0Uhjjhv7bsz}mx&L1>bcBU~hC)>X){(P5Wi@Ws5Umh#0k=2_jiNDog z*_!(0gI!saOxc!U?Sv?hb%{7*#L+Bwt8@y)Y3TD3wd02~lqrtKu9g+{Z z!gfWL?m)17icmsIxFXgVEW1!Yff-mX@Q6i(4h=JUo_i_xg_I|<-eAdrhKY0-(P=@I zOP1y`!1AQ3j0?CbcPm8}I-kv5g1|k3c7lQF1(YXo&|ukmhRG$_CZc*XSo&w_p@>b5 z6EyTm>0DL@C?r*8lMuChe`s;o!R|L6MiEXZ&B3TtDMQZP-C)+J^uh|&=62%+SewK0 zD!^&*T05l`j%V|QqgQg=qswnv6TpPb)2W$@@X^^TxoAF%vfgPtgzQG;JxUzx&d_9G zOELsYkd-(t;HOQR8;u9K5O>OUXigLKD^!k^IF`bM1QXeLDnt}tk8vJ(O{=U|*lMK( zo=+7nW{aQ~gpesTw5PYjO1>t=`*!02-t5wG54k+S-;m04^a{jKDNrS$uwVKjh3vrD z(vs>yV+z@6WK6NOean;*tkj!Q>~Aq9d0&-w{%{N_3Bi`iU&xD1uGm$Zj0vPQC?Bh4 z`V>SNTGaU15p_&|kdTr&q-%|H$fs96si~U}XbEOhS3IM{MTQ}SOkg%d$=qg~MJA*2 zF(0@}ZakhcwfAEgb93otR1PU*W^Vl;-pU!IcEXVEOZ@!q+RTq=i~dKg3Sc3@G7GxA zSNJk+o|D?5b-6~*{~H7Eq4EE})VwqDc+(e~?rBUle4=4@{Zno*#N~ zs2*bfKLWrT{fG1#EZ1N{KA$+GRGwcb7BqeXbTpTp=gt)+`dW9t`~TVd62Q2wv)qwp zq|vTyPMkQ2$8j9Tu{GAdSaBU$l5NG7H`#I0C{;!?SJKp@8D$n*itVH`nnhhHrLUCI zK!Nh;qopmhg+fDHpg^GoO35qbQJ_G&(2cH;ZsGmsKj+@L+nG7Ptt2hffySBp-Fv?C zpZ!1oLU>Wy%0@T5b(QA@>{(U5riJoh_sA0(^G&zS_1y&^C8F!M0|BNQ3?(&)2rIsA z(vu5OVdOO&ji-7Vif$5oO z8wSZhRA06+Fkf_t<}nAzG_pDgEfXBWOyASHzPRs$6`Ji3*omZ!V!~fp<=F_gXO$y7 zPNam`*$&1!a4e6c($$9sjA@Y(k1(cLj^YJ~WhsQtZjVkS?ETB-stzJcs=ipA_>NVc zc#e>#GCJ6CyLw~5se)g2*R;;<_r8+8W*eI zx5`te5f)joN8>w}lMr=;9ls{vpSeVG5p$b|Vy+e>gcqe1H)fKqRi2TIaBr(O+fgl( zhA^u7%$qtAiqB^tJ=~y*k>IALW>hw0R^7L9)AVHXezn*YMLdPfs|Q}N zOVDjBDe|rOYIXA4S7S(?5Dd;vp3UvFUh4LS3e~e$N)@io!pZ0oGZR!4uiG2dscv0; zDN-dEaoMHHwh_tb{DNw~b>gej+_@S>nh&UZusdn8o61ZQ*8xSD@^#YwPIbDQR~L}( z70d-}(;SMgC=<1!t5fu^&Lc%a{C~Sd*?@Lmkn}l}A*xboYDjwa*Ty}}SKs07@9OFA z;?aMg;U@pr{D0_wga4)e7x}NL|9t)L)xV~Gw!Xi<3A_h>tFBo0;<{j6t?yI5U-v!c zJL$W_`#tYpdVkq_&U>%-*4l5@{z>hdYU2%0G$h*&wG4XxMd=@9Jw($xYF=QH7KF?);vRC`=12te-N{U!nX4FdM- z1S~0jP}`xJ_bcu+zT)vH@8rG=Sk;}}mjJ7}lVj1cRd;e<1gz>#?hAlb-N}6(u&O(` z&jD6-C-*eAaj<{3bFQ<$Yp`Rsw|B0iHx%mW80sDhb!ffa;n|+hV7R+G>^n*55C2ZU z5_Xd{UnP9^D+KJ93D_?Yu>VHDevyFv0s;GZ0+#h1=x0j(;RKTB>9>wmuPt-g%+Bi@$U*VkTK zvj))T>rXbC=NayYbI@hfY-DvU$x)=@;u*tPi|3lBAhsKBpSy~DM?W30(hUyH;D8R~(BxQfE~1hI&gbeg$KKvFVEZH^o0^yO=i(BbR@s{K{$1|m}n z(M7&hW1ZFLxi(qB0t?0;GO(0jC3{>Z$y{UJgT(i)wyP4KVT?yvJ}}_J^4G?-7~4Bq z8Ou;os_SBZtYmVO0{&snKbR5~z;5;E>f4Ky;I$ZfE5K3&SEGY zagJ%*!>UBEC)~Dr5lIZGo5#$sf>Mx@5eCFO=CpM>+PPz-lz4B(L|W|Q`nQA2i!o$* zboBzVoXjc5Y!N1@cx>de&cS3Nvm`m2Rzpjfm#w><#=m&ZG>l`7=d4ET{C}12X;1rD z+h0KhfcLlF((-uoSDR;>KHT`}hNl|3{j>G2uKR%R>6)kXe>|`A89<}ef40)Hi(K=~JCAkjMVsK1bWZC>|lVcc6 zX+xL~ubkJ9LxXIFv`r5lkCz7&+reCF)C9pg?Kr%66}4rArhr?yXVNfFSq12PE|s5t z%JXBzJy?yiRoBg$T>P$5C{J73mF-Y1R_qH*HeA-aJD*Ew2x!3cEjG&F z)_~9S!?}l4SxK~1R%#NElpnV5@bjV2f_deY*a^|bNwNKTjHdNgXl1AJBFTO32G5SG zd@5RM(U8VYnv0!H;w-D}ZF>T@*%5Y`ixI=1Q!6QWF*ueCorjew$QDjW+qtW?DG<0# zxA+GfmH|HrH5z-fH`o{Kf*uqIT!LFm+`ttjD+TWY~!lO6Sk^Id@GrbU%T8Cz`)aTax zCalll>`v7K40APrV`_?TdU(XTGYcAOtYJi0=d>zd`#8HV6wBa%G=e(iJo%fD;ia5% z@V1dyEOB0o@C};bqziE+r&bncVV5aJyJ>xNi!^z;Uy;@+xD=Tk4g?qRf5CAB{;n-+ z@V_vTEJonpECDe5YbhCp|Ak%Za6sHAMDjPH3=%ozK+970S6F%3>+)Kw)f9K+x8^re zdm~G#eN69r5SJ7A!KMRj`d_MrZ5y8|U}+tU-kA00Z$O3}*-fhBgcLfcjt3y-A4j*;5(_Ehv-KE@U;sZn`N_D$FXD-G;LZW z0I{t>_m^1 zHW^rZf*V5G7i7}~P~%%7oG#R;NoE2oI|)ciP)rwaAbv%8x=`ceoDQs1oX@2!jOk+S zfH7StlDKjeMfRFC9=rQTLPc?G?LHJim&dbX)Td&Wi%kx_vh2`hJ(s@X;WZw4Xxr=e zdbW-F`g_Bj{XOBKj!vx`Onv*qgB_vnzF9E()n*5KW_$bQ!XdUE$6`^q8%T*;4WV6z zTWoD4IiG^V@Pq6a49DZ$@dR{pD4jPi`y~@kh32%5@(&Q*G<;Tzn0|>T&cn8EPuDM~)vi{k40hyZjPdXz^eXd!}z&2hAgx6-49Aa>O+zuUYj!eOsAC zguzX<#{b^KD$SH}W!xNGI)!4<`S_l`E>j&6;3~Noebn3liA;KrdHV@B&92(d)A4Bf z6l6b0;cWz}L|B6+GY|+sOADF6#=IOJfSz6Ylm?*%@U8%EgVS(B8V+DM@>DdX|2#c? z5=sn!z8|!=Nl^VpQ^2d-1cxB{$7llL2WZI|E+1XL0^*qhEl>hNibvBc zdfA|z1`#Nxry=liC^kKP3i5G)!G?(L08h=)yt{+Cx1r%HnPLHM z^@gN%0sULdbJ6^j-6nDF+;jAFSAS<8Y+eF^hqw++9NY|Loq4vxBKGd6gBiQ0Ck)OReRCas z+F)1TTvuOrUw`)mD049QBaEnII1?;@@zlKzm;D==rCLx) z6KmG9nDwvxchN@U=Rga@uE)hDfVC7bTGEnfFg}=`7BI82l_^w#4>xP*$U-I~H9q#v z#%pY5W=B-u$gKSI9##Gu^!+8)LwWCZlJ@cROQ?WT+qP8EsaZEtRlxS@I2CZPO@BK! zGb-TJ7)q5-Kc)&e*m3_F$^aE`S11E<8Z>RT&7*?dz_Si76`|~PX-l%j81#wNRE>N{ z*L(=;N$8Ddo>v7NoHfL)0#m@LwS-;8W*s2*r&2kZmp?8n51;=~0jEagxmCci`~Que z6;FG*?TOYuZ~0nFqWQ0y?`ir_(_rIU8rvH#_+MN9dv%|wdx`J!zJBlTdV{q~HJ_^q z!V6dWC)<_hIUz7okD4gB{qg>lGZ6MG97;w;=3skd1a{)rYHZfGDcqD_leVLNUd;C7 zdAf*DcAsijvX{=o(9v1T->XjBpWlOHlyDb&It3Rq220reIjq6m5Y?&d$qylwZOnp3 zt=XqU$~LDa-26mZbcMaHYM9MnDi&ws7j*f9$R2h1H1$H{9YoI@s)kF3_DWJdo1W8|gxxt4FtGo2li>y9zG%d~0ZIdyPGq-c7K#zBu25)jpVm zpj~Rcb#e+f8Da}-*v1}BIPJPCLZzZ%!R28?4yvBmLMRbDdU|T=)B!k`W3)>+hJ&gF z9%D5EhS!v7YmAmvSi6(?ZmivKPT4$4LfP8D5|Fe;SI{^0*5XEBSgZMb7uISh2d*)f z(`E#@e)xnnR>vr z-AvD=yjTZj!{xF3E@XkeiX;}UzKTLJx&fAKlI`jbb_ECealAI?cOru!X2VTlpliD- zF|lE!@&HCWvEI%3yO7QROGQDV15-Xzva;@w>gjfE87o9ehE0e9$|JgRl`Rw3#rzJW z*TGynN%W2;BH-cz3Q<>P##1RNW<_@?dw>2;EPpt+n^?Y3a~Uh5D-AnH%0?n0rh5)2 z6RE^pI&fb!l?lZHQ|U}3nwXyM?(71vHW(d+YWNw z_Co-EP;a_cNj!TlYa&J+R4D^jE%Eg9G!%+en#$k7yGsu2FG$@5)&0^+8BbrBYBl7y zVYPb5a|RNxG+~$l^2%{JGZC}%{|#W!-@d!;;nt6}Zf|*Q^E1uIo1%@MX!tw-SL>gu z`(WJ$-y3{h?@MYwRr_$wS0Ve~vjAV$Kj-uOQh%%Zq<~6uwpUEr<^$6MY4w-*Js#A4 zIe!uxVtdxB+P~QRIKr;L;AY5Hw&jtubarzFML&{1!LOb{39Z(%xjC3~*)0DqEfeyg zurinO$Fa;?lr8d)gQWMSeDc!2ZqwozKP!})_2rLZ zu{&5+Gd1yAj=;9EtCEQziVDVcji=R#8$beGBPV*W5P z+Q$5$>5Qzs)l5WM{+WA4ZLrz4P?rsCeK}j4zGfhuNu+OOe&pNR8*Z-0ZR$4!~VwD6`V7+1)NO~xd#pT34e-m@c!I} z{2^?<$yMb&Isdd=$7=0T)zB}DTsZp*g2^uA529eU!c$S` z*R}Z(-c8gmYiEodh_1U{N-+*DlB7ubuO^5BNQ1+sHpBZcngp}Qnf-&?QipJZG5Yz{bKv^_S@T@Y5REF zTiViXhuU0Y|F;abv^0OV`Mu3+&C|`D&Az5jH@$;p4lr^8ytVNP{;7SV>OY>(swyBb zhNV*U|Jni=iYR6Z@Imp)@n!!Du&VK8p9QRHeAyoY3Dx+rKLD(1eA({Y`!Yo=#Ga@-rHCB^?462O$e8K-fg*N4U_YUVg+zG1w<==q z0qn9O7OaDOM-;Iy0&GAL3!4w`w-vE>1NNheSlE(#->it;1K1@+>=0nb6tRPV-KdBi z0Q1VPC}R5o`(Z_FA7Edvi0uXJf+7~KFKX{r#C8MrDn%@)ryA~2#C8JqIz=qpj`_c+ zhz$bvNkwc2U|*|<-38c)A{HVM_y-lScL7#)ZP@|y?`IXidnaJutB3`A9Obp;4&1|x zs@PchmI&Ac0UIY^7YW!H0ehB!eJKGOC14i_*m(kWj)2t&*a!g|CSYd?*bo8xFabM5 zz`lfleTaaaCSV^VU|&qYK0v_U&#@{Ikm@fHuonr~3k2+=1ndd{yG+2I7gz;1R^J%{ z_B6+;I9vIq)X!PItoqYR$QMFRdPoh%etO#cUrZv*HTF{WnmkXVx=-CYca(YELyVwf=d|P;#M3$r zzSH;@t2CrAsVK_9ly1$>V>1!F&4dlbN$rtLG^rhlhFOq-kudYG4aLJYb$k`D0>hU4 z95NhIH$BlCKoou~j(T{j(A+WA`NZ-X+Wc(JsCMfyEf!jt0t+WA0pn+BnXKC^=ObAA zZR)mMtSH^i)mo(aiL_LsZ=B1AvFNSp##|5(9ilJvbnlPaOu0-Csu!GX%FklS1M2pC zr&u&><_8H&6l7@NPTy-|p2ze( zm;;@l+I$MZkf1A^iY{t;cYr_#$?yWZ$Fni22$izCFbYOh(Fpj7>jkizw$!EJ3x^<> zHY?@;m-6WRkr=_v(pDZw&2DJphNw=ZA^)JDvVqWWiP$-f8}@L!Lz{4* z+GoZj+AoQRyL8ppPT1iQf;zhBJhrtkp8Q*NTX@a=g**owy7_f*ajjd zgSA-m6KT=Ed zE!@Smt0sR+NMG4ZQC}3CUS{Se1QP80ztOYcX^*#ku&t@}u9oHIr{D?5ahQ{13^U`Ql%P_OGd2m=6nLJV z4)xt=UBgrr^T%0nx9BZ@5SzLN1o>ym|_8I)w%=E$#F)Tg#Zt*MFJOtC~Sq%?Lb$PSm`Ld zAZ2N+JlR|Gm#|F;2l*qgaZj;rdU!!FgV7}vXx%eiR+d%3Qik2&+KDX)40%SHbz|5` zVc1!LVP|j%Tl>~L&qlLO47(^Y1hAB07s!w^+?;<@v@+cMs`bZ84c9e8kRgC8FzgI+ zKP(7kv4RvI;33IWu31IoGh0s_NiB)a2to23{ZZ{5DPh(vxeM#A{PMc<>bWhTNceIf#Wf3W;( zwkReQfKp-*#J^*?pU>RacW~Rv9@SOL_D_=H0H&&JM`Yz(n2O(b?7A zEqFbWPb05mxk=R#yg$QS*C1#n2x?I)3+!Mrw6x%~fUXLc@~&_xQ~4&rof;-nz=?G@ zpTbI?QMd9HwGcwrkCA%b2-gNudPQ1!)@bD>3w90lboK-Xk$xbbMEaBJUWa8a;KLq| zKyWSV9>ChQs-dN%yMhop0XYWqk08f~)Tdo%skTEv6)EdAbAPK6XJkB`UutIgtg?;S zdDWo+2HR!U>|PR<2CHC4^f;Pg?JzGYR-s?nx1`lGX2;Ha0$b#8_Lyq*s?^QOzY17N z(Av>Ex(5b3dtrvcHrbVrBj*RRU=gUczi`lS?+#VCyT4Sa4#>AHzlePIs%sv^i~=2J zGPKO%61(Iq3E7yY3n`2VP|Vjn9n->@RoSwfnAM27FBT-|k}fp8T&6>y7{ATq>0c zhq2w73p~{pADJe|KsjP}(g35Uc zJSD@>s(NUI%q#@SUjTK`9Na(YQM|0YLU=J(dzlmV+?oPUCAA}at?H<<7FMYtR7qqCJX_ShoTf@* z#>#@}qus&-OJP!K&sX0->2EIZ+*Ah%jwS38o!Z zhWrZB9tpx7BPCVVp*0q+Lq5CJ9X`e8Q|4Oicr0aMUU5-4w-v4x!co5PS8;ZB$4F_J z7yj~m3#$RdfM@FHrouJIa*O)WNG7ZD?@~5qA-S-*ShyO=?8r8%j&pQ>fx#Hb*wgcgAEhO_T)TM- zZCEtn_D{cDI~7gatr;wj2sF+YTB!nn$TCZz_=>gB#gC+tTc{{Ov1d*fE+XlO@5@p;!CX?h?nb|Bb;D^PQxPE=Xhq)LR9Jcfz= ztW~x=l9p|q1%ohYvQVFlPE*?<&t&aA9^H?>{i*i1nPLIBL;(Puz$d`Vf4T8UW1!*N z4Igb7YuM=jivPp@*ZUXzcl)oZf4ctt^{=XbxW2c(0Wt@?yDndMf8DM+kMAkp+k6*& z$9=bZpYeX&`xbB7d&qlp?bmBR0$%^I+It$_+_2OVaYh*6@d#?KtNCKhlQplciPQ|% zw0J%%G!Iq(g+3N){{MeNzc-$TIu76JR41nj#B*mn`I?<8P$=3D}<^U|&PPzM6o26#@H70`?UI?8^z* zH3GH-2eG_b?33D$5x)E91ni#?utfs4K)~h+*i`~HN5EzY*q5>U|Baq^dDC!0na-_*Fh;nfW-{!IN+-I(un-p_fvYYR31R5K1QyfA;V*A#f# zGlEyL?m0V_2xnN(el(tUnxRQyQmsn)BIf!6PlqSc>h~Qp1|^~E0!5@w#EoQiyp*rbaz}yZNjstLsU^69`({cf zXYh7oSsgEBS(Yk_ba#PgRXdYaHg+kCsBBH3EY%9Hs8Zfh;HlDj)a@DBy+&FXjf7&c zS?~z3_DvUnl+G|*e)aZu@qcyWzXtgK^4|vdZ`^t@Sl}7Z9#roPBasOFAH?ovS<%Or zxf6qaGgD3rZmWRWOVUai!#{UtfhRJ1aCNt;7F#&MmRW;NmIb*!AY{$&ux%z$iZ4kS z4_~&yELdWL4Tlkgo!R^Z;kW7ObSOEmrQsRNWNrR`bR)IahK-(FO@Sv^>swX6lbJc1 zfikS$vNoj~LMnw9UI^K13p}aY7WFi;!bkwz(5+Q3|1Pa?ghZiM-_fpNt*6RyAo!h8-_C4;6WK8|ciJyfZNk${KwX_0IDmi0_FBNv; zUD0M`cP>74Ld<$=a~VHN%VfE?Q5f3_Je3`b=XfK82Y{>W#OZi+Zl&s)AqQ3`R#)H& z+Iq5At1c6!I2U@!*8b!Is7|D{z|*P?t6L$%t*bCGK9+>P=%(~mG7?aALfZ;FAsV5p zgFQ6#YKJ4osf5frM&X63MAL=a@$Pnux+5uA?L*0j5-Hn6Q2t$7sxkXKTG%SCjFs2S zDJ?d~9F#+Frm!U*vOKC@cy>eKHY}Ymo-CFLy3G)u`fNSMZ17+ON|_n+&dr4_NbIn> zJJ$>+VJsJ7mK4z*?z54r43-kqZI8DU0?3b${EK%|Te;QxbOpGSqb@{t{=d#M=V^ag z+n3w!Z+%zGhnqjxysPQejUQ?FTEjMfuKpM6e!s5PH{ng!{!&d2fS2_rx3RzzdL3Kc zq&oGvrkL}6MMvJ9v> z)7XhulAK!ul$lq0vpV&c6nL_)W4Z0B)PP7j6tqz%wb@KGX4@=Og-c0}# z=Bkt@;;={5k}g3ONv}$44)wAv$xs{DAF+y?3p``kVRaR)oF_`%+U2T%rPbgKh!n>Q zJXu&c8@lp-hTpxMHgLQsrS2wdtjNg%&n|X2r|#mz6>}~FhSY%&EG2x|qeL{%J-E?a zm*zpDm(x6s7p1k8HqWL4&tSG!JqW@?JT8VZ^mFEZwgM57GInQD8A?jgK7diqZ3Ujo ztSh@sbr?7cwhm6`b5%;n=&=G%N48sCn>uPK&rSzDYXj*jXZvk#bxKzkhL92=KccKKcZoTer>c^= zR2W1~y9sd+?KQW`$3a5Mfz2T7{C|DTHc$I(+qc@%tsiS0YI&livHAX{zi7In@uwR8 z(*Gy^&id85hkQ@^ZUG&@-`DN|@c*(@`l-wk z#28AYVE=t=ZVbY?O~QYGm|P>CR{={!VN5|A3Op)vpL#k5Rwk$pv<1G_{Mb;Kq`wo!8hN{KzcvxG5^=-aq5S<4n~M}Y^3K9lWJmC_W>q%6|GWC$*T zAXpWIh(7=!^)pG$7@^kbRp}L}|3B-I+}oKKbm3@f>~SZhtpxU3xFu= zqlt(%0>(-!sVKPkLTD5%jZ4F|1s)fBL_O;Y=(*YTqNqc1Vs7MIC>jgR+CA0fIB8{! zUca@#BW9mgH*g$f87>TEpT7BFrXUdUjNRQj<*e|AlqyQkU*Hk7cdK7)!;KrCmfYip zI37z0h!%!L1gUfsrf@_Ksb5>ba>F#GLSM)B9tSf~n?BYZBPDg=z{q%K@AznEM^9(h zfsU@Ov7V0o2YUB+basw*_8u4*8b7dql;^Oxs&Ens?^O>bVqPEBR#7ubO4^v#vKtF0 zkkh!j!o|!IYmiH`7MPx=u+z3XHZDjv*t$wsItPKD3g8nc*my zMkk|T*z0HJAo9UPobkiKYGtSJf|RK~n_Mg$MV{N$!w&FWkg)c!GO|39mM!+oY-q*` zlW5Cz%X!tMQr8Jt3)X*?mN~i=XM&pwN3gmRq)yCc0=)#jbpcy4$Xo>`u2ve*Un*dw z3e_Q?Ox|jKW!~2@rWX;G6 zX7{ga9I|Z%9wwKdf;E;y-M+-O?{Yqu3Jv1d1V)da-nBf~ze@z}&22v(t0w41hN6I zf?Q^uE~|eSmFg=buu|)13sS8bWs8a{YK?&ck0-p2wjk=vuT18T6?kZ2%2lA5n~R3Y z{#e?Z*K_4DW=;E5AhM}+6}Yj$vjr1Q3&LQ4NN~R z;B{@a(}Eo&WmG;}Z7cBPz+LLOm4!troF>Y2(38(N=M}0HjpK zA#$w1^XfjLE-i=Z3I_ya{DMopC};Hb?OT|N$(Dd zmT5LKKM(4;Sm<2B=KY2_2aRXa@{ReOe~Id*d_wDk$`O=VEqJaJpo%s!1@SSF9BOiz}6739(8Q(e-W_H60koc zV1GcsexHE-9s&DZ0`?gK_CE>O{~%z$L%@EUfc+K$`%MD&8wBjv3D|!pV82GdewBdz z3IY3N0`^M;?7tDPUsT6>TM5_}b!_by2-wdPu%9DfpC(}cm4N*h0`{K?*v}HMKMJvi zpQ9LK-s{!n;N3vLUPr)QOTb=3z+O$jUPZvR6R>US*xG+0VE>VT{RaZ}GX(746R`h} zfc-lH_R|FH-x9E&B4Gb7T;?jq3iRHn{$1}z0`>-VZ0%D7?B5WupCn-ant=TT0sB|% z{{JS=Z+hAn+y1<*xAkqUn_E_!ztDW9=@U)&HvWF&wuYZ>X!gI{-&lXX?%BFWe1GEG z=UuFQvUW#J3|@L6{$vM=?WmADrXI^|1`N((i4_+7kN*u+An1b1L#w52DOIY%rTQCN zo`uj7i$sR;22tfXROFdw9#B`go49~JPXC%R{|fJ@Rzh3WkbHZQC#)IC-k_>4(UG#N#Yc+yq*Qsk~90YGaWn-PxfYITK41a~pm9!fdbv)qWNV9Fq;R9Up(40RIU*AB-=qash(?G#QUUR!&ioqGA-X)U019@P9xbZs1W6Z&|g~yT1ZuO;r`@&?BxJ7dIW5;7D39J-fkP43p z7RmubW{xGJ^HFfd&<#Yb{l@WF%A%a!FxyhNgfs}LLXC1P)s}FvV5Ee~V%b=@h{Xu*tp$}sT34b|MoAfou7ncWTsV)chSY=Va#lt(zzY4yj*&7lr9(G5laaT* zy>LzlPB{$T15qs&8OtQWTKrTZ0l}?V>O+SUfD9$2sn6Q%{J+ujbDs8a+n=`G+WIT4 zJ6ldQN1NW>_^HMR8{XS+qyH8Drutaj`|7s&UhVw_@3z`E)_kF63|@Gy|75opd9>8S z*}b%pXEfDFN*L``@EehWrIHi&iQ=2Q=M{N8Rayimu=`IXk|W9RLNpDLzrk~LB&AQI z)?oq5bS1Eqs%bsyDDnua4`zo{r3p(+3Zf_{bxy#}jfa)0;!9G>dcVvz7I~;u!q!HC zo-0I3W-%3wu?9X@+=?DUJCvPDO-IGk$(U6{)@Rbfv1gqv^4P0U^#E@(x;6#uXCp9c zoYW%GB=j$c1P$&7i|}7yYzRsOzJst)BfTr7ULgs$?fk{`31M`I#_C>Imk_2i3z#%m{yJqm8-m zFhnzVL~E!Bm69_W`BL!~B)5~0jGe_xoQNL}L3EdxW*g=r9Gd=6O2Cj#bMa;*KnSCb zWrzkfBxLJk?wIOyZYXX-I-9c-$}0v7`N;aYwF_a~KK8S;P}{7)wK75W$`Np(ADIQ0 zd2p!@rkA;s{MO=4NbQ7rU>DiMR*v&x&d9P~4CI-X1L1Y?Ls1uXiAGMgc8A{r>CY) z9hjcx&a2#G5FTSw2aIi(_~EFQ4vuMy2_0bkw^?Vnr+5QaYD-S}EGD=+BwGIc`KS^$ z$$as8!G2zKJc&b-BSv5O_veFs1%;h$-0GPtrivSQ&(A4mNa9s;w3G4k3tUmwf|=rV zxC`8_ybI*)N5PXDHuu)~#qvm6IULN(#cNTT+p}KPjx!lr<^?!Mv*nSr;I7V2?6lj8 z*I?O%kOHRdEn9lhr)7G~L>Gi~sZfy1F0KX}+hu-|)T&KtK8O6A7%R1hRXJJR;|gobnb4x3l~Z;0PdO zeJ1Vq)5+<00RM20TYPP}{40z1Wd9Alit$GshttmPA`jpBkb15mt1I%fn`d&1s$BtG1rz7Bh{5cn#(Y;JY~3+dCmTbhSEOY1;T8x4 zmm;&KQ5sxcjG6y)DH%2YXIZmk{uPXwguY4tE)L^|iae#~136{AyuDU>6Hz4L{*H<$ z&PU^m5p%_$y5|3cdYUVk>nZXCpi`@p$}KJWoXA88onC}3oYF#|vv%$*joKt)pfygp zy&$dH1>4I9l8HtBfj!UN>|iTXbc|i>w&oJgK5?nYQ<3h=Wv*Ck)``+qc3D_b5k22> zz=1Jb6Z&;oKyr;iMjOoP*O9+8Z!Mg zFg11<^atiE!EMFexbq&!DtCKBGht?AZ97*8jkM{O5aoP*ktYNlP}gYLdJvD>W+mzh zkrK6`$Cr`Wt|Ctg`hfby(HU*lSRZD1+RY={d?>s!ehw^f;T$sw!7pvvQnh!a+{;Vt zhN7NARM}L=rR=E_M_gh*R{%|B2P8C zU0wGrG-z&4`Ibl0vcV`AdIR>c-Xc#RI;C#MtIr#JwlQvc&9|x6v7qpRoN6Ws+px6x z-4c~6~Xv0{;M*mm*ANIfAzu>>ye^vd{_3y8LRsF;D zz4Z-spQ(FyUB2%Ax?Oc1-&4M~`7Zj7`)>C>EkoV@=uh)L0_Kmf%hBr4X zwM1G5TUwew+x*__i0? zum=g)0|e|i0Xs&(juNo@3D^+=_8tOu9|8L!0(LI}dp7|)Ou+6TV223UK>~JwfPIjF zeK7&sPr&vOu)PFq4*}avz;+R^odoP|0yap%ZX#fBB4D2@zu^dhLzsY_C167Y?85}? z3<3KR0`?&ScA9|gAYgY9usaFZy9n4F1nivz>~;e74gz)?0ed?EyA`ew@2?FG4u<-* zp}ug(pw>6o(c9TM*fBH*e~a{Xb@lXx!aeI{ZBVO)i~4ej}2G*)AfH_zo+hrI-l=B?+3hh);I@2L_m%`)X?jCi` z5>oD%%2e8m_aYTi_=B`@Rlu&xWOe z4gM!vg61dHi5ckLPN=G#*p0;lNNh)Tl-$DI?1NVIu~8U4BalP9lic`alk2HK z4xbUoA>K)D?DEO=$tBmro>9m#yp!DMWs}2xB5789Mj+?f=O&8#k=)*#dPI+;wglI* z2NFrE1(ua2%(|{9DPhOFI1IlSH;ejpCC;qF@L$pS_>pM*?4*_sfyHTvO+k+pN3bpg z%O9)(+);{4ENc~P52ehF>y>2j9<&+Wc0qOgf?uIz)iOVkmdtCt@3Lymq&E_FI+tRt z%MYbxBMxVNU-3oApew8FgpV(S`54Q?C?#QeuE68E;$GxI@Fznay7vdS)k{hFxZKOpnBfHT?n?)R6p4Sm{iTF?%`FQ^|S3gfPY&APjQ!jx?K-QX@vhme`+`9wUN)L0O@ zP|QAjjza?1a*OEAYkF7bi6GwHz%Ue@N@XbFq0UXqM2r{_^Q~&k)>AH{AUE}NXF4w*T9>1V}^1dQZqa7_ipgLU|GX>igoQf{$ zt-pd@93v={?HFwatF(6&hV+He5V&G9HyC}>g6(k0)N-c3UN{tur&*U{Mv&|@WyW2w zoxF_Qr3IwhuI+KPm@^rlp|RsGgB7>j9ACD6EOegHWszUZzptaqUj+{{b6?FKD)L0( zQ(5KIoiN*g?BTR)x7U?fD=WO9(BeTi16@m9hKf9o_;^;?9nhd=?15z(g-S>T_F@?T zsg+Bzkbj`a(~Y0WZBQMM2Ot{3L>xT8qPhoq`P-7^Q|T3j>X=UrVnP?zsUqNuWk$^Q z6?qPELh?FDqLYb5CPT@yqhOpDipDLX8-WbpVE4F)8F#D)3c`!hiW(M%two+Yd{{mE zwXRKJ1H=@F#7a^Ek`mIxW@N7_@~q*6P;REOEDM*Ekp(5CR1So-ugFt`A5;&)={6iB zUA7`r%1$3!fdF*XiFgV&2r-RKT1OH*daSjB#)~|)_`aNSt}%0eG?t8$iG_CTmcLs}kZgS-ffVnFAa#*>-W|XwPt_!M>Y6(ry z*|sXbrN}dvpD68Aogqcv(HG;Y(_?kKv=-$K=k_=8VX+M=^DymEDoxEM7<6Yt=g9Rm z!bLBv*vi+y%`{k*_5UYZmbdrI9u6(i;YO1}GO!ZOD10R)1nZBbqpNTIW zg_FHm3Z@SUgg&9U7G|8~Z2`KRp8@TO>lcJ}%_bLJaL(_X(o$u`xn(oK&i~hVe#z7J z`L^RN4>xx3w7EcWNK1`Ap3n0D2+*WNV5dud#Xp&MA;E zM5BRWvAp8q#^ZP_?UcsWy1U3z3y0K$5zeq;^rnW4rEbb9J_}f<8uj<2w2#fru@U}Q zk>?yH+qd5+<2M6p33&Ooy|H}*tYwk;yf{fjYO z^x%>j1TR@!yEYejO5sWM=;L}z+UC}gbUGQGh2T`yMy!UG^4EJ@?wTS`D6FZcC$&)% zhg*FrGMJ{=*1<}b@}`tJ;V8iJ1y4_#^-y};UFLinbW(;eP`GB@Q52brOUefUp@BA5 zAZyL-Po=ffC)Yrr{5Quk=bU-nbmE|@2laPPx2|)Bc}n>7ncinBk}>xSf$)8)@oRZE z3AYA%j}sM|h%aSK`%--^6;Dm&%rNEXR64Y{#8+DvgzeK@l1%$N8VWC%_af}R!{vRW z1YEY90N0<*6!~lf=gC8$CU-u7FIpbp5N|E=^wRs(L!%33N21|n)X!G#GESJZ?;xT2 zqMk@vIe;_tFOWSC`3tIJ7XR1UdtCrh62`2&Qsk+mx2q?8fW?&G>^P?m%Oh#w`m~na zR^<7gd(`t-vtGq3Ds2!4KuSbqSYdM=E%NlxJ9Cr+JT#Ytq^$;0f`DUNY<&te4R$;k zu*aDsL<8QxBI1e-8@psEe7G#EVSSSbKVyw{7elz;4`l~b_xpU{jb>8m#G*c1=?vI1 zEZhI9zbBOgQf@CkEbi}>a}I&v*o{vpIS=O|SmZT_&h2pxXo}f6FE>9KR+Yh=ffbWxRbkG@j^xvHu%~>@~%Qczb6ztBwyS z$e89TEOt_uw7rcbFL!-$8d+(pHL9$}H2vyWbQTy}7=+2-D(je(H>KQl;o_Y@S(U?P zG*cUa^2eZ~^TS!sgc0-&oXh$DvQkBLc&jd8AZVPF(1~NEX_x=))v|=dNokgAos4F|8IuGKZjs^CX@NhntQqbjM zHa@glLg^SrH+O51X9n7#9<~g>C!=bqh5}7-BxbLfdaYsSW%YnDAq<(Ae`8-UCSlzO z!aSKHb=lzLhh+8*vxKrY6nTE43H9aHpl>!)lSr&GRw_Ykyq+S@SoFZEvU>_FXY#xR z5{hc?NU7@53w6^qyevBo$Z=9ydTRy(O1fgb1kjLzIRc(MbyB7UNJqguDt37~NG}ez2zA9I zN@76$zHJ=x?L;D4#G=)u7T7M)mS9}DCoK@cgvLh?p#h5c4xtM=T782siQu4Yq z0|7pp=~8B!W~REsgSQUCl`>LX!kV7Qcd2$hF=IPz+hvtYLU?Jdk}wqNqmwpWH`u?p z;<$KF6RS%`^xq6Da3#b#G^O<;jgSjY7m#u07vmC8Sig z`uuaQ7)NGX)pI#3GsYksDAThjxL zuWa~cLzn;Nx{vziytOsI006~5xqD0PSd*DmWj7i|7^iZ+)*MVGGE2N0!zB~6x}-}h zzb2J}-kkygQ-^Z>5f6K0H=9a3N~u;xFCS-yhf8fJ%}ic-S+e9E7M4LY!CuZ6eynBO zN|muO(SV@>%ZkVns?lV8IbZlOmhnqUEhytuUO9)2El)LF#Fz1bA7cq0FEyitwN>R% zcY>KQ-)P1CABgP-+krxJ9`F(jAJk0+pcB3Lvq2 zOMWEwU^b;n3=9b2`Y?^VC)qmWps4tglx$gjfqhTJUNBv`V{HzV>ajKts5{zl8xvY( z)ZV$mx*RLjVO<`|D#vk9THqZ`qxzO|ecUd6v3Yiue4RZw(A>AD% zuNWK3M`V@t-^msNDa>@N|p zZy{iRk%0XL0`>_4_RR$B&l9k3B4B@xfPEtY`?Cb>8wl9{MZmtEfc+T)_H_j8PZO}O zC18JwfPD=C`)UIARRruS3D{QE|Nx=RF0s9UD_U#1h zuM@Br2-rsn*cAeHnSeb{z@8&uGX!j!fK3swNdoo}xJp)y|NmaXci%(6E)lQ^0ya*- zE)uXY0`@Ec`%>=z|1X}lcecE*`HfAlZhTq8cN&KMPu72;zQ67bb#=ao_m8}R+PRvy zdHzMw1mH&umfc_4f{LUb^(6CV3%VnTd8U!J+E};%q)Ma;(d}&B>foLpw0)Unxkd%( zo>Bm7u}?h{ulZAGmZN)!$m?Q(txt`ReSc{)vJa~VGPkgwkAs=dWE5;y<(2`zLTP^ zSd6eo6*>sEgaxcZ@a8KasFyaC34`Nns`6-b0aqv3TH1sJ36b;65(H!4u*TK(a>QkAer6i1Do)6qCTDY%)NgZrdBWFU%I85_aa)cKZnEOi` zka=HLITpi&3uh}uH-wanK6&RFOV=TliB)CGD6`nu;07CU-Q^FifUKk{W2N*|qBE{T zXP%yB{F#wEIx6J+rE5_bQ#oZLCUXV(kkNa$E|V3xL?kar#q#_NaW=MdHjmhN49W`Rb-*Qm#jPNOo3vh)89HMO4hLv3$vt8Km5l5GBq=0MY-#t%03 zH@w#WJ^!)#Kd#?W_X^)rzGL1*?K^6HH7^JFf1^KHZ)p(w)VAy<)yd>kD0LRBGfpMW zYH{o8Vtyj+6+K%tf3Mcl#~Na}G=MC&W|dtHSy>`(nv;ZhAmz{->}3D2 zqO-S``myX$_55c?5}^pj^SlP3;$0U; z8H&QNYOofp!gQ$@tFSw#oY9Sic)=;3wYT$p;&?12y$-t)=L1S9Ls0o?WG_x zIh|EDt2HwLhdvgtmbr*d#Pu^TY*6Lmlf5D3YK)R=N*zdbpL*yB3)Ll-&_?W>EATVJ zq@>CMysfkgSslynRviUqR;QSstI!~%MpEO+WFlGNNbCw%VCgIE6dkVpvRJ|iB$2Tt zU$9hz1Efs4y7|goU%E^1xU7ypm04U2B`Y0}IY7#z+-|2zJ9ux(DbHT!3ZrgUgTY-{ zyK8|-S&`=6U@VkMhhgpyiI52?reLSqjB-8k1?HT7~w($I@DtNAC5d z+j)Cjo-Ussq)#Q_Y!8u3D=eZmpaQcyOIwlI>B}2?$IfZVWHh3&{Q~HnqO(y*tx&Pc z$lg%k+FQB}xt^c|wk@l@CKC~+$rb8xSn;b|bGhTCJoyA<{=A5&&F}x`J?)#@R$HHL z-P$tTywdcZrsl@I4Nv&LS^v$tPx;>KeSPiE*5p0&S8(VDbtX@r-nXh8e0DMg*F?)A zM5pV~zzrc4!Fry4jD$ZDg{FzPS)U0%-!JMD6cxf5vAx6-xp%E9t1~B4ET;or6-h*6 zfBqvO7-o)cl=~dIZz#Bg@y61MNHvWareh|6l_CnUCFAUrQ%A= zzI1vTeaoh&;~MN7V8^+%1Yzr^;m^ou_h{G1{*jK+(F6S*y}e_-9fMun108*Xg9pZX z$2vy_`#U>iMeDX6xtmKoAAKtKxayumzxEaX2P8TDwcdSTV&Q|>;OE1>ch)>T5CJ^iDd`}_8HboCB)b@cXh_jC-7b$50Q z9_Skx8{OaCGd9$%OK4+h93_;{RaZOEIQscSlQER#>Gb*mWl}q|z4#FgC%X?(L;}ur|BZBTq7|EelEBeIOQc zF_UyWmh!b(LWWaI5LG8=c|Vw5=GwmfrG3bAD5o40iG2uWC>}~(-w@dJLhhK$V60Dt z5%xt^*bxYQF)|CMBZy6B)Z#^@7hx^>)ccNL$j*S3>tI1#Ne%Q5xz-?3+KV+9$SKG7 zWHm7EGW6`G)=}vSk=9{7clEM6jlHG2v7TqL1d}pUp!2OcTOZiQOW*kD>xQ-7VrNqM z6}cR^#@;sr@0y*9Qj_YvWbMN(F_z>W&gmJTj6USM62q#j&YYg%-m} zDH)aKdpXNlcSDJ%%oxpXQDw?6MU9V|C26Hv0Vw5asFQCj@vIpK)dwaoPuvO_J7rfD zMMY?Ja`DoGI5M~8z@bPjed*B*1PgcNVtyhmdOg<~W1Bxcjb%?yb5l6u_aOdk?kBki zOE1P6Ka^L#>@hxfbo>-{5~E2{%$fjh$aqc@Yo}3t>-mZtkAhO{M!$ zenaZ{#UM7+SuH7I@))z3Rc7uOshs%12fsT#y_A`aMNbz%TQ8g>p9a+Mb5$jv)qq3nIQZ;Jw3~!DnTO9 zpfPeCxN_a}ftsBzoklq=s-OQ1qg4p4lx7j5A*IcNzDmW@NglQuesHy6qrNSb>-cgw zmVpZ?PC{G-@O3b?S6_B7lulvwI@Du9<642eFNF83*3M~vDy4_xq`5SO#S;Sai~3+w zfp#GTs;HP|^XhTONU7+;$X;JMDd=>lF7u)&7UnP-&uGWuXa!=Gi91G0hgp=2#KAX? ze-Z_?t8@aHJ&;wl&=;CvxRK;W=n*iXi_S$g$knm91Sc50p|qJ}sJ$a)UOvifE*(d{ z1W!gGC9pAKc@>T(;u_CD@4ktuhL&>F8~gFnF=W`0Z6>xgSchejDX=hbZDr$WRR-Cn z(ov)^kX6n&c?hnU4U40A#LeW$6(S{5Zn9fTlgMT`J4_S^+uG=xJLdsLw3HnrWyJTQ zIKHi$rtGz)Bgl{t>Tn`u6pvdTMvocbYPNLaa-5W>-jQ;5lnx`Up4BN;!C@V(%&eWs z1t2A|o(rf|EydPK{r_RsfDA1I)6)}Ut9v>aD8umL)~ZU z-d&fkyT5K%od>)K-sZdLJMO#P`;7PF-nV$u-b3D-YrkImk=i%b#v0z-u+$Q18Ek23 z{%rGmo7bABn>(9*ZEtBy*WOcmUCkG3o~(IoO{8Y9rp5DFlMG5U}4TV82JeewToKhJgJu z0`{W>?4J^_e?q|iF#-D#0``vx*gqs-|A2t~Fai4^IIt} z_DKTv0|e~v5wO2Yz`mc||6k`>@wC6P?b)^itxvSPuKD{--)Q`JRa=E-+Qw539xZnsd(6?JOsCUo^iEH-T7ra9)i@xU_Kqn#L^RKZP8}I<$Nwx(CC_? z)f-DZ-D$YcsH!OfB~c;<9`B*>S@4wNPPaT$Az1mKl1%1^tNx;tc-c2z&;To{vrL6% zgsU(ofv{mPG|r=1vI4}2lEJ>|f)lE1R(YmhOj{rsW?C4@gduk@<|T3W;)kPJIyj~+GC+kEv06U{LpXy5!=dm3 z`o?A-F1-|Gv0L4w)>6+ZVqlM-Hwi$`XH&ag$(N=Qah!Gnr6|^MT-~d4ERhD2p3zv8 zIlS=^Z^+*|<|`qkHMh8NA;<300&*PAhE@BdzAb^kV$pCkE!H604n+czGAo-yh>1K1 zGx;$59U|AoBcWs@cqBT@oT(V=+?LWjvYuE~4nAQNpj$9e%)%^KX&oGQVbjwQY|rWG z6f*+pfjC56ot+&J|M6T@JKx{Y+11_M-wWf;W;p^Bx!H)gzp@yPaFegyMt#_fE=-ZAn8(>{GWpb+6p11C!QKD1}LD&zlc> zvA+~ST7)2w?&TQ`bTW~!`N=E1Af@{J^k;0LZKW_D(CYX9y7LL!=4K!)v543tw(Ztt zs*IHjfX99kpG2a)rCHu)*<-5nrKIqYWOyN(hIJ#8v>9_Kyikp@`QymDu@pkyge~w< zh;)F?9Z7u-hAG%8S67IXo4y53mL6{O)YP2bqU`j0Iz2bYx0TlN%fDAGl%4;t_pEx_ z&$fM}t*`Z`TYlJbvia@J^-Y1sY{S$3fA;UFZ}$DG?~wO{-XKH)_`8~60I%<#Z0{P+ zC_JMc9DWAY`J=ESNGu*oq|#yyWIh&WqUnW1CT&CjuT;HxHXR3Wj~>zy}cbX|jw6y|rV_}fevN1G z?aL~koveJ|ggO^pwv7@C8IF)LiA86T$qj2fLGQSF6d9omnhZtt0K5}%J(#SOQ6;35 znLe-PZeQc+cn_<`9ubEsvBbgYd@edark!KwC#RQE6|j`xIvzRIBVHxB>1i0N6pU!+ zg8IQMchefr4?MoAoJYc~>gqz$kw3~3(xn5d6wgOIuI>dYla6z?uY{CJ$EHlMwyeSX z@zUeC#pzj9_LdfG^sy-I@n(ROWJ~D+*78_Z*~`E$QJv~lrDdeLH+zffv6X8dk3jE9r9~%)`7vyBT$Z7vlnixvHc&c` zoF)iK7*Y4n5yYIRSH+#*cbI6h4BV@Uo zWu7F}?r#l@l#^*-WRMPFU|8skFV1Qa7Fby5OsY|H#aE|!bt#QB_o??b3r#ka!vtTA zof%e@6g&Ul;7NGe7ux=yZENf4mY;38ruj`xuWNi$V_m~V|5yDN>pxlFT6f5|;Qa;f zwY5t%e^7H1z%TbtcKaGnV@H@A^qEuKKCM7ZoNzL_#3rwibUGQGg{9Csi>bU(RWjMF zYdmKip;y@pW|!&7==?%jk*E`2o#xIpo}lgl^@~#{O?JK!YnL)rw|A=3-Mq$=)e*eV zoOHoiF215n)QYZ7(Z9yi)e*8o*(pMRy+q|c$q?1499ZLt>GtLPstc4(C8CcPRubEg zxbtzqq@>iZHO(dKQP-x|L`J)u^5JquPYGk$OzMY5YjrAP*P%`kEft-QYe|SZkG`AY z0BszzM%H*nya9q!vR*&^5aB#%8PzgF*0VO&v&hIAPo6iCO{sP_gQ265oM)!WSZTE! z$EV6IamN}@i#MQt7hw?7#aw~A8A7IP5qEs;dQ|w|q8y{~XkscIak*~bze?E{8>Qwo zp7(CAdfq$RZM$)&W*vSql#~)~BkR_9?z;i?WFf{0hK&_HT(gynE2J`!tJiqmxzX&6 z%KctaWruOhj2u{*RCl18FkSHZ_3xiL_V^?jr)zx2@Z;8v;DA z>@e=Za_yr!7Ms3}^@^NtjVE{OTZPT3T40|UybDS)zj{gi-4H+8a>%UlRA*b%!$3gw zVe`KTmLb*=QT|<8Wz!u;&00GiQ+h~gMOoZUIJ-;c2Vh_aNU7k|uxG6eNsOx7+%dfp zsO{!ps{_TcB!5#Z)Q59XtpJpgsxT&khBXT-4%y$k){5+>)Du;~5`<>W706i|RpEto zs?AMnE#lm|TX{h8`&o9$c3?Sax}UYz0k3hLFe!7mZd!nk&MhbPE3mC=&B$(C-A2N6 zQ+PZ9)>owFr=A)s&RC}qy)p|OYHv<{{N$cgQ0$H zs4v_xsPzp(aPiK;j-fgDTco$EtEVp%?&+Tk`yNEU2{r7C3D^e+*!wxQzc<|3-xD5! zVCdZt>bpNY*b(aPn}vY#+U#J@Y;WIOIOID+z@FyVzP{OkfpBkE$Iw6@3nAVU?ih;n z&2{u?gI#@dU47ks{oTGP#0IsI|$fa1nf=%_AUZ;2LXF00lS@my@P<=M!?=qz>0ZYX|BGF z@LkFpFi!aH7y%m~U^f%6w-T_o5U@8Bu$u_jn+Vt&3D}JUY#jmXBVfG*EXBw8DB-)4 z1ndz4_Ami^F9AD2z#bxC4-&8k2-q74*y{<{4Fv3U1njj0>@@`J)dcKS1Z+D2+eW~$ zD`!>z|Ko)39wT5|2-s!9#kvey8=mmiIJ2+dSSh z(zu~v!T&CQPu*|(Uhn;TZ)0t^=F>HY0qFRXt6SqK&3n}SH)p06qKlYgTjIRw0+4Dm zDyZh3JI~D&R3I7$D?F3)X5$*qeZE881e*CXj=X9vxMX{xzNh zeW$vy2`ffhFr?q!dz@jT~~IpshQB5*kxPkHK?&9<%@TFP7xi4X`3m;VY4 z#cV*)((qJ5eA{@Aa_?K?S<%DU5oNut_^dDwJCIB)icdRYD%do+>Mu&`sb5p(Ze8O^ z)O%J(3B*Cw&%?d*3pd*Z*bFn3wF0C>%2H+y{GlYv-eZ?=ARuatVWF2+tPNpYgBUFm z2^~)r8xhiATWAfY-(OA=b3Hl{KEbeLO#}ETR3{Tv-H16KFQuLBRjCT`W1nVeA@-RIUZKE6Qu_?9tnm!>+q23B z^%E)4oXi;4I&dtHq|()gMz(K_C!VK_X-Il9x&VRph0vLinK5G+{maFw5W?Q8zF3|3 zjy0aCo{&df+=#jJoz&8yNGKh$N>k~Ts$_F_uJL5{6RXNIr!d9`pLITdBO+Qs3Z%Yd zn~N%ArS#ZF2fj8BktYH;-K?)8y&hHC@Qv1kqClO%lb%gVMx}yFtX4P`dDia4i>+wx zgz{9RPlYTYHRPBF%Ugb7YaXmH{D9<^n zPrs=nq4<0T;$c-W%dGlZb<&&Hwjt>&Fd-F^t-@8eW3;2IQ{1$6J5nS#_2_cd3q2K0 z$Epd_9b28==C!R?DnXctAkdepVY3}wo#N2iZAfum{m@{SpxandZ1IY(RwuuGZ3~jW z0xNem!Q1HTgK3`v7 zSMvS9*XT{vK3jXf<_k4P0Q`de$=h_Mqxw!wxR86PfvuR8+%(8S}ctzBg=a z=8tAlOImn1fXl!QYZJ(>FRL7X2bMU<*C0Z_aep1#X2A_1PS*Gr z)_5NC&FWz!L=h7S|Ff`7l z6H6j%jwOm1D^d?7mhD*MiO3&R_bcbg-jEh{bc{{L>%o&DfgS|TTh>}Az9g-a-pg`3 z*LcqHkyT}9ZaGyeM>$SPYyHo&Ah~jOBxK}hJR|9#`(sN+%hVcAaNd(!RNZY%961t= zW2cnlVsrsKR|4Wi@q}F%O^?ZDcbaL|ck_5_yUh@zThFiYeCGp%6TPWghDyoWL1x?_ zFJRT#%=7pfPmI1(-2ug14M*HN+e2yXxb$)ETUg`C&-bZE{4|$h2-_LsP70IKy21fz z?o9UnhIPGujVDtl#Gut<0lvlql%ybEx_iM6f`h<&@VXA-a|>$mxl!g-x10ZGXuwK-Jx zK^UBVBy>1?Ms-dUdx8kN^yS+%)>)07Ym*f$u%Jbffu#g1cp5Q5=B``oL*9E=x2f{R zOX$(02Db*0DL7R@pNp~Wqm{4>C1v`8-B*WsRe~u|7bX{W{%`Tsc-rG_|JpXv`asL+ z=J&w)Ki=eT{2kvEJow3*68P^cfn#g;p$eqS#^}syp$~`z^&uoH$R*c8uf4jP-t4aU5BaZN-*fvg0_Jpp0fl_QazZWgfB=+X>$& z1AEfhivZI-3m(4{Tg0tGguX`w(N6lmG<*#Eib-tU|5b!YC8 z6({UJrcIpre&2V0=iK+X=bn=cUmT7tjvPIidvKZO=idC}ZqMeCYL&|@%)kaGvoJCx z#qbK#D!a%CtLZRY=}d0i)qa0VV>|rWxHA?H;%b18uI6WvHdbZ!bFwnQvlu^F@o=7qsh_+9r(xFJ) z@9*m99OT(>)9~>{zyCxS4ouNm2v(&jSg8fuF%e2KlGJ~O;QmlzJ9sQ$+T2)VHa!bj zqoECgCl5QG1UeA;0ZEw^m%>cCyP(Zr5O#wq8*Qv^NWu5dAC|o>~%#`1B(v<6tGpB7W$Kpfv&gTkQ13{FR+1b#C8v_+)2a zM=va1+S?z%ayCNyuqKf)jTXAO$<+9RN$ANBnLl6|&EVyYcx9bSu~8Tl!4HHz(ZA<2 zTCStZ1;NcGFgFKjR!oH&@M1&Xh$AV_7tR<9Y51=T!7zq6f(Nm5Gzv>K7Je$Z%-i5?H%kI40yvmUBT(@z(BC8D`*a4#_YiQ zXZi1hH#(KK793+pQ@7Z}Zj6De@=ffOjbq3BK{@c6+(^t1>3H=m0`{cuwR8bW3GujS=FhtVKiTZ z$CY(QQwD4o=N?c!R+8c~uVmfhsy>@--c#3m9u z2M3uO2HW7WI$!Bmq%!&DLeoR-zFGVy%$o3bh>`*SJRgb8hTz{U01*6ZE)ju$F3bWz z94>^G52Fs^nQK&QNy`2*g_pf9ms(9y?9#U`Pf%OqDVQCP=xq<;{2@PBXo!wuZH;BX zQW=casMReWLWW-LCe@nYCtAFnIX#cff*o9gaJ#nkAu9xmi>Vm)H z=802=GB5yNmW0-tl*lHPs>CWz`R0RFviLI3y)u}YTA5_~V6EcpFjm2I%$1SLG_4x4 z>Juzm0$jDsbF2($>sKbSFbkh>M|xi7$xotpI39w7 zAK0F|GBch^Np)gv-Mf50mOn(EDaEGCm?>Rp*g;Y@5*1NwEX-UNkM;ZgmWWc?T)LhW`Bo)ed`pSq^ zzkDx>)y;C+(8Ve>V4!5>#xJ2~rs7;|`4n>5{Nzbyj{g6r>G=5Ap|Rn~F_XpE^w%q3 zJQn7BzaQKblFH^|lbsXIKXmhP(fYX)tfbt0Z04eBS7RpvuNjd4A2J{l8iBq4Mu>SdlX=HL=ozSJg^k#sT@pBt_=8!%%bzg>VUKHA$Ola-pARS?#ZoP<|$?-mXxPi{%L+2*Z6W7waC%0j9xgO z5{l8zF7vcD8!{2q=7PSPE-j<@`xOz^s1xBS?DxZ7431fi-+kQ4bWXo4shwXIxp6Yk zi`2RXTt11UB7vxNuWZ;8z%XuKE33RwQa`I^EfcxWarL}*ndjOWVcCY&MCX3bP|xgi z&yd)q-b(jZaAzz7(FW9rsnd)At<1~$pmvM0E zwk42KZm6#I2jU9DPHBh z^0e!gXOT8uhEvn^$t3s=7>gb7b#vTYazm7-vTHetR5r6vOlnjn<55g!=p0tfPozb! z#*CYcax-Dw@;PLW$IUeMY;1KN-RKhNPo+{AXC#8kBg+pVlPwx#k5Xe|@r1HAHp?R^ z2lOh!S!u^|1WVtC1*dl!U09mB(}f38x;{}! z7naXpxtp~nYPs^JqIAB;q;9Mq^>{0zp89ol->&;)-IH}^>+Yz#s`ka&kJc7yAFS=E zt*`lV%?E29uerBoM~$cY^VQE(U#vb_eOuL!s-CZUx++z5pz7wzmnxsDd}n2}{yp_` z&7tOj=H{jsnm*jL+~jZSXsTxas|ZyLR5W{DC~Y_>KE()zj!68g_a)c(|G(*8sw?&x z0`@lu*k5lv;rVjqh>9Ot?Ir$QCG154_5uO>2m!muu`2!`)o&+Y-$uZ`m4JPMfc-@R z_ALbLn+ezg0h=daa|CRbfL$hF-$cN^k%0XL0`_qNc8P$^5U?5n`vwB`F#`5c0#=Ms zN_}38QA$`bMk!&%7^Q@zj8O{&J)9?C=Ly&}0h=OVlLTy{{xhB(m5$lkb>4iK@ZEP1 zuunEWQF(_VhdIJ`;{$v3>&I`w7_l2-wpE?7cAd z-3vNf^}KNG{QqXpyFG2ETEEab*79`Ay5?VP-r4jAO+$^JY8+_z?S{_!kJOKWUEt>0 zCu(bIqSb#}^|PuqmHCRFRy+#v3!caQ1Z14mn*-d?O$5&}W3-J?NvM?Fg%P+4AAxY!MghzgoV+x2 z;Xnj}sY5X=emc8@2tRRGBP05qaefAmjt9=`M&wIqqdl4BIi|Za$_K(_MRWmNdBN6X zc@pVvZL4Z6LL_6%7tGxaCR2;{NG~n`sbJ#17KGX1xBF4sYqM8jM<=*IOrDF(@#8{t zCBZXLUct78RRSm_X`F-BWqHbH!r70lJQt@7H$-_V^;w?yxmR0DsJM7^KFpnec=#Zj za-ilM2q_g~Tux_s7U%WqQv#lTIPa1eKT8WWVlBW_q<_9Xn?gL;i#9zfTeeo0trvwW`%tkPlyXN3^=otb#Na%(GCv^_IiIaX9-GuVDXv+LV@wxo zZI~WNS%60i3j%{H#u`x>%59KMzQZhj3WOE0E!-@Rq@}a4D^T=&wi=7xpe{7`*K!U` z#owicip=Az;JIuS@9B@Jb~}AsvT9oYRa$0oa7kt>QP%aE^0}=3b=xdp{H#!_R-LWD zVz+7#cu%dLTgF^l+16)L0yq>6>8|6TaBV}@gFKF~V9{zkMk7fUw&wt>8R1SnpAcE| zVjLjbeac3yEL_S~9}~5<<;Rd@zq-HPsfo$ECt=mVTsw^=sbU>^f~Re+^)FfnTDCO(xanNuvyIyt9;yFy-9Od!)IL%3m(^dc9<6$&>Xypc ziceJB=vi2eEx&eamgo37sD8!y5bS@FY_}6UEAEs*IE2HHqX^>dbWU|z>#Ph|s+dl= zOgo5GxIW8sY#mhZd8G^OpfKzx!LY-}rvYt8mZzdRt$rn2MD4L@aX2|A6bS*Iy6n1C zdq+yR3slN4yza>I>{YADHyWk$#dj3=)@6CpD#E^Ks+imfas@!Pvq<>7wcgzjQZ9Nw z)z)QsN~#fUi|Ra3WTK~6g2+Fq$V?<`^R6lZloEsZH{ON59_%UCW_b#zohOCtx9kP`a^95=#fy3Q~GSDm<&SVwL$i2fI7E zeFI3pJ*y{oQeN*AxxkJ0SP1;E)tI}ZrKCH35NH88`m#K`(*x?mu9Kt9OjkzBI#>6% zN^wTU{_LhEPesK^twB4Z+7-Z`yU3c&u`o4Q6+5iQfD?0vc~Y_Vlgs;-R6JvJ?8x$L zQ3tgns^XQA&C0(FSW3{^(7j##106juLSdck$nqpn_iOzt=Iq`a%5Zmosgw-Jw>8_2 zOWNJ)J0dZnK*N~`EU=iGuB}{#D$8hlcD-Pve49?RRIUOz!e9;B8DvU^k}@hWn$JNA zJal?<_Ex03NmCAtC5T8FvIi?6#VRy&d-fLOgIO-A)3)dlqC%CjTw1UzVVz{@JtPx8 zCLF-vt-(Y*8J|h9h^0PE2$I4et?*3GaLN8{Y@YPUrU2EmT0k`mXyz? zSnvyox0t*eA_R&gNRURD?|7{}v=s7{(376BiLv+l&Hu$T!Xl?$(yq?lgbLZCZvPu$ zL78F8c4R)Bn1N`ICm|9g{>7>dDNL#g!z>KD${UdtVOJ@pwBzB2(~(5@P$bA=P!9)L z_|*VpCbn`b16E+zoV@`V4yuo$dIgBWkHqj4$V$u|Q=Sjo|KH^KT~FKVTc2&cy5&7B zTbtk8T-DUq`2NP;hL1E{TmQ@TTk3wZZhP(1wHs@Gy=F`GyQ;olHBQ=Bu zip|-5)Lv$l)!pLej**foj!O+$p7L)e%P*tWUi99M@xs{=zqP|V9!pu6Gozq#HfDLI zzd@E-RgH|w&2M*%l$JR*YAl>G2lqjijaiuZXKamz)Jb$^6<;nSOQa_M~&d72DTV)nM zN=tN(Mc54LvOLG$7WJbA{WK9XC|gNb9ucWr$nxZQ1Vw-)e=ZPPv~mzXl1t~g=m@0` zr@<8kn)i%Ps~QRxhlTR9N;1guEp|4 zU*j}?D+k>k)uexJsX%AKV zQz=Cw*9dq%bR&lw)Z6^wI4j3mrTjN(c`PZ>5c~tZJd)Wi9CSA;yHyFf4u$8#QER#8 zC(_dSY0m4ioha#~`f1Ksj5!>f3QvO(1y;`BWDtRKT1@08hGp9bQ^*^ZpO#SG~7eAo&>DE-`U6t2Xe52yoiYHbll7Q>o#D6E;$M`n__J;)Q4+z-r z6R`hEzPUc2-vR?uzy6reu98~mVo^@0sAom_74fzj}ovS zAz=T2fc>uo?5`5A|Al~knt=T_0sAci_L~IkKNGOuAYi{v!2S~f`;P?div;ZN6R;m9 zU_V5_{vHAQy9De93D^%1u)jmV{x$*oeggKl;LsTEva6Vxs=q_{?!OSQ?;~J;lYo8M z;=&O){2BrKUUvR}z2{j^Te9_B%ja75Hvf9_^>F(C)`ssj+*|*F`djPXQTvnH*_!8S zj#Yo8y07X#SH8O<2JlzKAI+ELS-$DKy{Wsd2E53s`OJKBNTBZ1s z)E;D5tia`pX9&B0N!bNV+&V6@7KYq?foOCZY;61t-Nsn)B`M>f5)N&v>4n8SvGCrm zRT^fN&QxT1F0;kThtp@mDeJeac2GBjR12@X60~cwJcHT>^0AwfMVK z;1Z|cbLr`5Bzcy*fIOPziPH#)?uwu|c=CMzwXD0*mM+TSq<@KzSl9*^@|QNZng;YCI_%x{oB_ zFZ#s=D;Wu>JfY3m6G({A)WQ24a-KV`&76qKymJ&@C{J{4b`psa+LpyDK=;z&!+=eD zW@iH{uspGwv&WIxLG_DdQ=Eh`bczu}gqpa=My@niO0Y{DnQh1(Lw@VB4?cC-|L*5Ghw{3Ia55WHlfynt^=99@}Fo^SOQ}zf7NEoSY3KfmS+Bn=X zQZeWCMzJn?80qX$zb$MlYFu*nQ3{ii(k~1^t3y>aW+#NIlq1$mO@d=q%HE%>s&dCj zS?QX(KFgEc?@0;^V-Hy!i&;IVhr^CS)Pe1tc|D+^!D=Y$z0T#mwK~2_19;4vZ^B) zCCxBhhdmHZh0G+8>4>MXAVqcp?Cc zYl!l|ei@0+E$Xwl5h8?5kIA7S>_}5tp7-iZMtPZBR>JWZEDOU4mn$};SEZ7JT(&6( zq;dTbMO>fdX|o7HDC9g*>#9|VV5u0q0+HfamM75)Y5~HrCuUzAdJQ#J~6 zJj>H(9n7fTERyyBQUgM;l<yzPx?kdW zEGD`#v^aJ@Xc1gr)Aoh83?-##@4%?%)-2DT)v0Y(?FP<*t?G0>SEYoE9?kMJT07MZ zs3Ydd$$9C>@mR{oVLENUePN()tLfft`#q6lIuLE2Or=ASxZmH^(K+b%v)D&zNO-yk z$VqH8@LM{O04Hg`e{Upa`hbtK@rlP`zu`;q+rFrQb6K8>YkMZFS|^7Bu`_A##1>PZ zwL)wUUk%cm#oe5J2p5(Fy)s?~vQneVXsY3Uk+(gHy6BsY_X&8f>R}m_J7;R{c00OF3ZwxjH+8 z4T^Bd+pxvj+mV%s6ecBPSlG4evSDO3q;3q*ozjghH^0DOg{-M~`+* zad+>H*$^^2rhaE}%Ah0$Fj#EsMBGT0$4mL@EVpKZg5{9vs3^F>Xpq#&8NA(Cmd8t3 z7PX2^aA$TJ$(~Z*ITlq^G-W7jwZto`l(%LBNV!`*0uwt;(Dw!6T-Vw*T>w%#LtuC5 z>FebG>cW5Z^MCT+`uT4dZ;9{!cX`^jx8B<_2z&p>ny!M=|8F$hQ~$C0*VUzKzgas{ z^Zx2@RsBQNMCH>Jk9fZ5*~Pd2+D$ngrFdN3kj$G3k8cP&J(y-gY(F;{JR6=3*oMv0 zSgG*F9CJ&q32Wh?x*^P^$dSNYU^)^7hXMOUSO#2{U}j^k5ebegDR)D@;whfwh((HQ zq2>yd5?jqBn-N{b@8`=reU&F_-DMe9Bx_m1ZO!p`%crzn)xl~qo=ybAEWpA<0M33O z8W@D(-v?<6(uwe7dX9zKwujPGdPS-^qs?8N^f%z5v);n$qqZ4;9_3 zDMv46;<5YMuny)2(8(jV3w>?~sTQW#x4^t~I362Yh(I)vSc=uPzBz@!zZj=}BpwP6 zgMZRuG6J`VA@E2T15Rt#Wlp!%bGdv%NMA>aU>--|eNrf?5 z{iYm`MtxF!JvPF!h#1B`CN}G`%x(=K<=AnpQ_l)-NU7pVqApj3b9@Oqv9n@gDVaJEUa>n9#?@vc;_E>j}l+=ZN!($yiV9F|}B^*4gj4Y3&Ws5}# z>zdK*MIPY(ylThPZ9>+9^n_;Ku9)9F)dMjhKxDdI@~#0`6lFw+y^mtu(}l zAi+w>`GPo3W!7aML3T%$ZcsWDZw zEoN@YE~0LZEv={NrqrpPwe~ofQutR!JDgET^=L=33#gt;>Qfw87GP-(<4xjFJc#zk z0gnmQXiSLW7+d4(qq~Ksx@9xq*4`YZ3rv=Oj)5`yZ_(a8whsy%>--4)bCBy zj1sUT1ngb{c9?*@i-6rjz}`u~?j~S&60kl3)=R+dAYiu>u-gdOtpx1t1nd?9_H_j8 zW&-v$0(KJtyODt1K)|*WuuuTMPBLUk$z}6G6bp&iJ0b4`BR;y#H zzem8nM8JNRfF;~9uGyvj-I_rHc7TBGCt&*s*j@s*hk)%SV7my|Dgw5WfUO{4J?hx% z|4YFBjDYPA$4r^j|kX*Ct&}Lfc+r>`vU^@ z`vmO2vh)A7o?r5`oofA~)>~TYn=6{mH+}#j{Au++sXtQpzPgIqc+Fo||7G=cRS#4y zSNuLeufU(oO*x($=dz7!;7%Bii8D;AO~u`~X2wg6Coik%(P>;mPL`9*BRQUm=Vdak znU!EwT1i8c&FE|Xid2i@AI^F{B1l z^NZz)_vd(OpjEu~VkS_U`Q_>S(HzgzL-Bexb8~T~u|JmTZ#4%7<4D5p^^9yvc|EVo z@zgwogHhpYEl!uksgdijSqdrD3%}pMLKU>;c!D1K!6m=%iC`;DxYLIKRB6pq8sCrT_!BRg_ zwYEZC2K}j&B=@hv6W~p`wMb&8c1%@c`sXLZiFwFZHEweQaXgmNxX?G)GdeoBx6iwG zps&~4Gt#@)J3Knj>mBIp?(G{Jgdkm`!ULo#cP-NFR!^vHH?5b73bxnxoeQd z!|K{{cdCN19>-}#qV`^!}2)*zGpnMKw1C;TPu4a`P$*R7;+LDSkJN` z+9NZg%*hY_1020wv(}XXOI2a)8rJ4okzlWSN=?H9n1_F1Gq}z3+zlb6GPV#NIu!wT z&hXHo$aEr*Sj16%bFKx65!|iJ4q&2b**(v!eM{j5DQ#VYZ2y0QXU^05V#|kGHZ;Gb z>03=F8~?2FE{N`5U%ycIy}EO?U#;C&^LsTrs(-Dzy((9=rt%FHKdaabudL{g)|unE zb;i^e2z(t1excF$A}9v$hqH0oIiZw7N>$-ZJmm|_vA}@E3@ImbJl#%M-E(+~^JQ1* zI4hV&^lz3Hg7T(RD1MvX=OWvz&>(B3kL7qGpH4zvF&?Xpc@T@)#M&sur8v`bDM#!m zEjgakXREsNFxO|P7N9?q7VV;!P&CR2&F6S#pAG7F?t~s35!{NuOL>5!-5gvYM@?Lv z)6?*Dt4_qC1YKNPP4K(v`a>xP7hRZGRpfZ?oek=qth|0?y_5Y;;DV~D%JJkoo77iJ zLN!hnrUz6SmANh))NfFC)`UsM5r~ohCN10?+Nl^p2jcsP^qW`Zczzy&FE+>w3Hidt zbfO2Zb2J!Nl$2?4IeTr6XW|)F&(b&LxQi-S+IBN?oC2?g9M8Ygub#XRhF@46PcF_* z$D=H*HB*U$k_$x2MxSc6%A60I&rbC;R{TCtI)PJ>vn@LwOG%(mqAz&`3+13GQ%4e! zGZDCuqQ{!Ewj0M|DT`uONv%1z18ETQARFaa#FlWeV5EeKYFU@tj(o@w#&n&)4K1)= zEIKQR4~H%=DJxh{hT`Xw?ft$Etg>~vZOE-(J@SRM(1~~~axQG!GrB^`aoQnn_U_iX z)#SDcZm^V83&d(I7*d8gO|GsGDK}J7eeQNaL^*nkwG<(lwLQ5)q(pS3T%Fs3Tz0Du zht`6Mw~$NGDWar|L{mZytn|QhvJFhMDT1R1<6U_g7$Dy?=)5omjO%lYg%%6b3C%_{o0@^MOZ~7 zBcX6Y=LGEBNL#5Yz9gk=3_T4w9%YuWdR3t33XzhTO-7<@_L$Ed!HaQQmG4fQ>=-8# zwsfS{XVSuP+KT6R?AJ&}IcLI@9$p5{PD3#29S?^h320wQbB=m2$nZs?wf1Q5N~xOz z^4fNBE*owzKKO$1*i7UMihOO3hm<93r{IRSvAsJSABhH%$%*jnbl9H%TaJ^8QrzY> z=Xh{g!lIpp*gv0u-hDhA2(hCUW{XB2XL}&n`j`=nDa4hHuOi~gmC1+IfrQA49mfJPuD7_-M z-_ZaZfkf?@hiwm~1PtvoM7!A*l#pWGWDdT4;yKN_9 zAx!NZ+J>Xl>D(BWy(y!dH(b<_bHitPAT6J*Hrc(-q~9M&hNEW#ao^#SlanX*`ThJ7 zG+!jbW7K22u_X~d91f>^kh>vu0^-Z+Aj@xNYwVr5Q50@NMmegtkintZ;_p{VG%;_E z2nm$$vWrb9#LKa^+~V(7N`ewbP?t_3Z1>u!++N-r)LSGM%~5B@&#wZ3S#73s!?^s~ zqP+a#5{y8k09aMKRi}6)6%af5Lhdfq=oYO?b+-rR-2~L%BPU+!6KA(WdgOC&JtqmRgkR&K)i# z=rYXMkA?5b4I%y0E28gmAY4|467NU_ksbxJDq#EnYbyTA({{4;tu0?{xxe|3nm0G4 z8~?7ct07sRs2i%i4>JFKta`9&U*+2Y_S&CUy#}=1IUXK$YesoVh-+iMqi_V?vObfx zGJnD!YsWv_eH&jJD*npiq}hMN3j_G0j>A!5XO2fveLy{}BVYf)c0QGeN5^9`@x&|- zV5|`Jul!q5p^ZIDJjX+-j%Adch+!iRS5s7c=t?1_)K=3pYjs5zDFAakF6(ynI898^ zkysjB!Nw8^y9cA~p;Rtop9<0Nc$if}G(46-#@HX~hv4kQ&bBI$8Z@D>WT^cPUGjpA%ofRFmr?svH8@Pb!`llUXha3yIXs^Z!R=#>ZLx&QDgqkxkSYL zpG7m3`PcA5SQxML?_xK8Ajfl^p3W$r<=CattB9fr_jgoOaXu284VeW4an1h;a+(Fq zbmw@2(#fR><(h^#zq8X|1dB((s+@#I4<g8%wwL!M;tfGVc2T=h68BNDoKyvq@tM^5Kq729PBZ(x#JT)bn+@`>>()t2+nVS`d$SXeH_jkrK6`#}<&;jvPQs2zm>;HihT22nGlAgZ*gUwH!v1Umg2viTS=8Q8r{yME^5S!-WVctK8;XLT`u9C_cB;~8FO)rS`a z8hWbt=pqDDCJSMCTPjhpzii3z6D41p@Xw0Xt8? zrU}>-0h=UX69nwT1ne9E8z*361nevU8zo@R5wH&tun_|GECG9lfSn;=!vt)IfDIC` z(*$gQfPIjFog!dgPryDv!1@W;`w7^`2-rsn*!u|B(**3j1nemS_8tQEBmsMZfSn{@ zj}x$a2-rIb*xdvyCC1n?;k$1lVBbi<{sIB}I03svz-9l^{SczPjkaW$4>TSsmSo(>SzPV9zU0*P(aM#weX#Xe}YbQ^`?GXgopJIOt~VshOj z$l)^rImA23&8?hVuUvE8>=}g|!#l~vS4T@fSnrUx1=)60PQWqae z%SIfI{MEUKkU^)W9Ag4s22VGZYE??Y@?3$(wYdoLAl#!v9{Tk`?pBzT@^QtL=auo~ z&LS`G60E<~X71woj&bf7sXmZ53hJ zE6(Rq9=d9vUPjJ_gXi?I^i(8u_Hf*;-69&FuxpiYtj)YYlBPPa4Y<}P_#!TnO~2bO z8M=JU2>9rH?O=G3cNo(n(PK@&z*#zkA`lKm6Qj)e&Gb~VUoatzJ_jjD1JTGMxcd;3 z0eEnjkUYuoB)C#WA%Q#GEJxovD@OmC9+J)cKFl_}iLoih^xdLfVMsRpZg3Y~PRPvk zd^{FRgjqniK-AuSjzW&+VvXq4YkF7bi6GwG;O-}0rj?<@DL?mu6Cs3{wv+njS+i&R z|2EIlo|gY;ey(}2>0-mF`d_R2Q0+^#{+d6pxw^WyYN7J|@c0$`^LTxpCohk@F`znB z8lwbT5}b(4>NUTJ%^QP*6x}Ut!w2nMh3HiKSR` zW4@YfCuPQ6;H}YE#TzS23rKgd+vV!mXY!FlpUfQwEAC)AwqX5O;5?(tT%^svukuaI z5|{nVZB;vv=Ly^=HRT(PFxr6rVYh2{EtWY}E4-jk>pnMMVv8(;d7jICOgp36t_^C& z8dzpgD1}sDFQx&|TCpSx`O|rx@%@zg<=A}?iD5hjm+&I`&HLiFCDW(UD+=P6cMW1d z7mlu!x^$A z&$G%8sVBJS>Ih>8F$H03Aya>SROBxLT^s(?QVY)9~MiYA`jC#rPR=D zficI#-S$1W3~@znTBd%G@aAb1W=G>m06Y!USR zmkB&|09FzZ^_YX%vWSg65YstT+%%Zr;@f;cV+JL7uL#7BJea$PjpUp*tlCIGYy68$ zRcv!8{>wU{Nk%8)%hKk9VmJT!9HkBAa2)0U~#Oie%q*Uf9nRZCvF91r z8+0-_L{;V%QES6*yril%(WZ+-f<#?%oPwTolI`uf@d-lS%ol?f7dugVyOK`zUd{tP z70H{_t~j2FFYz1ot{)NzeL!=?7<+kJa557g@fon5xPC!c*KB&x73cg8i+EG~kd9-m zfbIXU_B`cj{ioKW%?~zpG(OevO^Ey#uls&or1lHdzf<+j${$vquJ}U5MgU#uKUzhe z=Vsceo^)msv{V_Dsm=XsX`>X$jDSTTAvL&!}xLlqwZtiz1@ zds5m*W@cEuKAPuwmkk?G2;zMci#>t1aF3*!IZPvzAhZmgzGdH*> z28w4-q-DEA?6`L}s;eKa+xp-_FV0=-^E~I$gnH5ey*6!Q>u@TSh)hF3E-T-1XsHZ( ztIJ%S=lPbx>S=jw)WqgiABqg7Nw##b(xtp9rB2uiusm6Qzu6Y0hm~gcgndrR5Sa_- ztk>mv`X}$3ls5vx0xg-T>`$fA>4ThY8dEIQ{V5$f6cXyX=bB3`>Tt76u z&t@d!!$Ki^U26Plo(PSol|rEL*j(DQF4gB!@zhk#6f=-crUJ8be71E#=#AN;D`tHj z2?WoY*COn^!{vRW1e~^<0N0<*6#0k*`^f`fCU-u7FIpa87jMb)1Y3L5BmWC#ha$lR zw9%GsGESJZ?I5ArJkPV$ttrPRhxP@!=Mk;Je9XdITl=jGKuW?GnHTds)72LB3@0$5 z@{=9s&|!HbEnFYgw9R>*QL38|O%YlZ7gSmy4uF)1O1Hx5I-KX3rM71%8+fQL2}zp- zQi6bEt6hBvG<9~|8L-Fc1cXiAyC~wf4H>IsD13-@L0;65zDR_hQKFrBo{}n{^{YJzYo>UJ=xh>z!wVpxk(VQTu4>_(RrOb|e6H?m0 zbWl}`V)Lk?xROvQvEtn1kjgY#%GyuyZC-7z6>a)X*~-;F$f|HDnr}pX?RleeWF(ss zWQ|M;Q_vHu3+QFSmcyF}kEOdp!;g7ca1T4=z{6kTkjbPzFij;O>N#X6;0L}7u%l=H zWrFAmY;R}bq1drP8bVd0Ys}F=GTCW@x&pdPfa14o`ojKe0)k4|08+%;r$gh3{uled zvCCeaZ{YQ?aqm6+_LFll`oQ(oq}d_A%XFIA|r8V&2m$D*-7-@@2E23J|b zq`WERt}7SM1d65{Sh|~+ePH}C=;-`#)HBtipl{$Tr(Jyi{~b@;n_9o!@_6&Ro7Xhe zH7+!~*l?ajmnN`BB32SyOT^M zZU}{Xg>3+IxG>~)f)JZLtD(7S#aHcyZ$8_Lho)LC^(hwP99Og_;ymf4aRg-gz?2Ck zKF_06nK7UNNW#c`SoHGt!XXmGA_~mG7RH1Wjm~ZRV~XH82~#zMED4@F0!Gc^w@u%{ zMU0@McL^M4Hs^UxB(HiNN%5J|wP-w5s6mq?Ct<%e9gQT{UsnNZPeQKJWrC;sZFVlgSe@Sh6A#Nt@F8*g02^{^P$}nO2;s|nOpNbkJ484 z2y6H~iOQ_c6ljVqF_YfZCJjF?>kSwa!qAENH}(}{5Z0X_%sn3I$+{ljB(rarHKbji z=Q*9m)u&s7zS&JpqOsCgsRps~y7N4j)9EGU>nSju$@4KtD5||9rK)RFKT9y}988sW z<&I^veR-a5DybW`BLFy4^8SKE)<# znxbtJxsDlvQb!;X+%Q!6Vlu+rC$7qGz|}7yV8X?+D`EZ(F8lx>k` z;H0$l+H7xEvK8x0T%v=U#xNZP#fvqZ&Rj1Jmk2faTTv7J>Su0akY^|q3WqFKZEL-_ zLZtE|k(vB0NaUP){8+YSg|irQmM0hYZ$tl9io}7_ptc?K0sK5D7b6N*N0B=%iu&rZ*=07Z)7|4{B<4X{Yv2!|8`k zz=UQ?Eg2cp8`z?*&)Q*0r=JPiqvrX!0HduKu z{vHg6=dA76^gvp!F%CDvCi`gRU7p4-G`_#_4UH$-KHv6C+r_q{ZMU`lsP*~Q1Fbi= zywvhs%R5`5EqAqC+x(5@XPcjByuINk4WDaxZ^J^vp@#PQAJl)QezbmF-M1lI;FEP{ z>+Yz#s`ka&kJc7yAFS=Et*`lV%?E29uerBoM~$cY^VQE(U#vb_eOuL!s-CZUx++z5 zpz7wzmnxsDd}n2}{yp_`&7tOj=H{jsnm*jL+~jZSXsT{~x;0gKSH%;+=IM%1#XxJS zqS^C;s%?M}3*p-Y?6(?ERP$e{T~GEpAoR1Az=SI0sCnJ_P-IZ&k?YH zO2GaJ0sAQe_LBtc9}}>DM8JN6fPI#L{Wt;pF#`4v3D}Pkupc2{|A2t~eFFBw1nh?h z*xw^yf0uy$AOZUU0`_+Z*xx2#-%r5)76JP{0`@lv*v}HM&l9lE5U{^N!2UV``)dU3 zdkNV8O2Gaq0sCJF*ry5DHxsZ00ya;;Qeuq#4dJ{0i-7$+0sGek>|YVEe@Vc8j)482 z1ngfBu>YBW{Z9n!Qv~dL2-sgCVBbx^zKei;CjtA*1ne&nuNxqha&}|9NzRX2SEk|>eBkF;!$u)ssR_?^cISIgDf_iqRi$8v zfDl}%b+j@^65Ntdsba)rY7Rw^9CEpujsmRBcMAb-Q5HbDLU9UE3Mv%9eH@jm4=f?ZzCj9a zzZ`-(waIi4F1{w&LQGE31zetBOMV{`Bt*_LS28&J2E%+4Atz=6l_zv_ehdkXudGV< z1tQTSG0|U+ry=HR30@AYlwPq{T$dk3ek1ChbY_JyN7Y3!z}}m*U1~1@RA6>CKZ4A> znFm(V8Jri~&2-pPDF=mscK1Y*=|Hr7GL;TR;(mWuN9Um5&$is@SR}RR_n!y{X4}~{ ze@M9wF&R>kV7uEf(!XKu*vMpbk#4X$JyE4$PXfR|HLkv`rO z`P1*`jYb~;=R5m+9c%`*Wbp3Ho`lz{rhjvQikKbsqDK zE#NF*m{r3G{d1FN1EKhN9bAkjiU`8Yv`&k)csrC!P-yN-cs|% z>gTKXSDmSRZ^ch4G=Tp<@JFl42eB<})^1WAL{0>f=fEm+B7QC$v(B^TC(_o?-8nte zJ9i?Y4XX%N|kBdv+)u z2w^-g7PKT4feix5PVfN79J|ob34hdm}NwKQJ5W>p}S{@~`Lesoxre zDHsyLFfm-~#_?DxpT3sX`tlDTiGEGl$%k!vOsfxmd(GOCT_I93WUC*l@0z?{wD3); zW08%P#NI7KN%=zrF$^?>wA%CcBdu=r43lPUip_~toh|?=nTbd&GMk3zKtb3ehhuQX z6pb0a1T$%bQ^>J4f1lvEA~P?Hm(%fN34@XVNI4ew!&~yFk;zF-`RvN947kk40@kwY zq2n=qzYES%a`nmHka9J8%GLRMk?J1xun`ujb1b2aIN2@1&kU22Dk^Ye{uHu0qU}`e z1!h(!*d144JxC5G#}bKnqQrLC6|TUtI)9I7aBVAM3A>MY+Ln01A_fOYnRIsX*}FD> zQt((&!=Fsg&IS^tHpd(wcZ zDwb&1HlPBt+w+H!*~yjly(9DCL?RLjvo!Pr3nC<^>z2R`Ar*NwcRfae zABh3eMBHq@grDyf^7w>6I0Clid1Ca=C1vyFM3Uujz@s6FYV6Nn6^da8T8x-kEBbF{ zi>|hq=PB4HGRlGJAPX$??{LTzZWUL2Y!$&4(bk!(99pWb`1C`5KRRvs{jo5t6kwG( zHwR(r{qSdaq-&&ec<-=xWMp5Tx2I>c$2-v3)$i>c7}z)3Gukmc(AVLW6|MVsWNyy$ zH1EmGW2$Qm{g_+q)$d$p8+W*`8uN2CA~^V+9{rnTi;lcmh_MdWpEq zo9Z3x?ho|!cX&I(prW3R{(kR3fB&?1roXGJGZg3!g#w*ojDj$LJkxwyJ$kENSb6;v zo6kxWRQ$rKYN(^TZ=_>y?_O_b&p@ZQr@O1$J22YS;T_o5J3KnFx2t<}uuIp_x;#&J zAI~Uzf~sq1jKz?%AIOqlUsc_V^$+)V?(6UNcI+D%_4e!?-0R)j+c)MN80+uY+u5;q zq<6Gi=tjGkkMo&1qnttZR6KERCK^AF<~JjVv%Y?J4ld>KQt6GU#fM8|T%h={+#T)0 zmb+WnYm9=#ZAKPios%I!+^ z=KL&D-LIaP#l3i;ITfY?DkW+*nBSd`BEN~u3c^U6qmuEpu+yl^55bs$-5o7uSz?wN zXV>kR{ZuW3Ez!@@G+s{)}>LPo@>)JVVcHYVDz}5UCui zxx$y7YwXUSK{-!p1a~qtpz~!qn=9BNOkV`)bBI-Lu}UfZid+xeV(%yeJNf$j3<|JE zQ}!cawcz-ebJCN-q(q&a9x1NNhmqo#wwcl+j)xykgClT~D-RvvacHutma&u}r9_Ly zkq7c2uD*=2`Ongb_4;;KpV1x6Fexj`hgWHJpvV8SME!$^d#{Gbf5n)Dm3Og7fck&4 zIDx8`2=!P9wHpc84FqgE0lS`ny_JByg@C=8fW3);-A2IPK)|jeV6P`&uOndB60p}2 zu-6cj~I80=AZbts!8m3D_zEwvvFY zAYeV}*qZ-K!2XPY{V4(a69V?f1nhqhu>XgE{Sg8C@9NlE%G`3J`gd#ojez|j0s8|2 z_WK0vzY?(DBVb=5V82Vien%Z!dpiNUg@An>0lS%iy^Vm~M8Lkb_6&CS%>-)+YtNd0 zQP*$Hw+Yy95wPDRU`cVn3E%xX0sBt`>^~B)FA}i-K)`;Dfc<*{_5}j=e-p4@C1Afo zzerihh$ypTEkHi_c{F;e{QH27)s^n-&&=@-B!2UOsL$Qx|H$lOt=M)e+BQa&d(zVD`99M=en zMf|HxrG|vADmOPz?WRH%>Tggzzc>V8Iu}lem^{X4X4RQHMye-%5Q^XR`{#I0PMi;J zEmR`CF-<*G6Mn}1B-V|jJ5q%zHo)5o707V+(wORq#+73nBS`wQ3?*f@nj_t0=o}n4 z7uyjh<4Ooi3u(x21X=TL?ze;E>8m2-iS94<>i()9!`*Vfw%&Qqh&6U6;Qk=y+8-g(!-}6O6>t z;Uh7;31ZcWJ4Q-}-6|Q5L2MlUNmSI1{6%DTTK!~Ic#45XGQWf#f~$0qnMfFNbwCP zSoPhieU)$Z`~ZNL^GB;H@Z6%E>K9(dVl1ONxKIbu(bRYYXxZFw31@b1BmA-jO9{-Tt4@m{>30p3&RShq1kRJcTs z)G-(i=aF!t1jLAp!M^E&6VnwFo{cFf3nb0l3q$E3Q5o5`kjKfQKyc z`*itmTJ0?GWT;cxZq=^KT>M1M6g2`DKo?Ts1YGKiE(*2T1{menq!pvLC~b3rXBOQ< zFg6`dgW-!$q!aO21U5pj!Z8d|%)a08Cgo$5PA9Kb9}vodi$m(nPyr{Tgnq;^a@ zr>ZB>{#jgC?r$4Ua5rDc#FXZa4v`M6EbIAAJEFNqs>k33km; zVJ+%pXGVF=Vv)CuI@seYstM=?Y?#*teJB}^p`7i7Yf;WI^;?~z@f4W!MxqgR0gm^6 zqb{udzZ6m`z2#aLa_lNxgB*vnplbKmmnaZeEE0^Q#C&91tw=ypX2uFmAFO;Zst>^c z525a2p+F+!I~18_m#Y}-%!b0%$a;LKTa~p@fNsI0n1wA^sSJ*_u-_lT`t`^~=au@Ezz6onDl)|La^XkJ^TvupAT7)2w z?&TR3bUYrn-IG^%K}z+?v}dfL&4pIpq1E^Q`Xv;$nbl?3|Y=q%f%hg7Mk2@I}#jdFq`h_2~4{0#8L!XXELVVTQ5RjPh$r zyl*V<%sgZ2QNjfQSVb^>jxB)QG{p=oQkA+QrR)N8bB0!T6?mqdu%?{AqZB(O#k#V! zzp2FjngY+QLzo-|dqu@MVNzavxr9x%xxn+|45{0vrw+r(!aQ6?#D$`)VY-8)lzhoJ z@~JKG1US9g8r6Z{$_KW~Gm!;bFR_r}2q}|jWEz=VU-05kKBhjn2+Pz&Afjia9FOVI zY^{t+A*IapfjzUMz*E^AT-vXyB(bv-b1aw&W+G=s!}Dwt=e#vs1}r7G%KKkh=&b}l z0}G;PzEF7Hr|%Fm8w=ZcRjS9nc2j}6vUKFngQj#XX#<6AqQQqRr&Tf8ouj`@pA0G0 zl8cqYTFW~3OkH6sa_?SJHbV+VdjFL60y99$^c8LSAqbdlGRGvLs*u*6 zt_o_USqa`$*n$MdHRWhUY=sPW-eJ4U4AFQz!q=&+vDr)_rLoc))CX5>rtmtXw=)9) zs?;I_Fq@xcWVH_Vj>l3GtJ;iqTKftdb5lC`nK)dwhh->O@$4}k>Dd_Ij3cwB<0R>4UCkN(c(50+K~>SV_4{n z%}$3yEV!_+nUtgEiZ4%dO<_IK+@s#wEHv3j3FBuuc4k;vQf&W!z2{S&w$rU2Z~2#& zoz3rSUfWb?{ApvM;j0a6>krkPt$lZGTTQh36V=yMovZw0WmUz40DtxT0k>109FH(P z>6RpRmNYvZT|5y)c2MV<2Pxc!~-bk`Sn*1XFY3)tWuh%G7;wW7;YtSgM*97V{CX4fd|(51&vhA2t)d>k+-DcaG#xpX}W;e!R9EN?uceEncbPl;sNt1xk# zo8QvdRgx5wyibfXX6pzeLrE#&wyLJUvzzs+C($u>^sL|L z;mfUDTp^{2tSRimSztuFLDi-*B@x--I938!np8z$5J`2D(w(ySpkPyw)Dv`8JD@a) zU4;Rm8D+mfW=?`d`3#KPVr-aXSCE_oT?wF+R0#`|&+xKg#bE=xyU>s9CrRTPx?Yzc zXKijUew2G*m87|`(1+6OQMZSgj_CF+B+4Bpvm-hyOv)Tiq1eIa%tAsx8QWCoMRsHA zz8fZXN8{i{wx~kTfh@;y4nHkuHnv>rnee>eY5qp@v&~O5hnfePo10!}`f$^7liwV3 zukrSVpEP`~;k^wD4Tl=q>wi%HnflTCb#>pa`()jdb!Y4DsJp86#oCY77HS`??WwJY zTLT}gdA#P{njJNs>d#j{Q+=`eX!UJXKdO4Z>glRf)q$#;D_^R7uJWCg(aO6judVn7 z_!T@+5vmxdX!g9&_W5EgLQ14RcasDm7Q!3>8z*361nevU8zo@R5wH&tun_|GECG9l zfSn;=!vt)IfDIC`(*$gQfPIjFog!dgPryDv!1@W;`w7_l2-wpE?7al+DFXH$0`?>U zdxC(SBw&vdu*V44qXg^`0`@QgJ3+u6B47^^uy+%%;{@yh0(L(EyN`e!iU^@xe4gz*30qY}Ry#(wI zkEi0r$`O?m6Sdn3-`z&QZY5w}i=oM5N4{J!H0KF*ktSeM1ZVLcr^o=iuxq@SNcX)h*=WYF}vbY-ENzmuS(OiP#`U;bOPNf0T>Me-$u zOsJohf=FGie#02`gBjpQ+^-kA0A9UXiNwGK({#PKrNHx~Z&lB&#ctbPo>(zmNemt=*)3HU7BeRU22wub~tXdB;H;3?4u)xBV*Sdq!e zaEhhJ!-TC)qrN*vN{$~iZqED8!d|D}hKi_^JuFHysOsBLWYw==>0l!HWf+7k=#V zGs+s;8wO7@{kwpaNKZKug0H zp}(8(BD_oyXL=^;Mt3b`!EZNNM1)e^Bd}&y< zdx;wv5Ltou-;ju@kp!*J*~X&MSSdZW9E7i(fmkW+INYpmxCcGcWe=9pAC6jc{^lKV*Jw5qO*sGlrtdOQJ(W^sIP?yS)NxHq7+hH39gbxCtOgFwc< z^Kkvx&ezBNw8U(C7*C}MJyYC$(bg`p^k^zec^>L{PTPyHXDL}GdD~4 z*IXh3|6G^_0QTrx3rUpop!&X!MJqjmLcux)?%AMtFz1V@*V>EAfTc1NZ#2~vczS!U zc9ZJZdpMn(3kP{cIQLZcr&12)@e`HRTHu-FyVWm=PcF_*$06x427ZJkHr#TsD#!&O zC6vUEC4I5<+_^Kz=*9xi+`dIqzZ(u*WV<)>BPola_9^om#$+2Z7XDnhqH$ec;JMR# zHRXs>FdY^n1aotpZDHYtkaC5&i+yfL{2NyksJP|=Piszygkb8w!dua$7fV1=g5ddu ze5wjOcR3-pshA*5C0bjS^SP9TF;8Ay;3>*GHLq%C6K&gcQ^ZQc`CLjvK-*2nM=U3- z9IVfzrFTMDw+>eMj}>^T@b&5@e^JD^Enuthv$RmBxe=RRPl0Fg-lHDQ594UE0!v5I zmcpbI*#-u>$GKE|PK0H)gsEdi8kr8f1)jY7e)SOZJevU0!p4rl@3q_kFlD+-z`hTld;j>%l;Rvi~dnlEz7*0_;TL>V9J(+`-rEF&qMKc+r zP8du|>oPl}xiQ(>8;aieXD6x z`cKuLsIP(tul;#V1Fvb|H4VI`f!8$fng(8R4IF*52k%Hj5P|J=H7dUGxC^wb zZjOheVMw=oaX7ZvK63Qr;lgA3C%3HhlcVA3^cnqw4;3E84-TF5cC2Mab?w6 z;0fZq8n`EpRQFAH%yjg14tS?~dS<*mfk3x+uxl{j4fk{fr@I3K!LF{L_`I>2;)^L} z2RgAh7iJ~c5%x8du){DY+t2HSihqN!4|9_=-rh;s$4t;?@Gs!ACfl zWS$GK>xJ2X`6lxGYNyVI^`lDbrnd~(KKel(%`}ef#=&I9Q%K{c{8C0a)tK~*m!ASA hg^Aj9q~jKZaiw38%H*31O~a}1EdEpN@R%P{{C}@CncV;Y diff --git a/MathNet.Numerics/ArrayExtensions.cs b/MathNet.Numerics/ArrayExtensions.cs new file mode 100644 index 0000000..281ee47 --- /dev/null +++ b/MathNet.Numerics/ArrayExtensions.cs @@ -0,0 +1,80 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2011 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +/// +/// Useful extension methods for Arrays. +/// +internal static class ArrayExtensions +{ + /// + /// Copies the values from on array to another. + /// + /// The source array. + /// The destination array. + public static void Copy(this double[] source, double[] dest) + { + Buffer.BlockCopy(source, 0, dest, 0, source.Length * Constants.SizeOfDouble); + } + + /// + /// Copies the values from on array to another. + /// + /// The source array. + /// The destination array. + public static void Copy(this float[] source, float[] dest) + { + Buffer.BlockCopy(source, 0, dest, 0, source.Length * Constants.SizeOfFloat); + } + + /// + /// Copies the values from on array to another. + /// + /// The source array. + /// The destination array. + public static void Copy(this Complex[] source, Complex[] dest) + { + Array.Copy(source, 0, dest, 0, source.Length); + } + + /// + /// Copies the values from on array to another. + /// + /// The source array. + /// The destination array. + public static void Copy(this Complex32[] source, Complex32[] dest) + { + Array.Copy(source, 0, dest, 0, source.Length); + } +} diff --git a/MathNet.Numerics/Combinatorics.cs b/MathNet.Numerics/Combinatorics.cs new file mode 100644 index 0000000..d1f38a3 --- /dev/null +++ b/MathNet.Numerics/Combinatorics.cs @@ -0,0 +1,440 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics; + +/// +/// Enumerative Combinatorics and Counting. +/// +public static class Combinatorics +{ + /// + /// Count the number of possible variations without repetition. + /// The order matters and each object can be chosen only once. + /// + /// Number of elements in the set. + /// Number of elements to choose from the set. Each element is chosen at most once. + /// Maximum number of distinct variations. + public static double Variations(int n, int k) + { + if (k < 0 || n < 0 || k > n) + { + return 0; + } + + return Math.Floor( + 0.5 + Math.Exp( + SpecialFunctions.FactorialLn(n) + - SpecialFunctions.FactorialLn(n - k))); + } + + /// + /// Count the number of possible variations with repetition. + /// The order matters and each object can be chosen more than once. + /// + /// Number of elements in the set. + /// Number of elements to choose from the set. Each element is chosen 0, 1 or multiple times. + /// Maximum number of distinct variations with repetition. + public static double VariationsWithRepetition(int n, int k) + { + if (k < 0 || n < 0) + { + return 0; + } + + return Math.Pow(n, k); + } + + /// + /// Count the number of possible combinations without repetition. + /// The order does not matter and each object can be chosen only once. + /// + /// Number of elements in the set. + /// Number of elements to choose from the set. Each element is chosen at most once. + /// Maximum number of combinations. + public static double Combinations(int n, int k) + { + return SpecialFunctions.Binomial(n, k); + } + + /// + /// Count the number of possible combinations with repetition. + /// The order does not matter and an object can be chosen more than once. + /// + /// Number of elements in the set. + /// Number of elements to choose from the set. Each element is chosen 0, 1 or multiple times. + /// Maximum number of combinations with repetition. + public static double CombinationsWithRepetition(int n, int k) + { + if (k < 0 || n < 0 || (n == 0 && k > 0)) + { + return 0; + } + + if (n == 0 && k == 0) + { + return 1; + } + + return Math.Floor( + 0.5 + Math.Exp( + SpecialFunctions.FactorialLn(n + k - 1) + - SpecialFunctions.FactorialLn(k) + - SpecialFunctions.FactorialLn(n - 1))); + } + + /// + /// Count the number of possible permutations (without repetition). + /// + /// Number of (distinguishable) elements in the set. + /// Maximum number of permutations without repetition. + public static double Permutations(int n) + { + return SpecialFunctions.Factorial(n); + } + + /// + /// Generate a random permutation, without repetition, by generating the index numbers 0 to N-1 and shuffle them randomly. + /// Implemented using Fisher-Yates Shuffling. + /// + /// An array of length N that contains (in any order) the integers of the interval [0, N). + /// Number of (distinguishable) elements in the set. + /// The random number generator to use. Optional; the default random source will be used if null. + public static int[] GeneratePermutation(int n, System.Random randomSource = null) + { + if (n < 0) + throw new ArgumentOutOfRangeException(nameof(n), Resources.ArgumentNotNegative); + + int[] indices = new int[n]; + for (int i = 0; i < indices.Length; i++) + { + indices[i] = i; + } + + SelectPermutationInplace(indices, randomSource); + return indices; + } + + /// + /// Select a random permutation, without repetition, from a data array by reordering the provided array in-place. + /// Implemented using Fisher-Yates Shuffling. The provided data array will be modified. + /// + /// The data array to be reordered. The array will be modified by this routine. + /// The random number generator to use. Optional; the default random source will be used if null. + public static void SelectPermutationInplace(T[] data, System.Random randomSource = null) + { + var random = randomSource ?? SystemRandomSource.Default; + + // Fisher-Yates Shuffling + for (int i = data.Length - 1; i > 0; i--) + { + int swapIndex = random.Next(i + 1); + T swap = data[i]; + data[i] = data[swapIndex]; + data[swapIndex] = swap; + } + } + + /// + /// Select a random permutation from a data sequence by returning the provided data in random order. + /// Implemented using Fisher-Yates Shuffling. + /// + /// The data elements to be reordered. + /// The random number generator to use. Optional; the default random source will be used if null. + public static IEnumerable SelectPermutation(this IEnumerable data, System.Random randomSource = null) + { + var random = randomSource ?? SystemRandomSource.Default; + T[] array = data.ToArray(); + + // Fisher-Yates Shuffling + for (int i = array.Length - 1; i >= 0; i--) + { + int k = random.Next(i + 1); + yield return array[k]; + array[k] = array[i]; + } + } + + /// + /// Generate a random combination, without repetition, by randomly selecting some of N elements. + /// + /// Number of elements in the set. + /// The random number generator to use. Optional; the default random source will be used if null. + /// Boolean mask array of length N, for each item true if it is selected. + public static bool[] GenerateCombination(int n, System.Random randomSource = null) + { + if (n < 0) + throw new ArgumentOutOfRangeException(nameof(n), Resources.ArgumentNotNegative); + + var random = randomSource ?? SystemRandomSource.Default; + + bool[] mask = new bool[n]; + for (int i = 0; i < mask.Length; i++) + { + mask[i] = random.NextBoolean(); + } + + return mask; + } + + /// + /// Generate a random combination, without repetition, by randomly selecting k of N elements. + /// + /// Number of elements in the set. + /// Number of elements to choose from the set. Each element is chosen at most once. + /// The random number generator to use. Optional; the default random source will be used if null. + /// Boolean mask array of length N, for each item true if it is selected. + public static bool[] GenerateCombination(int n, int k, System.Random randomSource = null) + { + if (n < 0) + throw new ArgumentOutOfRangeException(nameof(n), Resources.ArgumentNotNegative); + if (k < 0) + throw new ArgumentOutOfRangeException(nameof(k), Resources.ArgumentNotNegative); + if (k > n) + throw new ArgumentOutOfRangeException(nameof(k), string.Format(Resources.ArgumentOutOfRangeSmallerEqual, "k", "n")); + + var random = randomSource ?? SystemRandomSource.Default; + + bool[] mask = new bool[n]; + if (k * 3 < n) + { + // just pick and try + int selectionCount = 0; + while (selectionCount < k) + { + int index = random.Next(n); + if (!mask[index]) + { + mask[index] = true; + selectionCount++; + } + } + + return mask; + } + + // based on permutation + int[] permutation = GeneratePermutation(n, random); + for (int i = 0; i < k; i++) + { + mask[permutation[i]] = true; + } + + return mask; + } + + /// + /// Select a random combination, without repetition, from a data sequence by selecting k elements in original order. + /// + /// The data source to choose from. + /// Number of elements (k) to choose from the data set. Each element is chosen at most once. + /// The random number generator to use. Optional; the default random source will be used if null. + /// The chosen combination, in the original order. + public static IEnumerable SelectCombination(this IEnumerable data, int elementsToChoose, System.Random randomSource = null) + { + T[] array = data as T[] ?? data.ToArray(); + + if (elementsToChoose < 0) + throw new ArgumentOutOfRangeException(nameof(elementsToChoose), Resources.ArgumentNotNegative); + if (elementsToChoose > array.Length) + throw new ArgumentOutOfRangeException(nameof(elementsToChoose), string.Format(Resources.ArgumentOutOfRangeSmallerEqual, "elementsToChoose", "data.Count")); + + bool[] mask = GenerateCombination(array.Length, elementsToChoose, randomSource); + + for (int i = 0; i < mask.Length; i++) + { + if (mask[i]) + { + yield return array[i]; + } + } + } + + /// + /// Generates a random combination, with repetition, by randomly selecting k of N elements. + /// + /// Number of elements in the set. + /// Number of elements to choose from the set. Elements can be chosen more than once. + /// The random number generator to use. Optional; the default random source will be used if null. + /// Integer mask array of length N, for each item the number of times it was selected. + public static int[] GenerateCombinationWithRepetition(int n, int k, System.Random randomSource = null) + { + if (n < 0) + throw new ArgumentOutOfRangeException(nameof(n), Resources.ArgumentNotNegative); + if (k < 0) + throw new ArgumentOutOfRangeException(nameof(k), Resources.ArgumentNotNegative); + + var random = randomSource ?? SystemRandomSource.Default; + + int[] mask = new int[n]; + for (int i = 0; i < k; i++) + { + mask[random.Next(n)]++; + } + + return mask; + } + + /// + /// Select a random combination, with repetition, from a data sequence by selecting k elements in original order. + /// + /// The data source to choose from. + /// Number of elements (k) to choose from the data set. Elements can be chosen more than once. + /// The random number generator to use. Optional; the default random source will be used if null. + /// The chosen combination with repetition, in the original order. + public static IEnumerable SelectCombinationWithRepetition(this IEnumerable data, int elementsToChoose, System.Random randomSource = null) + { + if (elementsToChoose < 0) + throw new ArgumentOutOfRangeException(nameof(elementsToChoose), Resources.ArgumentNotNegative); + + T[] array = data as T[] ?? data.ToArray(); + int[] mask = GenerateCombinationWithRepetition(array.Length, elementsToChoose, randomSource); + + for (int i = 0; i < mask.Length; i++) + { + for (int j = 0; j < mask[i]; j++) + { + yield return array[i]; + } + } + } + + /// + /// Generate a random variation, without repetition, by randomly selecting k of n elements with order. + /// Implemented using partial Fisher-Yates Shuffling. + /// + /// Number of elements in the set. + /// Number of elements to choose from the set. Each element is chosen at most once. + /// The random number generator to use. Optional; the default random source will be used if null. + /// An array of length K that contains the indices of the selections as integers of the interval [0, N). + public static int[] GenerateVariation(int n, int k, System.Random randomSource = null) + { + if (n < 0) + throw new ArgumentOutOfRangeException(nameof(n), Resources.ArgumentNotNegative); + if (k < 0) + throw new ArgumentOutOfRangeException(nameof(k), Resources.ArgumentNotNegative); + if (k > n) + throw new ArgumentOutOfRangeException(nameof(k), string.Format(Resources.ArgumentOutOfRangeSmallerEqual, "k", "n")); + + var random = randomSource ?? SystemRandomSource.Default; + + int[] indices = new int[n]; + for (int i = 0; i < indices.Length; i++) + { + indices[i] = i; + } + + // Partial Fisher-Yates Shuffling + int[] selection = new int[k]; + for (int i = 0, j = indices.Length - 1; i < selection.Length; i++, j--) + { + int swapIndex = random.Next(j + 1); + selection[i] = indices[swapIndex]; + indices[swapIndex] = indices[j]; + } + + return selection; + } + + /// + /// Select a random variation, without repetition, from a data sequence by randomly selecting k elements in random order. + /// Implemented using partial Fisher-Yates Shuffling. + /// + /// The data source to choose from. + /// Number of elements (k) to choose from the set. Each element is chosen at most once. + /// The random number generator to use. Optional; the default random source will be used if null. + /// The chosen variation, in random order. + public static IEnumerable SelectVariation(this IEnumerable data, int elementsToChoose, System.Random randomSource = null) + { + var random = randomSource ?? SystemRandomSource.Default; + T[] array = data.ToArray(); + + if (elementsToChoose < 0) + throw new ArgumentOutOfRangeException(nameof(elementsToChoose), Resources.ArgumentNotNegative); + if (elementsToChoose > array.Length) + throw new ArgumentOutOfRangeException(nameof(elementsToChoose), string.Format(Resources.ArgumentOutOfRangeSmallerEqual, "elementsToChoose", "data.Count")); + + // Partial Fisher-Yates Shuffling + for (int i = array.Length - 1; i >= array.Length - elementsToChoose; i--) + { + int swapIndex = random.Next(i + 1); + yield return array[swapIndex]; + array[swapIndex] = array[i]; + } + } + + /// + /// Generate a random variation, with repetition, by randomly selecting k of n elements with order. + /// + /// Number of elements in the set. + /// Number of elements to choose from the set. Elements can be chosen more than once. + /// The random number generator to use. Optional; the default random source will be used if null. + /// An array of length K that contains the indices of the selections as integers of the interval [0, N). + public static int[] GenerateVariationWithRepetition(int n, int k, System.Random randomSource = null) + { + if (n < 0) + throw new ArgumentOutOfRangeException(nameof(n), Resources.ArgumentNotNegative); + if (k < 0) + throw new ArgumentOutOfRangeException(nameof(k), Resources.ArgumentNotNegative); + + var random = randomSource ?? SystemRandomSource.Default; + + int[] ret = new int[k]; + random.NextInt32s(ret, 0, n); + return ret; + } + + /// + /// Select a random variation, with repetition, from a data sequence by randomly selecting k elements in random order. + /// + /// The data source to choose from. + /// Number of elements (k) to choose from the data set. Elements can be chosen more than once. + /// The random number generator to use. Optional; the default random source will be used if null. + /// The chosen variation with repetition, in random order. + public static IEnumerable SelectVariationWithRepetition(this IEnumerable data, int elementsToChoose, System.Random randomSource = null) + { + if (elementsToChoose < 0) + throw new ArgumentOutOfRangeException(nameof(elementsToChoose), Resources.ArgumentNotNegative); + + T[] array = data as T[] ?? data.ToArray(); + int[] indices = GenerateVariationWithRepetition(array.Length, elementsToChoose, randomSource); + + for (int i = 0; i < indices.Length; i++) + { + yield return array[indices[i]]; + } + } +} diff --git a/MathNet.Numerics/Compatibility.cs b/MathNet.Numerics/Compatibility.cs new file mode 100644 index 0000000..a214520 --- /dev/null +++ b/MathNet.Numerics/Compatibility.cs @@ -0,0 +1,158 @@ +#if NETSTANDARD1_3 +namespace MathNet.Numerics +{ + using System; + using System.Threading; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + internal class SerializableAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + internal class SpecialNameAttribute : Attribute + { + } + + internal static class Partitioner + { + public static IEnumerable> Create(int fromInclusive, int toExclusive) + { + var rangeSize = Math.Max(1, (toExclusive - fromInclusive) / Control.MaxDegreeOfParallelism); + return Create(fromInclusive, toExclusive, rangeSize); + } + + public static IEnumerable> Create(int fromInclusive, int toExclusive, int rangeSize) + { + if (toExclusive <= fromInclusive) throw new ArgumentOutOfRangeException(nameof(toExclusive)); + if (rangeSize <= 0) throw new ArgumentOutOfRangeException(nameof(rangeSize)); + return CreateRanges(fromInclusive, toExclusive, rangeSize); + } + + private static IEnumerable> CreateRanges(int fromInclusive, int toExclusive, int rangeSize) + { + bool flag = false; + int num = fromInclusive; + while (num < toExclusive && !flag) + { + int item = num; + int num2; + try + { + num2 = checked(num + rangeSize); + } + catch (OverflowException) + { + num2 = toExclusive; + flag = true; + } + if (num2 > toExclusive) + { + num2 = toExclusive; + } + yield return new Tuple(item, num2); + num += rangeSize; + } + } + } + + internal class ParallelOptions + { + public TaskScheduler TaskScheduler { get; set; } + public int MaxDegreeOfParallelism { get; set; } + public CancellationToken CancellationToken { get; set; } + + public ParallelOptions() + { + TaskScheduler = TaskScheduler.Default; + MaxDegreeOfParallelism = -1; + CancellationToken = CancellationToken.None; + } + } + + internal class ParallelLoopState + { + } + + internal static class Parallel + { + public static void ForEach(IEnumerable source, ParallelOptions parallelOptions, Action body) + { + var chunks = source.ToArray(); + var tasks = new Task[chunks.Length]; + + for (var i = 0; i < tasks.Length; i++) + { + var chunk = chunks[i]; + tasks[i] = Task.Factory.StartNew(() => body(chunk), parallelOptions.CancellationToken, TaskCreationOptions.None, parallelOptions.TaskScheduler); + } + + Task.WaitAll(tasks, parallelOptions.CancellationToken); + } + + public static void Invoke(ParallelOptions parallelOptions, params Action[] actions) + { + var tasks = new Task[actions.Length]; + + for (var i = 0; i < tasks.Length; i++) + { + var action = actions[i]; + if (action == null) + { + throw new ArgumentException(string.Format(Properties.Resources.ArgumentItemNull, nameof(actions)), nameof(actions)); + } + + tasks[i] = Task.Factory.StartNew(action, parallelOptions.CancellationToken, TaskCreationOptions.None, parallelOptions.TaskScheduler); + } + + Task.WaitAll(tasks, parallelOptions.CancellationToken); + } + + public static void ForEach( + IEnumerable source, + ParallelOptions parallelOptions, + Func localInit, + Func body, + Action localFinally) + { + var chunks = source.ToArray(); + var tasks = new Task[chunks.Length]; + var loopState = new ParallelLoopState(); + + for (var i = 0; i < tasks.Length; i++) + { + var chunk = chunks[i]; + tasks[i] = Task.Factory.StartNew(() => + { + var local = localInit(); + local = body(chunk, loopState, local); + localFinally(local); + }, parallelOptions.CancellationToken, TaskCreationOptions.None, parallelOptions.TaskScheduler); + } + + Task.WaitAll(tasks, parallelOptions.CancellationToken); + } + } +} +#endif + +#if NETSTANDARD1_3 +namespace MathNet.Numerics +{ + using System; + + [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + internal class TargetedPatchingOptOutAttribute : Attribute + { + public string Reason { get; private set; } + + public TargetedPatchingOptOutAttribute(string reason) + { + Reason = reason; + } + } +} +#endif diff --git a/MathNet.Numerics/Complex32.cs b/MathNet.Numerics/Complex32.cs new file mode 100644 index 0000000..46bd386 --- /dev/null +++ b/MathNet.Numerics/Complex32.cs @@ -0,0 +1,1526 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +using Complex = System.Numerics.Complex; +using BigInteger = System.Numerics.BigInteger; + +#if !NETSTANDARD1_3 +using System.Runtime; +#endif + +namespace MathNet.Numerics; + +/// +/// 32-bit single precision complex numbers class. +/// +/// +/// +/// The class Complex32 provides all elementary operations +/// on complex numbers. All the operators +, -, +/// *, /, ==, != are defined in the +/// canonical way. Additional complex trigonometric functions +/// are also provided. Note that the Complex32 structures +/// has two special constant values and +/// . +/// +/// +/// +/// Complex32 x = new Complex32(1f,2f); +/// Complex32 y = Complex32.FromPolarCoordinates(1f, Math.Pi); +/// Complex32 z = (x + y) / (x - y); +/// +/// +/// +/// For mathematical details about complex numbers, please +/// have a look at the +/// Wikipedia +/// +/// +[Serializable] +[StructLayout(LayoutKind.Sequential)] +[DataContract(Namespace = "urn:MathNet/Numerics")] +public struct Complex32 : IFormattable, IEquatable +{ + /// + /// The real component of the complex number. + /// + [DataMember(Order = 1)] + private readonly float _real; + + /// + /// The imaginary component of the complex number. + /// + [DataMember(Order = 2)] + private readonly float _imag; + + /// + /// Initializes a new instance of the Complex32 structure with the given real + /// and imaginary parts. + /// + /// The value for the real component. + /// The value for the imaginary component. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public Complex32(float real, float imaginary) + { + _real = real; + _imag = imaginary; + } + + /// + /// Creates a complex number from a point's polar coordinates. + /// + /// A complex number. + /// The magnitude, which is the distance from the origin (the intersection of the x-axis and the y-axis) to the number. + /// The phase, which is the angle from the line to the horizontal axis, measured in radians. + public static Complex32 FromPolarCoordinates(float magnitude, float phase) + { + return new Complex32(magnitude * (float)Math.Cos(phase), magnitude * (float)Math.Sin(phase)); + } + + /// + /// Returns a new instance + /// with a real number equal to zero and an imaginary number equal to zero. + /// + public static readonly Complex32 Zero = new Complex32(0.0f, 0.0f); + + /// + /// Returns a new instance + /// with a real number equal to one and an imaginary number equal to zero. + /// + public static readonly Complex32 One = new Complex32(1.0f, 0.0f); + + /// + /// Returns a new instance + /// with a real number equal to zero and an imaginary number equal to one. + /// + public static readonly Complex32 ImaginaryOne = new Complex32(0, 1); + + /// + /// Returns a new instance + /// with real and imaginary numbers positive infinite. + /// + public static readonly Complex32 PositiveInfinity = new Complex32(float.PositiveInfinity, float.PositiveInfinity); + + /// + /// Returns a new instance + /// with real and imaginary numbers not a number. + /// + public static readonly Complex32 NaN = new Complex32(float.NaN, float.NaN); + + /// + /// Gets the real component of the complex number. + /// + /// The real component of the complex number. + public float Real + { + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + get { return _real; } + } + + /// + /// Gets the real imaginary component of the complex number. + /// + /// The real imaginary component of the complex number. + public float Imaginary + { + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + get { return _imag; } + } + + /// + /// Gets the phase or argument of this Complex32. + /// + /// + /// Phase always returns a value bigger than negative Pi and + /// smaller or equal to Pi. If this Complex32 is zero, the Complex32 + /// is assumed to be positive real with an argument of zero. + /// + /// The phase or argument of this Complex32 + public float Phase + { + // NOTE: the special case for negative real numbers fixes negative-zero value behavior. Do not remove. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + get { return _imag == 0f && _real < 0f ? (float)Constants.Pi : (float)Math.Atan2(_imag, _real); } + } + + /// + /// Gets the magnitude (or absolute value) of a complex number. + /// + /// Assuming that magnitude of (inf,a) and (a,inf) and (inf,inf) is inf and (NaN,a), (a,NaN) and (NaN,NaN) is NaN + /// The magnitude of the current instance. + public float Magnitude + { + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + get + { + if (float.IsNaN(_real) || float.IsNaN(_imag)) + return float.NaN; + if (float.IsInfinity(_real) || float.IsInfinity(_imag)) + return float.PositiveInfinity; + float a = Math.Abs(_real); + float b = Math.Abs(_imag); + if (a > b) + { + double tmp = b / a; + return a * (float)Math.Sqrt(1.0f + tmp * tmp); + + } + + if (a == 0.0f) // one can write a >= float.Epsilon here + { + return b; + } + else + { + double tmp = a / b; + return b * (float)Math.Sqrt(1.0f + tmp * tmp); + } + } + } + + /// + /// Gets the squared magnitude (or squared absolute value) of a complex number. + /// + /// The squared magnitude of the current instance. + public float MagnitudeSquared + { + get { return (_real * _real) + (_imag * _imag); } + } + + /// + /// Gets the unity of this complex (same argument, but on the unit circle; exp(I*arg)) + /// + /// The unity of this Complex32. + public Complex32 Sign + { + get + { + if (float.IsPositiveInfinity(_real) && float.IsPositiveInfinity(_imag)) + { + return new Complex32((float)Constants.Sqrt1Over2, (float)Constants.Sqrt1Over2); + } + + if (float.IsPositiveInfinity(_real) && float.IsNegativeInfinity(_imag)) + { + return new Complex32((float)Constants.Sqrt1Over2, -(float)Constants.Sqrt1Over2); + } + + if (float.IsNegativeInfinity(_real) && float.IsPositiveInfinity(_imag)) + { + return new Complex32(-(float)Constants.Sqrt1Over2, -(float)Constants.Sqrt1Over2); + } + + if (float.IsNegativeInfinity(_real) && float.IsNegativeInfinity(_imag)) + { + return new Complex32(-(float)Constants.Sqrt1Over2, (float)Constants.Sqrt1Over2); + } + + // don't replace this with "Magnitude"! + var mod = SpecialFunctions.Hypotenuse(_real, _imag); + if (mod == 0.0f) + { + return Zero; + } + + return new Complex32(_real / mod, _imag / mod); + } + } + + /// + /// Gets a value indicating whether the Complex32 is zero. + /// + /// true if this instance is zero; otherwise, false. + public bool IsZero() + { + return _real == 0.0f && _imag == 0.0f; + } + + /// + /// Gets a value indicating whether the Complex32 is one. + /// + /// true if this instance is one; otherwise, false. + public bool IsOne() + { + return _real == 1.0f && _imag == 0.0f; + } + + /// + /// Gets a value indicating whether the Complex32 is the imaginary unit. + /// + /// true if this instance is ImaginaryOne; otherwise, false. + public bool IsImaginaryOne() + { + return _real == 0.0f && _imag == 1.0f; + } + + /// + /// Gets a value indicating whether the provided Complex32evaluates + /// to a value that is not a number. + /// + /// + /// true if this instance is ; otherwise, + /// false. + /// + public bool IsNaN() + { + return float.IsNaN(_real) || float.IsNaN(_imag); + } + + /// + /// Gets a value indicating whether the provided Complex32 evaluates to an + /// infinite value. + /// + /// + /// true if this instance is infinite; otherwise, false. + /// + /// + /// True if it either evaluates to a complex infinity + /// or to a directed infinity. + /// + public bool IsInfinity() + { + return float.IsInfinity(_real) || float.IsInfinity(_imag); + } + + /// + /// Gets a value indicating whether the provided Complex32 is real. + /// + /// true if this instance is a real number; otherwise, false. + public bool IsReal() + { + return _imag == 0.0f; + } + + /// + /// Gets a value indicating whether the provided Complex32 is real and not negative, that is >= 0. + /// + /// + /// true if this instance is real nonnegative number; otherwise, false. + /// + public bool IsRealNonNegative() + { + return _imag == 0.0f && _real >= 0; + } + + /// + /// Exponential of this Complex32 (exp(x), E^x). + /// + /// + /// The exponential of this complex number. + /// + public Complex32 Exponential() + { + var exp = (float)Math.Exp(_real); + if (IsReal()) + { + return new Complex32(exp, 0.0f); + } + + return new Complex32(exp * (float)Math.Cos(_imag), exp * (float)Math.Sin(_imag)); + } + + /// + /// Natural Logarithm of this Complex32 (Base E). + /// + /// The natural logarithm of this complex number. + public Complex32 NaturalLogarithm() + { + if (IsRealNonNegative()) + { + return new Complex32((float)Math.Log(_real), 0.0f); + } + + return new Complex32(0.5f * (float)Math.Log(MagnitudeSquared), Phase); + } + + /// + /// Common Logarithm of this Complex32 (Base 10). + /// + /// The common logarithm of this complex number. + public Complex32 CommonLogarithm() + { + return NaturalLogarithm() / (float)Constants.Ln10; + } + + /// + /// Logarithm of this Complex32 with custom base. + /// + /// The logarithm of this complex number. + public Complex32 Logarithm(float baseValue) + { + return NaturalLogarithm() / (float)Math.Log(baseValue); + } + + /// + /// Raise this Complex32 to the given value. + /// + /// + /// The exponent. + /// + /// + /// The complex number raised to the given exponent. + /// + public Complex32 Power(Complex32 exponent) + { + if (IsZero()) + { + if (exponent.IsZero()) + { + return One; + } + + if (exponent.Real > 0f) + { + return Zero; + } + + if (exponent.Real < 0f) + { + return exponent.Imaginary == 0f + ? new Complex32(float.PositiveInfinity, 0f) + : new Complex32(float.PositiveInfinity, float.PositiveInfinity); + } + + return NaN; + } + + return (exponent * NaturalLogarithm()).Exponential(); + } + + /// + /// Raise this Complex32 to the inverse of the given value. + /// + /// + /// The root exponent. + /// + /// + /// The complex raised to the inverse of the given exponent. + /// + public Complex32 Root(Complex32 rootExponent) + { + return Power(1 / rootExponent); + } + + /// + /// The Square (power 2) of this Complex32 + /// + /// + /// The square of this complex number. + /// + public Complex32 Square() + { + if (IsReal()) + { + return new Complex32(_real * _real, 0.0f); + } + + return new Complex32((_real * _real) - (_imag * _imag), 2 * _real * _imag); + } + + /// + /// The Square Root (power 1/2) of this Complex32 + /// + /// + /// The square root of this complex number. + /// + public Complex32 SquareRoot() + { + if (IsRealNonNegative()) + { + return new Complex32((float)Math.Sqrt(_real), 0.0f); + } + + Complex32 result; + + var absReal = Math.Abs(Real); + var absImag = Math.Abs(Imaginary); + double w; + if (absReal >= absImag) + { + var ratio = Imaginary / Real; + w = Math.Sqrt(absReal) * Math.Sqrt(0.5 * (1.0f + Math.Sqrt(1.0f + (ratio * ratio)))); + } + else + { + var ratio = Real / Imaginary; + w = Math.Sqrt(absImag) * Math.Sqrt(0.5 * (Math.Abs(ratio) + Math.Sqrt(1.0f + (ratio * ratio)))); + } + + if (Real >= 0.0f) + { + result = new Complex32((float)w, (float)(Imaginary / (2.0f * w))); + } + else if (Imaginary >= 0.0f) + { + result = new Complex32((float)(absImag / (2.0 * w)), (float)w); + } + else + { + result = new Complex32((float)(absImag / (2.0 * w)), (float)-w); + } + + return result; + } + + /// + /// Evaluate all square roots of this Complex32. + /// + public Tuple SquareRoots() + { + var principal = SquareRoot(); + return new Tuple(principal, -principal); + } + + /// + /// Evaluate all cubic roots of this Complex32. + /// + public Tuple CubicRoots() + { + float r = (float)Math.Pow(Magnitude, 1d / 3d); + float theta = Phase / 3; + const float shift = (float)Constants.Pi2 / 3; + return new Tuple( + FromPolarCoordinates(r, theta), + FromPolarCoordinates(r, theta + shift), + FromPolarCoordinates(r, theta - shift)); + } + + /// + /// Equality test. + /// + /// One of complex numbers to compare. + /// The other complex numbers to compare. + /// true if the real and imaginary components of the two complex numbers are equal; false otherwise. + public static bool operator ==(Complex32 complex1, Complex32 complex2) + { + return complex1.Equals(complex2); + } + + /// + /// Inequality test. + /// + /// One of complex numbers to compare. + /// The other complex numbers to compare. + /// true if the real or imaginary components of the two complex numbers are not equal; false otherwise. + public static bool operator !=(Complex32 complex1, Complex32 complex2) + { + return !complex1.Equals(complex2); + } + + /// + /// Unary addition. + /// + /// The complex number to operate on. + /// Returns the same complex number. + public static Complex32 operator +(Complex32 summand) + { + return summand; + } + + /// + /// Unary minus. + /// + /// The complex number to operate on. + /// The negated value of the . + public static Complex32 operator -(Complex32 subtrahend) + { + return new Complex32(-subtrahend._real, -subtrahend._imag); + } + + /// Addition operator. Adds two complex numbers together. + /// The result of the addition. + /// One of the complex numbers to add. + /// The other complex numbers to add. + public static Complex32 operator +(Complex32 summand1, Complex32 summand2) + { + return new Complex32(summand1._real + summand2._real, summand1._imag + summand2._imag); + } + + /// Subtraction operator. Subtracts two complex numbers. + /// The result of the subtraction. + /// The complex number to subtract from. + /// The complex number to subtract. + public static Complex32 operator -(Complex32 minuend, Complex32 subtrahend) + { + return new Complex32(minuend._real - subtrahend._real, minuend._imag - subtrahend._imag); + } + + /// Addition operator. Adds a complex number and float together. + /// The result of the addition. + /// The complex numbers to add. + /// The float value to add. + public static Complex32 operator +(Complex32 summand1, float summand2) + { + return new Complex32(summand1._real + summand2, summand1._imag); + } + + /// Subtraction operator. Subtracts float value from a complex value. + /// The result of the subtraction. + /// The complex number to subtract from. + /// The float value to subtract. + public static Complex32 operator -(Complex32 minuend, float subtrahend) + { + return new Complex32(minuend._real - subtrahend, minuend._imag); + } + + /// Addition operator. Adds a complex number and float together. + /// The result of the addition. + /// The float value to add. + /// The complex numbers to add. + public static Complex32 operator +(float summand1, Complex32 summand2) + { + return new Complex32(summand2._real + summand1, summand2._imag); + } + + /// Subtraction operator. Subtracts complex value from a float value. + /// The result of the subtraction. + /// The float vale to subtract from. + /// The complex value to subtract. + public static Complex32 operator -(float minuend, Complex32 subtrahend) + { + return new Complex32(minuend - subtrahend._real, -subtrahend._imag); + } + + /// Multiplication operator. Multiplies two complex numbers. + /// The result of the multiplication. + /// One of the complex numbers to multiply. + /// The other complex number to multiply. + public static Complex32 operator *(Complex32 multiplicand, Complex32 multiplier) + { + return new Complex32( + (multiplicand._real * multiplier._real) - (multiplicand._imag * multiplier._imag), + (multiplicand._real * multiplier._imag) + (multiplicand._imag * multiplier._real)); + } + + /// Multiplication operator. Multiplies a complex number with a float value. + /// The result of the multiplication. + /// The float value to multiply. + /// The complex number to multiply. + public static Complex32 operator *(float multiplicand, Complex32 multiplier) + { + return new Complex32(multiplier._real * multiplicand, multiplier._imag * multiplicand); + } + + /// Multiplication operator. Multiplies a complex number with a float value. + /// The result of the multiplication. + /// The complex number to multiply. + /// The float value to multiply. + public static Complex32 operator *(Complex32 multiplicand, float multiplier) + { + return new Complex32(multiplicand._real * multiplier, multiplicand._imag * multiplier); + } + + /// Division operator. Divides a complex number by another. + /// Enhanced Smith's algorithm for dividing two complex numbers + /// + /// The result of the division. + /// The dividend. + /// The divisor. + public static Complex32 operator /(Complex32 dividend, Complex32 divisor) + { + if (dividend.IsZero() && divisor.IsZero()) + { + return NaN; + } + + if (divisor.IsZero()) + { + return PositiveInfinity; + } + + float a = dividend.Real; + float b = dividend.Imaginary; + float c = divisor.Real; + float d = divisor.Imaginary; + if (Math.Abs(d) <= Math.Abs(c)) + return InternalDiv(a, b, c, d, false); + return InternalDiv(b, a, d, c, true); + } + /// + /// Helper method for dividing. + /// + /// Re first + /// Im first + /// Re second + /// Im second + /// + /// + private static Complex32 InternalDiv(float a, float b, float c, float d, bool swapped) + { + float r = d / c; + float t = 1 / (c + d * r); + float e, f; + if (r != 0.0f) // one can use r >= float.Epsilon || r <= float.Epsilon instead + { + e = (a + b * r) * t; + f = (b - a * r) * t; + } + else + { + e = (a + d * (b / c)) * t; + f = (b - d * (a / c)) * t; + } + + if (swapped) + f = -f; + return new Complex32(e, f); + } + + /// Division operator. Divides a float value by a complex number. + /// Algorithm based on Smith's algorithm + /// + /// The result of the division. + /// The dividend. + /// The divisor. + public static Complex32 operator /(float dividend, Complex32 divisor) + { + if (dividend == 0.0f && divisor.IsZero()) + { + return NaN; + } + + if (divisor.IsZero()) + { + return PositiveInfinity; + } + + float c = divisor.Real; + float d = divisor.Imaginary; + if (Math.Abs(d) <= Math.Abs(c)) + return InternalDiv(dividend, 0, c, d, false); + return InternalDiv(0, dividend, d, c, true); + } + + /// Division operator. Divides a complex number by a float value. + /// The result of the division. + /// The dividend. + /// The divisor. + public static Complex32 operator /(Complex32 dividend, float divisor) + { + if (dividend.IsZero() && divisor == 0.0f) + { + return NaN; + } + + if (divisor == 0.0f) + { + return PositiveInfinity; + } + + return new Complex32(dividend._real / divisor, dividend._imag / divisor); + } + + /// + /// Computes the conjugate of a complex number and returns the result. + /// + public Complex32 Conjugate() + { + return new Complex32(_real, -_imag); + } + + /// + /// Returns the multiplicative inverse of a complex number. + /// + public Complex32 Reciprocal() + { + if (IsZero()) + { + return Zero; + } + + return 1.0f / this; + } + + #region IFormattable Members + + /// + /// Converts the value of the current complex number to its equivalent string representation in Cartesian form. + /// + /// The string representation of the current instance in Cartesian form. + public override string ToString() + { + return string.Format(CultureInfo.CurrentCulture, "({0}, {1})", _real, _imag); + } + + /// + /// Converts the value of the current complex number to its equivalent string representation + /// in Cartesian form by using the specified format for its real and imaginary parts. + /// + /// The string representation of the current instance in Cartesian form. + /// A standard or custom numeric format string. + /// + /// is not a valid format string. + public string ToString(string format) + { + return string.Format(CultureInfo.CurrentCulture, "({0}, {1})", + _real.ToString(format, CultureInfo.CurrentCulture), + _imag.ToString(format, CultureInfo.CurrentCulture)); + } + + /// + /// Converts the value of the current complex number to its equivalent string representation + /// in Cartesian form by using the specified culture-specific formatting information. + /// + /// The string representation of the current instance in Cartesian form, as specified by . + /// An object that supplies culture-specific formatting information. + public string ToString(IFormatProvider provider) + { + return string.Format(provider, "({0}, {1})", _real, _imag); + } + + /// Converts the value of the current complex number to its equivalent string representation + /// in Cartesian form by using the specified format and culture-specific format information for its real and imaginary parts. + /// The string representation of the current instance in Cartesian form, as specified by and . + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// + /// is not a valid format string. + public string ToString(string format, IFormatProvider provider) + { + return string.Format(provider, "({0}, {1})", + _real.ToString(format, provider), + _imag.ToString(format, provider)); + } + + #endregion + + #region IEquatable Members + + /// + /// Checks if two complex numbers are equal. Two complex numbers are equal if their + /// corresponding real and imaginary components are equal. + /// + /// + /// Returns true if the two objects are the same object, or if their corresponding + /// real and imaginary components are equal, false otherwise. + /// + /// + /// The complex number to compare to with. + /// + public bool Equals(Complex32 other) + { + if (IsNaN() || other.IsNaN()) + { + return false; + } + + if (IsInfinity() && other.IsInfinity()) + { + return true; + } + + return _real.AlmostEqual(other._real) && _imag.AlmostEqual(other._imag); + } + + /// + /// The hash code for the complex number. + /// + /// + /// The hash code of the complex number. + /// + /// + /// The hash code is calculated as + /// System.Math.Exp(ComplexMath.Absolute(complexNumber)). + /// + public override int GetHashCode() + { + int hash = 27; + hash = (13 * hash) + _real.GetHashCode(); + hash = (13 * hash) + _imag.GetHashCode(); + return hash; + } + + /// + /// Checks if two complex numbers are equal. Two complex numbers are equal if their + /// corresponding real and imaginary components are equal. + /// + /// + /// Returns true if the two objects are the same object, or if their corresponding + /// real and imaginary components are equal, false otherwise. + /// + /// + /// The complex number to compare to with. + /// + public override bool Equals(object obj) + { + return (obj is Complex32) && Equals((Complex32)obj); + } + + #endregion + + #region Parse Functions + + /// + /// Creates a complex number based on a string. The string can be in the + /// following formats (without the quotes): 'n', 'ni', 'n +/- ni', + /// 'ni +/- n', 'n,n', 'n,ni,' '(n,n)', or '(n,ni)', where n is a float. + /// + /// + /// A complex number containing the value specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific + /// formatting information. + /// + public static Complex32 Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // keywords + var numberFormatInfo = formatProvider.GetNumberFormatInfo(); + var textInfo = formatProvider.GetTextInfo(); + var keywords = + new[] + { + textInfo.ListSeparator, numberFormatInfo.NaNSymbol, + numberFormatInfo.NegativeInfinitySymbol, numberFormatInfo.PositiveInfinitySymbol, + "+", "-", "i", "j" + }; + + // lexing + var tokens = new LinkedList(); + GlobalizationHelper.Tokenize(tokens.AddFirst(value), keywords, 0); + var token = tokens.First; + + // parse the left part + bool isLeftPartImaginary; + var leftPart = ParsePart(ref token, out isLeftPartImaginary, formatProvider); + if (token == null) + { + return isLeftPartImaginary ? new Complex32(0, leftPart) : new Complex32(leftPart, 0); + } + + // parse the right part + if (token.Value == textInfo.ListSeparator) + { + // format: real,imag + token = token.Next; + + if (isLeftPartImaginary) + { + // left must not contain 'i', right doesn't matter. + throw new FormatException(); + } + + bool isRightPartImaginary; + var rightPart = ParsePart(ref token, out isRightPartImaginary, formatProvider); + + return new Complex32(leftPart, rightPart); + } + else + { + // format: real + imag + bool isRightPartImaginary; + var rightPart = ParsePart(ref token, out isRightPartImaginary, formatProvider); + + if (!(isLeftPartImaginary ^ isRightPartImaginary)) + { + // either left or right part must contain 'i', but not both. + throw new FormatException(); + } + + return isLeftPartImaginary ? new Complex32(rightPart, leftPart) : new Complex32(leftPart, rightPart); + } + } + + /// + /// Parse a part (real or complex) from a complex number. + /// + /// Start Token. + /// Is set to true if the part identified itself as being imaginary. + /// + /// An that supplies culture-specific + /// formatting information. + /// + /// Resulting part as float. + /// + private static float ParsePart(ref LinkedListNode token, out bool imaginary, IFormatProvider format) + { + imaginary = false; + if (token == null) + { + throw new FormatException(); + } + + // handle prefix modifiers + if (token.Value == "+") + { + token = token.Next; + + if (token == null) + { + throw new FormatException(); + } + } + + var negative = false; + if (token.Value == "-") + { + negative = true; + token = token.Next; + + if (token == null) + { + throw new FormatException(); + } + } + + // handle prefix imaginary symbol + if (string.Compare(token.Value, "i", StringComparison.OrdinalIgnoreCase) == 0 + || string.Compare(token.Value, "j", StringComparison.OrdinalIgnoreCase) == 0) + { + imaginary = true; + token = token.Next; + + if (token == null) + { + return negative ? -1 : 1; + } + } + +#if NETSTANDARD1_3 + var value = GlobalizationHelper.ParseSingle(ref token); +#else + var value = GlobalizationHelper.ParseSingle(ref token, format.GetCultureInfo()); +#endif + + // handle suffix imaginary symbol + if (token != null && (string.Compare(token.Value, "i", StringComparison.OrdinalIgnoreCase) == 0 + || string.Compare(token.Value, "j", StringComparison.OrdinalIgnoreCase) == 0)) + { + if (imaginary) + { + // only one time allowed: either prefix or suffix, or neither. + throw new FormatException(); + } + + imaginary = true; + token = token.Next; + } + + return negative ? -value : value; + } + + /// + /// Converts the string representation of a complex number to a single-precision complex number equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex number to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will contain complex32.Zero. This parameter is passed uninitialized + /// + public static bool TryParse(string value, out Complex32 result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a complex number to single-precision complex number equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex number to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will contain complex32.Zero. This parameter is passed uninitialized + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out Complex32 result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = Zero; + ret = false; + } + catch (FormatException) + { + result = Zero; + ret = false; + } + + return ret; + } + + #endregion + + #region Conversion + + /// + /// Explicit conversion of a real decimal to a Complex32. + /// + /// The decimal value to convert. + /// The result of the conversion. + public static explicit operator Complex32(decimal value) + { + return new Complex32((float)value, 0.0f); + } + + /// + /// Explicit conversion of a Complex to a Complex32. + /// + /// The decimal value to convert. + /// The result of the conversion. + public static explicit operator Complex32(Complex value) + { + return new Complex32((float)value.Real, (float)value.Imaginary); + } + + /// + /// Implicit conversion of a real byte to a Complex32. + /// + /// The byte value to convert. + /// The result of the conversion. + public static implicit operator Complex32(byte value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a real short to a Complex32. + /// + /// The short value to convert. + /// The result of the conversion. + public static implicit operator Complex32(short value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a signed byte to a Complex32. + /// + /// The signed byte value to convert. + /// The result of the conversion. + [CLSCompliant(false)] + public static implicit operator Complex32(sbyte value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a unsigned real short to a Complex32. + /// + /// The unsigned short value to convert. + /// The result of the conversion. + [CLSCompliant(false)] + public static implicit operator Complex32(ushort value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a real int to a Complex32. + /// + /// The int value to convert. + /// The result of the conversion. + public static implicit operator Complex32(int value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a BigInteger int to a Complex32. + /// + /// The BigInteger value to convert. + /// The result of the conversion. + public static implicit operator Complex32(BigInteger value) + { + return new Complex32((long)value, 0.0f); + } + + /// + /// Implicit conversion of a real long to a Complex32. + /// + /// The long value to convert. + /// The result of the conversion. + public static implicit operator Complex32(long value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a real uint to a Complex32. + /// + /// The uint value to convert. + /// The result of the conversion. + [CLSCompliant(false)] + public static implicit operator Complex32(uint value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a real ulong to a Complex32. + /// + /// The ulong value to convert. + /// The result of the conversion. + [CLSCompliant(false)] + public static implicit operator Complex32(ulong value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a real float to a Complex32. + /// + /// The float value to convert. + /// The result of the conversion. + public static implicit operator Complex32(float value) + { + return new Complex32(value, 0.0f); + } + + /// + /// Implicit conversion of a real double to a Complex32. + /// + /// The double value to convert. + /// The result of the conversion. + public static explicit operator Complex32(double value) + { + return new Complex32((float)value, 0.0f); + } + + /// + /// Converts this Complex32 to a . + /// + /// A with the same values as this Complex32. + public Complex ToComplex() + { + return new Complex(_real, _imag); + } + + #endregion + + /// + /// Returns the additive inverse of a specified complex number. + /// + /// The result of the real and imaginary components of the value parameter multiplied by -1. + /// A complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Negate(Complex32 value) + { + return -value; + } + + /// + /// Computes the conjugate of a complex number and returns the result. + /// + /// The conjugate of . + /// A complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Conjugate(Complex32 value) + { + return value.Conjugate(); + } + + /// + /// Adds two complex numbers and returns the result. + /// + /// The sum of and . + /// The first complex number to add. + /// The second complex number to add. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Add(Complex32 left, Complex32 right) + { + return left + right; + } + + /// + /// Subtracts one complex number from another and returns the result. + /// + /// The result of subtracting from . + /// The value to subtract from (the minuend). + /// The value to subtract (the subtrahend). + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Subtract(Complex32 left, Complex32 right) + { + return left - right; + } + + /// + /// Returns the product of two complex numbers. + /// + /// The product of the and parameters. + /// The first complex number to multiply. + /// The second complex number to multiply. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Multiply(Complex32 left, Complex32 right) + { + return left * right; + } + + /// + /// Divides one complex number by another and returns the result. + /// + /// The quotient of the division. + /// The complex number to be divided. + /// The complex number to divide by. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Divide(Complex32 dividend, Complex32 divisor) + { + return dividend / divisor; + } + /// + /// Returns the multiplicative inverse of a complex number. + /// + /// The reciprocal of . + /// A complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Reciprocal(Complex32 value) + { + return value.Reciprocal(); + } + + /// + /// Returns the square root of a specified complex number. + /// + /// The square root of . + /// A complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Sqrt(Complex32 value) + { + return value.SquareRoot(); + } + + /// + /// Gets the absolute value (or magnitude) of a complex number. + /// + /// The absolute value of . + /// A complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double Abs(Complex32 value) + { + return value.Magnitude; + } + + /// + /// Returns e raised to the power specified by a complex number. + /// + /// The number e raised to the power . + /// A complex number that specifies a power. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Exp(Complex32 value) + { + return value.Exponential(); + } + + /// + /// Returns a specified complex number raised to a power specified by a complex number. + /// + /// The complex number raised to the power . + /// A complex number to be raised to a power. + /// A complex number that specifies a power. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Pow(Complex32 value, Complex32 power) + { + return value.Power(power); + } + + /// + /// Returns a specified complex number raised to a power specified by a single-precision floating-point number. + /// + /// The complex number raised to the power . + /// A complex number to be raised to a power. + /// A single-precision floating-point number that specifies a power. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Pow(Complex32 value, float power) + { + return value.Power(power); + } + + /// + /// Returns the natural (base e) logarithm of a specified complex number. + /// + /// The natural (base e) logarithm of . + /// A complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Log(Complex32 value) + { + return value.NaturalLogarithm(); + } + + /// + /// Returns the logarithm of a specified complex number in a specified base. + /// + /// The logarithm of in base . + /// A complex number. + /// The base of the logarithm. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Log(Complex32 value, float baseValue) + { + return value.Logarithm(baseValue); + } + + /// + /// Returns the base-10 logarithm of a specified complex number. + /// + /// The base-10 logarithm of . + /// A complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex32 Log10(Complex32 value) + { + return value.CommonLogarithm(); + } + + /// + /// Returns the sine of the specified complex number. + /// + /// The sine of . + /// A complex number. + public static Complex32 Sin(Complex32 value) + { + return (Complex32)Trig.Sin(value.ToComplex()); + } + + /// + /// Returns the cosine of the specified complex number. + /// + /// The cosine of . + /// A complex number. + public static Complex32 Cos(Complex32 value) + { + return (Complex32)Trig.Cos(value.ToComplex()); + } + + /// + /// Returns the tangent of the specified complex number. + /// + /// The tangent of . + /// A complex number. + public static Complex32 Tan(Complex32 value) + { + return (Complex32)Trig.Tan(value.ToComplex()); + } + + /// + /// Returns the angle that is the arc sine of the specified complex number. + /// + /// The angle which is the arc sine of . + /// A complex number. + public static Complex32 Asin(Complex32 value) + { + return (Complex32)Trig.Asin(value.ToComplex()); + } + + /// + /// Returns the angle that is the arc cosine of the specified complex number. + /// + /// The angle, measured in radians, which is the arc cosine of . + /// A complex number that represents a cosine. + public static Complex32 Acos(Complex32 value) + { + return (Complex32)Trig.Acos(value.ToComplex()); + } + + /// + /// Returns the angle that is the arc tangent of the specified complex number. + /// + /// The angle that is the arc tangent of . + /// A complex number. + public static Complex32 Atan(Complex32 value) + { + return (Complex32)Trig.Atan(value.ToComplex()); + } + + /// + /// Returns the hyperbolic sine of the specified complex number. + /// + /// The hyperbolic sine of . + /// A complex number. + public static Complex32 Sinh(Complex32 value) + { + return (Complex32)Trig.Sinh(value.ToComplex()); + } + + /// + /// Returns the hyperbolic cosine of the specified complex number. + /// + /// The hyperbolic cosine of . + /// A complex number. + public static Complex32 Cosh(Complex32 value) + { + return (Complex32)Trig.Cosh(value.ToComplex()); + } + + /// + /// Returns the hyperbolic tangent of the specified complex number. + /// + /// The hyperbolic tangent of . + /// A complex number. + public static Complex32 Tanh(Complex32 value) + { + return (Complex32)Trig.Tanh(value.ToComplex()); + } +} diff --git a/MathNet.Numerics/ComplexExtensions.cs b/MathNet.Numerics/ComplexExtensions.cs new file mode 100644 index 0000000..86bc826 --- /dev/null +++ b/MathNet.Numerics/ComplexExtensions.cs @@ -0,0 +1,765 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +using Complex = System.Numerics.Complex; + +#if !NETSTANDARD1_3 +using System.Runtime; +#endif + +namespace MathNet.Numerics; + +/// +/// Extension methods for the Complex type provided by System.Numerics +/// +public static class ComplexExtensions +{ + /// + /// Gets the squared magnitude of the Complex number. + /// + /// The number to perform this operation on. + /// The squared magnitude of the Complex number. + public static double MagnitudeSquared(this Complex32 complex) + { + return (complex.Real * complex.Real) + (complex.Imaginary * complex.Imaginary); + } + + /// + /// Gets the squared magnitude of the Complex number. + /// + /// The number to perform this operation on. + /// The squared magnitude of the Complex number. + public static double MagnitudeSquared(this Complex complex) + { + return (complex.Real * complex.Real) + (complex.Imaginary * complex.Imaginary); + } + + /// + /// Gets the unity of this complex (same argument, but on the unit circle; exp(I*arg)) + /// + /// The unity of this Complex. + public static Complex Sign(this Complex complex) + { + if (double.IsPositiveInfinity(complex.Real) && double.IsPositiveInfinity(complex.Imaginary)) + { + return new Complex(Constants.Sqrt1Over2, Constants.Sqrt1Over2); + } + + if (double.IsPositiveInfinity(complex.Real) && double.IsNegativeInfinity(complex.Imaginary)) + { + return new Complex(Constants.Sqrt1Over2, -Constants.Sqrt1Over2); + } + + if (double.IsNegativeInfinity(complex.Real) && double.IsPositiveInfinity(complex.Imaginary)) + { + return new Complex(-Constants.Sqrt1Over2, -Constants.Sqrt1Over2); + } + + if (double.IsNegativeInfinity(complex.Real) && double.IsNegativeInfinity(complex.Imaginary)) + { + return new Complex(-Constants.Sqrt1Over2, Constants.Sqrt1Over2); + } + + // don't replace this with "Magnitude"! + var mod = SpecialFunctions.Hypotenuse(complex.Real, complex.Imaginary); + if (mod == 0.0d) + { + return Complex.Zero; + } + + return new Complex(complex.Real / mod, complex.Imaginary / mod); + } + + /// + /// Gets the conjugate of the Complex number. + /// + /// The number to perform this operation on. + /// + /// The semantic of setting the conjugate is such that + /// + /// // a, b of type Complex32 + /// a.Conjugate = b; + /// + /// is equivalent to + /// + /// // a, b of type Complex32 + /// a = b.Conjugate + /// + /// + /// The conjugate of the number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex Conjugate(this Complex complex) + { + return Complex.Conjugate(complex); + } + + /// + /// Returns the multiplicative inverse of a complex number. + /// + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex Reciprocal(this Complex complex) + { + return Complex.Reciprocal(complex); + } + + /// + /// Exponential of this Complex (exp(x), E^x). + /// + /// The number to perform this operation on. + /// + /// The exponential of this complex number. + /// + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex Exp(this Complex complex) + { + return Complex.Exp(complex); + } + + /// + /// Natural Logarithm of this Complex (Base E). + /// + /// The number to perform this operation on. + /// + /// The natural logarithm of this complex number. + /// + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex Ln(this Complex complex) + { + return Complex.Log(complex); + } + + /// + /// Common Logarithm of this Complex (Base 10). + /// + /// The common logarithm of this complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex Log10(this Complex complex) + { + return Complex.Log10(complex); + } + + /// + /// Logarithm of this Complex with custom base. + /// + /// The logarithm of this complex number. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static Complex Log(this Complex complex, double baseValue) + { + return Complex.Log(complex, baseValue); + } + + /// + /// Raise this Complex to the given value. + /// + /// The number to perform this operation on. + /// + /// The exponent. + /// + /// + /// The complex number raised to the given exponent. + /// + public static Complex Power(this Complex complex, Complex exponent) + { + if (complex.IsZero()) + { + if (exponent.IsZero()) + { + return Complex.One; + } + + if (exponent.Real > 0d) + { + return Complex.Zero; + } + + if (exponent.Real < 0d) + { + return exponent.Imaginary == 0d + ? new Complex(double.PositiveInfinity, 0d) + : new Complex(double.PositiveInfinity, double.PositiveInfinity); + } + + return new Complex(double.NaN, double.NaN); + } + + return Complex.Pow(complex, exponent); + } + + /// + /// Raise this Complex to the inverse of the given value. + /// + /// The number to perform this operation on. + /// + /// The root exponent. + /// + /// + /// The complex raised to the inverse of the given exponent. + /// + public static Complex Root(this Complex complex, Complex rootExponent) + { + return Complex.Pow(complex, 1 / rootExponent); + } + + /// + /// The Square (power 2) of this Complex + /// + /// The number to perform this operation on. + /// + /// The square of this complex number. + /// + public static Complex Square(this Complex complex) + { + if (complex.IsReal()) + { + return new Complex(complex.Real * complex.Real, 0.0); + } + + return new Complex((complex.Real * complex.Real) - (complex.Imaginary * complex.Imaginary), 2 * complex.Real * complex.Imaginary); + } + + /// + /// The Square Root (power 1/2) of this Complex + /// + /// The number to perform this operation on. + /// + /// The square root of this complex number. + /// + public static Complex SquareRoot(this Complex complex) + { + // Note: the following code should be equivalent to Complex.Sqrt(complex), + // but it turns out that is implemented poorly in System.Numerics, + // hence we provide our own implementation here. Do not replace. + + if (complex.IsRealNonNegative()) + { + return new Complex(Math.Sqrt(complex.Real), 0.0); + } + + Complex result; + + var absReal = Math.Abs(complex.Real); + var absImag = Math.Abs(complex.Imaginary); + double w; + if (absReal >= absImag) + { + var ratio = complex.Imaginary / complex.Real; + w = Math.Sqrt(absReal) * Math.Sqrt(0.5 * (1.0 + Math.Sqrt(1.0 + (ratio * ratio)))); + } + else + { + var ratio = complex.Real / complex.Imaginary; + w = Math.Sqrt(absImag) * Math.Sqrt(0.5 * (Math.Abs(ratio) + Math.Sqrt(1.0 + (ratio * ratio)))); + } + + if (complex.Real >= 0.0) + { + result = new Complex(w, complex.Imaginary / (2.0 * w)); + } + else if (complex.Imaginary >= 0.0) + { + result = new Complex(absImag / (2.0 * w), w); + } + else + { + result = new Complex(absImag / (2.0 * w), -w); + } + + return result; + } + + /// + /// Evaluate all square roots of this Complex. + /// + public static Tuple SquareRoots(this Complex complex) + { + var principal = SquareRoot(complex); + return new Tuple(principal, -principal); + } + + /// + /// Evaluate all cubic roots of this Complex. + /// + public static Tuple CubicRoots(this Complex complex) + { + var r = Math.Pow(complex.Magnitude, 1d / 3d); + var theta = complex.Phase / 3; + const double shift = Constants.Pi2 / 3; + return new Tuple( + Complex.FromPolarCoordinates(r, theta), + Complex.FromPolarCoordinates(r, theta + shift), + Complex.FromPolarCoordinates(r, theta - shift)); + } + + /// + /// Gets a value indicating whether the Complex32 is zero. + /// + /// The number to perform this operation on. + /// true if this instance is zero; otherwise, false. + public static bool IsZero(this Complex complex) + { + return complex.Real == 0.0 && complex.Imaginary == 0.0; + } + + /// + /// Gets a value indicating whether the Complex32 is one. + /// + /// The number to perform this operation on. + /// true if this instance is one; otherwise, false. + public static bool IsOne(this Complex complex) + { + return complex.Real == 1.0 && complex.Imaginary == 0.0; + } + + /// + /// Gets a value indicating whether the Complex32 is the imaginary unit. + /// + /// true if this instance is ImaginaryOne; otherwise, false. + /// The number to perform this operation on. + public static bool IsImaginaryOne(this Complex complex) + { + return complex.Real == 0.0 && complex.Imaginary == 1.0; + } + + /// + /// Gets a value indicating whether the provided Complex32evaluates + /// to a value that is not a number. + /// + /// The number to perform this operation on. + /// + /// true if this instance is NaN; otherwise, + /// false. + /// + public static bool IsNaN(this Complex complex) + { + return double.IsNaN(complex.Real) || double.IsNaN(complex.Imaginary); + } + + /// + /// Gets a value indicating whether the provided Complex32 evaluates to an + /// infinite value. + /// + /// The number to perform this operation on. + /// + /// true if this instance is infinite; otherwise, false. + /// + /// + /// True if it either evaluates to a complex infinity + /// or to a directed infinity. + /// + public static bool IsInfinity(this Complex complex) + { + return double.IsInfinity(complex.Real) || double.IsInfinity(complex.Imaginary); + } + + /// + /// Gets a value indicating whether the provided Complex32 is real. + /// + /// The number to perform this operation on. + /// true if this instance is a real number; otherwise, false. + public static bool IsReal(this Complex complex) + { + return complex.Imaginary == 0.0; + } + + /// + /// Gets a value indicating whether the provided Complex32 is real and not negative, that is >= 0. + /// + /// The number to perform this operation on. + /// + /// true if this instance is real nonnegative number; otherwise, false. + /// + public static bool IsRealNonNegative(this Complex complex) + { + return complex.Imaginary == 0.0f && complex.Real >= 0; + } + + /// + /// Returns a Norm of a value of this type, which is appropriate for measuring how + /// close this value is to zero. + /// + public static double Norm(this Complex complex) + { + return complex.MagnitudeSquared(); + } + + /// + /// Returns a Norm of a value of this type, which is appropriate for measuring how + /// close this value is to zero. + /// + public static double Norm(this Complex32 complex) + { + return complex.MagnitudeSquared; + } + + /// + /// Returns a Norm of the difference of two values of this type, which is + /// appropriate for measuring how close together these two values are. + /// + public static double NormOfDifference(this Complex complex, Complex otherValue) + { + return (complex - otherValue).MagnitudeSquared(); + } + + /// + /// Returns a Norm of the difference of two values of this type, which is + /// appropriate for measuring how close together these two values are. + /// + public static double NormOfDifference(this Complex32 complex, Complex32 otherValue) + { + return (complex - otherValue).MagnitudeSquared; + } + + /// + /// Creates a complex number based on a string. The string can be in the + /// following formats (without the quotes): 'n', 'ni', 'n +/- ni', + /// 'ni +/- n', 'n,n', 'n,ni,' '(n,n)', or '(n,ni)', where n is a double. + /// + /// + /// A complex number containing the value specified by the given string. + /// + /// + /// The string to parse. + /// + public static Complex ToComplex(this string value) + { + return value.ToComplex(null); + } + + /// + /// Creates a complex number based on a string. The string can be in the + /// following formats (without the quotes): 'n', 'ni', 'n +/- ni', + /// 'ni +/- n', 'n,n', 'n,ni,' '(n,n)', or '(n,ni)', where n is a double. + /// + /// + /// A complex number containing the value specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific + /// formatting information. + /// + public static Complex ToComplex(this string value, IFormatProvider formatProvider) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // keywords + var numberFormatInfo = formatProvider.GetNumberFormatInfo(); + var textInfo = formatProvider.GetTextInfo(); + var keywords = + new[] + { + textInfo.ListSeparator, numberFormatInfo.NaNSymbol, + numberFormatInfo.NegativeInfinitySymbol, numberFormatInfo.PositiveInfinitySymbol, + "+", "-", "i", "j" + }; + + // lexing + var tokens = new LinkedList(); + GlobalizationHelper.Tokenize(tokens.AddFirst(value), keywords, 0); + var token = tokens.First; + + // parse the left part + bool isLeftPartImaginary; + var leftPart = ParsePart(ref token, out isLeftPartImaginary, formatProvider); + if (token == null) + { + return isLeftPartImaginary ? new Complex(0, leftPart) : new Complex(leftPart, 0); + } + + // parse the right part + if (token.Value == textInfo.ListSeparator) + { + // format: real,imag + token = token.Next; + + if (isLeftPartImaginary) + { + // left must not contain 'i', right doesn't matter. + throw new FormatException(); + } + + bool isRightPartImaginary; + var rightPart = ParsePart(ref token, out isRightPartImaginary, formatProvider); + + return new Complex(leftPart, rightPart); + } + else + { + // format: real + imag + bool isRightPartImaginary; + var rightPart = ParsePart(ref token, out isRightPartImaginary, formatProvider); + + if (!(isLeftPartImaginary ^ isRightPartImaginary)) + { + // either left or right part must contain 'i', but not both. + throw new FormatException(); + } + + return isLeftPartImaginary ? new Complex(rightPart, leftPart) : new Complex(leftPart, rightPart); + } + } + + /// + /// Parse a part (real or complex) from a complex number. + /// + /// Start Token. + /// Is set to true if the part identified itself as being imaginary. + /// + /// An that supplies culture-specific + /// formatting information. + /// + /// Resulting part as double. + /// + private static double ParsePart(ref LinkedListNode token, out bool imaginary, IFormatProvider format) + { + imaginary = false; + if (token == null) + { + throw new FormatException(); + } + + // handle prefix modifiers + if (token.Value == "+") + { + token = token.Next; + + if (token == null) + { + throw new FormatException(); + } + } + + var negative = false; + if (token.Value == "-") + { + negative = true; + token = token.Next; + + if (token == null) + { + throw new FormatException(); + } + } + + // handle prefix imaginary symbol + if (string.Compare(token.Value, "i", StringComparison.OrdinalIgnoreCase) == 0 + || string.Compare(token.Value, "j", StringComparison.OrdinalIgnoreCase) == 0) + { + imaginary = true; + token = token.Next; + + if (token == null) + { + return negative ? -1 : 1; + } + } + +#if NETSTANDARD1_3 + var value = GlobalizationHelper.ParseDouble(ref token); +#else + var value = GlobalizationHelper.ParseDouble(ref token, format.GetCultureInfo()); +#endif + + // handle suffix imaginary symbol + if (token != null && (string.Compare(token.Value, "i", StringComparison.OrdinalIgnoreCase) == 0 + || string.Compare(token.Value, "j", StringComparison.OrdinalIgnoreCase) == 0)) + { + if (imaginary) + { + // only one time allowed: either prefix or suffix, or neither. + throw new FormatException(); + } + + imaginary = true; + token = token.Next; + } + + return negative ? -value : value; + } + + /// + /// Converts the string representation of a complex number to a double-precision complex number equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex number to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will contain Complex.Zero. This parameter is passed uninitialized. + /// + public static bool TryToComplex(this string value, out Complex result) + { + return value.TryToComplex(null, out result); + } + + /// + /// Converts the string representation of a complex number to double-precision complex number equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex number to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will contain complex32.Zero. This parameter is passed uninitialized + /// + public static bool TryToComplex(this string value, IFormatProvider formatProvider, out Complex result) + { + bool ret; + try + { + result = value.ToComplex(formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = Complex.Zero; + ret = false; + } + catch (FormatException) + { + result = Complex.Zero; + ret = false; + } + + return ret; + } + + /// + /// Creates a Complex32 number based on a string. The string can be in the + /// following formats (without the quotes): 'n', 'ni', 'n +/- ni', + /// 'ni +/- n', 'n,n', 'n,ni,' '(n,n)', or '(n,ni)', where n is a double. + /// + /// + /// A complex number containing the value specified by the given string. + /// + /// + /// the string to parse. + /// + public static Complex32 ToComplex32(this string value) + { + return Complex32.Parse(value); + } + + /// + /// Creates a Complex32 number based on a string. The string can be in the + /// following formats (without the quotes): 'n', 'ni', 'n +/- ni', + /// 'ni +/- n', 'n,n', 'n,ni,' '(n,n)', or '(n,ni)', where n is a double. + /// + /// + /// A complex number containing the value specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific + /// formatting information. + /// + public static Complex32 ToComplex32(this string value, IFormatProvider formatProvider) + { + return Complex32.Parse(value, formatProvider); + } + + /// + /// Converts the string representation of a complex number to a single-precision complex number equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex number to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will contain complex32.Zero. This parameter is passed uninitialized. + /// + public static bool TryToComplex32(this string value, out Complex32 result) + { + return Complex32.TryParse(value, out result); + } + + /// + /// Converts the string representation of a complex number to single-precision complex number equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex number to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will contain Complex.Zero. This parameter is passed uninitialized. + /// + public static bool TryToComplex32(this string value, IFormatProvider formatProvider, out Complex32 result) + { + return Complex32.TryParse(value, formatProvider, out result); + } +} diff --git a/MathNet.Numerics/Constants.cs b/MathNet.Numerics/Constants.cs new file mode 100644 index 0000000..79bc647 --- /dev/null +++ b/MathNet.Numerics/Constants.cs @@ -0,0 +1,467 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics; + +/// +/// A collection of frequently used mathematical constants. +/// +public static class Constants +{ + #region Mathematical Constants + + /// The number e + public const double E = 2.7182818284590452353602874713526624977572470937000d; + + /// The number log[2](e) + public const double Log2E = 1.4426950408889634073599246810018921374266459541530d; + + /// The number log[10](e) + public const double Log10E = 0.43429448190325182765112891891660508229439700580366d; + + /// The number log[e](2) + public const double Ln2 = 0.69314718055994530941723212145817656807550013436026d; + + /// The number log[e](10) + public const double Ln10 = 2.3025850929940456840179914546843642076011014886288d; + + /// The number log[e](pi) + public const double LnPi = 1.1447298858494001741434273513530587116472948129153d; + + /// The number log[e](2*pi)/2 + public const double Ln2PiOver2 = 0.91893853320467274178032973640561763986139747363780d; + + /// The number 1/e + public const double InvE = 0.36787944117144232159552377016146086744581113103176d; + + /// The number sqrt(e) + public const double SqrtE = 1.6487212707001281468486507878141635716537761007101d; + + /// The number sqrt(2) + public const double Sqrt2 = 1.4142135623730950488016887242096980785696718753769d; + + /// The number sqrt(3) + public const double Sqrt3 = 1.7320508075688772935274463415058723669428052538104d; + + /// The number sqrt(1/2) = 1/sqrt(2) = sqrt(2)/2 + public const double Sqrt1Over2 = 0.70710678118654752440084436210484903928483593768845d; + + /// The number sqrt(3)/2 + public const double HalfSqrt3 = 0.86602540378443864676372317075293618347140262690520d; + + /// The number pi + public const double Pi = 3.1415926535897932384626433832795028841971693993751d; + + /// The number pi*2 + public const double Pi2 = 6.2831853071795864769252867665590057683943387987502d; + + /// The number pi/2 + public const double PiOver2 = 1.5707963267948966192313216916397514420985846996876d; + + /// The number pi*3/2 + public const double Pi3Over2 = 4.71238898038468985769396507491925432629575409906266d; + + /// The number pi/4 + public const double PiOver4 = 0.78539816339744830961566084581987572104929234984378d; + + /// The number sqrt(pi) + public const double SqrtPi = 1.7724538509055160272981674833411451827975494561224d; + + /// The number sqrt(2pi) + public const double Sqrt2Pi = 2.5066282746310005024157652848110452530069867406099d; + + /// The number sqrt(pi/2) + public const double SqrtPiOver2 = 1.2533141373155002512078826424055226265034933703050d; + + /// The number sqrt(2*pi*e) + public const double Sqrt2PiE = 4.1327313541224929384693918842998526494455219169913d; + + /// The number log(sqrt(2*pi)) + public const double LogSqrt2Pi = 0.91893853320467274178032973640561763986139747363778; + + /// The number log(sqrt(2*pi*e)) + public const double LogSqrt2PiE = 1.4189385332046727417803297364056176398613974736378d; + + /// The number log(2 * sqrt(e / pi)) + public const double LogTwoSqrtEOverPi = 0.6207822376352452223455184457816472122518527279025978; + + /// The number 1/pi + public const double InvPi = 0.31830988618379067153776752674502872406891929148091d; + + /// The number 2/pi + public const double TwoInvPi = 0.63661977236758134307553505349005744813783858296182d; + + /// The number 1/sqrt(pi) + public const double InvSqrtPi = 0.56418958354775628694807945156077258584405062932899d; + + /// The number 1/sqrt(2pi) + public const double InvSqrt2Pi = 0.39894228040143267793994605993438186847585863116492d; + + /// The number 2/sqrt(pi) + public const double TwoInvSqrtPi = 1.1283791670955125738961589031215451716881012586580d; + + /// The number 2 * sqrt(e / pi) + public const double TwoSqrtEOverPi = 1.8603827342052657173362492472666631120594218414085755; + + /// The number (pi)/180 - factor to convert from Degree (deg) to Radians (rad). + /// + /// + public const double Degree = 0.017453292519943295769236907684886127134428718885417d; + + /// The number (pi)/200 - factor to convert from NewGrad (grad) to Radians (rad). + /// + /// + public const double Grad = 0.015707963267948966192313216916397514420985846996876d; + + /// The number ln(10)/20 - factor to convert from Power Decibel (dB) to Neper (Np). Use this version when the Decibel represent a power gain but the compared values are not powers (e.g. amplitude, current, voltage). + public const double PowerDecibel = 0.11512925464970228420089957273421821038005507443144d; + + /// The number ln(10)/10 - factor to convert from Neutral Decibel (dB) to Neper (Np). Use this version when either both or neither of the Decibel and the compared values represent powers. + public const double NeutralDecibel = 0.23025850929940456840179914546843642076011014886288d; + + /// The Catalan constant + /// Sum(k=0 -> inf){ (-1)^k/(2*k + 1)2 } + public const double Catalan = 0.9159655941772190150546035149323841107741493742816721342664981196217630197762547694794d; + + /// The Euler-Mascheroni constant + /// lim(n -> inf){ Sum(k=1 -> n) { 1/k - log(n) } } + public const double EulerMascheroni = 0.5772156649015328606065120900824024310421593359399235988057672348849d; + + /// The number (1+sqrt(5))/2, also known as the golden ratio + public const double GoldenRatio = 1.6180339887498948482045868343656381177203091798057628621354486227052604628189024497072d; + + /// The Glaisher constant + /// e^(1/12 - Zeta(-1)) + public const double Glaisher = 1.2824271291006226368753425688697917277676889273250011920637400217404063088588264611297d; + + /// The Khinchin constant + /// prod(k=1 -> inf){1+1/(k*(k+2))^log(k,2)} + public const double Khinchin = 2.6854520010653064453097148354817956938203822939944629530511523455572188595371520028011d; + + /// + /// The size of a double in bytes. + /// + public const int SizeOfDouble = sizeof(double); + + /// + /// The size of an int in bytes. + /// + public const int SizeOfInt = sizeof(int); + + /// + /// The size of a float in bytes. + /// + public const int SizeOfFloat = sizeof(float); + + /// + /// The size of a Complex in bytes. + /// + public const int SizeOfComplex = 2 * SizeOfDouble; + + /// + /// The size of a Complex in bytes. + /// + public const int SizeOfComplex32 = 2 * SizeOfFloat; + #endregion + + #region UNIVERSAL CONSTANTS + + /// Speed of Light in Vacuum: c_0 = 2.99792458e8 [m s^-1] (defined, exact; 2007 CODATA) + public const double SpeedOfLight = 2.99792458e8; + + /// Magnetic Permeability in Vacuum: mu_0 = 4*Pi * 10^-7 [N A^-2 = kg m A^-2 s^-2] (defined, exact; 2007 CODATA) + public const double MagneticPermeability = 1.2566370614359172953850573533118011536788677597500e-6; + + /// Electric Permittivity in Vacuum: epsilon_0 = 1/(mu_0*c_0^2) [F m^-1 = A^2 s^4 kg^-1 m^-3] (defined, exact; 2007 CODATA) + public const double ElectricPermittivity = 8.8541878171937079244693661186959426889222899381429e-12; + + /// Characteristic Impedance of Vacuum: Z_0 = mu_0*c_0 [Ohm = m^2 kg s^-3 A^-2] (defined, exact; 2007 CODATA) + public const double CharacteristicImpedanceVacuum = 376.73031346177065546819840042031930826862350835242; + + /// Newtonian Constant of Gravitation: G = 6.67429e-11 [m^3 kg^-1 s^-2] (2007 CODATA) + public const double GravitationalConstant = 6.67429e-11; + + /// Planck's constant: h = 6.62606896e-34 [J s = m^2 kg s^-1] (2007 CODATA) + public const double PlancksConstant = 6.62606896e-34; + + /// Reduced Planck's constant: h_bar = h / (2*Pi) [J s = m^2 kg s^-1] (2007 CODATA) + public const double DiracsConstant = 1.054571629e-34; + + /// Planck mass: m_p = (h_bar*c_0/G)^(1/2) [kg] (2007 CODATA) + public const double PlancksMass = 2.17644e-8; + + /// Planck temperature: T_p = (h_bar*c_0^5/G)^(1/2)/k [K] (2007 CODATA) + public const double PlancksTemperature = 1.416786e32; + + /// Planck length: l_p = h_bar/(m_p*c_0) [m] (2007 CODATA) + public const double PlancksLength = 1.616253e-35; + + /// Planck time: t_p = l_p/c_0 [s] (2007 CODATA) + public const double PlancksTime = 5.39124e-44; + + #endregion + + #region ELECTROMAGNETIC CONSTANTS + + /// Elementary Electron Charge: e = 1.602176487e-19 [C = A s] (2007 CODATA) + public const double ElementaryCharge = 1.602176487e-19; + + /// Magnetic Flux Quantum: theta_0 = h/(2*e) [Wb = m^2 kg s^-2 A^-1] (2007 CODATA) + public const double MagneticFluxQuantum = 2.067833668e-15; + + /// Conductance Quantum: G_0 = 2*e^2/h [S = m^-2 kg^-1 s^3 A^2] (2007 CODATA) + public const double ConductanceQuantum = 7.7480917005e-5; + + /// Josephson Constant: K_J = 2*e/h [Hz V^-1] (2007 CODATA) + public const double JosephsonConstant = 483597.891e9; + + /// Von Klitzing Constant: R_K = h/e^2 [Ohm = m^2 kg s^-3 A^-2] (2007 CODATA) + public const double VonKlitzingConstant = 25812.807557; + + /// Bohr Magneton: mu_B = e*h_bar/2*m_e [J T^-1] (2007 CODATA) + public const double BohrMagneton = 927.400915e-26; + + /// Nuclear Magneton: mu_N = e*h_bar/2*m_p [J T^-1] (2007 CODATA) + public const double NuclearMagneton = 5.05078324e-27; + + #endregion + + #region ATOMIC AND NUCLEAR CONSTANTS + + /// Fine Structure Constant: alpha = e^2/4*Pi*e_0*h_bar*c_0 [1] (2007 CODATA) + public const double FineStructureConstant = 7.2973525376e-3; + + /// Rydberg Constant: R_infty = alpha^2*m_e*c_0/2*h [m^-1] (2007 CODATA) + public const double RydbergConstant = 10973731.568528; + + /// Bor Radius: a_0 = alpha/4*Pi*R_infty [m] (2007 CODATA) + public const double BohrRadius = 0.52917720859e-10; + + /// Hartree Energy: E_h = 2*R_infty*h*c_0 [J] (2007 CODATA) + public const double HartreeEnergy = 4.35974394e-18; + + /// Quantum of Circulation: h/2*m_e [m^2 s^-1] (2007 CODATA) + public const double QuantumOfCirculation = 3.6369475199e-4; + + /// Fermi Coupling Constant: G_F/(h_bar*c_0)^3 [GeV^-2] (2007 CODATA) + public const double FermiCouplingConstant = 1.16637e-5; + + /// Weak Mixin Angle: sin^2(theta_W) [1] (2007 CODATA) + public const double WeakMixingAngle = 0.22256; + + /// Electron Mass: [kg] (2007 CODATA) + public const double ElectronMass = 9.10938215e-31; + + /// Electron Mass Energy Equivalent: [J] (2007 CODATA) + public const double ElectronMassEnergyEquivalent = 8.18710438e-14; + + /// Electron Molar Mass: [kg mol^-1] (2007 CODATA) + public const double ElectronMolarMass = 5.4857990943e-7; + + /// Electron Compton Wavelength: [m] (2007 CODATA) + public const double ComptonWavelength = 2.4263102175e-12; + + /// Classical Electron Radius: [m] (2007 CODATA) + public const double ClassicalElectronRadius = 2.8179402894e-15; + + /// Thomson Cross Section: [m^2] (2002 CODATA) + public const double ThomsonCrossSection = 0.6652458558e-28; + + /// Electron Magnetic Moment: [J T^-1] (2007 CODATA) + public const double ElectronMagneticMoment = -928.476377e-26; + + /// Electon G-Factor: [1] (2007 CODATA) + public const double ElectronGFactor = -2.0023193043622; + + /// Muon Mass: [kg] (2007 CODATA) + public const double MuonMass = 1.88353130e-28; + + /// Muon Mass Energy Equivalent: [J] (2007 CODATA) + public const double MuonMassEnegryEquivalent = 1.692833511e-11; + + /// Muon Molar Mass: [kg mol^-1] (2007 CODATA) + public const double MuonMolarMass = 0.1134289256e-3; + + /// Muon Compton Wavelength: [m] (2007 CODATA) + public const double MuonComptonWavelength = 11.73444104e-15; + + /// Muon Magnetic Moment: [J T^-1] (2007 CODATA) + public const double MuonMagneticMoment = -4.49044786e-26; + + /// Muon G-Factor: [1] (2007 CODATA) + public const double MuonGFactor = -2.0023318414; + + /// Tau Mass: [kg] (2007 CODATA) + public const double TauMass = 3.16777e-27; + + /// Tau Mass Energy Equivalent: [J] (2007 CODATA) + public const double TauMassEnergyEquivalent = 2.84705e-10; + + /// Tau Molar Mass: [kg mol^-1] (2007 CODATA) + public const double TauMolarMass = 1.90768e-3; + + /// Tau Compton Wavelength: [m] (2007 CODATA) + public const double TauComptonWavelength = 0.69772e-15; + + /// Proton Mass: [kg] (2007 CODATA) + public const double ProtonMass = 1.672621637e-27; + + /// Proton Mass Energy Equivalent: [J] (2007 CODATA) + public const double ProtonMassEnergyEquivalent = 1.503277359e-10; + + /// Proton Molar Mass: [kg mol^-1] (2007 CODATA) + public const double ProtonMolarMass = 1.00727646677e-3; + + /// Proton Compton Wavelength: [m] (2007 CODATA) + public const double ProtonComptonWavelength = 1.3214098446e-15; + + /// Proton Magnetic Moment: [J T^-1] (2007 CODATA) + public const double ProtonMagneticMoment = 1.410606662e-26; + + /// Proton G-Factor: [1] (2007 CODATA) + public const double ProtonGFactor = 5.585694713; + + /// Proton Shielded Magnetic Moment: [J T^-1] (2007 CODATA) + public const double ShieldedProtonMagneticMoment = 1.410570419e-26; + + /// Proton Gyro-Magnetic Ratio: [s^-1 T^-1] (2007 CODATA) + public const double ProtonGyromagneticRatio = 2.675222099e8; + + /// Proton Shielded Gyro-Magnetic Ratio: [s^-1 T^-1] (2007 CODATA) + public const double ShieldedProtonGyromagneticRatio = 2.675153362e8; + + /// Neutron Mass: [kg] (2007 CODATA) + public const double NeutronMass = 1.674927212e-27; + + /// Neutron Mass Energy Equivalent: [J] (2007 CODATA) + public const double NeutronMassEnegryEquivalent = 1.505349506e-10; + + /// Neutron Molar Mass: [kg mol^-1] (2007 CODATA) + public const double NeutronMolarMass = 1.00866491597e-3; + + /// Neuron Compton Wavelength: [m] (2007 CODATA) + public const double NeutronComptonWavelength = 1.3195908951e-1; + + /// Neutron Magnetic Moment: [J T^-1] (2007 CODATA) + public const double NeutronMagneticMoment = -0.96623641e-26; + + /// Neutron G-Factor: [1] (2007 CODATA) + public const double NeutronGFactor = -3.82608545; + + /// Neutron Gyro-Magnetic Ratio: [s^-1 T^-1] (2007 CODATA) + public const double NeutronGyromagneticRatio = 1.83247185e8; + + /// Deuteron Mass: [kg] (2007 CODATA) + public const double DeuteronMass = 3.34358320e-27; + + /// Deuteron Mass Energy Equivalent: [J] (2007 CODATA) + public const double DeuteronMassEnegryEquivalent = 3.00506272e-10; + + /// Deuteron Molar Mass: [kg mol^-1] (2007 CODATA) + public const double DeuteronMolarMass = 2.013553212725e-3; + + /// Deuteron Magnetic Moment: [J T^-1] (2007 CODATA) + public const double DeuteronMagneticMoment = 0.433073465e-26; + + /// Helion Mass: [kg] (2007 CODATA) + public const double HelionMass = 5.00641192e-27; + + /// Helion Mass Energy Equivalent: [J] (2007 CODATA) + public const double HelionMassEnegryEquivalent = 4.49953864e-10; + + /// Helion Molar Mass: [kg mol^-1] (2007 CODATA) + public const double HelionMolarMass = 3.0149322473e-3; + + /// Avogadro constant: [mol^-1] (2010 CODATA) + public const double Avogadro = 6.0221412927e23; + + #endregion + + #region Scientific Prefixes + /// The SI prefix factor corresponding to 1 000 000 000 000 000 000 000 000 + public const double Yotta = 1e24; + + /// The SI prefix factor corresponding to 1 000 000 000 000 000 000 000 + public const double Zetta = 1e21; + + /// The SI prefix factor corresponding to 1 000 000 000 000 000 000 + public const double Exa = 1e18; + + /// The SI prefix factor corresponding to 1 000 000 000 000 000 + public const double Peta = 1e15; + + /// The SI prefix factor corresponding to 1 000 000 000 000 + public const double Tera = 1e12; + + /// The SI prefix factor corresponding to 1 000 000 000 + public const double Giga = 1e9; + + /// The SI prefix factor corresponding to 1 000 000 + public const double Mega = 1e6; + + /// The SI prefix factor corresponding to 1 000 + public const double Kilo = 1e3; + + /// The SI prefix factor corresponding to 100 + public const double Hecto = 1e2; + + /// The SI prefix factor corresponding to 10 + public const double Deca = 1e1; + + /// The SI prefix factor corresponding to 0.1 + public const double Deci = 1e-1; + + /// The SI prefix factor corresponding to 0.01 + public const double Centi = 1e-2; + + /// The SI prefix factor corresponding to 0.001 + public const double Milli = 1e-3; + + /// The SI prefix factor corresponding to 0.000 001 + public const double Micro = 1e-6; + + /// The SI prefix factor corresponding to 0.000 000 001 + public const double Nano = 1e-9; + + /// The SI prefix factor corresponding to 0.000 000 000 001 + public const double Pico = 1e-12; + + /// The SI prefix factor corresponding to 0.000 000 000 000 001 + public const double Femto = 1e-15; + + /// The SI prefix factor corresponding to 0.000 000 000 000 000 001 + public const double Atto = 1e-18; + + /// The SI prefix factor corresponding to 0.000 000 000 000 000 000 001 + public const double Zepto = 1e-21; + + /// The SI prefix factor corresponding to 0.000 000 000 000 000 000 000 001 + public const double Yocto = 1e-24; + #endregion +} diff --git a/MathNet.Numerics/Control.cs b/MathNet.Numerics/Control.cs new file mode 100644 index 0000000..5784fe3 --- /dev/null +++ b/MathNet.Numerics/Control.cs @@ -0,0 +1,342 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Providers.FourierTransform; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace MathNet.Numerics; + +/// +/// Sets parameters for the library. +/// +public static class Control +{ + static int _maxDegreeOfParallelism; + static int _parallelizeOrder; + static int _parallelizeElements; + static string _nativeProviderHintPath; + + static Control() + { + ConfigureAuto(); + } + + public static void ConfigureAuto() + { + // Random Numbers & Distributions + CheckDistributionParameters = true; + + // Parallelization & Threading + ThreadSafeRandomNumberGenerators = true; + _maxDegreeOfParallelism = Environment.ProcessorCount; + _parallelizeOrder = 64; + _parallelizeElements = 300; + TaskScheduler = TaskScheduler.Default; + } + + public static void UseManaged() + { + LinearAlgebraControl.UseManaged(); + FourierTransformControl.UseManaged(); + } + + public static void UseManagedReference() + { + LinearAlgebraControl.UseManagedReference(); + FourierTransformControl.UseManaged(); + } + + /// + /// Use a specific provider if configured, e.g. using + /// environment variables, or fall back to the best providers. + /// + public static void UseDefaultProviders() + { + LinearAlgebraControl.UseDefault(); + FourierTransformControl.UseDefault(); + } + + /// + /// Use the best provider available. + /// + public static void UseBestProviders() + { + LinearAlgebraControl.UseBest(); + FourierTransformControl.UseBest(); + } + +#if NATIVE + + /// + /// Use the Intel MKL native provider for linear algebra. + /// Throws if it is not available or failed to initialize, in which case the previous provider is still active. + /// + public static void UseNativeMKL() + { + LinearAlgebraControl.UseNativeMKL(); + FourierTransformControl.UseNativeMKL(); + } + + /// + /// Use the Intel MKL native provider for linear algebra, with the specified configuration parameters. + /// Throws if it is not available or failed to initialize, in which case the previous provider is still active. + /// + [CLSCompliant(false)] + public static void UseNativeMKL( + Providers.Common.Mkl.MklConsistency consistency = Providers.Common.Mkl.MklConsistency.Auto, + Providers.Common.Mkl.MklPrecision precision = Providers.Common.Mkl.MklPrecision.Double, + Providers.Common.Mkl.MklAccuracy accuracy = Providers.Common.Mkl.MklAccuracy.High) + { + LinearAlgebraControl.UseNativeMKL(consistency, precision, accuracy); + FourierTransformControl.UseNativeMKL(); + } + + /// + /// Try to use the Intel MKL native provider for linear algebra. + /// + /// + /// True if the provider was found and initialized successfully. + /// False if it failed and the previous provider is still active. + /// + public static bool TryUseNativeMKL() + { + bool linearAlgebra = LinearAlgebraControl.TryUseNativeMKL(); + bool fourierTransform = FourierTransformControl.TryUseNativeMKL(); + return linearAlgebra || fourierTransform; + } + + /// + /// Use the Nvidia CUDA native provider for linear algebra. + /// Throws if it is not available or failed to initialize, in which case the previous provider is still active. + /// + public static void UseNativeCUDA() + { + LinearAlgebraControl.UseNativeCUDA(); + } + + /// + /// Try to use the Nvidia CUDA native provider for linear algebra. + /// + /// + /// True if the provider was found and initialized successfully. + /// False if it failed and the previous provider is still active. + /// + public static bool TryUseNativeCUDA() + { + bool linearAlgebra = LinearAlgebraControl.TryUseNativeCUDA(); + return linearAlgebra; + } + + /// + /// Use the OpenBLAS native provider for linear algebra. + /// Throws if it is not available or failed to initialize, in which case the previous provider is still active. + /// + public static void UseNativeOpenBLAS() + { + LinearAlgebraControl.UseNativeOpenBLAS(); + } + + /// + /// Try to use the OpenBLAS native provider for linear algebra. + /// + /// + /// True if the provider was found and initialized successfully. + /// False if it failed and the previous provider is still active. + /// + public static bool TryUseNativeOpenBLAS() + { + bool linearAlgebra = LinearAlgebraControl.TryUseNativeOpenBLAS(); + return linearAlgebra; + } + + /// + /// Try to use any available native provider in an undefined order. + /// + /// + /// True if one of the native providers was found and successfully initialized. + /// False if it failed and the previous provider is still active. + /// + public static bool TryUseNative() + { + bool linearAlgebra = LinearAlgebraControl.TryUseNative(); + bool fourierTransform = FourierTransformControl.TryUseNative(); + return linearAlgebra || fourierTransform; + } +#endif + + public static void FreeResources() + { + LinearAlgebraControl.FreeResources(); + FourierTransformControl.FreeResources(); + } + + public static void UseSingleThread() + { + _maxDegreeOfParallelism = 1; + ThreadSafeRandomNumberGenerators = false; + + LinearAlgebraControl.Provider.InitializeVerify(); + FourierTransformControl.Provider.InitializeVerify(); + } + + public static void UseMultiThreading() + { + _maxDegreeOfParallelism = Environment.ProcessorCount; + ThreadSafeRandomNumberGenerators = true; + + LinearAlgebraControl.Provider.InitializeVerify(); + FourierTransformControl.Provider.InitializeVerify(); + } + + /// + /// Gets or sets a value indicating whether the distribution classes check validate each parameter. + /// For the multivariate distributions this could involve an expensive matrix factorization. + /// The default setting of this property is true. + /// + public static bool CheckDistributionParameters { get; set; } + + /// + /// Gets or sets a value indicating whether to use thread safe random number generators (RNG). + /// Thread safe RNG about two and half time slower than non-thread safe RNG. + /// + /// + /// true to use thread safe random number generators ; otherwise, false. + /// + public static bool ThreadSafeRandomNumberGenerators { get; set; } + + /// + /// Optional path to try to load native provider binaries from. + /// + public static string NativeProviderPath + { + get { return _nativeProviderHintPath; } + set + { + _nativeProviderHintPath = value; + LinearAlgebraControl.HintPath = value; + FourierTransformControl.HintPath = value; + } + } + + /// + /// Gets or sets a value indicating how many parallel worker threads shall be used + /// when parallelization is applicable. + /// + /// Default to the number of processor cores, must be between 1 and 1024 (inclusive). + public static int MaxDegreeOfParallelism + { + get { return _maxDegreeOfParallelism; } + set + { + _maxDegreeOfParallelism = Math.Max(1, Math.Min(1024, value)); + + // Reinitialize providers: + LinearAlgebraControl.Provider.InitializeVerify(); + FourierTransformControl.Provider.InitializeVerify(); + } + } + + /// + /// Gets or sets the TaskScheduler used to schedule the worker tasks. + /// + public static TaskScheduler TaskScheduler { get; set; } + + /// + /// Gets or sets the order of the matrix when linear algebra provider + /// must calculate multiply in parallel threads. + /// + /// The order. Default 64, must be at least 3. + internal static int ParallelizeOrder + { + get { return _parallelizeOrder; } + set { _parallelizeOrder = Math.Max(3, value); } + } + + /// + /// Gets or sets the number of elements a vector or matrix + /// must contain before we multiply threads. + /// + /// Number of elements. Default 300, must be at least 3. + internal static int ParallelizeElements + { + get { return _parallelizeElements; } + set { _parallelizeElements = Math.Max(3, value); } + } + + public static string Describe() + { +#if NET40 + var versionAttribute = typeof(Control).Assembly + .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false) + .OfType() + .FirstOrDefault(); +#else + var versionAttribute = typeof(Control).GetTypeInfo().Assembly.GetCustomAttribute(typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute; +#endif + + var sb = new StringBuilder(); + sb.AppendLine("Math.NET Numerics Configuration:"); + sb.AppendLine($"Version {versionAttribute?.InformationalVersion}"); +#if NETSTANDARD1_3 + sb.AppendLine("Built for .Net Standard 1.3"); +#elif NETSTANDARD2_0 + sb.AppendLine("Built for .Net Standard 2.0"); +#elif NET40 + sb.AppendLine("Built for .Net Framework 4.0"); +#elif NET461 + sb.AppendLine("Built for .Net Framework 4.6.1"); +#endif +#if !NATIVE + sb.AppendLine("No Native Provider Support"); +#endif + sb.AppendLine($"Linear Algebra Provider: {LinearAlgebraControl.Provider}"); + sb.AppendLine($"Fourier Transform Provider: {FourierTransformControl.Provider}"); + sb.AppendLine($"Max Degree of Parallelism: {MaxDegreeOfParallelism}"); + sb.AppendLine($"Parallelize Elements: {ParallelizeElements}"); + sb.AppendLine($"Parallelize Order: {ParallelizeOrder}"); + sb.AppendLine($"Check Distribution Parameters: {CheckDistributionParameters}"); + sb.AppendLine($"Thread-Safe RNGs: {ThreadSafeRandomNumberGenerators}"); +#if NETSTANDARD1_3 || NETSTANDARD2_0 + // This would also work in .Net 4.0, but we don't want the dependency just for that. + sb.AppendLine($"Operating System: {RuntimeInformation.OSDescription}"); + sb.AppendLine($"Operating System Architecture: {RuntimeInformation.OSArchitecture}"); + sb.AppendLine($"Framework: {RuntimeInformation.FrameworkDescription}"); + sb.AppendLine($"Process Architecture: {RuntimeInformation.ProcessArchitecture}"); +#else + sb.AppendLine($"Operating System: {Environment.OSVersion}"); + sb.AppendLine($"Framework: {Environment.Version}"); +#endif + return sb.ToString(); + } +} diff --git a/MathNet.Numerics/Differentiate.cs b/MathNet.Numerics/Differentiate.cs new file mode 100644 index 0000000..81bf2ac --- /dev/null +++ b/MathNet.Numerics/Differentiate.cs @@ -0,0 +1,207 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Differentiation; + +using System; + +namespace MathNet.Numerics; + +/// +/// Numerical Derivative. +/// +public static class Differentiate +{ + /// + /// Initialized a NumericalDerivative with the given points and center. + /// + public static NumericalDerivative Points(int points, int center) + { + return new NumericalDerivative(points, center); + } + + /// + /// Initialized a NumericalDerivative with the default points and center for the given order. + /// + public static NumericalDerivative Order(int order) + { + var points = order + (order.IsEven() ? 1 : 2); + return new NumericalDerivative(points, points / 2); + } + + /// + /// Evaluates the derivative of a scalar univariate function. + /// + /// Univariate function handle. + /// Point at which to evaluate the derivative. + /// Derivative order. + public static double Derivative(Func f, double x, int order) + { + return Order(order).EvaluateDerivative(f, x, order); + } + + /// + /// Creates a function handle for the derivative of a scalar univariate function. + /// + /// Univariate function handle. + /// Derivative order. + public static Func DerivativeFunc(Func f, int order) + { + return Order(order).CreateDerivativeFunctionHandle(f, order); + } + + /// + /// Evaluates the first derivative of a scalar univariate function. + /// + /// Univariate function handle. + /// Point at which to evaluate the derivative. + public static double FirstDerivative(Func f, double x) + { + return Order(1).EvaluateDerivative(f, x, 1); + } + + /// + /// Creates a function handle for the first derivative of a scalar univariate function. + /// + /// Univariate function handle. + public static Func FirstDerivativeFunc(Func f) + { + return Order(1).CreateDerivativeFunctionHandle(f, 1); + } + + /// + /// Evaluates the second derivative of a scalar univariate function. + /// + /// Univariate function handle. + /// Point at which to evaluate the derivative. + public static double SecondDerivative(Func f, double x) + { + return Order(2).EvaluateDerivative(f, x, 2); + } + + /// + /// Creates a function handle for the second derivative of a scalar univariate function. + /// + /// Univariate function handle. + public static Func SecondDerivativeFunc(Func f) + { + return Order(2).CreateDerivativeFunctionHandle(f, 2); + } + + /// + /// Evaluates the partial derivative of a multivariate function. + /// + /// Multivariate function handle. + /// Vector at which to evaluate the derivative. + /// Index of independent variable for partial derivative. + /// Derivative order. + public static double PartialDerivative(Func f, double[] x, int parameterIndex, int order) + { + return Order(order).EvaluatePartialDerivative(f, x, parameterIndex, order); + } + + /// + /// Creates a function handle for the partial derivative of a multivariate function. + /// + /// Multivariate function handle. + /// Index of independent variable for partial derivative. + /// Derivative order. + public static Func PartialDerivativeFunc(Func f, int parameterIndex, int order) + { + return Order(order).CreatePartialDerivativeFunctionHandle(f, parameterIndex, order); + } + + /// + /// Evaluates the first partial derivative of a multivariate function. + /// + /// Multivariate function handle. + /// Vector at which to evaluate the derivative. + /// Index of independent variable for partial derivative. + public static double FirstPartialDerivative(Func f, double[] x, int parameterIndex) + { + return PartialDerivative(f, x, parameterIndex, 1); + } + + /// + /// Creates a function handle for the first partial derivative of a multivariate function. + /// + /// Multivariate function handle. + /// Index of independent variable for partial derivative. + public static Func FirstPartialDerivativeFunc(Func f, int parameterIndex) + { + return PartialDerivativeFunc(f, parameterIndex, 1); + } + + /// + /// Evaluates the partial derivative of a bivariate function. + /// + /// Bivariate function handle. + /// First argument at which to evaluate the derivative. + /// Second argument at which to evaluate the derivative. + /// Index of independent variable for partial derivative. + /// Derivative order. + public static double PartialDerivative2(Func f, double x, double y, int parameterIndex, int order) + { + return Order(order).EvaluatePartialDerivative(array => f(array[0], array[1]), new[] { x, y }, parameterIndex, order); + } + + /// + /// Creates a function handle for the partial derivative of a bivariate function. + /// + /// Bivariate function handle. + /// Index of independent variable for partial derivative. + /// Derivative order. + public static Func PartialDerivative2Func(Func f, int parameterIndex, int order) + { + var handle = Order(order).CreatePartialDerivativeFunctionHandle(array => f(array[0], array[1]), parameterIndex, order); + return (x, y) => handle(new[] { x, y }); + } + + /// + /// Evaluates the first partial derivative of a bivariate function. + /// + /// Bivariate function handle. + /// First argument at which to evaluate the derivative. + /// Second argument at which to evaluate the derivative. + /// Index of independent variable for partial derivative. + public static double FirstPartialDerivative2(Func f, double x, double y, int parameterIndex) + { + return PartialDerivative2(f, x, y, parameterIndex, 1); + } + + /// + /// Creates a function handle for the first partial derivative of a bivariate function. + /// + /// Bivariate function handle. + /// Index of independent variable for partial derivative. + public static Func FirstPartialDerivative2Func(Func f, int parameterIndex) + { + return PartialDerivative2Func(f, parameterIndex, 1); + } +} diff --git a/MathNet.Numerics/Differentiation/FiniteDifferenceCoefficients.cs b/MathNet.Numerics/Differentiation/FiniteDifferenceCoefficients.cs new file mode 100644 index 0000000..bb4cfdb --- /dev/null +++ b/MathNet.Numerics/Differentiation/FiniteDifferenceCoefficients.cs @@ -0,0 +1,144 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Double; + +using System; + +namespace MathNet.Numerics.Differentiation; + +/// +/// Class to calculate finite difference coefficients using Taylor series expansion method. +/// +/// +/// For n points, coefficients are calculated up to the maximum derivative order possible (n-1). +/// The current function value position specifies the "center" for surrounding coefficients. +/// Selecting the first, middle or last positions represent forward, backwards and central difference methods. +/// +/// +/// +public class FiniteDifferenceCoefficients +{ + /// + /// Number of points for finite difference coefficients. Changing this value recalculates the coefficients table. + /// + public int Points + { + get { return _points; } + set + { + CalculateCoefficients(value); + _points = value; + } + } + + private double[][,] _coefficients; + private int _points; + + /// + /// Initializes a new instance of the class. + /// + /// Number of finite difference coefficients. + public FiniteDifferenceCoefficients(int points) + { + Points = points; + CalculateCoefficients(Points); + } + + /// + /// Gets the finite difference coefficients for a specified center and order. + /// + /// Current function position with respect to coefficients. Must be within point range. + /// Order of finite difference coefficients. + /// Vector of finite difference coefficients. + public double[] GetCoefficients(int center, int order) + { + if (center >= _coefficients.Length) + throw new ArgumentOutOfRangeException(nameof(center), "Center position must be within the point range."); + if (order >= _coefficients.Length) + throw new ArgumentOutOfRangeException(nameof(order), "Maximum difference order is points-1."); + + // Return proper row + var columns = _coefficients[center].GetLength(1); + var array = new double[columns]; + for (int i = 0; i < columns; ++i) + array[i] = _coefficients[center][order, i]; + return array; + } + + /// + /// Gets the finite difference coefficients for all orders at a specified center. + /// + /// Current function position with respect to coefficients. Must be within point range. + /// Rectangular array of coefficients, with columns specifying order. + public double[,] GetCoefficientsForAllOrders(int center) + { + if (center >= _coefficients.Length) + throw new ArgumentOutOfRangeException(nameof(center), "Center position must be within the point range."); + + return _coefficients[center]; + } + + private void CalculateCoefficients(int points) + { + var c = new double[points][,]; + + // For ever possible center given the number of points, compute ever possible coefficient for all possible orders. + for (int center = 0; center < points; center++) + { + // Deltas matrix for center located at 'center'. + var A = new DenseMatrix(points); + var l = points - center - 1; + for (int row = points - 1; row >= 0; row--) + { + A[row, 0] = 1.0; + for (int col = 1; col < points; col++) + { + A[row, col] = A[row, col - 1] * l / col; + } + + l -= 1; + } + + c[center] = A.Inverse().ToArray(); + + // "Polish" results by rounding. + var fac = SpecialFunctions.Factorial(points); + for (int j = 0; j < points; j++) + { + for (int k = 0; k < points; k++) + { + c[center][j, k] = (Math.Round(c[center][j, k] * fac, MidpointRounding.AwayFromZero)) / fac; + } + } + } + + _coefficients = c; + } +} diff --git a/MathNet.Numerics/Differentiation/NumericalDerivative.cs b/MathNet.Numerics/Differentiation/NumericalDerivative.cs new file mode 100644 index 0000000..b55dfb9 --- /dev/null +++ b/MathNet.Numerics/Differentiation/NumericalDerivative.cs @@ -0,0 +1,467 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Linq; + +namespace MathNet.Numerics.Differentiation; + +/// +/// Type of finite different step size. +/// +public enum StepType +{ + /// + /// The absolute step size value will be used in numerical derivatives, regardless of order or function parameters. + /// + Absolute, + + /// + /// A base step size value, h, will be scaled according to the function input parameter. A common example is hx = h*(1+abs(x)), however + /// this may vary depending on implementation. This definition only guarantees that the only scaling will be relative to the + /// function input parameter and not the order of the finite difference derivative. + /// + RelativeX, + + /// + /// A base step size value, eps (typically machine precision), is scaled according to the finite difference coefficient order + /// and function input parameter. The initial scaling according to finite different coefficient order can be thought of as producing a + /// base step size, h, that is equivalent to scaling. This step size is then scaled according to the function + /// input parameter. Although implementation may vary, an example of second order accurate scaling may be (eps)^(1/3)*(1+abs(x)). + /// + Relative +}; + +/// +/// Class to evaluate the numerical derivative of a function using finite difference approximations. +/// Variable point and center methods can be initialized . +/// This class can also be used to return function handles (delegates) for a fixed derivative order and variable. +/// It is possible to evaluate the derivative and partial derivative of univariate and multivariate functions respectively. +/// +public class NumericalDerivative +{ + readonly int _points; + int _center; + double _stepSize = Math.Pow(2, -10); + double _epsilon = Precision.PositiveMachineEpsilon; + double _baseStepSize = Math.Pow(2, -26); + StepType _stepType = StepType.Relative; + readonly FiniteDifferenceCoefficients _coefficients; + + /// + /// Initializes a NumericalDerivative class with the default 3 point center difference method. + /// + public NumericalDerivative() : this(3, 1) + { + } + + /// + /// Initialized a NumericalDerivative class. + /// + /// Number of points for finite difference derivatives. + /// Location of the center with respect to other points. Value ranges from zero to points-1. + public NumericalDerivative(int points, int center) + { + if (points < 2) + { + throw new ArgumentOutOfRangeException(nameof(points), "Points must be two or greater."); + } + + _center = center; + _points = points; + Center = center; + _coefficients = new FiniteDifferenceCoefficients(points); + } + + /// + /// Sets and gets the finite difference step size. This value is for each function evaluation if relative step size types are used. + /// If the base step size used in scaling is desired, see . + /// + /// + /// Setting then getting the StepSize may return a different value. This is not unusual since a user-defined step size is converted to a + /// base-2 representable number to improve finite difference accuracy. + /// + public double StepSize + { + get { return _stepSize; } + set + { + //Base 2 yields more accurate results... + var p = Math.Log(Math.Abs(value)) / Math.Log(2); + _stepSize = Math.Pow(2, Math.Round(p)); + } + } + + /// + /// Sets and gets the base finite difference step size. This assigned value to this parameter is only used if is set to RelativeX. + /// However, if the StepType is Relative, it will contain the base step size computed from based on the finite difference order. + /// + public double BaseStepSize + { + get { return _baseStepSize; } + set + { + //Base 2 yields more accurate results... + var p = Math.Log(Math.Abs(value)) / Math.Log(2); + _baseStepSize = Math.Pow(2, Math.Round(p)); + } + } + + /// + /// Sets and gets the base finite difference step size. This parameter is only used if is set to Relative. + /// By default this is set to machine epsilon, from which is computed. + /// + public double Epsilon + { + get { return _epsilon; } + set + { + //Base 2 yields more accurate results... + var p = Math.Log(Math.Abs(value)) / Math.Log(2); + _epsilon = Math.Pow(2, Math.Round(p)); + } + } + + /// + /// Sets and gets the location of the center point for the finite difference derivative. + /// + public int Center + { + get { return _center; } + set + { + if (value >= _points || value < 0) + throw new ArgumentOutOfRangeException(nameof(value), "Center must lie between 0 and points -1"); + _center = value; + } + } + + /// + /// Number of times a function is evaluated for numerical derivatives. + /// + public int Evaluations { get; private set; } + + /// + /// Type of step size for computing finite differences. If set to absolute, dx = h. + /// If set to relative, dx = (1+abs(x))*h^(2/(order+1)). This provides accurate results when + /// h is approximately equal to the square-root of machine accuracy, epsilon. + /// + public StepType StepType + { + get { return _stepType; } + set { _stepType = value; } + } + + /// + /// Evaluates the derivative of equidistant points using the finite difference method. + /// + /// Vector of points StepSize apart. + /// Derivative order. + /// Finite difference step size. + /// Derivative of points of the specified order. + public double EvaluateDerivative(double[] points, int order, double stepSize) + { + if (points == null) + throw new ArgumentNullException(nameof(points)); + + if (order >= _points || order < 0) + throw new ArgumentOutOfRangeException(nameof(order), "Order must be between zero and points-1."); + + var c = _coefficients.GetCoefficients(Center, order); + var result = c.Select((t, i) => t * points[i]).Sum(); + result /= Math.Pow(stepSize, order); + return result; + } + + /// + /// Evaluates the derivative of a scalar univariate function. + /// + /// + /// Supplying the optional argument currentValue will reduce the number of function evaluations + /// required to calculate the finite difference derivative. + /// + /// Function handle. + /// Point at which to compute the derivative. + /// Derivative order. + /// Current function value at center. + /// Function derivative at x of the specified order. + public double EvaluateDerivative(Func f, double x, int order, double? currentValue = null) + { + var c = _coefficients.GetCoefficients(Center, order); + var h = CalculateStepSize(_points, x, order); + + var points = new double[_points]; + for (int i = 0; i < _points; i++) + { + if (i == Center && currentValue.HasValue) + points[i] = currentValue.Value; + else if (c[i] != 0) // Only evaluate function if it will actually be used. + { + points[i] = f(x + (i - Center) * h); + Evaluations++; + } + } + + return EvaluateDerivative(points, order, h); + } + + /// + /// Creates a function handle for the derivative of a scalar univariate function. + /// + /// Input function handle. + /// Derivative order. + /// Function handle that evaluates the derivative of input function at a fixed order. + public Func CreateDerivativeFunctionHandle(Func f, int order) + { + return x => EvaluateDerivative(f, x, order); + } + + /// + /// Evaluates the partial derivative of a multivariate function. + /// + /// Multivariate function handle. + /// Vector at which to evaluate the derivative. + /// Index of independent variable for partial derivative. + /// Derivative order. + /// Current function value at center. + /// Function partial derivative at x of the specified order. + public double EvaluatePartialDerivative(Func f, double[] x, int parameterIndex, int order, double? currentValue = null) + { + var xi = x[parameterIndex]; + var c = _coefficients.GetCoefficients(Center, order); + var h = CalculateStepSize(_points, x[parameterIndex], order); + + var points = new double[_points]; + for (int i = 0; i < _points; i++) + { + if (i == Center && currentValue.HasValue) + points[i] = currentValue.Value; + else if (c[i] != 0) // Only evaluate function if it will actually be used. + { + x[parameterIndex] = xi + (i - Center) * h; + points[i] = f(x); + Evaluations++; + } + } + + //restore original value + x[parameterIndex] = xi; + return EvaluateDerivative(points, order, h); + } + + /// + /// Evaluates the partial derivatives of a multivariate function array. + /// + /// + /// This function assumes the input vector x is of the correct length for f. + /// + /// Multivariate vector function array handle. + /// Vector at which to evaluate the derivatives. + /// Index of independent variable for partial derivative. + /// Derivative order. + /// Current function value at center. + /// Vector of functions partial derivatives at x of the specified order. + public double[] EvaluatePartialDerivative(Func[] f, double[] x, int parameterIndex, int order, double?[] currentValue = null) + { + var df = new double[f.Length]; + for (int i = 0; i < f.Length; i++) + { + if (currentValue != null && currentValue[i].HasValue) + df[i] = EvaluatePartialDerivative(f[i], x, parameterIndex, order, currentValue[i].Value); + else + df[i] = EvaluatePartialDerivative(f[i], x, parameterIndex, order); + } + + return df; + } + + /// + /// Creates a function handle for the partial derivative of a multivariate function. + /// + /// Input function handle. + /// Index of the independent variable for partial derivative. + /// Derivative order. + /// Function handle that evaluates partial derivative of input function at a fixed order. + public Func CreatePartialDerivativeFunctionHandle(Func f, int parameterIndex, + int order) + { + return x => EvaluatePartialDerivative(f, x, parameterIndex, order); + } + + /// + /// Creates a function handle for the partial derivative of a vector multivariate function. + /// + /// Input function handle. + /// Index of the independent variable for partial derivative. + /// Derivative order. + /// Function handle that evaluates partial derivative of input function at fixed order. + public Func CreatePartialDerivativeFunctionHandle(Func[] f, + int parameterIndex, + int order) + { + return x => EvaluatePartialDerivative(f, x, parameterIndex, order); + } + + /// + /// Evaluates the mixed partial derivative of variable order for multivariate functions. + /// + /// + /// This function recursively uses to evaluate mixed partial derivative. + /// Therefore, it is more efficient to call for higher order derivatives of + /// a single independent variable. + /// + /// Multivariate function handle. + /// Points at which to evaluate the derivative. + /// Vector of indices for the independent variables at descending derivative orders. + /// Highest order of differentiation. + /// Current function value at center. + /// Function mixed partial derivative at x of the specified order. + public double EvaluateMixedPartialDerivative(Func f, double[] x, int[] parameterIndex, + int order, double? currentValue = null) + { + if (parameterIndex.Length != order) + throw new ArgumentOutOfRangeException(nameof(parameterIndex), + "The number of parameters must match derivative order."); + + if (order == 1) + return EvaluatePartialDerivative(f, x, parameterIndex[0], order, currentValue); + + int reducedOrder = order - 1; + var reducedParameterIndex = new int[reducedOrder]; + Array.Copy(parameterIndex, 0, reducedParameterIndex, 0, reducedOrder); + + var points = new double[_points]; + var currentParameterIndex = parameterIndex[order - 1]; + var h = CalculateStepSize(_points, x[currentParameterIndex], order); + + var xi = x[currentParameterIndex]; + for (int i = 0; i < _points; i++) + { + x[currentParameterIndex] = xi + (i - Center) * h; + points[i] = EvaluateMixedPartialDerivative(f, x, reducedParameterIndex, reducedOrder); + } + + // restore original value + x[currentParameterIndex] = xi; + + // This will always be to the first order + return EvaluateDerivative(points, 1, h); + } + + /// + /// Evaluates the mixed partial derivative of variable order for multivariate function arrays. + /// + /// + /// This function recursively uses to evaluate mixed partial derivative. + /// Therefore, it is more efficient to call for higher order derivatives of + /// a single independent variable. + /// + /// Multivariate function array handle. + /// Vector at which to evaluate the derivative. + /// Vector of indices for the independent variables at descending derivative orders. + /// Highest order of differentiation. + /// Current function value at center. + /// Function mixed partial derivatives at x of the specified order. + public double[] EvaluateMixedPartialDerivative(Func[] f, double[] x, int[] parameterIndex, + int order, double?[] currentValue = null) + { + var df = new double[f.Length]; + for (int i = 0; i < f.Length; i++) + { + if (currentValue != null && currentValue[i].HasValue) + df[i] = EvaluateMixedPartialDerivative(f[i], x, parameterIndex, order, currentValue[i].Value); + else + df[i] = EvaluateMixedPartialDerivative(f[i], x, parameterIndex, order); + } + + return df; + } + + /// + /// Creates a function handle for the mixed partial derivative of a multivariate function. + /// + /// Input function handle. + /// Vector of indices for the independent variables at descending derivative orders. + /// Highest derivative order. + /// Function handle that evaluates the fixed mixed partial derivative of input function at fixed order. + public Func CreateMixedPartialDerivativeFunctionHandle(Func f, + int[] parameterIndex, int order) + { + return x => EvaluateMixedPartialDerivative(f, x, parameterIndex, order); + } + + /// + /// Creates a function handle for the mixed partial derivative of a multivariate vector function. + /// + /// Input vector function handle. + /// Vector of indices for the independent variables at descending derivative orders. + /// Highest derivative order. + /// Function handle that evaluates the fixed mixed partial derivative of input function at fixed order. + public Func CreateMixedPartialDerivativeFunctionHandle(Func[] f, + int[] parameterIndex, int order) + { + return x => EvaluateMixedPartialDerivative(f, x, parameterIndex, order); + } + + /// + /// Resets the evaluation counter. + /// + public void ResetEvaluations() + { + Evaluations = 0; + } + + private double[] CalculateStepSize(int points, double[] x, double order) + { + var h = new double[x.Length]; + for (int i = 1; i < h.Length; i++) + h[i] = CalculateStepSize(points, x[i], order); + + return h; + } + + private double CalculateStepSize(int points, double x, double order) + { + // Step size relative to function input parameter + if (StepType == StepType.RelativeX) + { + StepSize = BaseStepSize * (1 + Math.Abs(x)); + } + // Step size relative to function input parameter and order + else if (StepType == StepType.Relative) + { + var accuracy = points - order; + BaseStepSize = Math.Pow(Epsilon, (1 / (accuracy + order))); + StepSize = BaseStepSize * (1 + Math.Abs(x)); + } + // Do nothing for absolute step size. + + return StepSize; + } +} diff --git a/MathNet.Numerics/Differentiation/NumericalHessian.cs b/MathNet.Numerics/Differentiation/NumericalHessian.cs new file mode 100644 index 0000000..84c28bd --- /dev/null +++ b/MathNet.Numerics/Differentiation/NumericalHessian.cs @@ -0,0 +1,118 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Differentiation; + +/// +/// Class for evaluating the Hessian of a smooth continuously differentiable function using finite differences. +/// By default, a central 3-point method is used. +/// +public class NumericalHessian +{ + /// + /// Number of function evaluations. + /// + public int FunctionEvaluations + { + get { return _df.Evaluations; } + } + + private readonly NumericalDerivative _df; + + /// + /// Creates a numerical Hessian object with a three point central difference method. + /// + public NumericalHessian() : this(3, 1) { } + + /// + /// Creates a numerical Hessian with a specified differentiation scheme. + /// + /// Number of points for Hessian evaluation. + /// Center point for differentiation. + public NumericalHessian(int points, int center) + { + _df = new NumericalDerivative(points, center); + } + + /// + /// Evaluates the Hessian of the scalar univariate function f at points x. + /// + /// Scalar univariate function handle. + /// Point at which to evaluate Hessian. + /// Hessian tensor. + public double[] Evaluate(Func f, double x) + { + return new[] { _df.EvaluateDerivative(f, x, 2) }; + } + + /// + /// Evaluates the Hessian of a multivariate function f at points x. + /// + /// + /// This method of computing the Hessian is only valid for Lipschitz continuous functions. + /// The function mirrors the Hessian along the diagonal since d2f/dxdy = d2f/dydx for continuously differentiable functions. + /// + /// Multivariate function handle.> + /// Points at which to evaluate Hessian.> + /// Hessian tensor. + public double[,] Evaluate(Func f, double[] x) + { + var hessian = new double[x.Length, x.Length]; + + // Compute diagonal elements + for (var row = 0; row < x.Length; row++) + { + hessian[row, row] = _df.EvaluatePartialDerivative(f, x, row, 2); + } + + // Compute non-diagonal elements + for (var row = 0; row < x.Length; row++) + { + for (var col = 0; col < row; col++) + { + var mixedPartial = _df.EvaluateMixedPartialDerivative(f, x, new[] { row, col }, 2); + + hessian[row, col] = mixedPartial; + hessian[col, row] = mixedPartial; + } + } + + return hessian; + } + + /// + /// Resets the function evaluation counter for the Hessian. + /// + public void ResetFunctionEvaluations() + { + _df.ResetEvaluations(); + } +} diff --git a/MathNet.Numerics/Differentiation/NumericalJacobian.cs b/MathNet.Numerics/Differentiation/NumericalJacobian.cs new file mode 100644 index 0000000..90408c3 --- /dev/null +++ b/MathNet.Numerics/Differentiation/NumericalJacobian.cs @@ -0,0 +1,170 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Differentiation; + +/// +/// Class for evaluating the Jacobian of a function using finite differences. +/// By default, a central 3-point method is used. +/// +public class NumericalJacobian +{ + /// + /// Number of function evaluations. + /// + public int FunctionEvaluations + { + get { return _df.Evaluations; } + } + + private readonly NumericalDerivative _df; + + /// + /// Creates a numerical Jacobian object with a three point central difference method. + /// + public NumericalJacobian() : this(3, 1) { } + + /// + /// Creates a numerical Jacobian with a specified differentiation scheme. + /// + /// Number of points for Jacobian evaluation. + /// Center point for differentiation. + public NumericalJacobian(int points, int center) + { + _df = new NumericalDerivative(points, center); + } + + /// + /// Evaluates the Jacobian of scalar univariate function f at point x. + /// + /// Scalar univariate function handle. + /// Point at which to evaluate Jacobian. + /// Jacobian vector. + public double[] Evaluate(Func f, double x) + { + return new[] { _df.EvaluateDerivative(f, x, 1) }; + } + + /// + /// Evaluates the Jacobian of a multivariate function f at vector x. + /// + /// + /// This function assumes that the length of vector x consistent with the argument count of f. + /// + /// Multivariate function handle. + /// Points at which to evaluate Jacobian. + /// Jacobian vector. + public double[] Evaluate(Func f, double[] x) + { + var jacobian = new double[x.Length]; + + for (var i = 0; i < jacobian.Length; i++) + jacobian[i] = _df.EvaluatePartialDerivative(f, x, i, 1); + + return jacobian; + } + + /// + /// Evaluates the Jacobian of a multivariate function f at vector x given a current function value. + /// + /// + /// To minimize the number of function evaluations, a user can supply the current value of the function + /// to be used in computing the Jacobian. This value must correspond to the "center" location for the + /// finite differencing. If a scheme is used where the center value is not evaluated, this will provide no + /// added efficiency. This method also assumes that the length of vector x consistent with the argument count of f. + /// + /// Multivariate function handle. + /// Points at which to evaluate Jacobian. + /// Current function value at finite difference center. + /// Jacobian vector. + public double[] Evaluate(Func f, double[] x, double currentValue) + { + var jacobian = new double[x.Length]; + + for (var i = 0; i < jacobian.Length; i++) + jacobian[i] = _df.EvaluatePartialDerivative(f, x, i, 1, currentValue); + + return jacobian; + } + + /// + /// Evaluates the Jacobian of a multivariate function array f at vector x. + /// + /// Multivariate function array handle. + /// Vector at which to evaluate Jacobian. + /// Jacobian matrix. + public double[,] Evaluate(Func[] f, double[] x) + { + var jacobian = new double[f.Length, x.Length]; + for (int i = 0; i < f.Length; i++) + { + var gradient = Evaluate(f[i], x); + for (int j = 0; j < gradient.Length; j++) + jacobian[i, j] = gradient[j]; + } + + return jacobian; + } + + /// + /// Evaluates the Jacobian of a multivariate function array f at vector x given a vector of current function values. + /// + /// + /// To minimize the number of function evaluations, a user can supply a vector of current values of the functions + /// to be used in computing the Jacobian. These value must correspond to the "center" location for the + /// finite differencing. If a scheme is used where the center value is not evaluated, this will provide no + /// added efficiency. This method also assumes that the length of vector x consistent with the argument count of f. + /// + /// Multivariate function array handle. + /// Vector at which to evaluate Jacobian. + /// Vector of current function values. + /// Jacobian matrix. + public double[,] Evaluate(Func[] f, double[] x, double[] currentValues) + { + var jacobian = new double[f.Length, x.Length]; + for (int i = 0; i < f.Length; i++) + { + var gradient = Evaluate(f[i], x, currentValues[i]); + for (int j = 0; j < gradient.Length; j++) + jacobian[i, j] = gradient[j]; + } + + return jacobian; + } + + /// + /// Resets the function evaluation counter for the Jacobian. + /// + public void ResetFunctionEvaluations() + { + _df.ResetEvaluations(); + } +} diff --git a/MathNet.Numerics/Distance.cs b/MathNet.Numerics/Distance.cs new file mode 100644 index 0000000..db7ef31 --- /dev/null +++ b/MathNet.Numerics/Distance.cs @@ -0,0 +1,580 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Statistics; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics; + +/// +/// Metrics to measure the distance between two structures. +/// +public static class Distance +{ + /// + /// Sum of Absolute Difference (SAD), i.e. the L1-norm (Manhattan) of the difference. + /// + public static double SAD(Vector a, Vector b) where T : struct, IEquatable, IFormattable + { + return (a - b).L1Norm(); + } + + /// + /// Sum of Absolute Difference (SAD), i.e. the L1-norm (Manhattan) of the difference. + /// + public static double SAD(double[] a, double[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + double sum = 0d; + for (var i = 0; i < a.Length; i++) + { + sum += Math.Abs(a[i] - b[i]); + } + + return sum; + } + + /// + /// Sum of Absolute Difference (SAD), i.e. the L1-norm (Manhattan) of the difference. + /// + public static float SAD(float[] a, float[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + float sum = 0f; + for (var i = 0; i < a.Length; i++) + { + sum += Math.Abs(a[i] - b[i]); + } + + return sum; + } + + /// + /// Mean-Absolute Error (MAE), i.e. the normalized L1-norm (Manhattan) of the difference. + /// + public static double MAE(Vector a, Vector b) where T : struct, IEquatable, IFormattable + { + return (a - b).L1Norm() / a.Count; + } + + /// + /// Mean-Absolute Error (MAE), i.e. the normalized L1-norm (Manhattan) of the difference. + /// + public static double MAE(double[] a, double[] b) + { + return SAD(a, b) / a.Length; + } + + /// + /// Mean-Absolute Error (MAE), i.e. the normalized L1-norm (Manhattan) of the difference. + /// + public static float MAE(float[] a, float[] b) + { + return SAD(a, b) / a.Length; + } + + /// + /// Sum of Squared Difference (SSD), i.e. the squared L2-norm (Euclidean) of the difference. + /// + public static double SSD(Vector a, Vector b) where T : struct, IEquatable, IFormattable + { + var norm = (a - b).L2Norm(); + return norm * norm; + } + + /// + /// Sum of Squared Difference (SSD), i.e. the squared L2-norm (Euclidean) of the difference. + /// + public static double SSD(double[] a, double[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var diff = new double[a.Length]; + LinearAlgebraControl.Provider.SubtractArrays(a, b, diff); + return LinearAlgebraControl.Provider.DotProduct(diff, diff); + } + + /// + /// Sum of Squared Difference (SSD), i.e. the squared L2-norm (Euclidean) of the difference. + /// + public static float SSD(float[] a, float[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var diff = new float[a.Length]; + LinearAlgebraControl.Provider.SubtractArrays(a, b, diff); + return LinearAlgebraControl.Provider.DotProduct(diff, diff); + } + + /// + /// Mean-Squared Error (MSE), i.e. the normalized squared L2-norm (Euclidean) of the difference. + /// + public static double MSE(Vector a, Vector b) where T : struct, IEquatable, IFormattable + { + var norm = (a - b).L2Norm(); + return norm * norm / a.Count; + } + + /// + /// Mean-Squared Error (MSE), i.e. the normalized squared L2-norm (Euclidean) of the difference. + /// + public static double MSE(double[] a, double[] b) + { + return SSD(a, b) / a.Length; + } + + /// + /// Mean-Squared Error (MSE), i.e. the normalized squared L2-norm (Euclidean) of the difference. + /// + public static float MSE(float[] a, float[] b) + { + return SSD(a, b) / a.Length; + } + + /// + /// Euclidean Distance, i.e. the L2-norm of the difference. + /// + public static double Euclidean(Vector a, Vector b) where T : struct, IEquatable, IFormattable + { + return (a - b).L2Norm(); + } + + /// + /// Euclidean Distance, i.e. the L2-norm of the difference. + /// + public static double Euclidean(double[] a, double[] b) + { + return Math.Sqrt(SSD(a, b)); + } + + /// + /// Euclidean Distance, i.e. the L2-norm of the difference. + /// + public static float Euclidean(float[] a, float[] b) + { + return (float)Math.Sqrt(SSD(a, b)); + } + + /// + /// Manhattan Distance, i.e. the L1-norm of the difference. + /// + public static double Manhattan(Vector a, Vector b) where T : struct, IEquatable, IFormattable + { + return (a - b).L1Norm(); + } + + /// + /// Manhattan Distance, i.e. the L1-norm of the difference. + /// + public static double Manhattan(double[] a, double[] b) + { + return SAD(a, b); + } + + /// + /// Manhattan Distance, i.e. the L1-norm of the difference. + /// + public static float Manhattan(float[] a, float[] b) + { + return SAD(a, b); + } + + /// + /// Chebyshev Distance, i.e. the Infinity-norm of the difference. + /// + public static double Chebyshev(Vector a, Vector b) where T : struct, IEquatable, IFormattable + { + return (a - b).InfinityNorm(); + } + + /// + /// Chebyshev Distance, i.e. the Infinity-norm of the difference. + /// + public static double Chebyshev(double[] a, double[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + double max = Math.Abs(a[0] - b[0]); + for (int i = 1; i < a.Length; i++) + { + var next = Math.Abs(a[i] - b[i]); + if (next > max) + { + max = next; + } + } + + return max; + } + + /// + /// Chebyshev Distance, i.e. the Infinity-norm of the difference. + /// + public static float Chebyshev(float[] a, float[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + float max = Math.Abs(a[0] - b[0]); + for (int i = 1; i < a.Length; i++) + { + var next = Math.Abs(a[i] - b[i]); + if (next > max) + { + max = next; + } + } + + return max; + } + + /// + /// Minkowski Distance, i.e. the generalized p-norm of the difference. + /// + public static double Minkowski(double p, Vector a, Vector b) where T : struct, IEquatable, IFormattable + { + return (a - b).Norm(p); + } + + /// + /// Minkowski Distance, i.e. the generalized p-norm of the difference. + /// + public static double Minkowski(double p, double[] a, double[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (p < 0d) + { + throw new ArgumentOutOfRangeException(nameof(p)); + } + + if (p == 1d) + { + return Manhattan(a, b); + } + + if (p == 2d) + { + return Euclidean(a, b); + } + + if (double.IsPositiveInfinity(p)) + { + return Chebyshev(a, b); + } + + double sum = 0d; + for (var i = 0; i < a.Length; i++) + { + sum += Math.Pow(Math.Abs(a[i] - b[i]), p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Minkowski Distance, i.e. the generalized p-norm of the difference. + /// + public static float Minkowski(double p, float[] a, float[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (p < 0d) + { + throw new ArgumentOutOfRangeException(nameof(p)); + } + + if (p == 1d) + { + return Manhattan(a, b); + } + + if (p == 2d) + { + return Euclidean(a, b); + } + + if (double.IsPositiveInfinity(p)) + { + return Chebyshev(a, b); + } + + double sum = 0d; + for (var i = 0; i < a.Length; i++) + { + sum += Math.Pow(Math.Abs(a[i] - b[i]), p); + } + + return (float)Math.Pow(sum, 1.0 / p); + } + + /// + /// Canberra Distance, a weighted version of the L1-norm of the difference. + /// + public static double Canberra(double[] a, double[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + double sum = 0d; + for (var i = 0; i < a.Length; i++) + { + sum += Math.Abs(a[i] - b[i]) / (Math.Abs(a[i]) + Math.Abs(b[i])); + } + + return sum; + } + + /// + /// Canberra Distance, a weighted version of the L1-norm of the difference. + /// + public static float Canberra(float[] a, float[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + float sum = 0f; + for (var i = 0; i < a.Length; i++) + { + sum += Math.Abs(a[i] - b[i]) / (Math.Abs(a[i]) + Math.Abs(b[i])); + } + + return sum; + } + + /// + /// Cosine Distance, representing the angular distance while ignoring the scale. + /// + public static double Cosine(double[] a, double[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var ab = LinearAlgebraControl.Provider.DotProduct(a, b); + var a2 = LinearAlgebraControl.Provider.DotProduct(a, a); + var b2 = LinearAlgebraControl.Provider.DotProduct(b, b); + return 1d - ab / Math.Sqrt(a2 * b2); + } + + /// + /// Cosine Distance, representing the angular distance while ignoring the scale. + /// + public static float Cosine(float[] a, float[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var ab = LinearAlgebraControl.Provider.DotProduct(a, b); + var a2 = LinearAlgebraControl.Provider.DotProduct(a, a); + var b2 = LinearAlgebraControl.Provider.DotProduct(b, b); + return (float)(1d - ab / Math.Sqrt(a2 * b2)); + } + + /// + /// Hamming Distance, i.e. the number of positions that have different values in the vectors. + /// + public static double Hamming(double[] a, double[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + int count = 0; + for (int i = 0; i < a.Length; i++) + { + if (a[i] != b[i]) + { + count++; + } + } + + return count; + } + + /// + /// Hamming Distance, i.e. the number of positions that have different values in the vectors. + /// + public static float Hamming(float[] a, float[] b) + { + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + int count = 0; + for (int i = 0; i < a.Length; i++) + { + if (a[i] != b[i]) + { + count++; + } + } + + return count; + } + + /// + /// Pearson's distance, i.e. 1 - the person correlation coefficient. + /// + public static double Pearson(IEnumerable a, IEnumerable b) + { + return 1.0 - Correlation.Pearson(a, b); + } + + /// + /// Jaccard distance, i.e. 1 - the Jaccard index. + /// + /// Thrown if a or b are null. + /// Throw if a and b are of different lengths. + /// Jaccard distance. + public static double Jaccard(double[] a, double[] b) + { + int intersection = 0, union = 0; + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (a.Length == 0 && b.Length == 0) + { + return 0; + } + + for (int x = 0, len = a.Length; x < len; x++) + { + if (a[x] != 0 && b[x] != 0) + { + if (a[x] == b[x]) + { + intersection++; + } + + union++; + } + } + + return 1.0 - ((double)intersection / (double)union); + } + + /// + /// Jaccard distance, i.e. 1 - the Jaccard index. + /// + /// Thrown if a or b are null. + /// Throw if a and b are of different lengths. + /// Jaccard distance. + public static double Jaccard(float[] a, float[] b) + { + int intersection = 0, union = 0; + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != b.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (a.Length == 0 && b.Length == 0) + { + return 0; + } + + for (int x = 0, len = a.Length; x < len; x++) + { + if (a[x] != 0 && b[x] != 0) + { + if (a[x] == b[x]) + { + intersection++; + } + + union++; + } + } + + return 1.0 - ((float)intersection / (float)union); + } +} diff --git a/MathNet.Numerics/Distributions/Bernoulli.cs b/MathNet.Numerics/Distributions/Bernoulli.cs new file mode 100644 index 0000000..517bf01 --- /dev/null +++ b/MathNet.Numerics/Distributions/Bernoulli.cs @@ -0,0 +1,485 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Bernoulli distribution. +/// The Bernoulli distribution is a distribution over bits. The parameter +/// p specifies the probability that a 1 is generated. +/// Wikipedia - Bernoulli distribution. +/// +public class Bernoulli : IDiscreteDistribution +{ + System.Random _random; + + readonly double _p; + + /// + /// Initializes a new instance of the Bernoulli class. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// If the Bernoulli parameter is not in the range [0,1]. + public Bernoulli(double p) + { + if (!IsValidParameterSet(p)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _p = p; + } + + /// + /// Initializes a new instance of the Bernoulli class. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// The random number generator which is used to draw random samples. + /// If the Bernoulli parameter is not in the range [0,1]. + public Bernoulli(double p, System.Random randomSource) + { + if (!IsValidParameterSet(p)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _p = p; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Bernoulli(p = " + _p + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public static bool IsValidParameterSet(double p) + { + return p >= 0.0 && p <= 1.0; + } + + /// + /// Gets the probability of generating a one. Range: 0 ≤ p ≤ 1. + /// + public double P + { + get { return _p; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return _p; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(_p * (1.0 - _p)); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return _p * (1.0 - _p); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return -(_p * Math.Log(_p)) - ((1.0 - _p) * Math.Log(1.0 - _p)); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return (1.0 - (2.0 * _p)) / Math.Sqrt(_p * (1.0 - _p)); } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return 0; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { return 1; } + } + + /// + /// Gets the mode of the distribution. + /// + public int Mode + { + get { return _p > 0.5 ? 1 : 0; } + } + + /// + /// Gets all modes of the distribution. + /// + public int[] Modes + { + get { return _p < 0.5 ? new[] { 0 } : P > 0.5 ? new[] { 1 } : new[] { 0, 1 }; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return _p < 0.5 ? 0.0 : _p > 0.5 ? 1.0 : 0.5; } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + if (k == 0) + { + return 1.0 - _p; + } + + if (k == 1) + { + return _p; + } + + return 0.0; + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + if (k == 0) + { + return Math.Log(1.0 - _p); + } + + return k == 1 ? Math.Log(_p) : double.NegativeInfinity; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + if (x < 0.0) + { + return 0.0; + } + + if (x >= 1.0) + { + return 1.0; + } + + return 1.0 - _p; + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// the probability mass at location . + public static double PMF(double p, int k) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (k == 0) + { + return 1.0 - p; + } + + if (k == 1) + { + return p; + } + + return 0.0; + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// the log probability mass at location . + public static double PMFLn(double p, int k) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (k == 0) + { + return Math.Log(1.0 - p); + } + + return k == 1 ? Math.Log(p) : double.NegativeInfinity; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// the cumulative distribution at location . + /// + public static double CDF(double p, double x) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0) + { + return 0.0; + } + + if (x >= 1.0) + { + return 1.0; + } + + return 1.0 - p; + } + + /// + /// Generates one sample from the Bernoulli distribution. + /// + /// The random source to use. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// A random sample from the Bernoulli distribution. + static int SampleUnchecked(System.Random rnd, double p) + { + if (rnd.NextDouble() < p) + { + return 1; + } + + return 0; + } + + static void SamplesUnchecked(System.Random rnd, int[] values, double p) + { + var uniform = rnd.NextDoubles(values.Length); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = uniform[i] < p ? 1 : 0; + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double p) + { + return rnd.NextDoubleSequence().Select(r => r < p ? 1 : 0); + } + + /// + /// Samples a Bernoulli distributed random variable. + /// + /// A sample from the Bernoulli distribution. + public int Sample() + { + return SampleUnchecked(_random, _p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _p); + } + + /// + /// Samples an array of Bernoulli distributed random variables. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + while (true) + { + yield return SampleUnchecked(_random, _p); + } + } + + /// + /// Samples a Bernoulli distributed random variable. + /// + /// The random number generator to use. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// A sample from the Bernoulli distribution. + public static int Sample(System.Random rnd, double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, p); + } + + /// + /// Samples a sequence of Bernoulli distributed random variables. + /// + /// The random number generator to use. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, int[] values, double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, p); + } + + /// + /// Samples a Bernoulli distributed random variable. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// A sample from the Bernoulli distribution. + public static int Sample(double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, p); + } + + /// + /// Samples a sequence of Bernoulli distributed random variables. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// a sequence of samples from the distribution. + public static void Samples(int[] values, double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, p); + } +} diff --git a/MathNet.Numerics/Distributions/Beta.cs b/MathNet.Numerics/Distributions/Beta.cs new file mode 100644 index 0000000..9dfc2c4 --- /dev/null +++ b/MathNet.Numerics/Distributions/Beta.cs @@ -0,0 +1,764 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.RootFinding; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Beta distribution. +/// For details about this distribution, see +/// Wikipedia - Beta distribution. +/// +/// +/// There are a few special cases for the parameterization of the Beta distribution. When both +/// shape parameters are positive infinity, the Beta distribution degenerates to a point distribution +/// at 0.5. When one of the shape parameters is positive infinity, the distribution degenerates to a point +/// distribution at the positive infinity. When both shape parameters are 0.0, the Beta distribution +/// degenerates to a Bernoulli distribution with parameter 0.5. When one shape parameter is 0.0, the +/// distribution degenerates to a point distribution at the non-zero shape parameter. +/// +public class Beta : IContinuousDistribution +{ + System.Random _random; + + readonly double _shapeA; + readonly double _shapeB; + + /// + /// Initializes a new instance of the Beta class. + /// + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + public Beta(double a, double b) + { + if (!IsValidParameterSet(a, b)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _shapeA = a; + _shapeB = b; + } + + /// + /// Initializes a new instance of the Beta class. + /// + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// The random number generator which is used to draw random samples. + public Beta(double a, double b, System.Random randomSource) + { + if (!IsValidParameterSet(a, b)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _shapeA = a; + _shapeB = b; + } + + /// + /// A string representation of the distribution. + /// + /// A string representation of the Beta distribution. + public override string ToString() + { + return "Beta(α = " + _shapeA + ", β = " + _shapeB + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + public static bool IsValidParameterSet(double a, double b) + { + return a >= 0.0 && b >= 0.0; + } + + /// + /// Gets the α shape parameter of the Beta distribution. Range: α ≥ 0. + /// + public double A + { + get { return _shapeA; } + } + + /// + /// Gets the β shape parameter of the Beta distribution. Range: β ≥ 0. + /// + public double B + { + get { return _shapeB; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the Beta distribution. + /// + public double Mean + { + get + { + if (_shapeA == 0.0 && _shapeB == 0.0) + { + return 0.5; + } + + if (_shapeA == 0.0) + { + return 0.0; + } + + if (_shapeB == 0.0) + { + return 1.0; + } + + if (double.IsPositiveInfinity(_shapeA) && double.IsPositiveInfinity(_shapeB)) + { + return 0.5; + } + + if (double.IsPositiveInfinity(_shapeA)) + { + return 1.0; + } + + if (double.IsPositiveInfinity(_shapeB)) + { + return 0.0; + } + + return _shapeA / (_shapeA + _shapeB); + } + } + + /// + /// Gets the variance of the Beta distribution. + /// + public double Variance + { + get { return (_shapeA * _shapeB) / ((_shapeA + _shapeB) * (_shapeA + _shapeB) * (_shapeA + _shapeB + 1.0)); } + } + + /// + /// Gets the standard deviation of the Beta distribution. + /// + public double StdDev + { + get { return Math.Sqrt((_shapeA * _shapeB) / ((_shapeA + _shapeB) * (_shapeA + _shapeB) * (_shapeA + _shapeB + 1.0))); } + } + + /// + /// Gets the entropy of the Beta distribution. + /// + public double Entropy + { + get + { + if (double.IsPositiveInfinity(_shapeA) || double.IsPositiveInfinity(_shapeB)) + { + return 0.0; + } + + if (_shapeA == 0.0 && _shapeB == 0.0) + { + return -Math.Log(0.5); + } + + if (_shapeA == 0.0 || _shapeB == 0.0) + { + return 0.0; + } + + return SpecialFunctions.BetaLn(_shapeA, _shapeB) + - ((_shapeA - 1.0) * SpecialFunctions.DiGamma(_shapeA)) + - ((_shapeB - 1.0) * SpecialFunctions.DiGamma(_shapeB)) + + ((_shapeA + _shapeB - 2.0) * SpecialFunctions.DiGamma(_shapeA + _shapeB)); + } + } + + /// + /// Gets the skewness of the Beta distribution. + /// + public double Skewness + { + get + { + if (double.IsPositiveInfinity(_shapeA) && double.IsPositiveInfinity(_shapeB)) + { + return 0.0; + } + + if (double.IsPositiveInfinity(_shapeA)) + { + return -2.0; + } + + if (double.IsPositiveInfinity(_shapeB)) + { + return 2.0; + } + + if (_shapeA == 0.0 && _shapeB == 0.0) + { + return 0.0; + } + + if (_shapeA == 0.0) + { + return 2.0; + } + + if (_shapeB == 0.0) + { + return -2.0; + } + + return 2.0 * (_shapeB - _shapeA) * Math.Sqrt(_shapeA + _shapeB + 1.0) + / ((_shapeA + _shapeB + 2.0) * Math.Sqrt(_shapeA * _shapeB)); + } + } + + /// + /// Gets the mode of the Beta distribution; when there are multiple answers, this routine will return 0.5. + /// + public double Mode + { + get + { + if (_shapeA == 0.0 && _shapeB == 0.0) + { + return 0.5; + } + + if (_shapeA == 0.0) + { + return 0.0; + } + + if (_shapeB == 0.0) + { + return 1.0; + } + + if (double.IsPositiveInfinity(_shapeA) && double.IsPositiveInfinity(_shapeB)) + { + return 0.5; + } + + if (double.IsPositiveInfinity(_shapeA)) + { + return 1.0; + } + + if (double.IsPositiveInfinity(_shapeB)) + { + return 0.0; + } + + if (_shapeA == 1.0 && _shapeB == 1.0) + { + return 0.5; + } + + return (_shapeA - 1) / (_shapeA + _shapeB - 2); + } + } + + /// + /// Gets the median of the Beta distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum of the Beta distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the Beta distribution. + /// + public double Maximum + { + get { return 1.0; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return PDF(_shapeA, _shapeB, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return PDFLn(_shapeA, _shapeB, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CDF(_shapeA, _shapeB, x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + /// WARNING: currently not an explicit implementation, hence slow and unreliable. + public double InverseCumulativeDistribution(double p) + { + return InvCDF(_shapeA, _shapeB, p); + } + + /// + /// Generates a sample from the Beta distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _shapeA, _shapeB); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _shapeA, _shapeB); + } + + /// + /// Generates a sequence of samples from the Beta distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _shapeA, _shapeB); + } + + /// + /// Samples Beta distributed random variables by sampling two Gamma variables and normalizing. + /// + /// The random number generator to use. + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// a random number from the Beta distribution. + internal static double SampleUnchecked(System.Random rnd, double a, double b) + { + var x = Gamma.SampleUnchecked(rnd, a, 1.0); + var y = Gamma.SampleUnchecked(rnd, b, 1.0); + return x / (x + y); + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double a, double b) + { + var y = new double[values.Length]; + Gamma.SamplesUnchecked(rnd, values, a, 1.0); + Gamma.SamplesUnchecked(rnd, y, b, 1.0); + CommonParallel.For(0, values.Length, 4096, (aa, bb) => + { + for (int i = aa; i < bb; i++) + { + values[i] = values[i] / (values[i] + y[i]); + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double a, double b) + { + while (true) + { + yield return SampleUnchecked(rnd, a, b); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double a, double b, double x) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0 || x > 1.0) + { + return 0.0; + } + + if (double.IsPositiveInfinity(a) && double.IsPositiveInfinity(b)) + { + return x == 0.5 ? double.PositiveInfinity : 0.0; + } + + if (double.IsPositiveInfinity(a)) + { + return x == 1.0 ? double.PositiveInfinity : 0.0; + } + + if (double.IsPositiveInfinity(b)) + { + return x == 0.0 ? double.PositiveInfinity : 0.0; + } + + if (a == 0.0 && b == 0.0) + { + if (x == 0.0 || x == 1.0) + { + return double.PositiveInfinity; + } + + return 0.0; + } + + if (a == 0.0) + { + return x == 0.0 ? double.PositiveInfinity : 0.0; + } + + if (b == 0.0) + { + return x == 1.0 ? double.PositiveInfinity : 0.0; + } + + if (a == 1.0 && b == 1.0) + { + return 1.0; + } + + if (a > 80.0 || b > 80.0) + { + return Math.Exp(PDFLn(a, b, x)); + } + + var bb = SpecialFunctions.Gamma(a + b) / (SpecialFunctions.Gamma(a) * SpecialFunctions.Gamma(b)); + return bb * Math.Pow(x, a - 1.0) * Math.Pow(1.0 - x, b - 1.0); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double a, double b, double x) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0 || x > 1.0) + { + return double.NegativeInfinity; + } + + if (double.IsPositiveInfinity(a) && double.IsPositiveInfinity(b)) + { + return x == 0.5 ? double.PositiveInfinity : double.NegativeInfinity; + } + + if (double.IsPositiveInfinity(a)) + { + return x == 1.0 ? double.PositiveInfinity : double.NegativeInfinity; + } + + if (double.IsPositiveInfinity(b)) + { + return x == 0.0 ? double.PositiveInfinity : double.NegativeInfinity; + } + + if (a == 0.0 && b == 0.0) + { + return x == 0.0 || x == 1.0 ? double.PositiveInfinity : double.NegativeInfinity; + } + + if (a == 0.0) + { + return x == 0.0 ? double.PositiveInfinity : double.NegativeInfinity; + } + + if (b == 0.0) + { + return x == 1.0 ? double.PositiveInfinity : double.NegativeInfinity; + } + + if (a == 1.0 && b == 1.0) + { + return 0.0; + } + + var aa = SpecialFunctions.GammaLn(a + b) - SpecialFunctions.GammaLn(a) - SpecialFunctions.GammaLn(b); + var bb = x == 0.0 ? (a == 1.0 ? 0.0 : double.NegativeInfinity) : (a - 1.0) * Math.Log(x); + var cc = x == 1.0 ? (b == 1.0 ? 0.0 : double.NegativeInfinity) : (b - 1.0) * Math.Log(1.0 - x); + + return aa + bb + cc; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// the cumulative distribution at location . + /// + public static double CDF(double a, double b, double x) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0) + { + return 0.0; + } + + if (x >= 1.0) + { + return 1.0; + } + + if (double.IsPositiveInfinity(a) && double.IsPositiveInfinity(b)) + { + return x < 0.5 ? 0.0 : 1.0; + } + + if (double.IsPositiveInfinity(a)) + { + return x < 1.0 ? 0.0 : 1.0; + } + + if (double.IsPositiveInfinity(b)) + { + return x >= 0.0 ? 1.0 : 0.0; + } + + if (a == 0.0 && b == 0.0) + { + if (x >= 0.0 && x < 1.0) + { + return 0.5; + } + + return 1.0; + } + + if (a == 0.0) + { + return 1.0; + } + + if (b == 0.0) + { + return x >= 1.0 ? 1.0 : 0.0; + } + + if (a == 1.0 && b == 1.0) + { + return x; + } + + return SpecialFunctions.BetaRegularized(a, b, x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// the inverse cumulative density at . + /// + /// WARNING: currently not an explicit implementation, hence slow and unreliable. + public static double InvCDF(double a, double b, double p) + { + if (a < 0.0 || b < 0.0 || p < 0.0 || p > 1.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Brent.FindRoot(x => SpecialFunctions.BetaRegularized(a, b, x) - p, 0.0, 1.0, accuracy: 1e-12); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double a, double b) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, a, b); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double a, double b) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, a, b); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double a, double b) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, a, b); + } + + /// + /// Generates a sample from the distribution. + /// + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// a sample from the distribution. + public static double Sample(double a, double b) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, a, b); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double a, double b) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, a, b); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The α shape parameter of the Beta distribution. Range: α ≥ 0. + /// The β shape parameter of the Beta distribution. Range: β ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double a, double b) + { + if (a < 0.0 || b < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, a, b); + } +} diff --git a/MathNet.Numerics/Distributions/BetaScaled.cs b/MathNet.Numerics/Distributions/BetaScaled.cs new file mode 100644 index 0000000..05e4406 --- /dev/null +++ b/MathNet.Numerics/Distributions/BetaScaled.cs @@ -0,0 +1,624 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +public class BetaScaled : IContinuousDistribution +{ + System.Random _random; + + readonly double _shapeA; + readonly double _shapeB; + readonly double _location; + readonly double _scale; + + /// + /// Initializes a new instance of the BetaScaled class. + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + public BetaScaled(double a, double b, double location, double scale) + { + if (!IsValidParameterSet(a, b, location, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _shapeA = a; + _shapeB = b; + _location = location; + _scale = scale; + } + + /// + /// Initializes a new instance of the BetaScaled class. + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The random number generator which is used to draw random samples. + public BetaScaled(double a, double b, double location, double scale, System.Random randomSource) + { + if (!IsValidParameterSet(a, b, location, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _shapeA = a; + _shapeB = b; + _location = location; + _scale = scale; + } + + /// + /// Create a Beta PERT distribution, used in risk analysis and other domains where an expert forecast + /// is used to construct an underlying beta distribution. + /// + /// The minimum value. + /// The maximum value. + /// The most likely value (mode). + /// The random number generator which is used to draw random samples. + /// The Beta distribution derived from the PERT parameters. + public static BetaScaled PERT(double min, double max, double likely, System.Random randomSource = null) + { + if (min > max || likely > max || likely < min) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + // specified to make the formulas match the literature; + // traditionally set to 4 so that the range between min and max + // represents six standard deviations (sometimes called + // "the six-sigma assumption"). + const double lambda = 4; + + // calculate the mean + double mean = (min + max + lambda * likely) / (lambda + 2); + + // derive the shape parameters a and b + double a; + + // special case where mean and mode are identical + if (mean == likely) + { + a = (lambda / 2) + 1; + } + else + { + a = ((mean - min) * (2 * likely - min - max)) / ((likely - mean) * (max - min)); + } + + double b = (a * (max - mean)) / (mean - min); + + return new BetaScaled(a, b, min, max - min, randomSource); + } + + /// + /// A string representation of the distribution. + /// + /// A string representation of the BetaScaled distribution. + public override string ToString() + { + return "BetaScaled(α = " + _shapeA + ", β = " + _shapeB + ", μ = " + _location + ", σ = " + _scale + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + public static bool IsValidParameterSet(double a, double b, double location, double scale) + { + return a > 0.0 && b > 0.0 && scale > 0.0 && !double.IsNaN(location); + } + + /// + /// Gets the α shape parameter of the BetaScaled distribution. Range: α > 0. + /// + public double A + { + get { return _shapeA; } + } + + /// + /// Gets the β shape parameter of the BetaScaled distribution. Range: β > 0. + /// + public double B + { + get { return _shapeB; } + } + + /// + /// Gets the location (μ) of the BetaScaled distribution. + /// + public double Location + { + get { return _location; } + } + + /// + /// Gets the scale (σ) of the BetaScaled distribution. Range: σ > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the BetaScaled distribution. + /// + public double Mean + { + get + { + if (double.IsPositiveInfinity(_shapeA) && double.IsPositiveInfinity(_shapeB)) + { + return _location + 0.5 * _scale; + } + + if (double.IsPositiveInfinity(_shapeA)) + { + return _location + _scale; + } + + if (double.IsPositiveInfinity(_shapeB)) + { + return _location; + } + + return (_shapeB * _location + _shapeA * (_location + _scale)) / (_shapeA + _shapeB); + } + } + + /// + /// Gets the variance of the BetaScaled distribution. + /// + public double Variance + { + get + { + double sum = _shapeA + _shapeB; + return (_shapeA * _shapeB * _scale * _scale) / (sum * sum * (1.0 + sum)); + } + } + + /// + /// Gets the standard deviation of the BetaScaled distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the entropy of the BetaScaled distribution. + /// + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the skewness of the BetaScaled distribution. + /// + public double Skewness + { + get + { + if (double.IsPositiveInfinity(_shapeA) && double.IsPositiveInfinity(_shapeB)) + { + return 0.0; + } + + if (double.IsPositiveInfinity(_shapeA)) + { + return -2.0 * _scale / Math.Sqrt(_shapeB * _scale * _scale); + } + + if (double.IsPositiveInfinity(_shapeB)) + { + return 2.0 * _scale / Math.Sqrt(_shapeA * _scale * _scale); + } + + double sum = _shapeA + _shapeB; + double variance = (_shapeA * _shapeB * _scale * _scale) / (sum * sum * (1.0 + sum)); + return 2.0 * (_shapeB - _shapeA) * _scale / (sum * (2.0 + sum) * Math.Sqrt(variance)); + } + } + + /// + /// Gets the mode of the BetaScaled distribution; when there are multiple answers, this routine will return 0.5. + /// + public double Mode + { + get + { + if (double.IsPositiveInfinity(_shapeA) && double.IsPositiveInfinity(_shapeB)) + { + return _location + 0.5 * _scale; + } + + if (double.IsPositiveInfinity(_shapeA)) + { + return _location + _scale; + } + + if (double.IsPositiveInfinity(_shapeB)) + { + return _location; + } + + if (_shapeA == 1.0 && _shapeB == 1.0) + { + return _location + 0.5 * _scale; + } + + return ((_shapeA - 1) / (_shapeA + _shapeB - 2)) * _scale + _location; + } + } + + /// + /// Gets the median of the BetaScaled distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum of the BetaScaled distribution. + /// + public double Minimum + { + get { return _location; } + } + + /// + /// Gets the maximum of the BetaScaled distribution. + /// + public double Maximum + { + get { return _location + _scale; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return PDF(_shapeA, _shapeB, _location, _scale, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return PDFLn(_shapeA, _shapeB, _location, _scale, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CDF(_shapeA, _shapeB, _location, _scale, x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + /// WARNING: currently not an explicit implementation, hence slow and unreliable. + public double InverseCumulativeDistribution(double p) + { + return InvCDF(_shapeA, _shapeB, _location, _scale, p); + } + + /// + /// Generates a sample from the distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _shapeA, _shapeB, _location, _scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _shapeA, _shapeB, _location, _scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _shapeA, _shapeB, _location, _scale); + } + + static double SampleUnchecked(System.Random rnd, double a, double b, double location, double scale) + { + return Beta.SampleUnchecked(rnd, a, b) * scale + location; + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double a, double b, double location, double scale) + { + Beta.SamplesUnchecked(rnd, values, a, b); + CommonParallel.For(0, values.Length, 4096, (aa, bb) => + { + for (int i = aa; i < bb; i++) + { + values[i] = values[i] * scale + location; + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double a, double b, double location, double scale) + { + while (true) + { + yield return SampleUnchecked(rnd, a, b, location, scale); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double a, double b, double location, double scale, double x) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Beta.PDF(a, b, (x - location) / scale) / Math.Abs(scale); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double a, double b, double location, double scale, double x) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Beta.PDFLn(a, b, (x - location) / scale) - Math.Log(Math.Abs(scale)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public static double CDF(double a, double b, double location, double scale, double x) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Beta.CDF(a, b, (x - location) / scale); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + /// WARNING: currently not an explicit implementation, hence slow and unreliable. + public static double InvCDF(double a, double b, double location, double scale, double p) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Beta.InvCDF(a, b, p) * scale + location; + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double a, double b, double location, double scale) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, a, b, location, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double a, double b, double location, double scale) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, a, b, location, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double a, double b, double location, double scale) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, a, b, location, scale); + } + + /// + /// Generates a sample from the distribution. + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sample from the distribution. + public static double Sample(double a, double b, double location, double scale) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, a, b, location, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double a, double b, double location, double scale) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, a, b, location, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The α shape parameter of the BetaScaled distribution. Range: α > 0. + /// The β shape parameter of the BetaScaled distribution. Range: β > 0. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double a, double b, double location, double scale) + { + if (!(a > 0.0 && b > 0.0 && scale > 0.0) || double.IsNaN(location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, a, b, location, scale); + } +} diff --git a/MathNet.Numerics/Distributions/Binomial.cs b/MathNet.Numerics/Distributions/Binomial.cs new file mode 100644 index 0000000..142f50b --- /dev/null +++ b/MathNet.Numerics/Distributions/Binomial.cs @@ -0,0 +1,554 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Binomial distribution. +/// For details about this distribution, see +/// Wikipedia - Binomial distribution. +/// +/// +/// The distribution is parameterized by a probability (between 0.0 and 1.0). +/// +public class Binomial : IDiscreteDistribution +{ + System.Random _random; + + readonly double _p; + readonly int _trials; + + /// + /// Initializes a new instance of the Binomial class. + /// + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// If is not in the interval [0.0,1.0]. + /// If is negative. + public Binomial(double p, int n) + { + if (!IsValidParameterSet(p, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _p = p; + _trials = n; + } + + /// + /// Initializes a new instance of the Binomial class. + /// + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// The random number generator which is used to draw random samples. + /// If is not in the interval [0.0,1.0]. + /// If is negative. + public Binomial(double p, int n, System.Random randomSource) + { + if (!IsValidParameterSet(p, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _p = p; + _trials = n; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Binomial(p = " + _p + ", n = " + _trials + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + public static bool IsValidParameterSet(double p, int n) + { + return p >= 0.0 && p <= 1.0 && n >= 0; + } + + /// + /// Gets the success probability in each trial. Range: 0 ≤ p ≤ 1. + /// + public double P + { + get { return _p; } + } + + /// + /// Gets the number of trials. Range: n ≥ 0. + /// + public int N + { + get { return _trials; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return _p * _trials; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(_p * (1.0 - _p) * _trials); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return _p * (1.0 - _p) * _trials; } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get + { + if (_p == 0.0 || _p == 1.0) + { + return 0.0; + } + + var e = 0.0; + for (var i = 0; i <= _trials; i++) + { + var p = Probability(i); + e -= p * Math.Log(p); + } + + return e; + } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return (1.0 - (2.0 * _p)) / Math.Sqrt(_trials * _p * (1.0 - _p)); } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return 0; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { return _trials; } + } + + /// + /// Gets the mode of the distribution. + /// + public int Mode + { + get + { + if (_p == 1.0) + { + return _trials; + } + + if (_p == 0.0) + { + return 0; + } + + return (int)Math.Floor((_trials + 1) * _p); + } + } + + /// + /// Gets all modes of the distribution. + /// + public int[] Modes + { + get + { + if (_p == 1.0) + { + return new[] { _trials }; + } + + if (_p == 0.0) + { + return new[] { 0 }; + } + + double td = (_trials + 1) * _p; + int t = (int)Math.Floor(td); + return t != td ? new[] { t } : new[] { t, t - 1 }; + } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return Math.Floor(_p * _trials); } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + return PMF(_p, _trials, k); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + return PMFLn(_p, _trials, k); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + return CDF(_p, _trials, x); + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// the probability mass at location . + public static double PMF(double p, int n, int k) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (k < 0 || k > n) + { + return 0.0; + } + + if (p == 0.0) + { + return k == 0 ? 1.0 : 0.0; + } + + if (p == 1.0) + { + return k == n ? 1.0 : 0.0; + } + + return Math.Exp(SpecialFunctions.BinomialLn(n, k) + (k * Math.Log(p)) + ((n - k) * Math.Log(1.0 - p))); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// the log probability mass at location . + public static double PMFLn(double p, int n, int k) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (k < 0 || k > n) + { + return double.NegativeInfinity; + } + + if (p == 0.0) + { + return k == 0 ? 0.0 : double.NegativeInfinity; + } + + if (p == 1.0) + { + return k == n ? 0.0 : double.NegativeInfinity; + } + + return SpecialFunctions.BinomialLn(n, k) + (k * Math.Log(p)) + ((n - k) * Math.Log(1.0 - p)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// the cumulative distribution at location . + /// + public static double CDF(double p, int n, double x) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0) + { + return 0.0; + } + + if (x > n) + { + return 1.0; + } + + double k = Math.Floor(x); + return SpecialFunctions.BetaRegularized(n - k, k + 1, 1 - p); + } + + /// + /// Generates a sample from the Binomial distribution without doing parameter checking. + /// + /// The random number generator to use. + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// The number of successful trials. + static int SampleUnchecked(System.Random rnd, double p, int n) + { + var k = 0; + for (var i = 0; i < n; i++) + { + k += rnd.NextDouble() < p ? 1 : 0; + } + + return k; + } + + static void SamplesUnchecked(System.Random rnd, int[] values, double p, int n) + { + var uniform = rnd.NextDoubles(values.Length * n); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + int k = i * n; + int sum = 0; + for (int j = 0; j < n; j++) + { + sum += uniform[k + j] < p ? 1 : 0; + } + + values[i] = sum; + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double p, int n) + { + while (true) + { + yield return SampleUnchecked(rnd, p, n); + } + } + + /// + /// Samples a Binomially distributed random variable. + /// + /// The number of successes in N trials. + public int Sample() + { + return SampleUnchecked(_random, _p, _trials); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _p, _trials); + } + + /// + /// Samples an array of Binomially distributed random variables. + /// + /// a sequence of successes in N trials. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _p, _trials); + } + + /// + /// Samples a binomially distributed random variable. + /// + /// The random number generator to use. + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// The number of successes in trials. + public static int Sample(System.Random rnd, double p, int n) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, p, n); + } + + /// + /// Samples a sequence of binomially distributed random variable. + /// + /// The random number generator to use. + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// a sequence of successes in trials. + public static IEnumerable Samples(System.Random rnd, double p, int n) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, p, n); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// a sequence of successes in trials. + public static void Samples(System.Random rnd, int[] values, double p, int n) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, p, n); + } + + /// + /// Samples a binomially distributed random variable. + /// + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// The number of successes in trials. + public static int Sample(double p, int n) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, p, n); + } + + /// + /// Samples a sequence of binomially distributed random variable. + /// + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// a sequence of successes in trials. + public static IEnumerable Samples(double p, int n) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, p, n); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The success probability (p) in each trial. Range: 0 ≤ p ≤ 1. + /// The number of trials (n). Range: n ≥ 0. + /// a sequence of successes in trials. + public static void Samples(int[] values, double p, int n) + { + if (!(p >= 0.0 && p <= 1.0 && n >= 0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, p, n); + } +} diff --git a/MathNet.Numerics/Distributions/Burr.cs b/MathNet.Numerics/Distributions/Burr.cs new file mode 100644 index 0000000..4193998 --- /dev/null +++ b/MathNet.Numerics/Distributions/Burr.cs @@ -0,0 +1,438 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2019 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +public class Burr : IContinuousDistribution +{ + private System.Random _random; + + /// + /// Gets the scale (a) of the distribution. Range: a > 0. + /// + public double a { get; } + + /// + /// Gets the first shape parameter (c) of the distribution. Range: c > 0. + /// + public double c { get; } + + /// + /// Gets the second shape parameter (k) of the distribution. Range: k > 0. + /// + public double k { get; } + + /// + /// Initializes a new instance of the Burr Type XII class. + /// + /// The scale parameter a of the Burr distribution. Range: a > 0. + /// The first shape parameter c of the Burr distribution. Range: c > 0. + /// The second shape parameter k of the Burr distribution. Range: k > 0. + /// The random number generator which is used to draw random samples. Optional, can be null. + public Burr(double a, double c, double k, System.Random randomSource = null) + { + if (!IsValidParameterSet(a, c, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + this.a = a; + this.c = c; + this.k = k; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Burr(a = " + a + ", c = " + c + ", k = " + k + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The scale parameter a of the Burr distribution. Range: a > 0. + /// The first shape parameter c of the Burr distribution. Range: c > 0. + /// The second shape parameter k of the Burr distribution. Range: k > 0. + public static bool IsValidParameterSet(double a, double c, double k) + { + var allFinite = a.IsFinite() && c.IsFinite() && k.IsFinite(); + return allFinite && a > 0.0 && c > 0.0 && k > 0.0; + } + + /// + /// Gets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the Burr distribution. + /// + public double Mean + { + get + { + return (1 / SpecialFunctions.Gamma(k)) * a * SpecialFunctions.Gamma(1 + 1 / c) * SpecialFunctions.Gamma(k - 1 / c); + } + } + + /// + /// Gets the variance of the Burr distribution. + /// + public double Variance + { + get + { + return (1 / SpecialFunctions.Gamma(k)) * Math.Pow(a, 2) * SpecialFunctions.Gamma(1 + 2 / c) * SpecialFunctions.Gamma(k - 2 / c) + - Math.Pow((1 / SpecialFunctions.Gamma(k)) * a * SpecialFunctions.Gamma(1 + 1 / c) * SpecialFunctions.Gamma(k - 1 / c), 2); + } + } + + /// + /// Gets the standard deviation of the Burr distribution. + /// + public double StdDev + { + get + { + return Math.Sqrt(Variance); + } + } + + /// + /// Gets the mode of the Burr distribution. + /// + public double Mode + { + get + { + return a * Math.Pow((c - 1) / (c * k + 1), 1 / c); + } + } + + /// + /// Gets the minimum of the Burr distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the Burr distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Gets the entropy of the Burr distribution (currently not supported). + /// + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the skewness of the Burr distribution. + /// + public double Skewness + { + get + { + var mean = Mean; + var variance = Variance; + var std = StdDev; + return (GetMoment(3) - 3 * mean * variance - mean * mean * mean) / (std * std * std); + } + } + + /// + /// Gets the median of the Burr distribution. + /// + public double Median + { + get + { + return a * Math.Pow(Math.Pow(2, 1 / k) - 1, 1 / c); + } + } + + /// + /// Generates a sample from the Burr distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, a, c, k); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, a, c, k); + } + + /// + /// Generates a sequence of samples from the Burr distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, a, c, k); + } + + /// + /// Generates a sample from the Burr distribution. + /// + /// The random number generator to use. + /// The scale parameter a of the Burr distribution. Range: a > 0. + /// The first shape parameter c of the Burr distribution. Range: c > 0. + /// The second shape parameter k of the Burr distribution. Range: k > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double a, double c, double k) + { + if (!IsValidParameterSet(a, c, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, a, c, k); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The scale parameter a of the Burr distribution. Range: a > 0. + /// The first shape parameter c of the Burr distribution. Range: c > 0. + /// The second shape parameter k of the Burr distribution. Range: k > 0. + public static void Samples(System.Random rnd, double[] values, double a, double c, double k) + { + if (!IsValidParameterSet(a, c, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, a, c, k); + } + + /// + /// Generates a sequence of samples from the Burr distribution. + /// + /// The random number generator to use. + /// The scale parameter a of the Burr distribution. Range: a > 0. + /// The first shape parameter c of the Burr distribution. Range: c > 0. + /// The second shape parameter k of the Burr distribution. Range: k > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double a, double c, double k) + { + if (!IsValidParameterSet(a, c, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, a, c, k); + } + + internal static double SampleUnchecked(System.Random rnd, double a, double c, double k) + { + var k_inv = 1 / k; + var c_inv = 1 / c; + double u = rnd.NextDouble(); + return a * Math.Pow(Math.Pow(1 - u, -k_inv) - 1, c_inv); + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double a, double c, double k) + { + if (values.Length == 0) + { + return; + } + + var k_inv = 1 / k; + var c_inv = 1 / c; + double[] u = rnd.NextDoubles(values.Length); + + for (var j = 0; j < values.Length; ++j) + { + values[j] = a * Math.Pow(Math.Pow(1 - u[j], -k_inv) - 1, c_inv); + } + } + + internal static IEnumerable SamplesUnchecked(System.Random rnd, double a, double c, double k) + { + while (true) + { + yield return SampleUnchecked(rnd, a, c, k); + } + } + + /// + /// Gets the n-th raw moment of the distribution. + /// + /// The order (n) of the moment. Range: n ≥ 1. + /// the n-th moment of the distribution. + public double GetMoment(double n) + { + if (n > k * c) + { + throw new ArgumentException(Resources.ArgumentParameterSetInvalid); + } + + var lambda_n = (n / c) * SpecialFunctions.Gamma(n / c) * SpecialFunctions.Gamma(k - n / c); + return Math.Pow(a, n) * lambda_n / SpecialFunctions.Gamma(k); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return DensityImpl(a, c, k, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return DensityLnImpl(a, c, k, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CumulativeDistributionImpl(a, c, k, x); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The scale parameter a of the Burr distribution. Range: a > 0. + /// The first shape parameter c of the Burr distribution. Range: c > 0. + /// The second shape parameter k of the Burr distribution. Range: k > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double a, double c, double k, double x) + { + if (!IsValidParameterSet(a, c, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return DensityImpl(a, c, k, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The scale parameter a of the Burr distribution. Range: a > 0. + /// The first shape parameter c of the Burr distribution. Range: c > 0. + /// The second shape parameter k of the Burr distribution. Range: k > 0. + /// The location at which to compute the log density. + /// the log density at . + /// + public static double PDFLn(double a, double c, double k, double x) + { + if (!IsValidParameterSet(a, c, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return DensityLnImpl(a, c, k, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The scale parameter a of the Burr distribution. Range: a > 0. + /// The first shape parameter c of the Burr distribution. Range: c > 0. + /// The second shape parameter k of the Burr distribution. Range: k > 0. + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public static double CDF(double a, double c, double k, double x) + { + if (!IsValidParameterSet(a, c, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return CumulativeDistributionImpl(a, c, k, x); + } + + internal static double DensityImpl(double a, double c, double k, double x) + { + var numerator = (k * c / a) * Math.Pow(x / a, c - 1); + var denominator = Math.Pow(1 + Math.Pow(x / a, c), k + 1); + return numerator / denominator; + } + + internal static double DensityLnImpl(double a, double c, double k, double x) + { + return Math.Log(DensityImpl(a, c, k, x)); + } + + internal static double CumulativeDistributionImpl(double a, double c, double k, double x) + { + var denominator = Math.Pow(1 + Math.Pow(x / a, c), k); + return 1 - 1 / denominator; + } +} diff --git a/MathNet.Numerics/Distributions/Categorical.cs b/MathNet.Numerics/Distributions/Categorical.cs new file mode 100644 index 0000000..e2a6a74 --- /dev/null +++ b/MathNet.Numerics/Distributions/Categorical.cs @@ -0,0 +1,839 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Statistics; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Categorical distribution. +/// For details about this distribution, see +/// Wikipedia - Categorical distribution. This +/// distribution is sometimes called the Discrete distribution. +/// +/// +/// The distribution is parameterized by a vector of ratios: in other words, the parameter +/// does not have to be normalized and sum to 1. The reason is that some vectors can't be exactly normalized +/// to sum to 1 in floating point representation. +/// +/// +/// Support: 0..k where k = length(probability mass array)-1 +/// +public class Categorical : IDiscreteDistribution +{ + System.Random _random; + + readonly double[] _pmfNormalized; + readonly double[] _cdfUnnormalized; + + /// + /// Initializes a new instance of the Categorical class. + /// + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// If any of the probabilities are negative or do not sum to one. + public Categorical(double[] probabilityMass) + : this(probabilityMass, SystemRandomSource.Default) + { + } + + /// + /// Initializes a new instance of the Categorical class. + /// + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// The random number generator which is used to draw random samples. + /// If any of the probabilities are negative or do not sum to one. + public Categorical(double[] probabilityMass, System.Random randomSource) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + + // Extract unnormalized cumulative distribution + _cdfUnnormalized = new double[probabilityMass.Length]; + _cdfUnnormalized[0] = probabilityMass[0]; + for (int i = 1; i < probabilityMass.Length; i++) + { + _cdfUnnormalized[i] = _cdfUnnormalized[i - 1] + probabilityMass[i]; + } + + // Extract normalized probability mass + var sum = _cdfUnnormalized[_cdfUnnormalized.Length - 1]; + _pmfNormalized = new double[probabilityMass.Length]; + for (int i = 0; i < probabilityMass.Length; i++) + { + _pmfNormalized[i] = probabilityMass[i] / sum; + } + } + + /// + /// Initializes a new instance of the Categorical class from a . The distribution + /// will not be automatically updated when the histogram changes. The categorical distribution will have + /// one value for each bucket and a probability for that value proportional to the bucket count. + /// + /// The histogram from which to create the categorical variable. + public Categorical(Histogram histogram) + { + if (histogram == null) + { + throw new ArgumentNullException(nameof(histogram)); + } + + // The probability distribution vector. + var p = new double[histogram.BucketCount]; + + // Fill in the distribution vector. + for (var i = 0; i < histogram.BucketCount; i++) + { + p[i] = histogram[i].Count; + } + + _random = SystemRandomSource.Default; + + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(p)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + // Extract unnormalized cumulative distribution + _cdfUnnormalized = new double[p.Length]; + _cdfUnnormalized[0] = p[0]; + for (int i1 = 1; i1 < p.Length; i1++) + { + _cdfUnnormalized[i1] = _cdfUnnormalized[i1 - 1] + p[i1]; + } + + // Extract normalized probability mass + var sum = _cdfUnnormalized[_cdfUnnormalized.Length - 1]; + _pmfNormalized = new double[p.Length]; + for (int i2 = 0; i2 < p.Length; i2++) + { + _pmfNormalized[i2] = p[i2] / sum; + } + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Categorical(Dimension = " + _pmfNormalized.Length + ")"; + } + + /// + /// Checks whether the parameters of the distribution are valid. + /// + /// An array of nonnegative ratios: this array does not need to be normalized as this is often impossible using floating point arithmetic. + /// If any of the probabilities are negative returns false, or if the sum of parameters is 0.0; otherwise true + public static bool IsValidProbabilityMass(double[] p) + { + var sum = 0.0; + for (int i = 0; i < p.Length; i++) + { + double t = p[i]; + if (t < 0.0 || double.IsNaN(t)) + { + return false; + } + + sum += t; + } + + return sum > 0.0; + } + + /// + /// Checks whether the parameters of the distribution are valid. + /// + /// An array of nonnegative ratios: this array does not need to be normalized as this is often impossible using floating point arithmetic. + /// If any of the probabilities are negative returns false, or if the sum of parameters is 0.0; otherwise true + public static bool IsValidCumulativeDistribution(double[] cdf) + { + var last = 0.0; + for (int i = 0; i < cdf.Length; i++) + { + double t = cdf[i]; + if (t < 0.0 || double.IsNaN(t) || t < last) + { + return false; + } + + last = t; + } + + return last > 0.0; + } + + /// + /// Gets the probability mass vector (non-negative ratios) of the multinomial. + /// + /// Sometimes the normalized probability vector cannot be represented exactly in a floating point representation. + public double[] P + { + get { return (double[])_pmfNormalized.Clone(); } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get + { + // Mean = E[X] = Sum(x * p(x), x=0..N-1) + // where f(x) is the probability mass function, and N is the number of categories. + var sum = 0.0; + for (int i = 0; i < _pmfNormalized.Length; i++) + { + sum += i * _pmfNormalized[i]; + } + + return sum; + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + // Variance = E[(X-E[X])^2] = E[X^2] - (E[X])^2 = Sum(p(x) * (x - E[X])^2), x=0..N-1) + var m = Mean; + var sum = 0.0; + for (int i = 0; i < _pmfNormalized.Length; i++) + { + var r = i - m; + sum += r * r * _pmfNormalized[i]; + } + + return sum; + } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return -_pmfNormalized.Sum(p => p * Math.Log(p)); } + } + + /// + /// Gets the skewness of the distribution. + /// + /// Throws a . + public double Skewness + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return 0; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { return _pmfNormalized.Length - 1; } + } + + /// + /// Gets he mode of the distribution. + /// + /// Throws a . + public int Mode + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return InverseCumulativeDistribution(0.5); } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + if (k < 0) + { + return 0.0; + } + + if (k >= _pmfNormalized.Length) + { + return 0.0; + } + + return _pmfNormalized[k]; + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + if (k < 0) + { + return 0.0; + } + + if (k >= _pmfNormalized.Length) + { + return 0.0; + } + + return Math.Log(_pmfNormalized[k]); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + if (x < 0.0) + { + return 0.0; + } + + if (x >= _cdfUnnormalized.Length) + { + return 1.0; + } + + return _cdfUnnormalized[(int)Math.Floor(x)] / _cdfUnnormalized[_cdfUnnormalized.Length - 1]; + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. + /// + /// A real number between 0 and 1. + /// An integer between 0 and the size of the categorical (exclusive), that corresponds to the inverse CDF for the given probability. + public int InverseCumulativeDistribution(double probability) + { + if (probability < 0.0 || probability > 1.0 || double.IsNaN(probability)) + { + throw new ArgumentOutOfRangeException(nameof(probability)); + } + + var denormalizedProbability = probability * _cdfUnnormalized[_cdfUnnormalized.Length - 1]; + int idx = Array.BinarySearch(_cdfUnnormalized, denormalizedProbability); + if (idx < 0) + { + idx = ~idx; + } + + return idx; + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// the probability mass at location . + public static double PMF(double[] probabilityMass, int k) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (k < 0) + { + return 0.0; + } + + if (k >= probabilityMass.Length) + { + return 0.0; + } + + return probabilityMass[k] / probabilityMass.Sum(); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// the log probability mass at location . + public static double PMFLn(double[] probabilityMass, int k) + { + return Math.Log(PMF(probabilityMass, k)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// the cumulative distribution at location . + /// + public static double CDF(double[] probabilityMass, double x) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0) + { + return 0.0; + } + + if (x >= probabilityMass.Length) + { + return 1.0; + } + + var cdfUnnormalized = ProbabilityMassToCumulativeDistribution(probabilityMass); + return cdfUnnormalized[(int)Math.Floor(x)] / cdfUnnormalized[cdfUnnormalized.Length - 1]; + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. + /// + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// A real number between 0 and 1. + /// An integer between 0 and the size of the categorical (exclusive), that corresponds to the inverse CDF for the given probability. + public static int InvCDF(double[] probabilityMass, double probability) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (probability < 0.0 || probability > 1.0 || double.IsNaN(probability)) + { + throw new ArgumentOutOfRangeException(nameof(probability)); + } + + var cdfUnnormalized = ProbabilityMassToCumulativeDistribution(probabilityMass); + var denormalizedProbability = probability * cdfUnnormalized[cdfUnnormalized.Length - 1]; + int idx = Array.BinarySearch(cdfUnnormalized, denormalizedProbability); + if (idx < 0) + { + idx = ~idx; + } + + return idx; + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. + /// + /// An array corresponding to a CDF for a categorical distribution. Not assumed to be normalized. + /// A real number between 0 and 1. + /// An integer between 0 and the size of the categorical (exclusive), that corresponds to the inverse CDF for the given probability. + public static int InvCDFWithCumulativeDistribution(double[] cdfUnnormalized, double probability) + { + if (Control.CheckDistributionParameters && !IsValidCumulativeDistribution(cdfUnnormalized)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (probability < 0.0 || probability > 1.0 || double.IsNaN(probability)) + { + throw new ArgumentOutOfRangeException(nameof(probability)); + } + + var denormalizedProbability = probability * cdfUnnormalized[cdfUnnormalized.Length - 1]; + int idx = Array.BinarySearch(cdfUnnormalized, denormalizedProbability); + if (idx < 0) + { + idx = ~idx; + } + + return idx; + } + + /// + /// Computes the cumulative distribution function. This method performs no parameter checking. + /// If the probability mass was normalized, the resulting cumulative distribution is normalized as well (up to numerical errors). + /// + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// An array representing the unnormalized cumulative distribution function. + internal static double[] ProbabilityMassToCumulativeDistribution(double[] probabilityMass) + { + var cdfUnnormalized = new double[probabilityMass.Length]; + cdfUnnormalized[0] = probabilityMass[0]; + for (int i = 1; i < probabilityMass.Length; i++) + { + cdfUnnormalized[i] = cdfUnnormalized[i - 1] + probabilityMass[i]; + } + + return cdfUnnormalized; + } + + /// + /// Returns one trials from the categorical distribution. + /// + /// The random number generator to use. + /// The (unnormalized) cumulative distribution of the probability distribution. + /// One sample from the categorical distribution implied by . + internal static int SampleUnchecked(System.Random rnd, double[] cdfUnnormalized) + { + // TODO : use binary search to speed up this procedure. + double u = rnd.NextDouble() * cdfUnnormalized[cdfUnnormalized.Length - 1]; + var idx = 0; + + if (u == 0.0d) + { + // skip zero-probability categories + while (0.0d == cdfUnnormalized[idx]) + { + idx++; + } + } + + while (u > cdfUnnormalized[idx]) + { + idx++; + } + + return idx; + } + + static void SamplesUnchecked(System.Random rnd, int[] values, double[] cdfUnnormalized) + { + // TODO : use binary search to speed up this procedure. + double[] uniform = rnd.NextDoubles(values.Length); + double w = cdfUnnormalized[cdfUnnormalized.Length - 1]; + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + var u = uniform[i] * w; + var idx = 0; + + if (u == 0.0d) + { + // skip zero-probability categories + while (0.0d == cdfUnnormalized[idx]) + { + idx++; + } + } + + while (u > cdfUnnormalized[idx]) + { + idx++; + } + + values[i] = idx; + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double[] cdfUnnormalized) + { + while (true) + { + yield return SampleUnchecked(rnd, cdfUnnormalized); + } + } + + /// + /// Samples a Binomially distributed random variable. + /// + /// The number of successful trials. + public int Sample() + { + return SampleUnchecked(_random, _cdfUnnormalized); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _cdfUnnormalized); + } + + /// + /// Samples an array of Bernoulli distributed random variables. + /// + /// a sequence of successful trial counts. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _cdfUnnormalized); + } + + /// + /// Samples one categorical distributed random variable; also known as the Discrete distribution. + /// + /// The random number generator to use. + /// An array of nonnegative ratios. Not assumed to be normalized. + /// One random integer between 0 and the size of the categorical (exclusive). + public static int Sample(System.Random rnd, double[] probabilityMass) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var cdf = ProbabilityMassToCumulativeDistribution(probabilityMass); + return SampleUnchecked(rnd, cdf); + } + + /// + /// Samples a categorically distributed random variable. + /// + /// The random number generator to use. + /// An array of nonnegative ratios. Not assumed to be normalized. + /// random integers between 0 and the size of the categorical (exclusive). + public static IEnumerable Samples(System.Random rnd, double[] probabilityMass) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var cdf = ProbabilityMassToCumulativeDistribution(probabilityMass); + return SamplesUnchecked(rnd, cdf); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// An array of nonnegative ratios. Not assumed to be normalized. + /// random integers between 0 and the size of the categorical (exclusive). + public static void Samples(System.Random rnd, int[] values, double[] probabilityMass) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var cdf = ProbabilityMassToCumulativeDistribution(probabilityMass); + SamplesUnchecked(rnd, values, cdf); + } + + /// + /// Samples one categorical distributed random variable; also known as the Discrete distribution. + /// + /// An array of nonnegative ratios. Not assumed to be normalized. + /// One random integer between 0 and the size of the categorical (exclusive). + public static int Sample(double[] probabilityMass) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var cdf = ProbabilityMassToCumulativeDistribution(probabilityMass); + return SampleUnchecked(SystemRandomSource.Default, cdf); + } + + /// + /// Samples a categorically distributed random variable. + /// + /// An array of nonnegative ratios. Not assumed to be normalized. + /// random integers between 0 and the size of the categorical (exclusive). + public static IEnumerable Samples(double[] probabilityMass) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var cdf = ProbabilityMassToCumulativeDistribution(probabilityMass); + return SamplesUnchecked(SystemRandomSource.Default, cdf); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// An array of nonnegative ratios. Not assumed to be normalized. + /// random integers between 0 and the size of the categorical (exclusive). + public static void Samples(int[] values, double[] probabilityMass) + { + if (Control.CheckDistributionParameters && !IsValidProbabilityMass(probabilityMass)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var cdf = ProbabilityMassToCumulativeDistribution(probabilityMass); + SamplesUnchecked(SystemRandomSource.Default, values, cdf); + } + + /// + /// Samples one categorical distributed random variable; also known as the Discrete distribution. + /// + /// The random number generator to use. + /// An array of the cumulative distribution. Not assumed to be normalized. + /// One random integer between 0 and the size of the categorical (exclusive). + public static int SampleWithCumulativeDistribution(System.Random rnd, double[] cdfUnnormalized) + { + if (Control.CheckDistributionParameters && !IsValidCumulativeDistribution(cdfUnnormalized)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, cdfUnnormalized); + } + + /// + /// Samples a categorically distributed random variable. + /// + /// The random number generator to use. + /// An array of the cumulative distribution. Not assumed to be normalized. + /// random integers between 0 and the size of the categorical (exclusive). + public static IEnumerable SamplesWithCumulativeDistribution(System.Random rnd, double[] cdfUnnormalized) + { + if (Control.CheckDistributionParameters && !IsValidCumulativeDistribution(cdfUnnormalized)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, cdfUnnormalized); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// An array of the cumulative distribution. Not assumed to be normalized. + /// random integers between 0 and the size of the categorical (exclusive). + public static void SamplesWithCumulativeDistribution(System.Random rnd, int[] values, double[] cdfUnnormalized) + { + if (Control.CheckDistributionParameters && !IsValidCumulativeDistribution(cdfUnnormalized)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, cdfUnnormalized); + } + + /// + /// Samples one categorical distributed random variable; also known as the Discrete distribution. + /// + /// An array of the cumulative distribution. Not assumed to be normalized. + /// One random integer between 0 and the size of the categorical (exclusive). + public static int SampleWithCumulativeDistribution(double[] cdfUnnormalized) + { + if (Control.CheckDistributionParameters && !IsValidCumulativeDistribution(cdfUnnormalized)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, cdfUnnormalized); + } + + /// + /// Samples a categorically distributed random variable. + /// + /// An array of the cumulative distribution. Not assumed to be normalized. + /// random integers between 0 and the size of the categorical (exclusive). + public static IEnumerable SamplesWithCumulativeDistribution(double[] cdfUnnormalized) + { + if (Control.CheckDistributionParameters && !IsValidCumulativeDistribution(cdfUnnormalized)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, cdfUnnormalized); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// An array of the cumulative distribution. Not assumed to be normalized. + /// random integers between 0 and the size of the categorical (exclusive). + public static void SamplesWithCumulativeDistribution(int[] values, double[] cdfUnnormalized) + { + if (Control.CheckDistributionParameters && !IsValidCumulativeDistribution(cdfUnnormalized)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, cdfUnnormalized); + } +} diff --git a/MathNet.Numerics/Distributions/Cauchy.cs b/MathNet.Numerics/Distributions/Cauchy.cs new file mode 100644 index 0000000..d05623d --- /dev/null +++ b/MathNet.Numerics/Distributions/Cauchy.cs @@ -0,0 +1,480 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Cauchy distribution. +/// The Cauchy distribution is a symmetric continuous probability distribution. For details about this distribution, see +/// Wikipedia - Cauchy distribution. +/// +public class Cauchy : IContinuousDistribution +{ + System.Random _random; + + readonly double _location; + readonly double _scale; + + /// + /// Initializes a new instance of the class with the location parameter set to 0 and the scale parameter set to 1 + /// + public Cauchy() : this(0, 1) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + public Cauchy(double location, double scale) + { + if (!IsValidParameterSet(location, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _location = location; + _scale = scale; + } + + /// + /// Initializes a new instance of the class. + /// + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// The random number generator which is used to draw random samples. + public Cauchy(double location, double scale, System.Random randomSource) + { + if (!IsValidParameterSet(location, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _location = location; + _scale = scale; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Cauchy(x0 = " + _location + ", γ = " + _scale + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + public static bool IsValidParameterSet(double location, double scale) + { + return scale > 0.0 && !double.IsNaN(location); + } + + /// + /// Gets the location (x0) of the distribution. + /// + public double Location + { + get { return _location; } + } + + /// + /// Gets the scale (γ) of the distribution. Range: γ > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return Math.Log(4.0 * Constants.Pi * _scale); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get { return _location; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return _location; } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return double.NegativeInfinity; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return 1.0 / (Constants.Pi * _scale * (1.0 + (((x - _location) / _scale) * ((x - _location) / _scale)))); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return -Math.Log(Constants.Pi * _scale * (1.0 + (((x - _location) / _scale) * ((x - _location) / _scale)))); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return ((1.0 / Constants.Pi) * Math.Atan((x - _location) / _scale)) + 0.5; + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return p <= 0.0 ? double.NegativeInfinity : p >= 1.0 ? double.PositiveInfinity + : _location + _scale * Math.Tan((p - 0.5) * Constants.Pi); + } + + /// + /// Draws a random sample from the distribution. + /// + /// A random number from this distribution. + public double Sample() + { + return SampleUnchecked(_random, _location, _scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _location, _scale); + } + + /// + /// Generates a sequence of samples from the Cauchy distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _location, _scale); + } + + static double SampleUnchecked(System.Random rnd, double location, double scale) + { + return location + scale * Math.Tan(Constants.Pi * (rnd.NextDouble() - 0.5)); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double location, double scale) + { + rnd.NextDoubles(values); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = location + scale * Math.Tan(Constants.Pi * (values[i] - 0.5)); + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double location, double scale) + { + while (true) + { + yield return location + scale * Math.Tan(Constants.Pi * (rnd.NextDouble() - 0.5)); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double location, double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return 1.0 / (Constants.Pi * scale * (1.0 + (((x - location) / scale) * ((x - location) / scale)))); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double location, double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return -Math.Log(Constants.Pi * scale * (1.0 + (((x - location) / scale) * ((x - location) / scale)))); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double location, double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Math.Atan((x - location) / scale) / Constants.Pi + 0.5; + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// the inverse cumulative density at . + /// + public static double InvCDF(double location, double scale, double p) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return p <= 0.0 ? double.NegativeInfinity : p >= 1.0 ? double.PositiveInfinity + : location + scale * Math.Tan((p - 0.5) * Constants.Pi); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, location, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, location, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, location, scale); + } + + /// + /// Generates a sample from the distribution. + /// + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// a sample from the distribution. + public static double Sample(double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, location, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, location, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The location (x0) of the distribution. + /// The scale (γ) of the distribution. Range: γ > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, location, scale); + } +} diff --git a/MathNet.Numerics/Distributions/Chi.cs b/MathNet.Numerics/Distributions/Chi.cs new file mode 100644 index 0000000..d7a4219 --- /dev/null +++ b/MathNet.Numerics/Distributions/Chi.cs @@ -0,0 +1,476 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Chi distribution. +/// This distribution is a continuous probability distribution. The distribution usually arises when a k-dimensional vector's orthogonal +/// components are independent and each follow a standard normal distribution. The length of the vector will +/// then have a chi distribution. +/// Wikipedia - Chi distribution. +/// +public class Chi : IContinuousDistribution +{ + System.Random _random; + + readonly double _freedom; + + /// + /// Initializes a new instance of the class. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + public Chi(double freedom) + { + if (!IsValidParameterSet(freedom)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _freedom = freedom; + } + + /// + /// Initializes a new instance of the class. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// The random number generator which is used to draw random samples. + public Chi(double freedom, System.Random randomSource) + { + if (!IsValidParameterSet(freedom)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _freedom = freedom; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Chi(k = " + _freedom + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + public static bool IsValidParameterSet(double freedom) + { + return freedom > 0.0; + } + + /// + /// Gets the degrees of freedom (k) of the Chi distribution. Range: k > 0. + /// + public double DegreesOfFreedom + { + get { return _freedom; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return Constants.Sqrt2 * (SpecialFunctions.Gamma((_freedom + 1.0) / 2.0) / SpecialFunctions.Gamma(_freedom / 2.0)); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return _freedom - (Mean * Mean); } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return SpecialFunctions.GammaLn(_freedom / 2.0) + ((_freedom - Math.Log(2) - ((_freedom - 1.0) * SpecialFunctions.DiGamma(_freedom / 2.0))) / 2.0); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get + { + var sigma = StdDev; + return (Mean * (1.0 - (2.0 * (sigma * sigma)))) / (sigma * sigma * sigma); + } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get + { + if (_freedom < 1) + { + throw new NotSupportedException(); + } + + return Math.Sqrt(_freedom - 1.0); + } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return PDF(_freedom, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return PDFLn(_freedom, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CDF(_freedom, x); + } + + /// + /// Generates a sample from the Chi distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, (int)_freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, (int)_freedom); + } + + /// + /// Generates a sequence of samples from the Chi distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, (int)_freedom); + } + + /// + /// Samples the distribution. + /// + /// The random number generator to use. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a random number from the distribution. + static double SampleUnchecked(System.Random rnd, int freedom) + { + double sum = 0; + for (var i = 0; i < freedom; i++) + { + sum += Math.Pow(Normal.Sample(rnd, 0.0, 1.0), 2); + } + + return Math.Sqrt(sum); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, int freedom) + { + var standard = new double[values.Length * freedom]; + Normal.SamplesUnchecked(rnd, standard, 0.0, 1.0); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + int k = i * freedom; + double sum = 0; + for (int j = 0; j < freedom; j++) + { + sum += standard[k + j] * standard[k + j]; + } + + values[i] = Math.Sqrt(sum); + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, int freedom) + { + while (true) + { + yield return SampleUnchecked(rnd, freedom); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double freedom, double x) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(freedom) || double.IsPositiveInfinity(x) || x == 0.0) + { + return 0.0; + } + + if (freedom > 160.0) + { + return Math.Exp(PDFLn(freedom, x)); + } + + return (Math.Pow(2.0, 1.0 - (freedom / 2.0)) * Math.Pow(x, freedom - 1.0) * Math.Exp(-x * x / 2.0)) / SpecialFunctions.Gamma(freedom / 2.0); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double freedom, double x) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(freedom) || double.IsPositiveInfinity(x) || x == 0.0) + { + return double.NegativeInfinity; + } + + return ((1.0 - (freedom / 2.0)) * Math.Log(2.0)) + ((freedom - 1.0) * Math.Log(x)) - (x * x / 2.0) - SpecialFunctions.GammaLn(freedom / 2.0); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double freedom, double x) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(x)) + { + return 1.0; + } + + if (double.IsPositiveInfinity(freedom)) + { + return 1.0; + } + + return SpecialFunctions.GammaLowerRegularized(freedom / 2.0, x * x / 2.0); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, int freedom) + { + if (freedom <= 0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, freedom); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, int freedom) + { + if (freedom <= 0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, int freedom) + { + if (freedom <= 0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, freedom); + } + + /// + /// Generates a sample from the distribution. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sample from the distribution. + public static double Sample(int freedom) + { + if (freedom <= 0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, freedom); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(int freedom) + { + if (freedom <= 0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, int freedom) + { + if (freedom <= 0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, freedom); + } +} diff --git a/MathNet.Numerics/Distributions/ChiSquared.cs b/MathNet.Numerics/Distributions/ChiSquared.cs new file mode 100644 index 0000000..d8eb6e6 --- /dev/null +++ b/MathNet.Numerics/Distributions/ChiSquared.cs @@ -0,0 +1,510 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Chi-Squared distribution. +/// This distribution is a sum of the squares of k independent standard normal random variables. +/// Wikipedia - ChiSquare distribution. +/// +public class ChiSquared : IContinuousDistribution +{ + System.Random _random; + + readonly double _freedom; + + /// + /// Initializes a new instance of the class. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + public ChiSquared(double freedom) + { + if (!IsValidParameterSet(freedom)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _freedom = freedom; + } + + /// + /// Initializes a new instance of the class. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// The random number generator which is used to draw random samples. + public ChiSquared(double freedom, System.Random randomSource) + { + if (!IsValidParameterSet(freedom)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _freedom = freedom; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "ChiSquared(k = " + _freedom + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + public static bool IsValidParameterSet(double freedom) + { + return freedom > 0.0; + } + + /// + /// Gets the degrees of freedom (k) of the Chi-Squared distribution. Range: k > 0. + /// + public double DegreesOfFreedom + { + get { return _freedom; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return _freedom; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return 2.0 * _freedom; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(2.0 * _freedom); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return (_freedom / 2.0) + Math.Log(2.0 * SpecialFunctions.Gamma(_freedom / 2.0)) + ((1.0 - (_freedom / 2.0)) * SpecialFunctions.DiGamma(_freedom / 2.0)); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return Math.Sqrt(8.0 / _freedom); } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get { return _freedom - 2.0; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return _freedom - (2.0 / 3.0); } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return PDF(_freedom, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return PDFLn(_freedom, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CDF(_freedom, x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return InvCDF(_freedom, p); + } + + /// + /// Generates a sample from the ChiSquare distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _freedom); + } + + /// + /// Generates a sequence of samples from the ChiSquare distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _freedom); + } + + /// + /// Samples the distribution. + /// + /// The random number generator to use. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a random number from the distribution. + static double SampleUnchecked(System.Random rnd, double freedom) + { + // Use the simple method if the degrees of freedom is an integer anyway + if (Math.Floor(freedom) == freedom && freedom < int.MaxValue) + { + double sum = 0; + var n = (int)freedom; + for (var i = 0; i < n; i++) + { + sum += Math.Pow(Normal.Sample(rnd, 0.0, 1.0), 2); + } + + return sum; + } + + // Call the gamma function (see http://en.wikipedia.org/wiki/Gamma_distribution#Specializations + // for a justification) + return Gamma.SampleUnchecked(rnd, freedom / 2.0, .5); + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double freedom) + { + // Use the simple method if the degrees of freedom is an integer anyway + if (Math.Floor(freedom) == freedom && freedom < int.MaxValue) + { + var n = (int)freedom; + var standard = new double[values.Length * n]; + Normal.SamplesUnchecked(rnd, standard, 0.0, 1.0); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + int k = i * n; + double sum = 0; + for (int j = 0; j < n; j++) + { + sum += standard[k + j] * standard[k + j]; + } + + values[i] = sum; + } + }); + return; + } + + // Call the gamma function (see http://en.wikipedia.org/wiki/Gamma_distribution#Specializations + // for a justification) + Gamma.SamplesUnchecked(rnd, values, freedom / 2.0, .5); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double freedom) + { + while (true) + { + yield return SampleUnchecked(rnd, freedom); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double freedom, double x) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(freedom) || double.IsPositiveInfinity(x) || x == 0.0) + { + return 0.0; + } + + if (freedom > 160.0) + { + return Math.Exp(PDFLn(freedom, x)); + } + + return (Math.Pow(x, (freedom / 2.0) - 1.0) * Math.Exp(-x / 2.0)) / (Math.Pow(2.0, freedom / 2.0) * SpecialFunctions.Gamma(freedom / 2.0)); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double freedom, double x) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(freedom) || double.IsPositiveInfinity(x) || x == 0.0) + { + return double.NegativeInfinity; + } + + return (-x / 2.0) + (((freedom / 2.0) - 1.0) * Math.Log(x)) - ((freedom / 2.0) * Math.Log(2)) - SpecialFunctions.GammaLn(freedom / 2.0); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double freedom, double x) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(x)) + { + return 1.0; + } + + if (double.IsPositiveInfinity(freedom)) + { + return 1.0; + } + + return SpecialFunctions.GammaLowerRegularized(freedom / 2.0, x / 2.0); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + public static double InvCDF(double freedom, double p) + { + if (!IsValidParameterSet(freedom)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SpecialFunctions.GammaLowerRegularizedInv(freedom / 2.0, p) / 0.5; + } + + /// + /// Generates a sample from the ChiSquare distribution. + /// + /// The random number generator to use. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double freedom) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, freedom); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sample from the distribution. + public static IEnumerable Samples(System.Random rnd, double freedom) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sample from the distribution. + public static void Samples(System.Random rnd, double[] values, double freedom) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, freedom); + } + + /// + /// Generates a sample from the ChiSquare distribution. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sample from the distribution. + public static double Sample(double freedom) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, freedom); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sample from the distribution. + public static IEnumerable Samples(double freedom) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The degrees of freedom (k) of the distribution. Range: k > 0. + /// a sample from the distribution. + public static void Samples(double[] values, double freedom) + { + if (freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, freedom); + } +} diff --git a/MathNet.Numerics/Distributions/ContinuousUniform.cs b/MathNet.Numerics/Distributions/ContinuousUniform.cs new file mode 100644 index 0000000..e4a1785 --- /dev/null +++ b/MathNet.Numerics/Distributions/ContinuousUniform.cs @@ -0,0 +1,485 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Uniform distribution. +/// The continuous uniform distribution is a distribution over real numbers. For details about this distribution, see +/// Wikipedia - Continuous uniform distribution. +/// +public class ContinuousUniform : IContinuousDistribution +{ + System.Random _random; + + readonly double _lower; + readonly double _upper; + + /// + /// Initializes a new instance of the ContinuousUniform class with lower bound 0 and upper bound 1. + /// + public ContinuousUniform() : this(0.0, 1.0) + { + } + + /// + /// Initializes a new instance of the ContinuousUniform class with given lower and upper bounds. + /// + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// If the upper bound is smaller than the lower bound. + public ContinuousUniform(double lower, double upper) + { + if (!IsValidParameterSet(lower, upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _lower = lower; + _upper = upper; + } + + /// + /// Initializes a new instance of the ContinuousUniform class with given lower and upper bounds. + /// + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// The random number generator which is used to draw random samples. + /// If the upper bound is smaller than the lower bound. + public ContinuousUniform(double lower, double upper, System.Random randomSource) + { + if (!IsValidParameterSet(lower, upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _lower = lower; + _upper = upper; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "ContinuousUniform(Lower = " + _lower + ", Upper = " + _upper + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + public static bool IsValidParameterSet(double lower, double upper) + { + return lower <= upper; + } + + /// + /// Gets the lower bound of the distribution. + /// + public double LowerBound + { + get { return _lower; } + } + + /// + /// Gets the upper bound of the distribution. + /// + public double UpperBound + { + get { return _upper; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return (_lower + _upper) / 2.0; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return (_upper - _lower) * (_upper - _lower) / 12.0; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return (_upper - _lower) / Math.Sqrt(12.0); } + } + + /// + /// Gets the entropy of the distribution. + /// + /// + public double Entropy + { + get { return Math.Log(_upper - _lower); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return 0.0; } + } + + /// + /// Gets the mode of the distribution. + /// + /// + public double Mode + { + get { return (_lower + _upper) / 2.0; } + } + + /// + /// Gets the median of the distribution. + /// + /// + public double Median + { + get { return (_lower + _upper) / 2.0; } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return _lower; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return _upper; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return x < _lower || x > _upper ? 0.0 : 1.0 / (_upper - _lower); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return x < _lower || x > _upper ? double.NegativeInfinity : -Math.Log(_upper - _lower); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return x <= _lower ? 0.0 : x >= _upper ? 1.0 : (x - _lower) / (_upper - _lower); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return p <= 0.0 ? _lower : p >= 1.0 ? _upper : _lower * (1.0 - p) + _upper * p; + } + + /// + /// Generates a sample from the ContinuousUniform distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _lower, _upper); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _lower, _upper); + } + + /// + /// Generates a sequence of samples from the ContinuousUniform distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _lower, _upper); + } + + static double SampleUnchecked(System.Random rnd, double lower, double upper) + { + return lower + rnd.NextDouble() * (upper - lower); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double lower, double upper) + { + double difference = upper - lower; + while (true) + { + yield return lower + rnd.NextDouble() * difference; + } + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double lower, double upper) + { + rnd.NextDoubles(values); + var difference = upper - lower; + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = lower + values[i] * difference; + } + }); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double lower, double upper, double x) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return x < lower || x > upper ? 0.0 : 1.0 / (upper - lower); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double lower, double upper, double x) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return x < lower || x > upper ? double.NegativeInfinity : -Math.Log(upper - lower); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// the cumulative distribution at location . + /// + public static double CDF(double lower, double upper, double x) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return x <= lower ? 0.0 : x >= upper ? 1.0 : (x - lower) / (upper - lower); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// the inverse cumulative density at . + /// + public static double InvCDF(double lower, double upper, double p) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return p <= 0.0 ? lower : p >= 1.0 ? upper : lower * (1.0 - p) + upper * p; + } + + /// + /// Generates a sample from the ContinuousUniform distribution. + /// + /// The random number generator to use. + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// a uniformly distributed sample. + public static double Sample(System.Random rnd, double lower, double upper) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, lower, upper); + } + + /// + /// Generates a sequence of samples from the ContinuousUniform distribution. + /// + /// The random number generator to use. + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// a sequence of uniformly distributed samples. + public static IEnumerable Samples(System.Random rnd, double lower, double upper) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, lower, upper); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double lower, double upper) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, lower, upper); + } + + /// + /// Generates a sample from the ContinuousUniform distribution. + /// + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// a uniformly distributed sample. + public static double Sample(double lower, double upper) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, lower, upper); + } + + /// + /// Generates a sequence of samples from the ContinuousUniform distribution. + /// + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// a sequence of uniformly distributed samples. + public static IEnumerable Samples(double lower, double upper) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, lower, upper); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// Lower bound. Range: lower ≤ upper. + /// Upper bound. Range: lower ≤ upper. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double lower, double upper) + { + if (upper < lower) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, lower, upper); + } +} diff --git a/MathNet.Numerics/Distributions/ConwayMaxwellPoisson.cs b/MathNet.Numerics/Distributions/ConwayMaxwellPoisson.cs new file mode 100644 index 0000000..08382b8 --- /dev/null +++ b/MathNet.Numerics/Distributions/ConwayMaxwellPoisson.cs @@ -0,0 +1,679 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Conway-Maxwell-Poisson distribution. +/// The Conway-Maxwell-Poisson distribution is a generalization of the Poisson, Geometric and Bernoulli +/// distributions. It is parameterized by two real numbers "lambda" and "nu". For +/// +/// nu = 0 the distribution reverts to a Geometric distribution +/// nu = 1 the distribution reverts to the Poisson distribution +/// nu -> infinity the distribution converges to a Bernoulli distribution +/// +/// This implementation will cache the value of the normalization constant. +/// Wikipedia - ConwayMaxwellPoisson distribution. +/// +public class ConwayMaxwellPoisson : IDiscreteDistribution +{ + System.Random _random; + + readonly double _lambda; + readonly double _nu; + + /// + /// The mean of the distribution. + /// + double _mean = double.MinValue; + + /// + /// The variance of the distribution. + /// + double _variance = double.MinValue; + + /// + /// Caches the value of the normalization constant. + /// + double _z = double.MinValue; + + /// + /// Since many properties of the distribution can only be computed approximately, the tolerance + /// level specifies how much error we accept. + /// + const double Tolerance = 1e-12; + + /// + /// Initializes a new instance of the class. + /// + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + public ConwayMaxwellPoisson(double lambda, double nu) + { + if (!IsValidParameterSet(lambda, nu)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _lambda = lambda; + _nu = nu; + } + + /// + /// Initializes a new instance of the class. + /// + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + /// The random number generator which is used to draw random samples. + public ConwayMaxwellPoisson(double lambda, double nu, System.Random randomSource) + { + if (!IsValidParameterSet(lambda, nu)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _lambda = lambda; + _nu = nu; + } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return "ConwayMaxwellPoisson(λ = " + _lambda + ", ν = " + _nu + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + public static bool IsValidParameterSet(double lambda, double nu) + { + return lambda > 0.0 && nu >= 0.0; + } + + /// + /// Gets the lambda (λ) parameter. Range: λ > 0. + /// + public double Lambda + { + get { return _lambda; } + } + + /// + /// Gets the rate of decay (ν) parameter. Range: ν ≥ 0. + /// + public double Nu + { + get { return _nu; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get + { + // Special case requiring no computation. + if (_lambda == 0) + { + return 0.0; + } + + if (_mean != double.MinValue) + { + return _mean; + } + + // The normalization constant for the distribution. + var z = 1 + _lambda; + + // The probability of the next term. + var a1 = _lambda * _lambda / Math.Pow(2, _nu); + + // The unnormalized mean. + var zx = _lambda; + + // The contribution of the next term to the mean. + var ax1 = 2 * a1; + + for (var i = 3; i < 1000; i++) + { + var e = _lambda / Math.Pow(i, _nu); + var ex = _lambda / Math.Pow(i, _nu - 1) / (i - 1); + var a2 = a1 * e; + var ax2 = ax1 * ex; + + if ((ax2 < ax1) && (a2 < a1)) + { + var m = zx / z; + var upper = (zx + (ax1 / (1 - (ax2 / ax1)))) / z; + var lower = zx / (z + (a1 / (1 - (a2 / a1)))); + + var r = (upper - lower) / m; + if (r < Tolerance) + { + break; + } + } + + z = z + a1; + zx = zx + ax1; + a1 = a2; + ax1 = ax2; + } + + _mean = zx / z; + return _mean; + } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + // Special case requiring no computation. + if (_lambda == 0) + { + return 0.0; + } + + if (_variance != double.MinValue) + { + return _variance; + } + + // The normalization constant for the distribution. + var z = 1 + _lambda; + + // The probability of the next term. + var a1 = _lambda * _lambda / Math.Pow(2, _nu); + + // The unnormalized second moment. + var zxx = _lambda; + + // The contribution of the next term to the second moment. + var axx1 = 4 * a1; + + for (var i = 3; i < 1000; i++) + { + var e = _lambda / Math.Pow(i, _nu); + var exx = _lambda / Math.Pow(i, _nu - 2) / (i - 1) / (i - 1); + var a2 = a1 * e; + var axx2 = axx1 * exx; + + if ((axx2 < axx1) && (a2 < a1)) + { + var m = zxx / z; + var upper = (zxx + (axx1 / (1 - (axx2 / axx1)))) / z; + var lower = zxx / (z + (a1 / (1 - (a2 / a1)))); + + var r = (upper - lower) / m; + if (r < Tolerance) + { + break; + } + } + + z = z + a1; + zxx = zxx + axx1; + a1 = a2; + axx1 = axx2; + } + + var mean = Mean; + _variance = (zxx / z) - (mean * mean); + return _variance; + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the mode of the distribution + /// + public int Mode + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return 0; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { throw new NotSupportedException(); } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + return Math.Pow(_lambda, k) / Math.Pow(SpecialFunctions.Factorial(k), _nu) / Z; + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + return k * Math.Log(_lambda) - _nu * SpecialFunctions.FactorialLn(k) - Math.Log(Z); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + var z = Z; + double sum = 0; + for (var i = 0; i < x + 1; i++) + { + sum += Math.Pow(_lambda, i) / Math.Pow(SpecialFunctions.Factorial(i), _nu) / z; + } + + return sum; + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + /// the probability mass at location . + public static double PMF(double lambda, double nu, int k) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + return Math.Pow(lambda, k) / Math.Pow(SpecialFunctions.Factorial(k), nu) / z; + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + /// the log probability mass at location . + public static double PMFLn(double lambda, double nu, int k) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + return k * Math.Log(lambda) - nu * SpecialFunctions.FactorialLn(k) - Math.Log(z); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + /// the cumulative distribution at location . + /// + public static double CDF(double lambda, double nu, double x) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + double sum = 0; + for (var i = 0; i < x + 1; i++) + { + sum += Math.Pow(lambda, i) / Math.Pow(SpecialFunctions.Factorial(i), nu) / z; + } + + return sum; + } + + /// + /// Gets the normalization constant of the Conway-Maxwell-Poisson distribution. + /// + double Z + { + get + { + if (_z != double.MinValue) + { + return _z; + } + + _z = Normalization(_lambda, _nu); + return _z; + } + } + + /// + /// Computes an approximate normalization constant for the CMP distribution. + /// + /// The lambda (λ) parameter for the CMP distribution. + /// The rate of decay (ν) parameter for the CMP distribution. + /// + /// an approximate normalization constant for the CMP distribution. + /// + static double Normalization(double lambda, double nu) + { + // Initialize Z with the first two terms. + var z = 1.0 + lambda; + + // Remembers the last term added. + var t = lambda; + + // Start adding more terms until convergence. + for (var i = 2; i < 1000; i++) + { + // The new addition for term i. + var e = lambda / Math.Pow(i, nu); + + // The new term. + t = t * e; + + // The updated normalization constant. + z = z + t; + + // The stopping criterion. + if (e < 1) + { + if (t / (1 - e) / z < Tolerance) + { + break; + } + } + } + + return z; + } + + /// + /// Returns one trials from the distribution. + /// + /// The random number generator to use. + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + /// The z parameter. + /// + /// One sample from the distribution implied by , , and . + /// + static int SampleUnchecked(System.Random rnd, double lambda, double nu, double z) + { + var u = rnd.NextDouble(); + var p = 1.0 / z; + var cdf = p; + var i = 0; + + while (u > cdf) + { + i++; + p = p * lambda / Math.Pow(i, nu); + cdf += p; + } + + return i; + } + + static void SamplesUnchecked(System.Random rnd, int[] values, double lambda, double nu, double z) + { + var uniform = rnd.NextDoubles(values.Length); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + var u = uniform[i]; + var p = 1.0 / z; + var cdf = p; + var k = 0; + while (u > cdf) + { + k++; + p = p * lambda / Math.Pow(k, nu); + cdf += p; + } + + values[i] = k; + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double lambda, double nu, double z) + { + while (true) + { + yield return SampleUnchecked(rnd, lambda, nu, z); + } + } + + /// + /// Samples a Conway-Maxwell-Poisson distributed random variable. + /// + /// a sample from the distribution. + public int Sample() + { + return SampleUnchecked(_random, _lambda, _nu, Z); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _lambda, _nu, Z); + } + + /// + /// Samples a sequence of a Conway-Maxwell-Poisson distributed random variables. + /// + /// + /// a sequence of samples from a Conway-Maxwell-Poisson distribution. + /// + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _lambda, _nu, Z); + } + + /// + /// Samples a random variable. + /// + /// The random number generator to use. + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + public static int Sample(System.Random rnd, double lambda, double nu) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + return SampleUnchecked(rnd, lambda, nu, z); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The random number generator to use. + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + public static IEnumerable Samples(System.Random rnd, double lambda, double nu) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + return SamplesUnchecked(rnd, lambda, nu, z); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + public static void Samples(System.Random rnd, int[] values, double lambda, double nu) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + SamplesUnchecked(rnd, values, lambda, nu, z); + } + + /// + /// Samples a random variable. + /// + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + public static int Sample(double lambda, double nu) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + return SampleUnchecked(SystemRandomSource.Default, lambda, nu, z); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + public static IEnumerable Samples(double lambda, double nu) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + return SamplesUnchecked(SystemRandomSource.Default, lambda, nu, z); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The lambda (λ) parameter. Range: λ > 0. + /// The rate of decay (ν) parameter. Range: ν ≥ 0. + public static void Samples(int[] values, double lambda, double nu) + { + if (!(lambda > 0.0 && nu >= 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var z = Normalization(lambda, nu); + SamplesUnchecked(SystemRandomSource.Default, values, lambda, nu, z); + } +} diff --git a/MathNet.Numerics/Distributions/Dirichlet.cs b/MathNet.Numerics/Distributions/Dirichlet.cs new file mode 100644 index 0000000..4b71a8f --- /dev/null +++ b/MathNet.Numerics/Distributions/Dirichlet.cs @@ -0,0 +1,357 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2011 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Multivariate Dirichlet distribution. For details about this distribution, see +/// Wikipedia - Dirichlet distribution. +/// +public class Dirichlet : IDistribution +{ + System.Random _random; + + readonly double[] _alpha; + + /// + /// Initializes a new instance of the Dirichlet class. The distribution will + /// be initialized with the default random number generator. + /// + /// An array with the Dirichlet parameters. + public Dirichlet(double[] alpha) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(alpha)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _alpha = (double[])alpha.Clone(); + } + + /// + /// Initializes a new instance of the Dirichlet class. The distribution will + /// be initialized with the default random number generator. + /// + /// An array with the Dirichlet parameters. + /// The random number generator which is used to draw random samples. + public Dirichlet(double[] alpha, System.Random randomSource) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(alpha)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _alpha = (double[])alpha.Clone(); + } + + /// + /// Initializes a new instance of the class. + /// random number generator. + /// The value of each parameter of the Dirichlet distribution. + /// The dimension of the Dirichlet distribution. + public Dirichlet(double alpha, int k) + { + // Create a parameter structure. + var parm = new double[k]; + for (var i = 0; i < k; i++) + { + parm[i] = alpha; + } + + _random = SystemRandomSource.Default; + if (Control.CheckDistributionParameters && !IsValidParameterSet(parm)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _alpha = (double[])parm.Clone(); + } + + /// + /// Initializes a new instance of the class. + /// random number generator. + /// The value of each parameter of the Dirichlet distribution. + /// The dimension of the Dirichlet distribution. + /// The random number generator which is used to draw random samples. + public Dirichlet(double alpha, int k, System.Random randomSource) + { + // Create a parameter structure. + var parm = new double[k]; + for (var i = 0; i < k; i++) + { + parm[i] = alpha; + } + + _random = randomSource ?? SystemRandomSource.Default; + if (Control.CheckDistributionParameters && !IsValidParameterSet(parm)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _alpha = (double[])parm.Clone(); + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return "Dirichlet(Dimension = " + Dimension + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// No parameter can be less than zero and at least one parameter should be larger than zero. + /// + /// The parameters of the Dirichlet distribution. + public static bool IsValidParameterSet(double[] alpha) + { + var allzero = true; + + for (int i = 0; i < alpha.Length; i++) + { + var t = alpha[i]; + if (t < 0.0) + { + return false; + } + + if (t > 0.0) + { + allzero = false; + } + } + + return !allzero; + } + + /// + /// Gets or sets the parameters of the Dirichlet distribution. + /// + public double[] Alpha + { + get { return _alpha; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the dimension of the Dirichlet distribution. + /// + public int Dimension + { + get { return _alpha.Length; } + } + + /// + /// Gets the sum of the Dirichlet parameters. + /// + double AlphaSum + { + get { return _alpha.Sum(); } + } + + /// + /// Gets the mean of the Dirichlet distribution. + /// + public double[] Mean + { + get + { + var sum = AlphaSum; + var parm = new double[Dimension]; + for (var i = 0; i < Dimension; i++) + { + parm[i] = _alpha[i] / sum; + } + + return parm; + } + } + + /// + /// Gets the variance of the Dirichlet distribution. + /// + public double[] Variance + { + get + { + var s = AlphaSum; + var v = new double[_alpha.Length]; + for (var i = 0; i < _alpha.Length; i++) + { + v[i] = _alpha[i] * (s - _alpha[i]) / (s * s * (s + 1.0)); + } + + return v; + } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get + { + var num = _alpha.Sum(t => (t - 1) * SpecialFunctions.DiGamma(t)); + return SpecialFunctions.GammaLn(AlphaSum) + ((AlphaSum - Dimension) * SpecialFunctions.DiGamma(AlphaSum)) - num; + } + } + + /// + /// Computes the density of the distribution. + /// + /// The locations at which to compute the density. + /// the density at . + /// The Dirichlet distribution requires that the sum of the components of x equals 1. + /// You can also leave out the last component, and it will be computed from the others. + public double Density(double[] x) + { + return Math.Exp(DensityLn(x)); + } + + /// + /// Computes the log density of the distribution. + /// + /// The locations at which to compute the density. + /// the density at . + public double DensityLn(double[] x) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + var shortVersion = x.Length == (_alpha.Length - 1); + if ((x.Length != _alpha.Length) && !shortVersion) + { + throw new ArgumentException("x"); + } + + var term = 0.0; + var sumxi = 0.0; + var sumalpha = 0.0; + for (var i = 0; i < x.Length; i++) + { + var xi = x[i]; + if ((xi <= 0.0) || (xi >= 1.0)) + { + return 0.0; + } + + term += (_alpha[i] - 1.0) * Math.Log(xi) - SpecialFunctions.GammaLn(_alpha[i]); + sumxi += xi; + sumalpha += _alpha[i]; + } + + // Calculate x[Length - 1] element, if needed + if (shortVersion) + { + if (sumxi >= 1.0) + { + return 0.0; + } + + term += (_alpha[_alpha.Length - 1] - 1.0) * Math.Log(1.0 - sumxi) - SpecialFunctions.GammaLn(_alpha[_alpha.Length - 1]); + sumalpha += _alpha[_alpha.Length - 1]; + } + else if (!sumxi.AlmostEqualRelative(1.0, 8)) + { + return 0.0; + } + + return term + SpecialFunctions.GammaLn(sumalpha); + } + + /// + /// Samples a Dirichlet distributed random vector. + /// + /// A sample from this distribution. + public double[] Sample() + { + return Sample(_random, _alpha); + } + + /// + /// Samples a Dirichlet distributed random vector. + /// + /// The random number generator to use. + /// The Dirichlet distribution parameter. + /// a sample from the distribution. + public static double[] Sample(System.Random rnd, double[] alpha) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(alpha)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var n = alpha.Length; + var gv = new double[n]; + var sum = 0.0; + for (var i = 0; i < n; i++) + { + if (alpha[i] == 0.0) + { + gv[i] = 0.0; + } + else + { + gv[i] = Gamma.Sample(rnd, alpha[i], 1.0); + } + + sum += gv[i]; + } + + for (var i = 0; i < n; i++) + { + gv[i] /= sum; + } + + return gv; + } +} diff --git a/MathNet.Numerics/Distributions/DiscreteUniform.cs b/MathNet.Numerics/Distributions/DiscreteUniform.cs new file mode 100644 index 0000000..57aab87 --- /dev/null +++ b/MathNet.Numerics/Distributions/DiscreteUniform.cs @@ -0,0 +1,444 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Uniform distribution. +/// The discrete uniform distribution is a distribution over integers. The distribution +/// is parameterized by a lower and upper bound (both inclusive). +/// Wikipedia - Discrete uniform distribution. +/// +public class DiscreteUniform : IDiscreteDistribution +{ + System.Random _random; + + readonly int _lower; + readonly int _upper; + + /// + /// Initializes a new instance of the DiscreteUniform class. + /// + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + public DiscreteUniform(int lower, int upper) + { + if (!IsValidParameterSet(lower, upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _lower = lower; + _upper = upper; + } + + /// + /// Initializes a new instance of the DiscreteUniform class. + /// + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// The random number generator which is used to draw random samples. + public DiscreteUniform(int lower, int upper, System.Random randomSource) + { + if (!IsValidParameterSet(lower, upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _lower = lower; + _upper = upper; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return "DiscreteUniform(Lower = " + _lower + ", Upper = " + _upper + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + public static bool IsValidParameterSet(int lower, int upper) + { + return lower <= upper; + } + + /// + /// Gets the inclusive lower bound of the probability distribution. + /// + public int LowerBound + { + get { return _lower; } + } + + /// + /// Gets the inclusive upper bound of the probability distribution. + /// + public int UpperBound + { + get { return _upper; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return (_lower + _upper) / 2.0; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt((((_upper - _lower + 1.0) * (_upper - _lower + 1.0)) - 1.0) / 12.0); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return (((_upper - _lower + 1.0) * (_upper - _lower + 1.0)) - 1.0) / 12.0; } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return Math.Log(_upper - _lower + 1.0); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return 0.0; } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return _lower; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { return _upper; } + } + + /// + /// Gets the mode of the distribution; since every element in the domain has the same probability this method returns the middle one. + /// + public int Mode + { + get { return (int)Math.Floor((_lower + _upper) / 2.0); } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return (_lower + _upper) / 2.0; } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + return PMF(_lower, _upper, k); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + return PMFLn(_lower, _upper, k); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + return CDF(_lower, _upper, x); + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// the probability mass at location . + public static double PMF(int lower, int upper, int k) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return k >= lower && k <= upper ? 1.0 / (upper - lower + 1) : 0.0; + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// the log probability mass at location . + public static double PMFLn(int lower, int upper, int k) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return k >= lower && k <= upper ? -Math.Log(upper - lower + 1) : double.NegativeInfinity; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// the cumulative distribution at location . + /// + public static double CDF(int lower, int upper, double x) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < lower) + { + return 0.0; + } + + if (x >= upper) + { + return 1.0; + } + + return Math.Min(1.0, (Math.Floor(x) - lower + 1) / (upper - lower + 1)); + } + + /// + /// Generates one sample from the discrete uniform distribution. This method does not do any parameter checking. + /// + /// The random source to use. + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// A random sample from the discrete uniform distribution. + static int SampleUnchecked(System.Random rnd, int lower, int upper) + { + return rnd.Next(lower, upper + 1); + } + + static void SamplesUnchecked(System.Random rnd, int[] values, int lower, int upper) + { + rnd.NextInt32s(values, lower, upper + 1); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, int lower, int upper) + { + return rnd.NextInt32Sequence(lower, upper + 1); + } + + /// + /// Draws a random sample from the distribution. + /// + /// a sample from the distribution. + public int Sample() + { + return SampleUnchecked(_random, _lower, _upper); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _lower, _upper); + } + + /// + /// Samples an array of uniformly distributed random variables. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _lower, _upper); + } + + /// + /// Samples a uniformly distributed random variable. + /// + /// The random number generator to use. + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// A sample from the discrete uniform distribution. + public static int Sample(System.Random rnd, int lower, int upper) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, lower, upper); + } + + /// + /// Samples a sequence of uniformly distributed random variables. + /// + /// The random number generator to use. + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// a sequence of samples from the discrete uniform distribution. + public static IEnumerable Samples(System.Random rnd, int lower, int upper) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, lower, upper); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// a sequence of samples from the discrete uniform distribution. + public static void Samples(System.Random rnd, int[] values, int lower, int upper) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, lower, upper); + } + + /// + /// Samples a uniformly distributed random variable. + /// + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// A sample from the discrete uniform distribution. + public static int Sample(int lower, int upper) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, lower, upper); + } + + /// + /// Samples a sequence of uniformly distributed random variables. + /// + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// a sequence of samples from the discrete uniform distribution. + public static IEnumerable Samples(int lower, int upper) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, lower, upper); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// Lower bound, inclusive. Range: lower ≤ upper. + /// Upper bound, inclusive. Range: lower ≤ upper. + /// a sequence of samples from the discrete uniform distribution. + public static void Samples(int[] values, int lower, int upper) + { + if (!(lower <= upper)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, lower, upper); + } +} diff --git a/MathNet.Numerics/Distributions/Erlang.cs b/MathNet.Numerics/Distributions/Erlang.cs new file mode 100644 index 0000000..2ae4b97 --- /dev/null +++ b/MathNet.Numerics/Distributions/Erlang.cs @@ -0,0 +1,548 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Erlang distribution. +/// This distribution is a continuous probability distribution with wide applicability primarily due to its +/// relation to the exponential and Gamma distributions. +/// Wikipedia - Erlang distribution. +/// +public class Erlang : IContinuousDistribution +{ + System.Random _random; + + readonly int _shape; + readonly double _rate; + + /// + /// Initializes a new instance of the class. + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + public Erlang(int shape, double rate) + { + if (!IsValidParameterSet(shape, rate)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _shape = shape; + _rate = rate; + } + + /// + /// Initializes a new instance of the class. + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// The random number generator which is used to draw random samples. + public Erlang(int shape, double rate, System.Random randomSource) + { + if (!IsValidParameterSet(shape, rate)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _shape = shape; + _rate = rate; + } + + /// + /// Constructs a Erlang distribution from a shape and scale parameter. The distribution will + /// be initialized with the default random number generator. + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The scale (μ) of the Erlang distribution. Range: μ ≥ 0. + /// The random number generator which is used to draw random samples. Optional, can be null. + public static Erlang WithShapeScale(int shape, double scale, System.Random randomSource = null) + { + return new Erlang(shape, 1.0 / scale, randomSource); + } + + /// + /// Constructs a Erlang distribution from a shape and inverse scale parameter. The distribution will + /// be initialized with the default random number generator. + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// The random number generator which is used to draw random samples. Optional, can be null. + public static Erlang WithShapeRate(int shape, double rate, System.Random randomSource = null) + { + return new Erlang(shape, rate, randomSource); + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Erlang(k = " + _shape + ", λ = " + _rate + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + public static bool IsValidParameterSet(int shape, double rate) + { + return shape >= 0 && rate >= 0.0; + } + + /// + /// Gets the shape (k) of the Erlang distribution. Range: k ≥ 0. + /// + public int Shape + { + get { return _shape; } + } + + /// + /// Gets the rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// + public double Rate + { + get { return _rate; } + } + + /// + /// Gets the scale of the Erlang distribution. + /// + public double Scale + { + get { return 1.0 / _rate; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return _shape; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return _shape / _rate; + } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return 0.0; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return _shape / (_rate * _rate); + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return 0.0; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return Math.Sqrt(_shape) / _rate; + } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return 0.0; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return _shape - Math.Log(_rate) + SpecialFunctions.GammaLn(_shape) + ((1.0 - _shape) * SpecialFunctions.DiGamma(_shape)); + } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return 0.0; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return 2.0 / Math.Sqrt(_shape); + } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get + { + if (_shape < 1) + { + throw new NotSupportedException(); + } + + if (double.IsPositiveInfinity(_rate)) + { + return _shape; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return (_shape - 1.0) / _rate; + } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum value. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the Maximum value. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return PDF(_shape, _rate, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return PDFLn(_shape, _rate, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CDF(_shape, _rate, x); + } + + /// + /// Generates a sample from the Erlang distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return Gamma.SampleUnchecked(_random, _shape, _rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + Gamma.SamplesUnchecked(_random, values, _shape, _rate); + } + + /// + /// Generates a sequence of samples from the Erlang distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + while (true) + { + yield return Gamma.SampleUnchecked(_random, _shape, _rate); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(int shape, double rate, double x) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(rate)) + { + return x == shape ? double.PositiveInfinity : 0.0; + } + + if (shape == 0.0 && rate == 0.0) + { + return 0.0; + } + + if (shape == 1.0) + { + return rate * Math.Exp(-rate * x); + } + + if (shape > 160.0) + { + return Math.Exp(PDFLn(shape, rate, x)); + } + + return Math.Pow(rate, shape) * Math.Pow(x, shape - 1.0) * Math.Exp(-rate * x) / SpecialFunctions.Gamma(shape); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(int shape, double rate, double x) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(rate)) + { + return x == shape ? double.PositiveInfinity : double.NegativeInfinity; + } + + if (shape == 0.0 && rate == 0.0) + { + return double.NegativeInfinity; + } + + if (shape == 1.0) + { + return Math.Log(rate) - (rate * x); + } + + return (shape * Math.Log(rate)) + ((shape - 1.0) * Math.Log(x)) - (rate * x) - SpecialFunctions.GammaLn(shape); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// the cumulative distribution at location . + /// + public static double CDF(int shape, double rate, double x) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(rate)) + { + return x >= shape ? 1.0 : 0.0; + } + + if (shape == 0.0 && rate == 0.0) + { + return 0.0; + } + + return SpecialFunctions.GammaLowerRegularized(shape, x * rate); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, int shape, double rate) + { + return Gamma.Sample(rnd, shape, rate); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, int shape, double rate) + { + return Gamma.Samples(rnd, shape, rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, int shape, double rate) + { + Gamma.Samples(rnd, values, shape, rate); + } + + /// + /// Generates a sample from the distribution. + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// a sample from the distribution. + public static double Sample(int shape, double rate) + { + return Gamma.Sample(shape, rate); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(int shape, double rate) + { + return Gamma.Samples(shape, rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The shape (k) of the Erlang distribution. Range: k ≥ 0. + /// The rate or inverse scale (λ) of the Erlang distribution. Range: λ ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, int shape, double rate) + { + Gamma.Samples(values, shape, rate); + } +} diff --git a/MathNet.Numerics/Distributions/Exponential.cs b/MathNet.Numerics/Distributions/Exponential.cs new file mode 100644 index 0000000..0bbdbd6 --- /dev/null +++ b/MathNet.Numerics/Distributions/Exponential.cs @@ -0,0 +1,458 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Exponential distribution. +/// The exponential distribution is a distribution over the real numbers parameterized by one non-negative parameter. +/// Wikipedia - exponential distribution. +/// +public class Exponential : IContinuousDistribution +{ + System.Random _random; + + readonly double _rate; + + /// + /// Initializes a new instance of the class. + /// + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + public Exponential(double rate) + { + if (!IsValidParameterSet(rate)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _rate = rate; + } + + /// + /// Initializes a new instance of the class. + /// + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// The random number generator which is used to draw random samples. + public Exponential(double rate, System.Random randomSource) + { + if (!IsValidParameterSet(rate)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _rate = rate; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Exponential(λ = " + _rate + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + public static bool IsValidParameterSet(double rate) + { + return rate >= 0.0; + } + + /// + /// Gets the rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// + public double Rate + { + get { return _rate; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return 1.0 / _rate; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return 1.0 / (_rate * _rate); } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return 1.0 / _rate; } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return 1.0 - Math.Log(_rate); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return 2.0; } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get { return 0.0; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return Math.Log(2.0) / _rate; } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return x < 0.0 ? 0.0 : _rate * Math.Exp(-_rate * x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return Math.Log(_rate) - (_rate * x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return x < 0.0 ? 0.0 : 1.0 - Math.Exp(-_rate * x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return p >= 1.0 ? double.PositiveInfinity : -Math.Log(1 - p) / _rate; + } + + /// + /// Draws a random sample from the distribution. + /// + /// A random number from this distribution. + public double Sample() + { + return SampleUnchecked(_random, _rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _rate); + } + + /// + /// Generates a sequence of samples from the Exponential distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _rate); + } + + static double SampleUnchecked(System.Random rnd, double rate) + { + var r = rnd.NextDouble(); + while (r == 0.0) + { + r = rnd.NextDouble(); + } + + return -Math.Log(r) / rate; + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double rate) + { + rnd.NextDoubles(values); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + // this happens very rarely + var r = values[i]; + while (r == 0.0) + { + r = rnd.NextDouble(); + } + + values[i] = -Math.Log(r) / rate; + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double rate) + { + return rnd.NextDoubleSequence().Where(r => r != 0.0).Select(r => -Math.Log(r) / rate); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double rate, double x) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return x < 0.0 ? 0.0 : rate * Math.Exp(-rate * x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double rate, double x) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Math.Log(rate) - (rate * x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// the cumulative distribution at location . + /// + public static double CDF(double rate, double x) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return x < 0.0 ? 0.0 : 1.0 - Math.Exp(-rate * x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// the inverse cumulative density at . + /// + public static double InvCDF(double rate, double p) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return p >= 1.0 ? double.PositiveInfinity : -Math.Log(1 - p) / rate; + } + + /// + /// Draws a random sample from the distribution. + /// + /// The random number generator to use. + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// A random number from this distribution. + public static double Sample(System.Random rnd, double rate) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double rate) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, rate); + } + + /// + /// Generates a sequence of samples from the Exponential distribution. + /// + /// The random number generator to use. + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double rate) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, rate); + } + + /// + /// Draws a random sample from the distribution. + /// + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// A random number from this distribution. + public static double Sample(double rate) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double rate) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, rate); + } + + /// + /// Generates a sequence of samples from the Exponential distribution. + /// + /// The rate (λ) parameter of the distribution. Range: λ ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double rate) + { + if (rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, rate); + } +} diff --git a/MathNet.Numerics/Distributions/FisherSnedecor.cs b/MathNet.Numerics/Distributions/FisherSnedecor.cs new file mode 100644 index 0000000..8020f98 --- /dev/null +++ b/MathNet.Numerics/Distributions/FisherSnedecor.cs @@ -0,0 +1,512 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.RootFinding; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate F-distribution, also known as Fisher-Snedecor distribution. +/// For details about this distribution, see +/// Wikipedia - FisherSnedecor distribution. +/// +public class FisherSnedecor : IContinuousDistribution +{ + System.Random _random; + + readonly double _freedom1; + readonly double _freedom2; + + /// + /// Initializes a new instance of the class. + /// + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + public FisherSnedecor(double d1, double d2) + { + if (!IsValidParameterSet(d1, d2)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _freedom1 = d1; + _freedom2 = d2; + } + + /// + /// Initializes a new instance of the class. + /// + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// The random number generator which is used to draw random samples. + public FisherSnedecor(double d1, double d2, System.Random randomSource) + { + if (!IsValidParameterSet(d1, d2)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _freedom1 = d1; + _freedom2 = d2; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "FisherSnedecor(d1 = " + _freedom1 + ", d2 = " + _freedom2 + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + public static bool IsValidParameterSet(double d1, double d2) + { + return d1 > 0.0 && d2 > 0.0; + } + + /// + /// Gets the first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// + public double DegreesOfFreedom1 + { + get { return _freedom1; } + } + + /// + /// Gets the second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// + public double DegreesOfFreedom2 + { + get { return _freedom2; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get + { + if (_freedom2 <= 2) + { + throw new NotSupportedException(); + } + + return _freedom2 / (_freedom2 - 2.0); + } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + if (_freedom2 <= 4) + { + throw new NotSupportedException(); + } + + return (2.0 * _freedom2 * _freedom2 * (_freedom1 + _freedom2 - 2.0)) / (_freedom1 * (_freedom2 - 2.0) * (_freedom2 - 2.0) * (_freedom2 - 4.0)); + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get + { + if (_freedom2 <= 6) + { + throw new NotSupportedException(); + } + + return (((2.0 * _freedom1) + _freedom2 - 2.0) * Math.Sqrt(8.0 * (_freedom2 - 4.0))) / ((_freedom2 - 6.0) * Math.Sqrt(_freedom1 * (_freedom1 + _freedom2 - 2.0))); + } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get + { + if (_freedom1 <= 2) + { + throw new NotSupportedException(); + } + + return (_freedom2 * (_freedom1 - 2.0)) / (_freedom1 * (_freedom2 + 2.0)); + } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return Math.Sqrt(Math.Pow(_freedom1 * x, _freedom1) * Math.Pow(_freedom2, _freedom2) / Math.Pow((_freedom1 * x) + _freedom2, _freedom1 + _freedom2)) / (x * SpecialFunctions.Beta(_freedom1 / 2.0, _freedom2 / 2.0)); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return Math.Log(Density(x)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return SpecialFunctions.BetaRegularized(_freedom1 / 2.0, _freedom2 / 2.0, _freedom1 * x / ((_freedom1 * x) + _freedom2)); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + /// WARNING: currently not an explicit implementation, hence slow and unreliable. + public double InverseCumulativeDistribution(double p) + { + return InvCDF(_freedom1, _freedom2, p); + } + + /// + /// Generates a sample from the FisherSnedecor distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _freedom1, _freedom2); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _freedom1, _freedom2); + } + + /// + /// Generates a sequence of samples from the FisherSnedecor distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _freedom1, _freedom2); + } + + /// + /// Generates one sample from the FisherSnedecor distribution without parameter checking. + /// + /// The random number generator to use. + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// a FisherSnedecor distributed random number. + static double SampleUnchecked(System.Random rnd, double d1, double d2) + { + return (ChiSquared.Sample(rnd, d1) * d2) / (ChiSquared.Sample(rnd, d2) * d1); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double d1, double d2) + { + var values2 = new double[values.Length]; + ChiSquared.SamplesUnchecked(rnd, values, d1); + ChiSquared.SamplesUnchecked(rnd, values2, d2); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = (values[i] * d2) / (values2[i] * d1); + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double d1, double d2) + { + while (true) + { + yield return SampleUnchecked(rnd, d1, d2); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double d1, double d2, double x) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Math.Sqrt(Math.Pow(d1 * x, d1) * Math.Pow(d2, d2) / Math.Pow((d1 * x) + d2, d1 + d2)) / (x * SpecialFunctions.Beta(d1 / 2.0, d2 / 2.0)); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double d1, double d2, double x) + { + return Math.Log(PDF(d1, d2, x)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double d1, double d2, double x) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SpecialFunctions.BetaRegularized(d1 / 2.0, d2 / 2.0, d1 * x / (d1 * x + d2)); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// the inverse cumulative density at . + /// + /// WARNING: currently not an explicit implementation, hence slow and unreliable. + public static double InvCDF(double d1, double d2, double p) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Brent.FindRoot( + x => SpecialFunctions.BetaRegularized(d1 / 2.0, d2 / 2.0, d1 * x / (d1 * x + d2)) - p, + 0, 1000, accuracy: 1e-12); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double d1, double d2) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, d1, d2); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double d1, double d2) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, d1, d2); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double d1, double d2) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, d1, d2); + } + + /// + /// Generates a sample from the distribution. + /// + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// a sample from the distribution. + public static double Sample(double d1, double d2) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, d1, d2); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double d1, double d2) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, d1, d2); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The first degree of freedom (d1) of the distribution. Range: d1 > 0. + /// The second degree of freedom (d2) of the distribution. Range: d2 > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double d1, double d2) + { + if (d1 <= 0.0 || d2 <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, d1, d2); + } +} diff --git a/MathNet.Numerics/Distributions/Gamma.cs b/MathNet.Numerics/Distributions/Gamma.cs new file mode 100644 index 0000000..85f5408 --- /dev/null +++ b/MathNet.Numerics/Distributions/Gamma.cs @@ -0,0 +1,680 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Gamma distribution. +/// For details about this distribution, see +/// Wikipedia - Gamma distribution. +/// +/// +/// The Gamma distribution is parametrized by a shape and inverse scale parameter. When we want +/// to specify a Gamma distribution which is a point distribution we set the shape parameter to be the +/// location of the point distribution and the inverse scale as positive infinity. The distribution +/// with shape and inverse scale both zero is undefined. +/// +/// Random number generation for the Gamma distribution is based on the algorithm in: +/// "A Simple Method for Generating Gamma Variables" - Marsaglia & Tsang +/// ACM Transactions on Mathematical Software, Vol. 26, No. 3, September 2000, Pages 363–372. +/// +public class Gamma : IContinuousDistribution +{ + System.Random _random; + + readonly double _shape; + readonly double _rate; + + /// + /// Initializes a new instance of the Gamma class. + /// + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + public Gamma(double shape, double rate) + { + if (!IsValidParameterSet(shape, rate)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _shape = shape; + _rate = rate; + } + + /// + /// Initializes a new instance of the Gamma class. + /// + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// The random number generator which is used to draw random samples. + public Gamma(double shape, double rate, System.Random randomSource) + { + if (!IsValidParameterSet(shape, rate)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _shape = shape; + _rate = rate; + } + + /// + /// Constructs a Gamma distribution from a shape and scale parameter. The distribution will + /// be initialized with the default random number generator. + /// + /// The shape (k) of the Gamma distribution. Range: k ≥ 0. + /// The scale (θ) of the Gamma distribution. Range: θ ≥ 0 + /// The random number generator which is used to draw random samples. Optional, can be null. + public static Gamma WithShapeScale(double shape, double scale, System.Random randomSource = null) + { + return new Gamma(shape, 1.0 / scale, randomSource); + } + + /// + /// Constructs a Gamma distribution from a shape and inverse scale parameter. The distribution will + /// be initialized with the default random number generator. + /// + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// The random number generator which is used to draw random samples. Optional, can be null. + public static Gamma WithShapeRate(double shape, double rate, System.Random randomSource = null) + { + return new Gamma(shape, rate, randomSource); + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Gamma(α = " + _shape + ", β = " + _rate + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + public static bool IsValidParameterSet(double shape, double rate) + { + return shape >= 0.0 && rate >= 0.0; + } + + /// + /// Gets or sets the shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// + public double Shape + { + get { return _shape; } + } + + /// + /// Gets or sets the rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// + public double Rate + { + get { return _rate; } + } + + /// + /// Gets or sets the scale (θ) of the Gamma distribution. + /// + public double Scale + { + get { return 1.0 / _rate; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the Gamma distribution. + /// + public double Mean + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return _shape; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return _shape / _rate; + } + } + + /// + /// Gets the variance of the Gamma distribution. + /// + public double Variance + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return 0.0; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return _shape / (_rate * _rate); + } + } + + /// + /// Gets the standard deviation of the Gamma distribution. + /// + public double StdDev + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return 0.0; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return Math.Sqrt(_shape / (_rate * _rate)); + } + } + + /// + /// Gets the entropy of the Gamma distribution. + /// + public double Entropy + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return 0.0; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return _shape - Math.Log(_rate) + SpecialFunctions.GammaLn(_shape) + ((1.0 - _shape) * SpecialFunctions.DiGamma(_shape)); + } + } + + /// + /// Gets the skewness of the Gamma distribution. + /// + public double Skewness + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return 0.0; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return 2.0 / Math.Sqrt(_shape); + } + } + + /// + /// Gets the mode of the Gamma distribution. + /// + public double Mode + { + get + { + if (double.IsPositiveInfinity(_rate)) + { + return _shape; + } + + if (_rate == 0.0 && _shape == 0.0) + { + return double.NaN; + } + + return (_shape - 1.0) / _rate; + } + } + + /// + /// Gets the median of the Gamma distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum of the Gamma distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the Gamma distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return PDF(_shape, _rate, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return PDFLn(_shape, _rate, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CDF(_shape, _rate, x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return InvCDF(_shape, _rate, p); + } + + /// + /// Generates a sample from the Gamma distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _shape, _rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _shape, _rate); + } + + /// + /// Generates a sequence of samples from the Gamma distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _shape, _rate); + } + + /// + /// Sampling implementation based on: + /// "A Simple Method for Generating Gamma Variables" - Marsaglia & Tsang + /// ACM Transactions on Mathematical Software, Vol. 26, No. 3, September 2000, Pages 363–372. + /// This method performs no parameter checks. + /// + /// The random number generator to use. + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// A sample from a Gamma distributed random variable. + internal static double SampleUnchecked(System.Random rnd, double shape, double rate) + { + if (double.IsPositiveInfinity(rate)) + { + return shape; + } + + var a = shape; + var alphafix = 1.0; + + // Fix when alpha is less than one. + if (shape < 1.0) + { + a = shape + 1.0; + alphafix = Math.Pow(rnd.NextDouble(), 1.0 / shape); + } + + var d = a - (1.0 / 3.0); + var c = 1.0 / Math.Sqrt(9.0 * d); + while (true) + { + var x = Normal.Sample(rnd, 0.0, 1.0); + var v = 1.0 + (c * x); + while (v <= 0.0) + { + x = Normal.Sample(rnd, 0.0, 1.0); + v = 1.0 + (c * x); + } + + v = v * v * v; + var u = rnd.NextDouble(); + x = x * x; + if (u < 1.0 - (0.0331 * x * x)) + { + return alphafix * d * v / rate; + } + + if (Math.Log(u) < (0.5 * x) + (d * (1.0 - v + Math.Log(v)))) + { + return alphafix * d * v / rate; + } + } + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double shape, double rate) + { + for (int i = 0; i < values.Length; i++) + { + values[i] = SampleUnchecked(rnd, shape, rate); + } + } + + internal static IEnumerable SamplesUnchecked(System.Random rnd, double location, double scale) + { + while (true) + { + yield return SampleUnchecked(rnd, location, scale); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double shape, double rate, double x) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(rate)) + { + return x == shape ? double.PositiveInfinity : 0.0; + } + + if (shape == 0.0 && rate == 0.0) + { + return 0.0; + } + + if (shape == 1.0) + { + return rate * Math.Exp(-rate * x); + } + + if (shape > 160.0) + { + return Math.Exp(PDFLn(shape, rate, x)); + } + + return Math.Pow(rate, shape) * Math.Pow(x, shape - 1.0) * Math.Exp(-rate * x) / SpecialFunctions.Gamma(shape); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double shape, double rate, double x) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(rate)) + { + return x == shape ? double.PositiveInfinity : double.NegativeInfinity; + } + + if (shape == 0.0 && rate == 0.0) + { + return double.NegativeInfinity; + } + + if (shape == 1.0) + { + return Math.Log(rate) - (rate * x); + } + + return (shape * Math.Log(rate)) + ((shape - 1.0) * Math.Log(x)) - (rate * x) - SpecialFunctions.GammaLn(shape); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// the cumulative distribution at location . + /// + public static double CDF(double shape, double rate, double x) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (double.IsPositiveInfinity(rate)) + { + return x >= shape ? 1.0 : 0.0; + } + + if (shape == 0.0 && rate == 0.0) + { + return 0.0; + } + + return SpecialFunctions.GammaLowerRegularized(shape, x * rate); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// the inverse cumulative density at . + /// + public static double InvCDF(double shape, double rate, double p) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SpecialFunctions.GammaLowerRegularizedInv(shape, p) / rate; + } + + /// + /// Generates a sample from the Gamma distribution. + /// + /// The random number generator to use. + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double shape, double rate) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, shape, rate); + } + + /// + /// Generates a sequence of samples from the Gamma distribution. + /// + /// The random number generator to use. + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double shape, double rate) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, shape, rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double shape, double rate) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, shape, rate); + } + + /// + /// Generates a sample from the Gamma distribution. + /// + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// a sample from the distribution. + public static double Sample(double shape, double rate) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, shape, rate); + } + + /// + /// Generates a sequence of samples from the Gamma distribution. + /// + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double shape, double rate) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, shape, rate); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The shape (k, α) of the Gamma distribution. Range: α ≥ 0. + /// The rate or inverse scale (β) of the Gamma distribution. Range: β ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double shape, double rate) + { + if (shape < 0.0 || rate < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, shape, rate); + } +} diff --git a/MathNet.Numerics/Distributions/Geometric.cs b/MathNet.Numerics/Distributions/Geometric.cs new file mode 100644 index 0000000..595c4aa --- /dev/null +++ b/MathNet.Numerics/Distributions/Geometric.cs @@ -0,0 +1,451 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Geometric distribution. +/// The Geometric distribution is a distribution over positive integers parameterized by one positive real number. +/// This implementation of the Geometric distribution will never generate 0's. +/// Wikipedia - geometric distribution. +/// +public class Geometric : IDiscreteDistribution +{ + System.Random _random; + + readonly double _p; + + /// + /// Initializes a new instance of the Geometric class. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public Geometric(double p) + { + if (!IsValidParameterSet(p)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _p = p; + } + + /// + /// Initializes a new instance of the Geometric class. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// The random number generator which is used to draw random samples. + public Geometric(double p, System.Random randomSource) + { + if (!IsValidParameterSet(p)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _p = p; + } + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() + { + return "Geometric(p = " + _p + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public static bool IsValidParameterSet(double p) + { + return p >= 0.0 && p <= 1.0; + } + + /// + /// Gets the probability of generating a one. Range: 0 ≤ p ≤ 1. + /// + public double P + { + get { return _p; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return 1.0 / _p; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return (1.0 - _p) / (_p * _p); } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(1.0 - _p) / _p; } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return ((-_p * Math.Log(_p, 2.0)) - ((1.0 - _p) * Math.Log(1.0 - _p, 2.0))) / _p; } + } + + /// + /// Gets the skewness of the distribution. + /// + /// Throws a not supported exception. + public double Skewness + { + get { return (2.0 - _p) / Math.Sqrt(1.0 - _p); } + } + + /// + /// Gets the mode of the distribution. + /// + public int Mode + { + get { return 1; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return _p == 0.0 ? double.PositiveInfinity : _p == 1.0 ? 1.0 : Math.Ceiling(-Constants.Ln2 / Math.Log(1 - _p)); } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return 1; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { return int.MaxValue; } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + if (k <= 0) + { + return 0.0; + } + + return Math.Pow(1.0 - _p, k - 1) * _p; + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + if (k <= 0) + { + return double.NegativeInfinity; + } + + return ((k - 1) * Math.Log(1.0 - _p)) + Math.Log(_p); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + return 1.0 - Math.Pow(1.0 - _p, x); + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// the probability mass at location . + public static double PMF(double p, int k) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (k <= 0) + { + return 0.0; + } + + return Math.Pow(1.0 - p, k - 1) * p; + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// the log probability mass at location . + public static double PMFLn(double p, int k) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (k <= 0) + { + return double.NegativeInfinity; + } + + return ((k - 1) * Math.Log(1.0 - p)) + Math.Log(p); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// the cumulative distribution at location . + /// + public static double CDF(double p, double x) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return 1.0 - Math.Pow(1.0 - p, x); + } + + /// + /// Returns one sample from the distribution. + /// + /// The random number generator to use. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + /// One sample from the distribution implied by . + static int SampleUnchecked(System.Random rnd, double p) + { + return p == 1.0 ? 1 : (int)Math.Ceiling(Math.Log(1.0 - rnd.NextDouble(), 1.0 - p)); + } + + static void SamplesUnchecked(System.Random rnd, int[] values, double p) + { + if (p == 1.0) + { + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = 1; + } + }); + return; + } + + var uniform = rnd.NextDoubles(values.Length); + double rp = 1.0 - p; + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = (int)Math.Ceiling(Math.Log(1.0 - uniform[i], rp)); + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double p) + { + if (p == 1.0) + { + return Generate.RepeatSequence(1); + } + + double rp = 1.0 - p; + return rnd.NextDoubleSequence().Select(r => (int)Math.Ceiling(Math.Log(1.0 - r, rp))); + } + + /// + /// Samples a Geometric distributed random variable. + /// + /// A sample from the Geometric distribution. + public int Sample() + { + return SampleUnchecked(_random, _p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _p); + } + + /// + /// Samples an array of Geometric distributed random variables. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _p); + } + + /// + /// Samples a random variable. + /// + /// The random number generator to use. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public static int Sample(System.Random rnd, double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, p); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The random number generator to use. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public static IEnumerable Samples(System.Random rnd, double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public static void Samples(System.Random rnd, int[] values, double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, p); + } + + /// + /// Samples a random variable. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public static int Sample(double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, p); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public static IEnumerable Samples(double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The probability (p) of generating one. Range: 0 ≤ p ≤ 1. + public static void Samples(int[] values, double p) + { + if (!(p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, p); + } +} diff --git a/MathNet.Numerics/Distributions/Hypergeometric.cs b/MathNet.Numerics/Distributions/Hypergeometric.cs new file mode 100644 index 0000000..9101e69 --- /dev/null +++ b/MathNet.Numerics/Distributions/Hypergeometric.cs @@ -0,0 +1,494 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Hypergeometric distribution. +/// This distribution is a discrete probability distribution that describes the number of successes in a sequence +/// of n draws from a finite population without replacement, just as the binomial distribution +/// describes the number of successes for draws with replacement +/// Wikipedia - Hypergeometric distribution. +/// +public class Hypergeometric : IDiscreteDistribution +{ + System.Random _random; + + readonly int _population; + readonly int _success; + readonly int _draws; + + /// + /// Initializes a new instance of the Hypergeometric class. + /// + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + public Hypergeometric(int population, int success, int draws) + { + if (!IsValidParameterSet(population, success, draws)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _population = population; + _success = success; + _draws = draws; + } + + /// + /// Initializes a new instance of the Hypergeometric class. + /// + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + /// The random number generator which is used to draw random samples. + public Hypergeometric(int population, int success, int draws, System.Random randomSource) + { + if (!IsValidParameterSet(population, success, draws)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _population = population; + _success = success; + _draws = draws; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return "Hypergeometric(N = " + _population + ", M = " + _success + ", n = " + _draws + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + public static bool IsValidParameterSet(int population, int success, int draws) + { + return population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population; + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the size of the population (N). + /// + public int Population + { + get { return _population; } + } + + /// + /// Gets the number of draws without replacement (n). + /// + public int Draws + { + get { return _draws; } + } + + /// + /// Gets the number successes within the population (K, M). + /// + public int Success + { + get { return _success; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return (double)_success * _draws / _population; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return _draws * _success * (_population - _draws) * (_population - _success) / (_population * _population * (_population - 1.0)); } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return (Math.Sqrt(_population - 1.0) * (_population - (2 * _draws)) * (_population - (2 * _success))) / (Math.Sqrt(_draws * _success * (_population - _success) * (_population - _draws)) * (_population - 2.0)); } + } + + /// + /// Gets the mode of the distribution. + /// + public int Mode + { + get { return (_draws + 1) * (_success + 1) / (_population + 2); } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum of the distribution. + /// + public int Minimum + { + get { return Math.Max(0, _draws + _success - _population); } + } + + /// + /// Gets the maximum of the distribution. + /// + public int Maximum + { + get { return Math.Min(_success, _draws); } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + return SpecialFunctions.Binomial(_success, k) * SpecialFunctions.Binomial(_population - _success, _draws - k) / SpecialFunctions.Binomial(_population, _draws); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + return SpecialFunctions.BinomialLn(_success, k) + SpecialFunctions.BinomialLn(_population - _success, _draws - k) - SpecialFunctions.BinomialLn(_population, _draws); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + return CDF(_population, _success, _draws, x); + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + /// the probability mass at location . + public static double PMF(int population, int success, int draws, int k) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SpecialFunctions.Binomial(success, k) * SpecialFunctions.Binomial(population - success, draws - k) / SpecialFunctions.Binomial(population, draws); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + /// the log probability mass at location . + public static double PMFLn(int population, int success, int draws, int k) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SpecialFunctions.BinomialLn(success, k) + SpecialFunctions.BinomialLn(population - success, draws - k) - SpecialFunctions.BinomialLn(population, draws); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + /// the cumulative distribution at location . + /// + public static double CDF(int population, int success, int draws, double x) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < Math.Max(0, draws + success - population)) + { + return 0.0; + } + + if (x >= Math.Min(success, draws)) + { + return 1.0; + } + + var k = (int)Math.Floor(x); + var denominatorLn = SpecialFunctions.BinomialLn(population, draws); + var sum = 0.0; + for (var i = 0; i <= k; i++) + { + sum += Math.Exp(SpecialFunctions.BinomialLn(success, i) + SpecialFunctions.BinomialLn(population - success, draws - i) - denominatorLn); + } + + return sum; + } + + /// + /// Generates a sample from the Hypergeometric distribution without doing parameter checking. + /// + /// The random number generator to use. + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The n parameter of the distribution. + /// a random number from the Hypergeometric distribution. + static int SampleUnchecked(System.Random rnd, int population, int success, int draws) + { + var x = 0; + + do + { + var p = (double)success / population; + var r = rnd.NextDouble(); + if (r < p) + { + x++; + success--; + } + + population--; + draws--; + } + while (0 < draws); + + return x; + } + + static void SamplesUnchecked(System.Random rnd, int[] values, int population, int success, int draws) + { + for (int i = 0; i < values.Length; i++) + { + values[i] = SampleUnchecked(rnd, population, success, draws); + } + } + + static IEnumerable SamplesUnchecked(System.Random rnd, int population, int success, int draws) + { + while (true) + { + yield return SampleUnchecked(rnd, population, success, draws); + } + } + + /// + /// Samples a Hypergeometric distributed random variable. + /// + /// The number of successes in n trials. + public int Sample() + { + return SampleUnchecked(_random, _population, _success, _draws); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _population, _success, _draws); + } + + /// + /// Samples an array of Hypergeometric distributed random variables. + /// + /// a sequence of successes in n trials. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _population, _success, _draws); + } + + /// + /// Samples a random variable. + /// + /// The random number generator to use. + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + public static int Sample(System.Random rnd, int population, int success, int draws) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, population, success, draws); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The random number generator to use. + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + public static IEnumerable Samples(System.Random rnd, int population, int success, int draws) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, population, success, draws); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + public static void Samples(System.Random rnd, int[] values, int population, int success, int draws) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, population, success, draws); + } + + /// + /// Samples a random variable. + /// + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + public static int Sample(int population, int success, int draws) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, population, success, draws); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + public static IEnumerable Samples(int population, int success, int draws) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, population, success, draws); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The size of the population (N). + /// The number successes within the population (K, M). + /// The number of draws without replacement (n). + public static void Samples(int[] values, int population, int success, int draws) + { + if (!(population >= 0 && success >= 0 && draws >= 0 && success <= population && draws <= population)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, population, success, draws); + } +} diff --git a/MathNet.Numerics/Distributions/IContinuousDistribution.cs b/MathNet.Numerics/Distributions/IContinuousDistribution.cs new file mode 100644 index 0000000..9d3f171 --- /dev/null +++ b/MathNet.Numerics/Distributions/IContinuousDistribution.cs @@ -0,0 +1,85 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Distributions; + +using System.Collections.Generic; + +/// +/// Continuous Univariate Probability Distribution. +/// +/// +public interface IContinuousDistribution : IUnivariateDistribution +{ + /// + /// Gets the mode of the distribution. + /// + double Mode { get; } + + /// + /// Gets the smallest element in the domain of the distribution which can be represented by a double. + /// + double Minimum { get; } + + /// + /// Gets the largest element in the domain of the distribution which can be represented by a double. + /// + double Maximum { get; } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + double Density(double x); + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + double DensityLn(double x); + + /// + /// Draws a random sample from the distribution. + /// + /// a sample from the distribution. + double Sample(); + + /// + /// Fills an array with samples generated from the distribution. + /// + void Samples(double[] values); + + /// + /// Draws a sequence of random samples from the distribution. + /// + /// an infinite sequence of samples from the distribution. + IEnumerable Samples(); +} diff --git a/MathNet.Numerics/Distributions/IDiscreteDistribution.cs b/MathNet.Numerics/Distributions/IDiscreteDistribution.cs new file mode 100644 index 0000000..1ce5a76 --- /dev/null +++ b/MathNet.Numerics/Distributions/IDiscreteDistribution.cs @@ -0,0 +1,85 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Distributions; + +using System.Collections.Generic; + +/// +/// Discrete Univariate Probability Distribution. +/// +/// +public interface IDiscreteDistribution : IUnivariateDistribution +{ + /// + /// Gets the mode of the distribution. + /// + int Mode { get; } + + /// + /// Gets the smallest element in the domain of the distribution which can be represented by an integer. + /// + int Minimum { get; } + + /// + /// Gets the largest element in the domain of the distribution which can be represented by an integer. + /// + int Maximum { get; } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + double Probability(int k); + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + double ProbabilityLn(int k); + + /// + /// Draws a random sample from the distribution. + /// + /// a sample from the distribution. + int Sample(); + + /// + /// Fills an array with samples generated from the distribution. + /// + void Samples(int[] values); + + /// + /// Draws a sequence of random samples from the distribution. + /// + /// an infinite sequence of samples from the distribution. + IEnumerable Samples(); +} diff --git a/MathNet.Numerics/Distributions/IDistribution.cs b/MathNet.Numerics/Distributions/IDistribution.cs new file mode 100644 index 0000000..e6910d8 --- /dev/null +++ b/MathNet.Numerics/Distributions/IDistribution.cs @@ -0,0 +1,43 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Distributions; + +/// +/// Probability Distribution. +/// +/// +/// +public interface IDistribution +{ + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + System.Random RandomSource { get; set; } +} diff --git a/MathNet.Numerics/Distributions/IUnivariateDistribution.cs b/MathNet.Numerics/Distributions/IUnivariateDistribution.cs new file mode 100644 index 0000000..a637918 --- /dev/null +++ b/MathNet.Numerics/Distributions/IUnivariateDistribution.cs @@ -0,0 +1,75 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Distributions; + +/// +/// Univariate Probability Distribution. +/// +/// +/// +public interface IUnivariateDistribution : IDistribution +{ + /// + /// Gets the mean of the distribution. + /// + double Mean { get; } + + /// + /// Gets the variance of the distribution. + /// + double Variance { get; } + + /// + /// Gets the standard deviation of the distribution. + /// + double StdDev { get; } + + /// + /// Gets the entropy of the distribution. + /// + double Entropy { get; } + + /// + /// Gets the skewness of the distribution. + /// + double Skewness { get; } + + /// + /// Gets the median of the distribution. + /// + double Median { get; } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + double CumulativeDistribution(double x); +} diff --git a/MathNet.Numerics/Distributions/InverseGamma.cs b/MathNet.Numerics/Distributions/InverseGamma.cs new file mode 100644 index 0000000..a2ed21c --- /dev/null +++ b/MathNet.Numerics/Distributions/InverseGamma.cs @@ -0,0 +1,459 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Inverse Gamma distribution. +/// The inverse Gamma distribution is a distribution over the positive real numbers parameterized by +/// two positive parameters. +/// Wikipedia - InverseGamma distribution. +/// +public class InverseGamma : IContinuousDistribution +{ + System.Random _random; + + readonly double _shape; + readonly double _scale; + + /// + /// Initializes a new instance of the class. + /// + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + public InverseGamma(double shape, double scale) + { + if (!IsValidParameterSet(shape, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _shape = shape; + _scale = scale; + } + + /// + /// Initializes a new instance of the class. + /// + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// The random number generator which is used to draw random samples. + public InverseGamma(double shape, double scale, System.Random randomSource) + { + if (!IsValidParameterSet(shape, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _shape = shape; + _scale = scale; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "InverseGamma(α = " + _shape + ", β = " + _scale + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + public static bool IsValidParameterSet(double shape, double scale) + { + return shape > 0.0 && scale > 0.0; + } + + /// + /// Gets or sets the shape (α) parameter. Range: α > 0. + /// + public double Shape + { + get { return _shape; } + } + + /// + /// Gets or sets The scale (β) parameter. Range: β > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get + { + if (_shape <= 1) + { + throw new NotSupportedException(); + } + + return _scale / (_shape - 1.0); + } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + if (_shape <= 2) + { + throw new NotSupportedException(); + } + + return _scale * _scale / ((_shape - 1.0) * (_shape - 1.0) * (_shape - 2.0)); + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return _scale / (Math.Abs(_shape - 1.0) * Math.Sqrt(_shape - 2.0)); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return _shape + Math.Log(_scale) + SpecialFunctions.GammaLn(_shape) - ((1 + _shape) * SpecialFunctions.DiGamma(_shape)); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get + { + if (_shape <= 3) + { + throw new NotSupportedException(); + } + + return (4 * Math.Sqrt(_shape - 2)) / (_shape - 3); + } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get { return _scale / (_shape + 1.0); } + } + + /// + /// Gets the median of the distribution. + /// + /// Throws . + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return x < 0.0 ? 0.0 : Math.Pow(_scale, _shape) * Math.Pow(x, -_shape - 1.0) * Math.Exp(-_scale / x) / SpecialFunctions.Gamma(_shape); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return Math.Log(Density(x)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return SpecialFunctions.GammaUpperRegularized(_shape, _scale / x); + } + + /// + /// Draws a random sample from the distribution. + /// + /// A random number from this distribution. + public double Sample() + { + return SampleUnchecked(_random, _shape, _scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _shape, _scale); + } + + /// + /// Generates a sequence of samples from the Cauchy distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _shape, _scale); + } + + static double SampleUnchecked(System.Random rnd, double shape, double scale) + { + return 1.0 / Gamma.SampleUnchecked(rnd, shape, scale); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double shape, double scale) + { + Gamma.SamplesUnchecked(rnd, values, shape, scale); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = 1.0 / values[i]; + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double shape, double scale) + { + return Gamma.SamplesUnchecked(rnd, shape, scale).Select(z => 1.0 / z); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double shape, double scale, double x) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return x < 0.0 ? 0.0 : Math.Pow(scale, shape) * Math.Pow(x, -shape - 1.0) * Math.Exp(-scale / x) / SpecialFunctions.Gamma(shape); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double shape, double scale, double x) + { + return Math.Log(PDF(shape, scale, x)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double shape, double scale, double x) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SpecialFunctions.GammaUpperRegularized(shape, scale / x); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, shape, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, shape, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, shape, scale); + } + + /// + /// Generates a sample from the distribution. + /// + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// a sample from the distribution. + public static double Sample(double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, shape, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, shape, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The shape (α) of the distribution. Range: α > 0. + /// The scale (β) of the distribution. Range: β > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, shape, scale); + } +} diff --git a/MathNet.Numerics/Distributions/InverseGaussian.cs b/MathNet.Numerics/Distributions/InverseGaussian.cs new file mode 100644 index 0000000..5786e5b --- /dev/null +++ b/MathNet.Numerics/Distributions/InverseGaussian.cs @@ -0,0 +1,458 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2019 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Statistics; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +public class InverseGaussian : IContinuousDistribution +{ + private System.Random _random; + + /// + /// Gets the mean (μ) of the distribution. Range: μ > 0. + /// + public double Mu { get; } + + /// + /// Gets the shape (λ) of the distribution. Range: λ > 0. + /// + public double Lambda { get; } + + /// + /// Initializes a new instance of the InverseGaussian class. + /// + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + /// The random number generator which is used to draw random samples. + public InverseGaussian(double mu, double lambda, System.Random randomSource = null) + { + if (!IsValidParameterSet(mu, lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + Mu = mu; + Lambda = lambda; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "InverseGaussian(μ = " + Mu + ", λ = " + Lambda + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + public static bool IsValidParameterSet(double mu, double lambda) + { + var allFinite = mu.IsFinite() && lambda.IsFinite(); + return allFinite && mu > 0.0 && lambda > 0.0; + } + + /// + /// Gets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the Inverse Gaussian distribution. + /// + public double Mean + { + get + { + return Mu; + } + } + + /// + /// Gets the variance of the Inverse Gaussian distribution. + /// + public double Variance + { + get + { + return Math.Pow(Mu, 3) / Lambda; + } + } + + /// + /// Gets the standard deviation of the Inverse Gaussian distribution. + /// + public double StdDev + { + get + { + return Math.Sqrt(Variance); + } + } + + /// + /// Gets the median of the Inverse Gaussian distribution. + /// No closed form analytical expression exists, so this value is approximated numerically and can throw an exception. + /// + public double Median + { + get { return InvCDF(0.5); } + } + + /// + /// Gets the minimum of the Inverse Gaussian distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the Inverse Gaussian distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Gets the skewness of the Inverse Gaussian distribution. + /// + public double Skewness + { + get { return 3 * Math.Sqrt(Mu / Lambda); } + } + + /// + /// Gets the kurtosis of the Inverse Gaussian distribution. + /// + public double Kurtosis + { + get { return 15 * Mu / Lambda; } + } + + /// + /// Gets the mode of the Inverse Gaussian distribution. + /// + public double Mode + { + get { return Mu * (Math.Sqrt(1 + (9 * Mu * Mu) / (4 * Lambda * Lambda)) - (3 * Mu) / (2 * Lambda)); } + } + + /// + /// Gets the entropy of the Inverse Gaussian distribution (currently not supported). + /// + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Generates a sample from the inverse Gaussian distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, Mu, Lambda); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, Mu, Lambda); + } + + /// + /// Generates a sequence of samples from the inverse Gaussian distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, Mu, Lambda); + } + + /// + /// Generates a sample from the inverse Gaussian distribution. + /// + /// The random number generator to use. + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double mu, double lambda) + { + if (!IsValidParameterSet(mu, lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, mu, lambda); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + public static void Samples(System.Random rnd, double[] values, double mu, double lambda) + { + if (!IsValidParameterSet(mu, lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, mu, lambda); + } + + /// + /// Generates a sequence of samples from the Burr distribution. + /// + /// The random number generator to use. + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double mu, double lambda) + { + if (!IsValidParameterSet(mu, lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, mu, lambda); + } + + internal static double SampleUnchecked(System.Random rnd, double mu, double lambda) + { + double v = MathNet.Numerics.Distributions.Normal.Sample(rnd, 0, 1); + double test = rnd.NextDouble(); + return InverseGaussianSampleImpl(mu, lambda, v, test); + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double mu, double lambda) + { + if (values.Length == 0) + { + return; + } + + double[] v = new double[values.Length]; + MathNet.Numerics.Distributions.Normal.Samples(rnd, v, 0, 1); + double[] test = rnd.NextDoubles(values.Length); + for (var j = 0; j < values.Length; ++j) + { + values[j] = InverseGaussianSampleImpl(mu, lambda, v[j], test[j]); + } + } + + internal static IEnumerable SamplesUnchecked(System.Random rnd, double mu, double lambda) + { + while (true) + { + yield return SampleUnchecked(rnd, mu, lambda); + } + } + + internal static double InverseGaussianSampleImpl(double mu, double lambda, double normalSample, double uniformSample) + { + double y = normalSample * normalSample; + double x = mu + (mu * mu * y) / (2 * lambda) - (mu / (2 * lambda)) * Math.Sqrt(4 * mu * lambda * y + mu * mu * y * y); + if (uniformSample <= mu / (mu + x)) + return x; + else + return mu * mu / x; + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return DensityImpl(Mu, Lambda, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return DensityLnImpl(Mu, Lambda, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CumulativeDistributionImpl(Mu, Lambda, x); + } + + /// + /// Computes the inverse cumulative distribution (CDF) of the distribution at p, i.e. solving for P(X ≤ x) = p. + /// + /// The location at which to compute the inverse cumulative distribution function. + /// the inverse cumulative distribution at location . + public double InvCDF(double p) + { + Func equationToSolve = (x) => CumulativeDistribution(x) - p; + if (RootFinding.NewtonRaphson.TryFindRoot(equationToSolve, Density, Mode, 0, double.PositiveInfinity, 1e-8, 100, out double quantile)) + return quantile; + else + throw new NonConvergenceException(Resources.NumericalEstimationFailed); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double mu, double lambda, double x) + { + if (!IsValidParameterSet(mu, lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return DensityImpl(mu, lambda, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + /// The location at which to compute the log density. + /// the log density at . + /// + public static double PDFLn(double mu, double lambda, double x) + { + if (!IsValidParameterSet(mu, lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return DensityLnImpl(mu, lambda, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public static double CDF(double mu, double lambda, double x) + { + if (!IsValidParameterSet(mu, lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return CumulativeDistributionImpl(mu, lambda, x); + } + + /// + /// Computes the inverse cumulative distribution (CDF) of the distribution at p, i.e. solving for P(X ≤ x) = p. + /// + /// The mean (μ) of the distribution. Range: μ > 0. + /// The shape (λ) of the distribution. Range: λ > 0. + /// The location at which to compute the inverse cumulative distribution function. + /// the inverse cumulative distribution at location . + /// + public static double ICDF(double mu, double lambda, double p) + { + if (!IsValidParameterSet(mu, lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var igDist = new InverseGaussian(mu, lambda); + return igDist.InvCDF(p); + } + + /// + /// Estimates the Inverse Gaussian parameters from sample data with maximum-likelihood. + /// + /// The samples to estimate the distribution parameters from. + /// The random number generator which is used to draw random samples. Optional, can be null. + /// An Inverse Gaussian distribution. + public static InverseGaussian Estimate(IEnumerable samples, System.Random randomSource = null) + { + var sampleVec = samples.ToArray(); + var muHat = sampleVec.Mean(); + var lambdahat = 1 / (1 / samples.HarmonicMean() - 1 / muHat); + return new InverseGaussian(muHat, lambdahat, randomSource); + } + + internal static double DensityImpl(double mu, double lambda, double x) + { + return Math.Sqrt(lambda / (2 * Math.PI * Math.Pow(x, 3))) * Math.Exp(-((lambda * Math.Pow(x - mu, 2)) / (2 * mu * mu * x))); + } + + internal static double DensityLnImpl(double mu, double lambda, double x) + { + return Math.Log(Math.Sqrt(lambda / (2 * Math.PI * Math.Pow(x, 3)))) - ((lambda * Math.Pow(x - mu, 2)) / (2 * mu * mu * x)); + } + + internal static double CumulativeDistributionImpl(double mu, double lambda, double x) + { + return Normal.CDF(0, 1, Math.Sqrt(lambda / x) * (x / mu - 1)) + Math.Exp(2 * lambda / mu) * Normal.CDF(0, 1, -Math.Sqrt(lambda / x) * (x / mu + 1)); + } +} diff --git a/MathNet.Numerics/Distributions/InverseWishart.cs b/MathNet.Numerics/Distributions/InverseWishart.cs new file mode 100644 index 0000000..c9b006d --- /dev/null +++ b/MathNet.Numerics/Distributions/InverseWishart.cs @@ -0,0 +1,249 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; + +namespace MathNet.Numerics.Distributions; + +/// +/// Multivariate Inverse Wishart distribution. This distribution is +/// parameterized by the degrees of freedom nu and the scale matrix S. The inverse Wishart distribution +/// is the conjugate prior for the covariance matrix of a multivariate normal distribution. +/// Wikipedia - Inverse-Wishart distribution. +/// +public class InverseWishart : IDistribution +{ + System.Random _random; + + readonly double _freedom; + readonly Matrix _scale; + + /// + /// Caches the Cholesky factorization of the scale matrix. + /// + readonly Cholesky _chol; + + /// + /// Initializes a new instance of the class. + /// + /// The degree of freedom (ν) for the inverse Wishart distribution. + /// The scale matrix (Ψ) for the inverse Wishart distribution. + public InverseWishart(double degreesOfFreedom, Matrix scale) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(degreesOfFreedom, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _freedom = degreesOfFreedom; + _scale = scale; + _chol = _scale.Cholesky(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The degree of freedom (ν) for the inverse Wishart distribution. + /// The scale matrix (Ψ) for the inverse Wishart distribution. + /// The random number generator which is used to draw random samples. + public InverseWishart(double degreesOfFreedom, Matrix scale, System.Random randomSource) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(degreesOfFreedom, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _freedom = degreesOfFreedom; + _scale = scale; + _chol = _scale.Cholesky(); + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "InverseWishart(ν = " + _freedom + ", Rows = " + _scale.RowCount + ", Columns = " + _scale.ColumnCount + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The degree of freedom (ν) for the inverse Wishart distribution. + /// The scale matrix (Ψ) for the inverse Wishart distribution. + public static bool IsValidParameterSet(double degreesOfFreedom, Matrix scale) + { + if (scale.RowCount != scale.ColumnCount) + { + return false; + } + + for (var i = 0; i < scale.RowCount; i++) + { + if (scale.At(i, i) <= 0.0) + { + return false; + } + } + + return degreesOfFreedom > 0.0; + } + + /// + /// Gets or sets the degree of freedom (ν) for the inverse Wishart distribution. + /// + public double DegreesOfFreedom + { + get { return _freedom; } + } + + /// + /// Gets or sets the scale matrix (Ψ) for the inverse Wishart distribution. + /// + public Matrix Scale + { + get { return _scale; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean. + /// + /// The mean of the distribution. + public Matrix Mean + { + get { return _scale * (1.0 / (_freedom - _scale.RowCount - 1.0)); } + } + + /// + /// Gets the mode of the distribution. + /// + /// The mode of the distribution. + /// A. O'Hagan, and J. J. Forster (2004). Kendall's Advanced Theory of Statistics: Bayesian Inference. 2B (2 ed.). Arnold. ISBN 0-340-80752-0. + public Matrix Mode + { + get { return _scale * (1.0 / (_freedom + _scale.RowCount + 1.0)); } + } + + /// + /// Gets the variance of the distribution. + /// + /// The variance of the distribution. + /// Kanti V. Mardia, J. T. Kent and J. M. Bibby (1979). Multivariate Analysis. + public Matrix Variance + { + get + { + return Matrix.Build.Dense(_scale.RowCount, _scale.ColumnCount, (i, j) => + { + var num1 = ((_freedom - _scale.RowCount + 1) * _scale.At(i, j) * _scale.At(i, j)) + ((_freedom - _scale.RowCount - 1) * _scale.At(i, i) * _scale.At(j, j)); + var num2 = (_freedom - _scale.RowCount) * (_freedom - _scale.RowCount - 1) * (_freedom - _scale.RowCount - 1) * (_freedom - _scale.RowCount - 3); + return num1 / num2; + }); + } + } + + /// + /// Evaluates the probability density function for the inverse Wishart distribution. + /// + /// The matrix at which to evaluate the density at. + /// If the argument does not have the same dimensions as the scale matrix. + /// the density at . + public double Density(Matrix x) + { + var p = _scale.RowCount; + + if (x.RowCount != p || x.ColumnCount != p) + { + throw new ArgumentOutOfRangeException(nameof(x), Resources.ArgumentMatrixDimensions); + } + + var chol = x.Cholesky(); + var dX = chol.Determinant; + var sXi = chol.Solve(Scale); + + // Compute the multivariate Gamma function. + var gp = Math.Pow(Constants.Pi, p * (p - 1.0) / 4.0); + for (var j = 1; j <= p; j++) + { + gp *= SpecialFunctions.Gamma((_freedom + 1.0 - j) / 2.0); + } + + return Math.Pow(dX, -(_freedom + p + 1.0) / 2.0) + * Math.Exp(-0.5 * sXi.Trace()) + * Math.Pow(_chol.Determinant, _freedom / 2.0) + / Math.Pow(2.0, _freedom * p / 2.0) + / gp; + } + + /// + /// Samples an inverse Wishart distributed random variable by sampling + /// a Wishart random variable and inverting the matrix. + /// + /// a sample from the distribution. + public Matrix Sample() + { + return Sample(_random, _freedom, _scale); + } + + /// + /// Samples an inverse Wishart distributed random variable by sampling + /// a Wishart random variable and inverting the matrix. + /// + /// The random number generator to use. + /// The degree of freedom (ν) for the inverse Wishart distribution. + /// The scale matrix (Ψ) for the inverse Wishart distribution. + /// a sample from the distribution. + public static Matrix Sample(System.Random rnd, double degreesOfFreedom, Matrix scale) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(degreesOfFreedom, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var r = Wishart.Sample(rnd, degreesOfFreedom, scale.Inverse()); + return r.Inverse(); + } +} diff --git a/MathNet.Numerics/Distributions/Laplace.cs b/MathNet.Numerics/Distributions/Laplace.cs new file mode 100644 index 0000000..9d5232a --- /dev/null +++ b/MathNet.Numerics/Distributions/Laplace.cs @@ -0,0 +1,454 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Laplace distribution. +/// The Laplace distribution is a distribution over the real numbers parameterized by a mean and +/// scale parameter. The PDF is: +/// p(x) = \frac{1}{2 * scale} \exp{- |x - mean| / scale}. +/// Wikipedia - Laplace distribution. +/// +public class Laplace : IContinuousDistribution +{ + System.Random _random; + + readonly double _location; + readonly double _scale; + + /// + /// Initializes a new instance of the class (location = 0, scale = 1). + /// + public Laplace() + : this(0.0, 1.0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// If is negative. + public Laplace(double location, double scale) + { + if (!IsValidParameterSet(location, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _location = location; + _scale = scale; + } + + /// + /// Initializes a new instance of the class. + /// + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// The random number generator which is used to draw random samples. + /// If is negative. + public Laplace(double location, double scale, System.Random randomSource) + { + if (!IsValidParameterSet(location, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _location = location; + _scale = scale; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Laplace(μ = " + _location + ", b = " + _scale + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + public static bool IsValidParameterSet(double location, double scale) + { + return scale > 0.0 && !double.IsNaN(location); + } + + /// + /// Gets the location (μ) of the Laplace distribution. + /// + public double Location + { + get { return _location; } + } + + /// + /// Gets the scale (b) of the Laplace distribution. Range: b > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return _location; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return 2.0 * _scale * _scale; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Constants.Sqrt2 * _scale; } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return Math.Log(2.0 * Constants.E * _scale); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return 0.0; } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get { return _location; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return _location; } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return double.NegativeInfinity; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return Math.Exp(-Math.Abs(x - _location) / _scale) / (2.0 * _scale); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return -Math.Abs(x - _location) / _scale - Math.Log(2.0 * _scale); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return 0.5 * (1.0 + (Math.Sign(x - _location) * (1.0 - Math.Exp(-Math.Abs(x - _location) / _scale)))); + } + + /// + /// Samples a Laplace distributed random variable. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _location, _scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _location, _scale); + } + + /// + /// Generates a sample from the Laplace distribution. + /// + /// a sample from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _location, _scale); + } + + static double SampleUnchecked(System.Random rnd, double location, double scale) + { + var u = rnd.NextDouble() - 0.5; + return location - (scale * Math.Sign(u) * Math.Log(1.0 - (2.0 * Math.Abs(u)))); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double location, double scale) + { + rnd.NextDoubles(values); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + var u = values[i] - 0.5; + values[i] = location - (scale * Math.Sign(u) * Math.Log(1.0 - (2.0 * Math.Abs(u)))); + } + }); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double location, double scale) + { + while (true) + { + yield return SampleUnchecked(rnd, location, scale); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double location, double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Math.Exp(-Math.Abs(x - location) / scale) / (2.0 * scale); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double location, double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return -Math.Abs(x - location) / scale - Math.Log(2.0 * scale); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double location, double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return 0.5 * (1.0 + (Math.Sign(x - location) * (1.0 - Math.Exp(-Math.Abs(x - location) / scale)))); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, location, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, location, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, location, scale); + } + + /// + /// Generates a sample from the distribution. + /// + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// a sample from the distribution. + public static double Sample(double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, location, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, location, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The location (μ) of the distribution. + /// The scale (b) of the distribution. Range: b > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double location, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, location, scale); + } +} diff --git a/MathNet.Numerics/Distributions/LogNormal.cs b/MathNet.Numerics/Distributions/LogNormal.cs new file mode 100644 index 0000000..8440bb5 --- /dev/null +++ b/MathNet.Numerics/Distributions/LogNormal.cs @@ -0,0 +1,555 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Statistics; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Log-Normal distribution. +/// For details about this distribution, see +/// Wikipedia - Log-Normal distribution. +/// +public class LogNormal : IContinuousDistribution +{ + System.Random _random; + + readonly double _mu; + readonly double _sigma; + + /// + /// Initializes a new instance of the class. + /// The distribution will be initialized with the default + /// random number generator. + /// + /// The log-scale (μ) of the logarithm of the distribution. + /// The shape (σ) of the logarithm of the distribution. Range: σ ≥ 0. + public LogNormal(double mu, double sigma) + { + if (!IsValidParameterSet(mu, sigma)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _mu = mu; + _sigma = sigma; + } + + /// + /// Initializes a new instance of the class. + /// The distribution will be initialized with the default + /// random number generator. + /// + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// The random number generator which is used to draw random samples. + public LogNormal(double mu, double sigma, System.Random randomSource) + { + if (!IsValidParameterSet(mu, sigma)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _mu = mu; + _sigma = sigma; + } + + /// + /// Constructs a log-normal distribution with the desired mu and sigma parameters. + /// + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// The random number generator which is used to draw random samples. Optional, can be null. + /// A log-normal distribution. + public static LogNormal WithMuSigma(double mu, double sigma, System.Random randomSource = null) + { + return new LogNormal(mu, sigma, randomSource); + } + + /// + /// Constructs a log-normal distribution with the desired mean and variance. + /// + /// The mean of the log-normal distribution. + /// The variance of the log-normal distribution. + /// The random number generator which is used to draw random samples. Optional, can be null. + /// A log-normal distribution. + public static LogNormal WithMeanVariance(double mean, double var, System.Random randomSource = null) + { + var sigma2 = Math.Log(var / (mean * mean) + 1.0); + return new LogNormal(Math.Log(mean) - sigma2 / 2.0, Math.Sqrt(sigma2), randomSource); + } + + /// + /// Estimates the log-normal distribution parameters from sample data with maximum-likelihood. + /// + /// The samples to estimate the distribution parameters from. + /// The random number generator which is used to draw random samples. Optional, can be null. + /// A log-normal distribution. + /// MATLAB: lognfit + public static LogNormal Estimate(IEnumerable samples, System.Random randomSource = null) + { + var muSigma = samples.Select(s => Math.Log(s)).MeanStandardDeviation(); + return new LogNormal(muSigma.Item1, muSigma.Item2, randomSource); + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "LogNormal(μ = " + _mu + ", σ = " + _sigma + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + public static bool IsValidParameterSet(double mu, double sigma) + { + return sigma >= 0.0 && !double.IsNaN(mu); + } + + /// + /// Gets the log-scale (μ) (mean of the logarithm) of the distribution. + /// + public double Mu + { + get { return _mu; } + } + + /// + /// Gets the shape (σ) (standard deviation of the logarithm) of the distribution. Range: σ ≥ 0. + /// + public double Sigma + { + get { return _sigma; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mu of the log-normal distribution. + /// + public double Mean + { + get { return Math.Exp(_mu + (_sigma * _sigma / 2.0)); } + } + + /// + /// Gets the variance of the log-normal distribution. + /// + public double Variance + { + get + { + var sigma2 = _sigma * _sigma; + return (Math.Exp(sigma2) - 1.0) * Math.Exp(_mu + _mu + sigma2); + } + } + + /// + /// Gets the standard deviation of the log-normal distribution. + /// + public double StdDev + { + get + { + var sigma2 = _sigma * _sigma; + return Math.Sqrt((Math.Exp(sigma2) - 1.0) * Math.Exp(_mu + _mu + sigma2)); + } + } + + /// + /// Gets the entropy of the log-normal distribution. + /// + public double Entropy + { + get { return 0.5 + Math.Log(_sigma) + _mu + Constants.LogSqrt2Pi; } + } + + /// + /// Gets the skewness of the log-normal distribution. + /// + public double Skewness + { + get + { + var expsigma2 = Math.Exp(_sigma * _sigma); + return (expsigma2 + 2.0) * Math.Sqrt(expsigma2 - 1); + } + } + + /// + /// Gets the mode of the log-normal distribution. + /// + public double Mode + { + get { return Math.Exp(_mu - (_sigma * _sigma)); } + } + + /// + /// Gets the median of the log-normal distribution. + /// + public double Median + { + get { return Math.Exp(_mu); } + } + + /// + /// Gets the minimum of the log-normal distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the log-normal distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + if (x < 0.0) + { + return 0.0; + } + + var a = (Math.Log(x) - _mu) / _sigma; + return Math.Exp(-0.5 * a * a) / (x * _sigma * Constants.Sqrt2Pi); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + if (x < 0.0) + { + return double.NegativeInfinity; + } + + var a = (Math.Log(x) - _mu) / _sigma; + return (-0.5 * a * a) - Math.Log(x * _sigma) - Constants.LogSqrt2Pi; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return x < 0.0 ? 0.0 + : 0.5 * SpecialFunctions.Erfc((_mu - Math.Log(x)) / (_sigma * Constants.Sqrt2)); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return p <= 0.0 ? 0.0 : p >= 1.0 ? double.PositiveInfinity + : Math.Exp(_mu - _sigma * Constants.Sqrt2 * SpecialFunctions.ErfcInv(2.0 * p)); + } + + /// + /// Generates a sample from the log-normal distribution using the Box-Muller algorithm. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _mu, _sigma); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _mu, _sigma); + } + + /// + /// Generates a sequence of samples from the log-normal distribution using the Box-Muller algorithm. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _mu, _sigma); + } + + static double SampleUnchecked(System.Random rnd, double mu, double sigma) + { + return Math.Exp(Normal.SampleUnchecked(rnd, mu, sigma)); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double mu, double sigma) + { + return Normal.SamplesUnchecked(rnd, mu, sigma).Select(Math.Exp); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double mu, double sigma) + { + Normal.SamplesUnchecked(rnd, values, mu, sigma); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = Math.Exp(values[i]); + } + }); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// the density at . + /// + /// MATLAB: lognpdf + public static double PDF(double mu, double sigma, double x) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0) + { + return 0.0; + } + + var a = (Math.Log(x) - mu) / sigma; + return Math.Exp(-0.5 * a * a) / (x * sigma * Constants.Sqrt2Pi); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the density. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// the log density at . + /// + public static double PDFLn(double mu, double sigma, double x) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0) + { + return double.NegativeInfinity; + } + + var a = (Math.Log(x) - mu) / sigma; + return (-0.5 * a * a) - Math.Log(x * sigma) - Constants.LogSqrt2Pi; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// the cumulative distribution at location . + /// + /// MATLAB: logncdf + public static double CDF(double mu, double sigma, double x) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return x < 0.0 ? 0.0 + : 0.5 * (1.0 + SpecialFunctions.Erf((Math.Log(x) - mu) / (sigma * Constants.Sqrt2))); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// the inverse cumulative density at . + /// + /// MATLAB: logninv + public static double InvCDF(double mu, double sigma, double p) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return p <= 0.0 ? 0.0 : p >= 1.0 ? double.PositiveInfinity + : Math.Exp(mu - sigma * Constants.Sqrt2 * SpecialFunctions.ErfcInv(2.0 * p)); + } + + /// + /// Generates a sample from the log-normal distribution using the Box-Muller algorithm. + /// + /// The random number generator to use. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double mu, double sigma) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, mu, sigma); + } + + /// + /// Generates a sequence of samples from the log-normal distribution using the Box-Muller algorithm. + /// + /// The random number generator to use. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double mu, double sigma) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, mu, sigma); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double mu, double sigma) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, mu, sigma); + } + + /// + /// Generates a sample from the log-normal distribution using the Box-Muller algorithm. + /// + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// a sample from the distribution. + public static double Sample(double mu, double sigma) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, mu, sigma); + } + + /// + /// Generates a sequence of samples from the log-normal distribution using the Box-Muller algorithm. + /// + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double mu, double sigma) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, mu, sigma); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double mu, double sigma) + { + if (sigma < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, mu, sigma); + } +} diff --git a/MathNet.Numerics/Distributions/MatrixNormal.cs b/MathNet.Numerics/Distributions/MatrixNormal.cs new file mode 100644 index 0000000..a88421c --- /dev/null +++ b/MathNet.Numerics/Distributions/MatrixNormal.cs @@ -0,0 +1,278 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Double; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; + +namespace MathNet.Numerics.Distributions; + +/// +/// Multivariate Matrix-valued Normal distributions. The distribution +/// is parameterized by a mean matrix (M), a covariance matrix for the rows (V) and a covariance matrix +/// for the columns (K). If the dimension of M is d-by-m then V is d-by-d and K is m-by-m. +/// Wikipedia - MatrixNormal distribution. +/// +public class MatrixNormal : IDistribution +{ + System.Random _random; + + /// + /// The mean of the matrix normal distribution. + /// + readonly Matrix _m; + + /// + /// The covariance matrix for the rows. + /// + readonly Matrix _v; + + /// + /// The covariance matrix for the columns. + /// + readonly Matrix _k; + + /// + /// Initializes a new instance of the class. + /// + /// The mean of the matrix normal. + /// The covariance matrix for the rows. + /// The covariance matrix for the columns. + /// If the dimensions of the mean and two covariance matrices don't match. + public MatrixNormal(Matrix m, Matrix v, Matrix k) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(m, v, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _m = m; + _v = v; + _k = k; + } + + /// + /// Initializes a new instance of the class. + /// + /// The mean of the matrix normal. + /// The covariance matrix for the rows. + /// The covariance matrix for the columns. + /// The random number generator which is used to draw random samples. + /// If the dimensions of the mean and two covariance matrices don't match. + public MatrixNormal(Matrix m, Matrix v, Matrix k, System.Random randomSource) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(m, v, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _m = m; + _v = v; + _k = k; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return "MatrixNormal(Rows = " + _m.RowCount + ", Columns = " + _m.ColumnCount + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The mean of the matrix normal. + /// The covariance matrix for the rows. + /// The covariance matrix for the columns. + public static bool IsValidParameterSet(Matrix m, Matrix v, Matrix k) + { + var n = m.RowCount; + var p = m.ColumnCount; + if (v.ColumnCount != n || v.RowCount != n) + { + return false; + } + + if (k.ColumnCount != p || k.RowCount != p) + { + return false; + } + + for (var i = 0; i < v.RowCount; i++) + { + if (v.At(i, i) <= 0) + { + return false; + } + } + + for (var i = 0; i < k.RowCount; i++) + { + if (k.At(i, i) <= 0) + { + return false; + } + } + + return true; + } + + /// + /// Gets the mean. (M) + /// + /// The mean of the distribution. + public Matrix Mean + { + get { return _m; } + } + + /// + /// Gets the row covariance. (V) + /// + /// The row covariance. + public Matrix RowCovariance + { + get { return _v; } + } + + /// + /// Gets the column covariance. (K) + /// + /// The column covariance. + public Matrix ColumnCovariance + { + get { return _k; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Evaluates the probability density function for the matrix normal distribution. + /// + /// The matrix at which to evaluate the density at. + /// the density at + /// If the argument does not have the correct dimensions. + public double Density(Matrix x) + { + if (x.RowCount != _m.RowCount || x.ColumnCount != _m.ColumnCount) + { + throw Matrix.DimensionsDontMatch(x, _m, "x"); + } + + var a = x - _m; + var cholV = _v.Cholesky(); + var cholK = _k.Cholesky(); + + return Math.Exp(-0.5 * cholK.Solve(a.Transpose() * cholV.Solve(a)).Trace()) + / Math.Pow(2.0 * Constants.Pi, x.RowCount * x.ColumnCount / 2.0) + / Math.Pow(cholK.Determinant, x.RowCount / 2.0) + / Math.Pow(cholV.Determinant, x.ColumnCount / 2.0); + } + + /// + /// Samples a matrix normal distributed random variable. + /// + /// A random number from this distribution. + public Matrix Sample() + { + return Sample(_random, _m, _v, _k); + } + + /// + /// Samples a matrix normal distributed random variable. + /// + /// The random number generator to use. + /// The mean of the matrix normal. + /// The covariance matrix for the rows. + /// The covariance matrix for the columns. + /// If the dimensions of the mean and two covariance matrices don't match. + /// a sequence of samples from the distribution. + public static Matrix Sample(System.Random rnd, Matrix m, Matrix v, Matrix k) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(m, v, k)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var n = m.RowCount; + var p = m.ColumnCount; + + // Compute the Kronecker product of V and K, this is the covariance matrix for the stacked matrix. + var vki = v.KroneckerProduct(k.Inverse()); + + // Sample a vector valued random variable with VKi as the covariance. + var vector = SampleVectorNormal(rnd, new DenseVector(n * p), vki); + + // Unstack the vector v and add the mean. + var r = m.Clone(); + for (var i = 0; i < n; i++) + { + for (var j = 0; j < p; j++) + { + r.At(i, j, r.At(i, j) + vector[(j * n) + i]); + } + } + + return r; + } + + /// + /// Samples a vector normal distributed random variable. + /// + /// The random number generator to use. + /// The mean of the vector normal distribution. + /// The covariance matrix of the vector normal distribution. + /// a sequence of samples from defined distribution. + static Vector SampleVectorNormal(System.Random rnd, Vector mean, Matrix covariance) + { + var chol = covariance.Cholesky(); + + // Sample a standard normal variable. + var v = Vector.Build.Random(mean.Count, new Normal(rnd)); + + // Return the transformed variable. + return mean + (chol.Factor * v); + } +} diff --git a/MathNet.Numerics/Distributions/Multinomial.cs b/MathNet.Numerics/Distributions/Multinomial.cs new file mode 100644 index 0000000..e67207c --- /dev/null +++ b/MathNet.Numerics/Distributions/Multinomial.cs @@ -0,0 +1,391 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Double; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Statistics; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Multivariate Multinomial distribution. For details about this distribution, see +/// Wikipedia - Multinomial distribution. +/// +/// +/// The distribution is parameterized by a vector of ratios: in other words, the parameter +/// does not have to be normalized and sum to 1. The reason is that some vectors can't be exactly normalized +/// to sum to 1 in floating point representation. +/// +public class Multinomial : IDistribution +{ + System.Random _random; + + /// + /// Stores the normalized multinomial probabilities. + /// + readonly double[] _p; + + /// + /// The number of trials. + /// + readonly int _trials; + + /// + /// Initializes a new instance of the Multinomial class. + /// + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// The number of trials. + /// If any of the probabilities are negative or do not sum to one. + /// If is negative. + public Multinomial(double[] p, int n) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(p, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _p = (double[])p.Clone(); + _trials = n; + } + + /// + /// Initializes a new instance of the Multinomial class. + /// + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// The number of trials. + /// The random number generator which is used to draw random samples. + /// If any of the probabilities are negative or do not sum to one. + /// If is negative. + public Multinomial(double[] p, int n, System.Random randomSource) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(p, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _p = (double[])p.Clone(); + _trials = n; + } + + /// + /// Initializes a new instance of the Multinomial class from histogram . The distribution will + /// not be automatically updated when the histogram changes. + /// + /// Histogram instance + /// The number of trials. + /// If any of the probabilities are negative or do not sum to one. + /// If is negative. + public Multinomial(Histogram h, int n) + { + if (h == null) + { + throw new ArgumentNullException(nameof(h)); + } + + // The probability distribution vector. + var p = new double[h.BucketCount]; + + // Fill in the distribution vector. + for (var i = 0; i < h.BucketCount; i++) + { + p[i] = h[i].Count; + } + + if (Control.CheckDistributionParameters && !IsValidParameterSet(p, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _p = (double[])p.Clone(); + _trials = n; + RandomSource = SystemRandomSource.Default; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Multinomial(Dimension = " + _p.Length + ", Number of Trails = " + _trials + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// The number of trials. + /// If any of the probabilities are negative returns false, + /// if the sum of parameters is 0.0, or if the number of trials is negative; otherwise true. + public static bool IsValidParameterSet(IEnumerable p, int n) + { + var sum = 0.0; + foreach (var t in p) + { + if (t < 0.0 || double.IsNaN(t)) + { + return false; + } + + sum += t; + } + + if (sum == 0.0) + { + return false; + } + + return n >= 0; + } + + /// + /// Gets the proportion of ratios. + /// + public double[] P + { + get { return (double[])_p.Clone(); } + } + + /// + /// Gets the number of trials. + /// + public int N + { + get { return _trials; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public Vector Mean + { + get { return _trials * (DenseVector)P; } + } + + /// + /// Gets the variance of the distribution. + /// + public Vector Variance + { + get + { + // Do not use _p, because operations below will modify _p array. Use P or _p.Clone(). + var res = (DenseVector)P; + for (var i = 0; i < res.Count; i++) + { + res[i] *= _trials * (1 - res[i]); + } + + return res; + } + } + + /// + /// Gets the skewness of the distribution. + /// + public Vector Skewness + { + get + { + // Do not use _p, because operations below will modify _p array. Use P or _p.Clone(). + var res = (DenseVector)P; + for (var i = 0; i < res.Count; i++) + { + res[i] = (1.0 - (2.0 * res[i])) / Math.Sqrt(_trials * (1.0 - res[i]) * res[i]); + } + + return res; + } + } + + /// + /// Computes values of the probability mass function. + /// + /// Non-negative integers x1, ..., xk + /// The probability mass at location . + /// When is null. + /// When length of is not equal to event probabilities count. + public double Probability(int[] x) + { + if (null == x) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != _p.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(x)); + } + + if (x.Sum() == _trials) + { + var coef = SpecialFunctions.Multinomial(_trials, x); + var num = 1.0; + for (var i = 0; i < x.Length; i++) + { + num *= Math.Pow(_p[i], x[i]); + } + + return coef * num; + } + + return 0.0; + } + + /// + /// Computes values of the log probability mass function. + /// + /// Non-negative integers x1, ..., xk + /// The log probability mass at location . + /// When is null. + /// When length of is not equal to event probabilities count. + public double ProbabilityLn(int[] x) + { + if (null == x) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != _p.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(x)); + } + + if (x.Sum() == _trials) + { + var coef = Math.Log(SpecialFunctions.Multinomial(_trials, x)); + var num = x.Select((t, i) => t * Math.Log(_p[i])).Sum(); + return coef + num; + } + + return 0.0; + } + + /// + /// Samples one multinomial distributed random variable. + /// + /// the counts for each of the different possible values. + public int[] Sample() + { + return Sample(_random, _p, _trials); + } + + /// + /// Samples a sequence multinomially distributed random variables. + /// + /// a sequence of counts for each of the different possible values. + public IEnumerable Samples() + { + while (true) + { + yield return Sample(_random, _p, _trials); + } + } + + /// + /// Samples one multinomial distributed random variable. + /// + /// The random number generator to use. + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// The number of trials. + /// the counts for each of the different possible values. + public static int[] Sample(System.Random rnd, double[] p, int n) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(p, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + // The cumulative density of p. + var cp = Categorical.ProbabilityMassToCumulativeDistribution(p); + + // The variable that stores the counts. + var ret = new int[p.Length]; + + for (var i = 0; i < n; i++) + { + ret[Categorical.SampleUnchecked(rnd, cp)]++; + } + + return ret; + } + + /// + /// Samples a multinomially distributed random variable. + /// + /// The random number generator to use. + /// An array of nonnegative ratios: this array does not need to be normalized + /// as this is often impossible using floating point arithmetic. + /// The number of variables needed. + /// a sequence of counts for each of the different possible values. + public static IEnumerable Samples(System.Random rnd, double[] p, int n) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(p, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + // The cumulative density of p. + var cp = Categorical.ProbabilityMassToCumulativeDistribution(p); + + while (true) + { + // The variable that stores the counts. + var ret = new int[p.Length]; + + for (var i = 0; i < n; i++) + { + ret[Categorical.SampleUnchecked(rnd, cp)]++; + } + + yield return ret; + } + } +} diff --git a/MathNet.Numerics/Distributions/NegativeBinomial.cs b/MathNet.Numerics/Distributions/NegativeBinomial.cs new file mode 100644 index 0000000..4881fb5 --- /dev/null +++ b/MathNet.Numerics/Distributions/NegativeBinomial.cs @@ -0,0 +1,444 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Negative Binomial distribution. +/// The negative binomial is a distribution over the natural numbers with two parameters r, p. For the special +/// case that r is an integer one can interpret the distribution as the number of failures before the r'th success +/// when the probability of success is p. +/// Wikipedia - NegativeBinomial distribution. +/// +public class NegativeBinomial : IDiscreteDistribution +{ + System.Random _random; + + readonly double _r; + readonly double _p; + + /// + /// Initializes a new instance of the class. + /// + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + public NegativeBinomial(double r, double p) + { + if (!IsValidParameterSet(r, p)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _p = p; + _r = r; + } + + /// + /// Initializes a new instance of the class. + /// + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// The random number generator which is used to draw random samples. + public NegativeBinomial(double r, double p, System.Random randomSource) + { + if (!IsValidParameterSet(r, p)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _p = p; + _r = r; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return "NegativeBinomial(R = " + _r + ", P = " + _p + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + public static bool IsValidParameterSet(double r, double p) + { + return r >= 0.0 && p >= 0.0 && p <= 1.0; + } + + /// + /// Gets the number of successes. Range: r ≥ 0. + /// + public double R + { + get { return _r; } + } + + /// + /// Gets the probability of success. Range: 0 ≤ p ≤ 1. + /// + public double P + { + get { return _p; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return _r * (1.0 - _p) / _p; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return _r * (1.0 - _p) / (_p * _p); } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(_r * (1.0 - _p)) / _p; } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return (2.0 - _p) / Math.Sqrt(_r * (1.0 - _p)); } + } + + /// + /// Gets the mode of the distribution + /// + public int Mode + { + get { return _r > 1.0 ? (int)Math.Floor((_r - 1.0) * (1.0 - _p) / _p) : 0; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return 0; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { return int.MaxValue; } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + return PMF(_r, _p, k); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + return PMFLn(_r, _p, k); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + return CDF(_r, _p, x); + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// the probability mass at location . + public static double PMF(double r, double p, int k) + { + return Math.Exp(PMFLn(r, p, k)); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// the log probability mass at location . + public static double PMFLn(double r, double p, int k) + { + if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SpecialFunctions.GammaLn(r + k) + - SpecialFunctions.GammaLn(r) + - SpecialFunctions.GammaLn(k + 1.0) + + (r * Math.Log(p)) + + (k * Math.Log(1.0 - p)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// the cumulative distribution at location . + /// + public static double CDF(double r, double p, double x) + { + if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return 1 - SpecialFunctions.BetaRegularized(x + 1, r, 1 - p); + } + + /// + /// Samples a negative binomial distributed random variable. + /// + /// The random number generator to use. + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + /// a sample from the distribution. + static int SampleUnchecked(System.Random rnd, double r, double p) + { + var lambda = Gamma.SampleUnchecked(rnd, r, p); + var c = Math.Exp(-lambda); + var p1 = 1.0; + var k = 0; + do + { + k = k + 1; + p1 = p1 * rnd.NextDouble(); + } + while (p1 >= c); + return k - 1; + } + + static void SamplesUnchecked(System.Random rnd, int[] values, double r, double p) + { + for (int i = 0; i < values.Length; i++) + { + values[i] = SampleUnchecked(rnd, r, p); + } + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double r, double p) + { + while (true) + { + yield return SampleUnchecked(rnd, r, p); + } + } + + /// + /// Samples a NegativeBinomial distributed random variable. + /// + /// a sample from the distribution. + public int Sample() + { + return SampleUnchecked(_random, _r, _p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _r, _p); + } + + /// + /// Samples an array of NegativeBinomial distributed random variables. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _r, _p); + } + + /// + /// Samples a random variable. + /// + /// The random number generator to use. + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + public static int Sample(System.Random rnd, double r, double p) + { + if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, r, p); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The random number generator to use. + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + public static IEnumerable Samples(System.Random rnd, double r, double p) + { + if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, r, p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + public static void Samples(System.Random rnd, int[] values, double r, double p) + { + if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, r, p); + } + + /// + /// Samples a random variable. + /// + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + public static int Sample(double r, double p) + { + if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, r, p); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + public static IEnumerable Samples(double r, double p) + { + if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, r, p); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The number of successes (r) required to stop the experiment. Range: r ≥ 0. + /// The probability (p) of a trial resulting in success. Range: 0 ≤ p ≤ 1. + public static void Samples(int[] values, double r, double p) + { + if (!(r >= 0.0 && p >= 0.0 && p <= 1.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, r, p); + } +} diff --git a/MathNet.Numerics/Distributions/Normal.cs b/MathNet.Numerics/Distributions/Normal.cs new file mode 100644 index 0000000..40421d3 --- /dev/null +++ b/MathNet.Numerics/Distributions/Normal.cs @@ -0,0 +1,622 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Statistics; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Normal distribution, also known as Gaussian distribution. +/// For details about this distribution, see +/// Wikipedia - Normal distribution. +/// +public class Normal : IContinuousDistribution +{ + System.Random _random; + + readonly double _mean; + readonly double _stdDev; + + /// + /// Initializes a new instance of the Normal class. This is a normal distribution with mean 0.0 + /// and standard deviation 1.0. The distribution will + /// be initialized with the default random number generator. + /// + public Normal() + : this(0.0, 1.0) + { + } + + /// + /// Initializes a new instance of the Normal class. This is a normal distribution with mean 0.0 + /// and standard deviation 1.0. The distribution will + /// be initialized with the default random number generator. + /// + /// The random number generator which is used to draw random samples. + public Normal(System.Random randomSource) + : this(0.0, 1.0, randomSource) + { + } + + /// + /// Initializes a new instance of the Normal class with a particular mean and standard deviation. The distribution will + /// be initialized with the default random number generator. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + public Normal(double mean, double stddev) + { + if (!IsValidParameterSet(mean, stddev)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _mean = mean; + _stdDev = stddev; + } + + /// + /// Initializes a new instance of the Normal class with a particular mean and standard deviation. The distribution will + /// be initialized with the default random number generator. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// The random number generator which is used to draw random samples. + public Normal(double mean, double stddev, System.Random randomSource) + { + if (!IsValidParameterSet(mean, stddev)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _mean = mean; + _stdDev = stddev; + } + + /// + /// Constructs a normal distribution from a mean and standard deviation. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// The random number generator which is used to draw random samples. Optional, can be null. + /// a normal distribution. + public static Normal WithMeanStdDev(double mean, double stddev, System.Random randomSource = null) + { + return new Normal(mean, stddev, randomSource); + } + + /// + /// Constructs a normal distribution from a mean and variance. + /// + /// The mean (μ) of the normal distribution. + /// The variance (σ^2) of the normal distribution. + /// The random number generator which is used to draw random samples. Optional, can be null. + /// A normal distribution. + public static Normal WithMeanVariance(double mean, double var, System.Random randomSource = null) + { + return new Normal(mean, Math.Sqrt(var), randomSource); + } + + /// + /// Constructs a normal distribution from a mean and precision. + /// + /// The mean (μ) of the normal distribution. + /// The precision of the normal distribution. + /// The random number generator which is used to draw random samples. Optional, can be null. + /// A normal distribution. + public static Normal WithMeanPrecision(double mean, double precision, System.Random randomSource = null) + { + return new Normal(mean, 1.0 / Math.Sqrt(precision), randomSource); + } + + /// + /// Estimates the normal distribution parameters from sample data with maximum-likelihood. + /// + /// The samples to estimate the distribution parameters from. + /// The random number generator which is used to draw random samples. Optional, can be null. + /// A normal distribution. + /// MATLAB: normfit + public static Normal Estimate(IEnumerable samples, System.Random randomSource = null) + { + var meanStdDev = samples.MeanStandardDeviation(); + return new Normal(meanStdDev.Item1, meanStdDev.Item2, randomSource); + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Normal(μ = " + _mean + ", σ = " + _stdDev + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + public static bool IsValidParameterSet(double mean, double stddev) + { + return stddev >= 0.0 && !double.IsNaN(mean); + } + + /// + /// Gets the mean (μ) of the normal distribution. + /// + public double Mean + { + get { return _mean; } + } + + /// + /// Gets the standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// + public double StdDev + { + get { return _stdDev; } + } + + /// + /// Gets the variance of the normal distribution. + /// + public double Variance + { + get { return _stdDev * _stdDev; } + } + + /// + /// Gets the precision of the normal distribution. + /// + public double Precision + { + get { return 1.0 / (_stdDev * _stdDev); } + } + + /// + /// Gets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the entropy of the normal distribution. + /// + public double Entropy + { + get { return Math.Log(_stdDev) + Constants.LogSqrt2PiE; } + } + + /// + /// Gets the skewness of the normal distribution. + /// + public double Skewness + { + get { return 0.0; } + } + + /// + /// Gets the mode of the normal distribution. + /// + public double Mode + { + get { return _mean; } + } + + /// + /// Gets the median of the normal distribution. + /// + public double Median + { + get { return _mean; } + } + + /// + /// Gets the minimum of the normal distribution. + /// + public double Minimum + { + get { return double.NegativeInfinity; } + } + + /// + /// Gets the maximum of the normal distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + var d = (x - _mean) / _stdDev; + return Math.Exp(-0.5 * d * d) / (Constants.Sqrt2Pi * _stdDev); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + var d = (x - _mean) / _stdDev; + return (-0.5 * d * d) - Math.Log(_stdDev) - Constants.LogSqrt2Pi; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return 0.5 * SpecialFunctions.Erfc((_mean - x) / (_stdDev * Constants.Sqrt2)); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return _mean - (_stdDev * Constants.Sqrt2 * SpecialFunctions.ErfcInv(2.0 * p)); + } + + /// + /// Generates a sample from the normal distribution using the Box-Muller algorithm. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _mean, _stdDev); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _mean, _stdDev); + } + + /// + /// Generates a sequence of samples from the normal distribution using the Box-Muller algorithm. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _mean, _stdDev); + } + + internal static double SampleUnchecked(System.Random rnd, double mean, double stddev) + { + double x, y; + while (!PolarTransform(rnd.NextDouble(), rnd.NextDouble(), out x, out y)) + { + } + + return mean + (stddev * x); + } + + internal static IEnumerable SamplesUnchecked(System.Random rnd, double mean, double stddev) + { + while (true) + { + double x, y; + if (!PolarTransform(rnd.NextDouble(), rnd.NextDouble(), out x, out y)) + { + continue; + } + + yield return mean + (stddev * x); + yield return mean + (stddev * y); + } + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double mean, double stddev) + { + if (values.Length == 0) + { + return; + } + + // Since we only accept points within the unit circle + // we need to generate roughly 4/pi=1.27 times the numbers needed. + int n = (int)Math.Ceiling(values.Length * 4 * Constants.InvPi); + if (n.IsOdd()) + { + n++; + } + + var uniform = rnd.NextDoubles(n); + + // Polar transform + double x, y; + int index = 0; + for (int i = 0; i < uniform.Length && index < values.Length; i += 2) + { + if (!PolarTransform(uniform[i], uniform[i + 1], out x, out y)) + { + continue; + } + + values[index++] = mean + stddev * x; + if (index == values.Length) + { + return; + } + + values[index++] = mean + stddev * y; + if (index == values.Length) + { + return; + } + } + + // remaining, if any + while (index < values.Length) + { + if (!PolarTransform(rnd.NextDouble(), rnd.NextDouble(), out x, out y)) + { + continue; + } + + values[index++] = mean + stddev * x; + if (index == values.Length) + { + return; + } + + values[index++] = mean + stddev * y; + if (index == values.Length) + { + return; + } + } + } + + static bool PolarTransform(double a, double b, out double x, out double y) + { + var v1 = (2.0 * a) - 1.0; + var v2 = (2.0 * b) - 1.0; + var r = (v1 * v1) + (v2 * v2); + if (r >= 1.0 || r == 0.0) + { + x = 0; + y = 0; + return false; + } + + var fac = Math.Sqrt(-2.0 * Math.Log(r) / r); + x = v1 * fac; + y = v2 * fac; + return true; + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// The location at which to compute the density. + /// the density at . + /// + /// MATLAB: normpdf + public static double PDF(double mean, double stddev, double x) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var d = (x - mean) / stddev; + return Math.Exp(-0.5 * d * d) / (Constants.Sqrt2Pi * stddev); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double mean, double stddev, double x) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var d = (x - mean) / stddev; + return (-0.5 * d * d) - Math.Log(stddev) - Constants.LogSqrt2Pi; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// the cumulative distribution at location . + /// + /// MATLAB: normcdf + public static double CDF(double mean, double stddev, double x) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return 0.5 * SpecialFunctions.Erfc((mean - x) / (stddev * Constants.Sqrt2)); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// the inverse cumulative density at . + /// + /// MATLAB: norminv + public static double InvCDF(double mean, double stddev, double p) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return mean - (stddev * Constants.Sqrt2 * SpecialFunctions.ErfcInv(2.0 * p)); + } + + /// + /// Generates a sample from the normal distribution using the Box-Muller algorithm. + /// + /// The random number generator to use. + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double mean, double stddev) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, mean, stddev); + } + + /// + /// Generates a sequence of samples from the normal distribution using the Box-Muller algorithm. + /// + /// The random number generator to use. + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double mean, double stddev) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, mean, stddev); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double mean, double stddev) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, mean, stddev); + } + + /// + /// Generates a sample from the normal distribution using the Box-Muller algorithm. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// a sample from the distribution. + public static double Sample(double mean, double stddev) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, mean, stddev); + } + + /// + /// Generates a sequence of samples from the normal distribution using the Box-Muller algorithm. + /// + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double mean, double stddev) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, mean, stddev); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The mean (μ) of the normal distribution. + /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double mean, double stddev) + { + if (stddev < 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, mean, stddev); + } +} diff --git a/MathNet.Numerics/Distributions/NormalGamma.cs b/MathNet.Numerics/Distributions/NormalGamma.cs new file mode 100644 index 0000000..7d8f839 --- /dev/null +++ b/MathNet.Numerics/Distributions/NormalGamma.cs @@ -0,0 +1,414 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// This structure represents the type over which the distribution +/// is defined. +/// +public struct MeanPrecisionPair +{ + /// + /// The mean value. + /// + double _mean; + + /// + /// The precision value. + /// + double _precision; + + /// + /// Initializes a new instance of the struct. + /// + /// The mean of the pair. + /// The precision of the pair. + public MeanPrecisionPair(double m, double p) + { + _mean = m; + _precision = p; + } + + /// + /// Gets or sets the mean of the pair. + /// + public double Mean + { + get { return _mean; } + set { _mean = value; } + } + + /// + /// Gets or sets the precision of the pair. + /// + public double Precision + { + get { return _precision; } + set { _precision = value; } + } +} + +/// +/// Multivariate Normal-Gamma Distribution. +/// The distribution is the conjugate prior distribution for the +/// distribution. It specifies a prior over the mean and precision of the distribution. +/// It is parameterized by four numbers: the mean location, the mean scale, the precision shape and the +/// precision inverse scale. +/// The distribution NG(mu, tau | mloc,mscale,psscale,pinvscale) = Normal(mu | mloc, 1/(mscale*tau)) * Gamma(tau | psscale,pinvscale). +/// The following degenerate cases are special: when the precision is known, +/// the precision shape will encode the value of the precision while the precision inverse scale is positive +/// infinity. When the mean is known, the mean location will encode the value of the mean while the scale +/// will be positive infinity. A completely degenerate NormalGamma distribution with known mean and precision is possible as well. +/// Wikipedia - Normal-Gamma distribution. +/// +public class NormalGamma : IDistribution +{ + System.Random _random; + + readonly double _meanLocation; + readonly double _meanScale; + readonly double _precisionShape; + readonly double _precisionInvScale; + + /// + /// Initializes a new instance of the class. + /// + /// The location of the mean. + /// The scale of the mean. + /// The shape of the precision. + /// The inverse scale of the precision. + public NormalGamma(double meanLocation, double meanScale, double precisionShape, double precisionInverseScale) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(meanLocation, meanScale, precisionShape, precisionInverseScale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _meanLocation = meanLocation; + _meanScale = meanScale; + _precisionShape = precisionShape; + _precisionInvScale = precisionInverseScale; + } + + /// + /// Initializes a new instance of the class. + /// + /// The location of the mean. + /// The scale of the mean. + /// The shape of the precision. + /// The inverse scale of the precision. + /// The random number generator which is used to draw random samples. + public NormalGamma(double meanLocation, double meanScale, double precisionShape, double precisionInverseScale, System.Random randomSource) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(meanLocation, meanScale, precisionShape, precisionInverseScale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _meanLocation = meanLocation; + _meanScale = meanScale; + _precisionShape = precisionShape; + _precisionInvScale = precisionInverseScale; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "NormalGamma(Mean Location = " + _meanLocation + ", Mean Scale = " + _meanScale + + ", Precision Shape = " + _precisionShape + ", Precision Inverse Scale = " + _precisionInvScale + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The location of the mean. + /// The scale of the mean. + /// The shape of the precision. + /// The inverse scale of the precision. + public static bool IsValidParameterSet(double meanLocation, double meanScale, double precShape, double precInvScale) + { + return meanScale > 0.0 && precShape > 0.0 && precInvScale > 0.0 && !double.IsNaN(meanLocation); + } + + /// + /// Gets the location of the mean. + /// + public double MeanLocation + { + get { return _meanLocation; } + } + + /// + /// Gets the scale of the mean. + /// + public double MeanScale + { + get { return _meanScale; } + } + + /// + /// Gets the shape of the precision. + /// + public double PrecisionShape + { + get { return _precisionShape; } + } + + /// + /// Gets the inverse scale of the precision. + /// + public double PrecisionInverseScale + { + get { return _precisionInvScale; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Returns the marginal distribution for the mean of the NormalGamma distribution. + /// + /// the marginal distribution for the mean of the NormalGamma distribution. + public StudentT MeanMarginal() + { + if (double.IsPositiveInfinity(_precisionInvScale)) + { + return new StudentT(_meanLocation, 1.0 / (_meanScale * _precisionShape), double.PositiveInfinity); + } + + return new StudentT(_meanLocation, Math.Sqrt(_precisionInvScale / (_meanScale * _precisionShape)), 2.0 * _precisionShape); + } + + /// + /// Returns the marginal distribution for the precision of the distribution. + /// + /// The marginal distribution for the precision of the distribution/ + public Gamma PrecisionMarginal() + { + return new Gamma(_precisionShape, _precisionInvScale); + } + + /// + /// Gets the mean of the distribution. + /// + /// The mean of the distribution. + public MeanPrecisionPair Mean + { + get { return double.IsPositiveInfinity(_precisionInvScale) ? new MeanPrecisionPair(_meanLocation, _precisionShape) : new MeanPrecisionPair(_meanLocation, _precisionShape / _precisionInvScale); } + } + + /// + /// Gets the variance of the distribution. + /// + /// The mean of the distribution. + public MeanPrecisionPair Variance + { + get { return new MeanPrecisionPair(_precisionInvScale / (_meanScale * (_precisionShape - 1)), _precisionShape / Math.Sqrt(_precisionInvScale)); } + } + + /// + /// Evaluates the probability density function for a NormalGamma distribution. + /// + /// The mean/precision pair of the distribution + /// Density value + public double Density(MeanPrecisionPair mp) + { + return Density(mp.Mean, mp.Precision); + } + + /// + /// Evaluates the probability density function for a NormalGamma distribution. + /// + /// The mean of the distribution + /// The precision of the distribution + /// Density value + public double Density(double mean, double prec) + { + if (double.IsPositiveInfinity(_precisionInvScale) && _meanScale == 0.0) + { + throw new NotSupportedException(); + } + + if (double.IsPositiveInfinity(_precisionInvScale)) + { + throw new NotSupportedException(); + } + + if (_meanScale <= 0.0) + { + throw new NotSupportedException(); + } + + if (_precisionShape > 160.0) + { + return Math.Exp(DensityLn(mean, prec)); + } + + // double e = -0.5 * prec * (mean - _meanLocation) * (mean - _meanLocation) - prec * _precisionInvScale; + // return Math.Pow(prec * _precisionInvScale, _precisionShape) * Math.Exp(e) / (Constants.Sqrt2Pi * Math.Sqrt(prec) * SpecialFunctions.Gamma(_precisionShape)); + double e = -(0.5 * prec * _meanScale * (mean - _meanLocation) * (mean - _meanLocation)) - (prec * _precisionInvScale); + return Math.Pow(prec * _precisionInvScale, _precisionShape) * Math.Exp(e) * Math.Sqrt(_meanScale) + / (Constants.Sqrt2Pi * Math.Sqrt(prec) * SpecialFunctions.Gamma(_precisionShape)); + } + + /// + /// Evaluates the log probability density function for a NormalGamma distribution. + /// + /// The mean/precision pair of the distribution + /// The log of the density value + public double DensityLn(MeanPrecisionPair mp) + { + return DensityLn(mp.Mean, mp.Precision); + } + + /// + /// Evaluates the log probability density function for a NormalGamma distribution. + /// + /// The mean of the distribution + /// The precision of the distribution + /// The log of the density value + public double DensityLn(double mean, double prec) + { + if (double.IsPositiveInfinity(_precisionInvScale) && _meanScale == 0.0) + { + throw new NotSupportedException(); + } + + if (double.IsPositiveInfinity(_precisionInvScale)) + { + throw new NotSupportedException(); + } + + if (_meanScale <= 0.0) + { + throw new NotSupportedException(); + } + + // double e = -0.5 * prec * (mean - _meanLocation) * (mean - _meanLocation) - prec * _precisionInvScale; + // return (_precisionShape - 0.5) * Math.Log(prec) + _precisionShape * Math.Log(_precisionInvScale) + e - Constants.LogSqrt2Pi - SpecialFunctions.GammaLn(_precisionShape); + double e = -(0.5 * prec * _meanScale * (mean - _meanLocation) * (mean - _meanLocation)) - (prec * _precisionInvScale); + return ((_precisionShape - 0.5) * Math.Log(prec)) + (_precisionShape * Math.Log(_precisionInvScale)) - (0.5 * Math.Log(_meanScale)) + e - Constants.LogSqrt2Pi - SpecialFunctions.GammaLn(_precisionShape); + } + + /// + /// Generates a sample from the NormalGamma distribution. + /// + /// a sample from the distribution. + public MeanPrecisionPair Sample() + { + return Sample(_random, _meanLocation, _meanScale, _precisionShape, _precisionInvScale); + } + + /// + /// Generates a sequence of samples from the NormalGamma distribution + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + while (true) + { + yield return Sample(_random, _meanLocation, _meanScale, _precisionShape, _precisionInvScale); + } + } + + /// + /// Generates a sample from the NormalGamma distribution. + /// + /// The random number generator to use. + /// The location of the mean. + /// The scale of the mean. + /// The shape of the precision. + /// The inverse scale of the precision. + /// a sample from the distribution. + public static MeanPrecisionPair Sample(System.Random rnd, double meanLocation, double meanScale, double precisionShape, double precisionInverseScale) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(meanLocation, meanScale, precisionShape, precisionInverseScale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var mp = new MeanPrecisionPair(); + + // Sample the precision. + mp.Precision = double.IsPositiveInfinity(precisionInverseScale) ? precisionShape : Gamma.Sample(rnd, precisionShape, precisionInverseScale); + + // Sample the mean. + mp.Mean = meanScale == 0.0 ? meanLocation : Normal.Sample(rnd, meanLocation, Math.Sqrt(1.0 / (meanScale * mp.Precision))); + + return mp; + } + + /// + /// Generates a sequence of samples from the NormalGamma distribution + /// + /// The random number generator to use. + /// The location of the mean. + /// The scale of the mean. + /// The shape of the precision. + /// The inverse scale of the precision. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double meanLocation, double meanScale, double precisionShape, double precisionInvScale) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(meanLocation, meanScale, precisionShape, precisionInvScale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + while (true) + { + var mp = new MeanPrecisionPair(); + + // Sample the precision. + mp.Precision = double.IsPositiveInfinity(precisionInvScale) ? precisionShape : Gamma.Sample(rnd, precisionShape, precisionInvScale); + + // Sample the mean. + mp.Mean = meanScale == 0.0 ? meanLocation : Normal.Sample(rnd, meanLocation, Math.Sqrt(1.0 / (meanScale * mp.Precision))); + + yield return mp; + } + } +} diff --git a/MathNet.Numerics/Distributions/Pareto.cs b/MathNet.Numerics/Distributions/Pareto.cs new file mode 100644 index 0000000..83bb1e1 --- /dev/null +++ b/MathNet.Numerics/Distributions/Pareto.cs @@ -0,0 +1,491 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Pareto distribution. +/// The Pareto distribution is a power law probability distribution that coincides with social, +/// scientific, geophysical, actuarial, and many other types of observable phenomena. +/// For details about this distribution, see +/// Wikipedia - Pareto distribution. +/// +public class Pareto : IContinuousDistribution +{ + System.Random _random; + + readonly double _scale; + readonly double _shape; + + /// + /// Initializes a new instance of the class. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// If or are negative. + public Pareto(double scale, double shape) + { + if (!IsValidParameterSet(scale, shape)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _scale = scale; + _shape = shape; + } + + /// + /// Initializes a new instance of the class. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The random number generator which is used to draw random samples. + /// If or are negative. + public Pareto(double scale, double shape, System.Random randomSource) + { + if (!IsValidParameterSet(scale, shape)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _scale = scale; + _shape = shape; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Pareto(xm = " + _scale + ", α = " + _shape + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + public static bool IsValidParameterSet(double scale, double shape) + { + return scale > 0.0 && shape > 0.0; + } + + /// + /// Gets the scale (xm) of the distribution. Range: xm > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets the shape (α) of the distribution. Range: α > 0. + /// + public double Shape + { + get { return _shape; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get + { + if (_shape <= 1) + { + throw new NotSupportedException(); + } + + return _shape * _scale / (_shape - 1.0); + } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + if (_shape <= 2.0) + { + return double.PositiveInfinity; + } + + return _scale * _scale * _shape / ((_shape - 1.0) * (_shape - 1.0) * (_shape - 2.0)); + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return (_scale * Math.Sqrt(_shape)) / (Math.Abs(_shape - 1.0) * Math.Sqrt(_shape - 2.0)); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return Math.Log(_shape / _scale) - (1.0 / _shape) - 1.0; } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return (2.0 * (_shape + 1.0) / (_shape - 3.0)) * Math.Sqrt((_shape - 2.0) / _shape); } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get { return _scale; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return _scale * Math.Pow(2.0, 1.0 / _shape); } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return _scale; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return _shape * Math.Pow(_scale, _shape) / Math.Pow(x, _shape + 1.0); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return Math.Log(_shape) + _shape * Math.Log(_scale) - (_shape + 1.0) * Math.Log(x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return 1.0 - Math.Pow(_scale / x, _shape); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return _scale * Math.Pow(1.0 - p, -1.0 / _shape); + } + + /// + /// Draws a random sample from the distribution. + /// + /// A random number from this distribution. + public double Sample() + { + return SampleUnchecked(_random, _scale, _shape); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _scale, _shape); + } + + /// + /// Generates a sequence of samples from the Pareto distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _scale, _shape); + } + + static double SampleUnchecked(System.Random rnd, double scale, double shape) + { + return scale * Math.Pow(rnd.NextDouble(), -1.0 / shape); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double scale, double shape) + { + var power = -1.0 / shape; + return rnd.NextDoubleSequence().Select(x => scale * Math.Pow(x, power)); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double scale, double shape) + { + var power = -1.0 / shape; + rnd.NextDoubles(values); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = scale * Math.Pow(values[i], power); + } + }); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double scale, double shape, double x) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return shape * Math.Pow(scale, shape) / Math.Pow(x, shape + 1.0); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double scale, double shape, double x) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Math.Log(shape) + shape * Math.Log(scale) - (shape + 1.0) * Math.Log(x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double scale, double shape, double x) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return 1.0 - Math.Pow(scale / x, shape); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// the inverse cumulative density at . + /// + public static double InvCDF(double scale, double shape, double p) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return scale * Math.Pow(1.0 - p, -1.0 / shape); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double scale, double shape) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return scale * Math.Pow(rnd.NextDouble(), -1.0 / shape); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double scale, double shape) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, scale, shape); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double scale, double shape) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, scale, shape); + } + + /// + /// Generates a sample from the distribution. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// a sample from the distribution. + public static double Sample(double scale, double shape) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, scale, shape); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double scale, double shape) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, scale, shape); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double scale, double shape) + { + if (scale <= 0.0 || shape <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, scale, shape); + } +} diff --git a/MathNet.Numerics/Distributions/Poisson.cs b/MathNet.Numerics/Distributions/Poisson.cs new file mode 100644 index 0000000..78162cd --- /dev/null +++ b/MathNet.Numerics/Distributions/Poisson.cs @@ -0,0 +1,529 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Poisson distribution. +/// +/// +/// Distribution is described at Wikipedia - Poisson distribution. +/// Knuth's method is used to generate Poisson distributed random variables. +/// f(x) = exp(-λ)*λ^x/x!; +/// +public class Poisson : IDiscreteDistribution +{ + System.Random _random; + + readonly double _lambda; + + /// + /// Initializes a new instance of the class. + /// + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// If is equal or less then 0.0. + public Poisson(double lambda) + { + if (!IsValidParameterSet(lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _lambda = lambda; + } + + /// + /// Initializes a new instance of the class. + /// + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// The random number generator which is used to draw random samples. + /// If is equal or less then 0.0. + public Poisson(double lambda, System.Random randomSource) + { + if (!IsValidParameterSet(lambda)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _lambda = lambda; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return "Poisson(λ = " + _lambda + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + public static bool IsValidParameterSet(double lambda) + { + return lambda > 0.0; + } + + /// + /// Gets the Poisson distribution parameter λ. Range: λ > 0. + /// + public double Lambda + { + get { return _lambda; } + } + + /// + /// Gets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return _lambda; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return _lambda; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(_lambda); } + } + + /// + /// Gets the entropy of the distribution. + /// + /// Approximation, see Wikipedia Poisson distribution + public double Entropy + { + get { return (0.5 * Math.Log(2 * Constants.Pi * Constants.E * _lambda)) - (1.0 / (12.0 * _lambda)) - (1.0 / (24.0 * _lambda * _lambda)) - (19.0 / (360.0 * _lambda * _lambda * _lambda)); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return 1.0 / Math.Sqrt(_lambda); } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return 0; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { return int.MaxValue; } + } + + /// + /// Gets the mode of the distribution. + /// + public int Mode + { + get { return (int)Math.Floor(_lambda); } + } + + /// + /// Gets the median of the distribution. + /// + /// Approximation, see Wikipedia Poisson distribution + public double Median + { + get { return Math.Floor(_lambda + (1.0 / 3.0) - (0.02 / _lambda)); } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + return Math.Exp(-_lambda + (k * Math.Log(_lambda)) - SpecialFunctions.FactorialLn(k)); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + return -_lambda + (k * Math.Log(_lambda)) - SpecialFunctions.FactorialLn(k); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + return 1.0 - SpecialFunctions.GammaLowerRegularized(x + 1, _lambda); + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// the probability mass at location . + public static double PMF(double lambda, int k) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Math.Exp(-lambda + (k * Math.Log(lambda)) - SpecialFunctions.FactorialLn(k)); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// the log probability mass at location . + public static double PMFLn(double lambda, int k) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return -lambda + (k * Math.Log(lambda)) - SpecialFunctions.FactorialLn(k); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double lambda, double x) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return 1.0 - SpecialFunctions.GammaLowerRegularized(x + 1, lambda); + } + + /// + /// Generates one sample from the Poisson distribution. + /// + /// The random source to use. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// A random sample from the Poisson distribution. + static int SampleUnchecked(System.Random rnd, double lambda) + { + return (lambda < 30.0) ? DoSampleShort(rnd, lambda) : DoSampleLarge(rnd, lambda); + } + + static void SamplesUnchecked(System.Random rnd, int[] values, double lambda) + { + if (lambda < 30.0) + { + var limit = Math.Exp(-lambda); + for (int i = 0; i < values.Length; i++) + { + var count = 0; + for (var product = rnd.NextDouble(); product >= limit; product *= rnd.NextDouble()) + { + count++; + } + + values[i] = count; + } + } + else + { + var c = 0.767 - (3.36 / lambda); + var beta = Math.PI / Math.Sqrt(3.0 * lambda); + var alpha = beta * lambda; + var k = Math.Log(c) - lambda - Math.Log(beta); + for (int i = 0; i < values.Length; i++) + { + for (; ; ) + { + var u = rnd.NextDouble(); + var x = (alpha - Math.Log((1.0 - u) / u)) / beta; + var n = (int)Math.Floor(x + 0.5); + if (n < 0) + { + continue; + } + + var v = rnd.NextDouble(); + var y = alpha - (beta * x); + var temp = 1.0 + Math.Exp(y); + var lhs = y + Math.Log(v / (temp * temp)); + var rhs = k + (n * Math.Log(lambda)) - SpecialFunctions.FactorialLn(n); + if (lhs <= rhs) + { + values[i] = n; + break; + } + } + } + } + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double lambda) + { + if (lambda < 30.0) + { + while (true) + { + yield return DoSampleShort(rnd, lambda); + } + } + else + { + while (true) + { + yield return DoSampleLarge(rnd, lambda); + } + } + } + + /// + /// Generates one sample from the Poisson distribution by Knuth's method. + /// + /// The random source to use. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// A random sample from the Poisson distribution. + static int DoSampleShort(System.Random rnd, double lambda) + { + var limit = Math.Exp(-lambda); + var count = 0; + for (var product = rnd.NextDouble(); product >= limit; product *= rnd.NextDouble()) + { + count++; + } + + return count; + } + + /// + /// Generates one sample from the Poisson distribution by "Rejection method PA". + /// + /// The random source to use. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// A random sample from the Poisson distribution. + /// "Rejection method PA" from "The Computer Generation of Poisson Random Variables" by A. C. Atkinson, + /// Journal of the Royal Statistical Society Series C (Applied Statistics) Vol. 28, No. 1. (1979) + /// The article is on pages 29-35. The algorithm given here is on page 32. + static int DoSampleLarge(System.Random rnd, double lambda) + { + var c = 0.767 - (3.36 / lambda); + var beta = Math.PI / Math.Sqrt(3.0 * lambda); + var alpha = beta * lambda; + var k = Math.Log(c) - lambda - Math.Log(beta); + + for (; ; ) + { + var u = rnd.NextDouble(); + var x = (alpha - Math.Log((1.0 - u) / u)) / beta; + var n = (int)Math.Floor(x + 0.5); + if (n < 0) + { + continue; + } + + var v = rnd.NextDouble(); + var y = alpha - (beta * x); + var temp = 1.0 + Math.Exp(y); + var lhs = y + Math.Log(v / (temp * temp)); + var rhs = k + (n * Math.Log(lambda)) - SpecialFunctions.FactorialLn(n); + if (lhs <= rhs) + { + return n; + } + } + } + + /// + /// Samples a Poisson distributed random variable. + /// + /// A sample from the Poisson distribution. + public int Sample() + { + return SampleUnchecked(_random, _lambda); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _lambda); + } + + /// + /// Samples an array of Poisson distributed random variables. + /// + /// a sequence of successes in N trials. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _lambda); + } + + /// + /// Samples a Poisson distributed random variable. + /// + /// The random number generator to use. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// A sample from the Poisson distribution. + public static int Sample(System.Random rnd, double lambda) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, lambda); + } + + /// + /// Samples a sequence of Poisson distributed random variables. + /// + /// The random number generator to use. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double lambda) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, lambda); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, int[] values, double lambda) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, lambda); + } + + /// + /// Samples a Poisson distributed random variable. + /// + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// A sample from the Poisson distribution. + public static int Sample(double lambda) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, lambda); + } + + /// + /// Samples a sequence of Poisson distributed random variables. + /// + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double lambda) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, lambda); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The lambda (λ) parameter of the Poisson distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static void Samples(int[] values, double lambda) + { + if (!(lambda > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, lambda); + } +} diff --git a/MathNet.Numerics/Distributions/Rayleigh.cs b/MathNet.Numerics/Distributions/Rayleigh.cs new file mode 100644 index 0000000..01f0629 --- /dev/null +++ b/MathNet.Numerics/Distributions/Rayleigh.cs @@ -0,0 +1,450 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Rayleigh distribution. +/// The Rayleigh distribution (pronounced /ˈreɪli/) is a continuous probability distribution. As an +/// example of how it arises, the wind speed will have a Rayleigh distribution if the components of +/// the two-dimensional wind velocity vector are uncorrelated and normally distributed with equal variance. +/// For details about this distribution, see +/// Wikipedia - Rayleigh distribution. +/// +public class Rayleigh : IContinuousDistribution +{ + System.Random _random; + + readonly double _scale; + + /// + /// Initializes a new instance of the class. + /// + /// The scale (σ) of the distribution. Range: σ > 0. + /// If is negative. + public Rayleigh(double scale) + { + if (!IsValidParameterSet(scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _scale = scale; + } + + /// + /// Initializes a new instance of the class. + /// + /// The scale (σ) of the distribution. Range: σ > 0. + /// The random number generator which is used to draw random samples. + /// If is negative. + public Rayleigh(double scale, System.Random randomSource) + { + if (!IsValidParameterSet(scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _scale = scale; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Rayleigh(σ = " + _scale + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The scale (σ) of the distribution. Range: σ > 0. + public static bool IsValidParameterSet(double scale) + { + return scale > 0.0; + } + + /// + /// Gets the scale (σ) of the distribution. Range: σ > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return _scale * Math.Sqrt(Constants.PiOver2); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get { return (2.0 - Constants.PiOver2) * _scale * _scale; } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(2.0 - Constants.PiOver2) * _scale; } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get { return 1.0 + Math.Log(_scale / Constants.Sqrt2) + (Constants.EulerMascheroni / 2.0); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get { return (2.0 * Math.Sqrt(Constants.Pi) * (Constants.Pi - 3.0)) / Math.Pow(4.0 - Constants.Pi, 1.5); } + } + + /// + /// Gets the mode of the distribution. + /// + public double Mode + { + get { return _scale; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { return _scale * Math.Sqrt(Math.Log(4.0)); } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return (x / (_scale * _scale)) * Math.Exp(-x * x / (2.0 * _scale * _scale)); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return Math.Log(x / (_scale * _scale)) - (x * x / (2.0 * _scale * _scale)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return 1.0 - Math.Exp(-x * x / (2.0 * _scale * _scale)); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return _scale * Math.Sqrt(-2 * Math.Log(1 - p)); + } + + /// + /// Draws a random sample from the distribution. + /// + /// A random number from this distribution. + public double Sample() + { + return SampleUnchecked(_random, _scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _scale); + } + + /// + /// Generates a sequence of samples from the Rayleigh distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _scale); + } + + static double SampleUnchecked(System.Random rnd, double scale) + { + return scale * Math.Sqrt(-2.0 * Math.Log(rnd.NextDouble())); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double scale) + { + return rnd.NextDoubleSequence().Select(x => scale * Math.Sqrt(-2.0 * Math.Log(x))); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double scale) + { + rnd.NextDoubles(values); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = scale * Math.Sqrt(-2.0 * Math.Log(values[i])); + } + }); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The scale (σ) of the distribution. Range: σ > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return (x / (scale * scale)) * Math.Exp(-x * x / (2.0 * scale * scale)); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The scale (σ) of the distribution. Range: σ > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Math.Log(x / (scale * scale)) - (x * x / (2.0 * scale * scale)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The scale (σ) of the distribution. Range: σ > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double scale, double x) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return 1.0 - Math.Exp(-x * x / (2.0 * scale * scale)); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The scale (σ) of the distribution. Range: σ > 0. + /// the inverse cumulative density at . + /// + public static double InvCDF(double scale, double p) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return scale * Math.Sqrt(-2 * Math.Log(1 - p)); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, scale); + } + + /// + /// Generates a sample from the distribution. + /// + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sample from the distribution. + public static double Sample(double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, scale); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The scale (σ) of the distribution. Range: σ > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double scale) + { + if (scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, scale); + } +} diff --git a/MathNet.Numerics/Distributions/Stable.cs b/MathNet.Numerics/Distributions/Stable.cs new file mode 100644 index 0000000..92082c7 --- /dev/null +++ b/MathNet.Numerics/Distributions/Stable.cs @@ -0,0 +1,649 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Stable distribution. +/// A random variable is said to be stable (or to have a stable distribution) if it has +/// the property that a linear combination of two independent copies of the variable has +/// the same distribution, up to location and scale parameters. +/// For details about this distribution, see +/// Wikipedia - Stable distribution. +/// +public class Stable : IContinuousDistribution +{ + System.Random _random; + + readonly double _alpha; + readonly double _beta; + readonly double _scale; + readonly double _location; + + /// + /// Initializes a new instance of the class. + /// + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + public Stable(double alpha, double beta, double scale, double location) + { + if (!IsValidParameterSet(alpha, beta, scale, location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _alpha = alpha; + _beta = beta; + _scale = scale; + _location = location; + } + + /// + /// Initializes a new instance of the class. + /// + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// The random number generator which is used to draw random samples. + public Stable(double alpha, double beta, double scale, double location, System.Random randomSource) + { + if (!IsValidParameterSet(alpha, beta, scale, location)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _alpha = alpha; + _beta = beta; + _scale = scale; + _location = location; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Stable(α = " + _alpha + ", β = " + _beta + ", c = " + _scale + ", μ = " + _location + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + public static bool IsValidParameterSet(double alpha, double beta, double scale, double location) + { + return alpha > 0.0 && alpha <= 2.0 && beta >= -1.0 && beta <= 1.0 && scale > 0.0 && !double.IsNaN(location); + } + + /// + /// Gets the stability (α) of the distribution. Range: 2 ≥ α > 0. + /// + public double Alpha + { + get { return _alpha; } + } + + /// + /// Gets The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// + public double Beta + { + get { return _beta; } + } + + /// + /// Gets the scale (c) of the distribution. Range: c > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets the location (μ) of the distribution. + /// + public double Location + { + get { return _location; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get + { + if (_alpha <= 1d) + { + throw new NotSupportedException(); + } + + return _location; + } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + if (_alpha == 2d) + { + return 2.0 * _scale * _scale; + } + + return double.PositiveInfinity; + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get + { + if (_alpha == 2d) + { + return Constants.Sqrt2 * _scale; + } + + return double.PositiveInfinity; + } + } + + /// + /// Gets he entropy of the distribution. + /// + /// Always throws a not supported exception. + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the skewness of the distribution. + /// + /// Throws a not supported exception of Alpha != 2. + public double Skewness + { + get + { + if (_alpha != 2d) + { + throw new NotSupportedException(); + } + + return 0.0; + } + } + + /// + /// Gets the mode of the distribution. + /// + /// Throws a not supported exception if Beta != 0. + public double Mode + { + get + { + if (_beta != 0d) + { + throw new NotSupportedException(); + } + + return _location; + } + } + + /// + /// Gets the median of the distribution. + /// + /// Throws a not supported exception if Beta != 0. + public double Median + { + get + { + if (_beta != 0) + { + throw new NotSupportedException(); + } + + return _location; + } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get + { + if (Math.Abs(_beta) == 1) + { + return 0.0; + } + + return double.NegativeInfinity; + } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + public double Density(double x) + { + return PDF(_alpha, _beta, _scale, _location, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + public double DensityLn(double x) + { + return PDFLn(_alpha, _beta, _scale, _location, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// Throws a not supported exception if Alpha != 2, (Alpha != 1 and Beta !=0), or (Alpha != 0.5 and Beta != 1) + public double CumulativeDistribution(double x) + { + return CDF(_alpha, _beta, _scale, _location, x); + } + + /// + /// Samples the distribution. + /// + /// The random number generator to use. + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// a random number from the distribution. + static double SampleUnchecked(System.Random rnd, double alpha, double beta, double scale, double location) + { + var randTheta = ContinuousUniform.Sample(rnd, -Constants.PiOver2, Constants.PiOver2); + var randW = Exponential.Sample(rnd, 1.0); + + if (!1.0.AlmostEqual(alpha)) + { + var theta = (1.0 / alpha) * Math.Atan(beta * Math.Tan(Constants.PiOver2 * alpha)); + var angle = alpha * (randTheta + theta); + var part1 = beta * Math.Tan(Constants.PiOver2 * alpha); + + var factor = Math.Pow(1.0 + (part1 * part1), 1.0 / (2.0 * alpha)); + var factor1 = Math.Sin(angle) / Math.Pow(Math.Cos(randTheta), 1.0 / alpha); + var factor2 = Math.Pow(Math.Cos(randTheta - angle) / randW, (1 - alpha) / alpha); + + return location + scale * (factor * factor1 * factor2); + } + else + { + var part1 = Constants.PiOver2 + (beta * randTheta); + var summand = part1 * Math.Tan(randTheta); + var subtrahend = beta * Math.Log(Constants.PiOver2 * randW * Math.Cos(randTheta) / part1); + + return location + scale * Constants.TwoInvPi * (summand - subtrahend); + } + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double alpha, double beta, double scale, double location) + { + var randThetas = new double[values.Length]; + var randWs = new double[values.Length]; + ContinuousUniform.SamplesUnchecked(rnd, randThetas, -Constants.PiOver2, Constants.PiOver2); + Exponential.SamplesUnchecked(rnd, randWs, 1.0); + + if (!1.0.AlmostEqual(alpha)) + { + for (int i = 0; i < values.Length; i++) + { + var randTheta = randThetas[i]; + + var theta = (1.0 / alpha) * Math.Atan(beta * Math.Tan(Constants.PiOver2 * alpha)); + var angle = alpha * (randTheta + theta); + var part1 = beta * Math.Tan(Constants.PiOver2 * alpha); + + var factor = Math.Pow(1.0 + (part1 * part1), 1.0 / (2.0 * alpha)); + var factor1 = Math.Sin(angle) / Math.Pow(Math.Cos(randTheta), 1.0 / alpha); + var factor2 = Math.Pow(Math.Cos(randTheta - angle) / randWs[i], (1 - alpha) / alpha); + + values[i] = location + scale * (factor * factor1 * factor2); + } + } + else + { + for (int i = 0; i < values.Length; i++) + { + var randTheta = randThetas[i]; + + var part1 = Constants.PiOver2 + (beta * randTheta); + var summand = part1 * Math.Tan(randTheta); + var subtrahend = beta * Math.Log(Constants.PiOver2 * randWs[i] * Math.Cos(randTheta) / part1); + + values[i] = location + scale * Constants.TwoInvPi * (summand - subtrahend); + } + } + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double alpha, double beta, double scale, double location) + { + while (true) + { + yield return SampleUnchecked(rnd, alpha, beta, scale, location); + } + } + + /// + /// Draws a random sample from the distribution. + /// + /// A random number from this distribution. + public double Sample() + { + return SampleUnchecked(_random, _alpha, _beta, _scale, _location); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _alpha, _beta, _scale, _location); + } + + /// + /// Generates a sequence of samples from the Stable distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _alpha, _beta, _scale, _location); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double alpha, double beta, double scale, double location, double x) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (alpha == 2d) + { + return Normal.PDF(location, Constants.Sqrt2 * scale, x); + } + + if (alpha == 1d && beta == 0d) + { + return Cauchy.PDF(location, scale, x); + } + + if (alpha == 0.5d && beta == 1d && x >= location) + { + return (Math.Sqrt(scale / Constants.Pi2) * Math.Exp(-scale / (2 * (x - location)))) / Math.Pow(x - location, 1.5); + } + + throw new NotSupportedException(); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double alpha, double beta, double scale, double location, double x) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (alpha == 2d) + { + return Normal.PDFLn(location, Constants.Sqrt2 * scale, x); + } + + if (alpha == 1d && beta == 0d) + { + return Cauchy.PDFLn(location, scale, x); + } + + if (alpha == 0.5d && beta == 1d && x >= location) + { + return Math.Log(scale / Constants.Pi2) / 2 - scale / (2 * (x - location)) - 1.5 * Math.Log(x - location); + } + + throw new NotSupportedException(); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// the cumulative distribution at location . + /// + public static double CDF(double alpha, double beta, double scale, double location, double x) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (alpha == 2d) + { + return Normal.CDF(location, Constants.Sqrt2 * scale, x); + } + + if (alpha == 1d && beta == 0d) + { + return Cauchy.CDF(location, scale, x); + } + + if (alpha == 0.5d && beta == 1d) + { + return SpecialFunctions.Erfc(Math.Sqrt(scale / (2 * (x - location)))); + } + + throw new NotSupportedException(); + } + + /// + /// Generates a sample from the distribution. + /// + /// The random number generator to use. + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double alpha, double beta, double scale, double location) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, alpha, beta, scale, location); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The random number generator to use. + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double alpha, double beta, double scale, double location) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, alpha, beta, scale, location); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double alpha, double beta, double scale, double location) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, alpha, beta, scale, location); + } + + /// + /// Generates a sample from the distribution. + /// + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// a sample from the distribution. + public static double Sample(double alpha, double beta, double scale, double location) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, alpha, beta, scale, location); + } + + /// + /// Generates a sequence of samples from the distribution. + /// + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double alpha, double beta, double scale, double location) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, alpha, beta, scale, location); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The stability (α) of the distribution. Range: 2 ≥ α > 0. + /// The skewness (β) of the distribution. Range: 1 ≥ β ≥ -1. + /// The scale (c) of the distribution. Range: c > 0. + /// The location (μ) of the distribution. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double alpha, double beta, double scale, double location) + { + if (alpha <= 0.0 || alpha > 2.0 || beta < -1.0 || beta > 1.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, alpha, beta, scale, location); + } +} diff --git a/MathNet.Numerics/Distributions/StudentT.cs b/MathNet.Numerics/Distributions/StudentT.cs new file mode 100644 index 0000000..69eb13e --- /dev/null +++ b/MathNet.Numerics/Distributions/StudentT.cs @@ -0,0 +1,622 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.RootFinding; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Student's T-distribution. +/// Implements the univariate Student t-distribution. For details about this +/// distribution, see +/// +/// Wikipedia - Student's t-distribution. +/// +/// We use a slightly generalized version (compared to +/// Wikipedia) of the Student t-distribution. Namely, one which also +/// parameterizes the location and scale. See the book "Bayesian Data +/// Analysis" by Gelman et al. for more details. +/// The density of the Student t-distribution p(x|mu,scale,dof) = +/// Gamma((dof+1)/2) (1 + (x - mu)^2 / (scale * scale * dof))^(-(dof+1)/2) / +/// (Gamma(dof/2)*Sqrt(dof*pi*scale)). +/// The distribution will use the by +/// default. Users can get/set the random number generator by using the +/// property. +/// The statistics classes will check all the incoming parameters +/// whether they are in the allowed range. This might involve heavy +/// computation. Optionally, by setting Control.CheckDistributionParameters +/// to false, all parameter checks can be turned off. +public class StudentT : IContinuousDistribution +{ + System.Random _random; + + readonly double _location; + readonly double _scale; + readonly double _freedom; + + /// + /// Initializes a new instance of the StudentT class. This is a Student t-distribution with location 0.0 + /// scale 1.0 and degrees of freedom 1. + /// + public StudentT() + { + _random = SystemRandomSource.Default; + _location = 0.0; + _scale = 1.0; + _freedom = 1.0; + } + + /// + /// Initializes a new instance of the StudentT class with a particular location, scale and degrees of + /// freedom. + /// + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + public StudentT(double location, double scale, double freedom) + { + if (!IsValidParameterSet(location, scale, freedom)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _location = location; + _scale = scale; + _freedom = freedom; + } + + /// + /// Initializes a new instance of the StudentT class with a particular location, scale and degrees of + /// freedom. + /// + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// The random number generator which is used to draw random samples. + public StudentT(double location, double scale, double freedom, System.Random randomSource) + { + if (!IsValidParameterSet(location, scale, freedom)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _location = location; + _scale = scale; + _freedom = freedom; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "StudentT(μ = " + _location + ", σ = " + _scale + ", ν = " + _freedom + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + public static bool IsValidParameterSet(double location, double scale, double freedom) + { + return scale > 0.0 && freedom > 0.0 && !double.IsNaN(location); + } + + /// + /// Gets the location (μ) of the Student t-distribution. + /// + public double Location + { + get { return _location; } + } + + /// + /// Gets the scale (σ) of the Student t-distribution. Range: σ > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets the degrees of freedom (ν) of the Student t-distribution. Range: ν > 0. + /// + public double DegreesOfFreedom + { + get { return _freedom; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the Student t-distribution. + /// + public double Mean + { + get { return _freedom > 1.0 ? _location : double.NaN; } + } + + /// + /// Gets the variance of the Student t-distribution. + /// + public double Variance + { + get + { + if (double.IsPositiveInfinity(_freedom)) + { + return _scale * _scale; + } + + if (_freedom > 2.0) + { + return _freedom * _scale * _scale / (_freedom - 2.0); + } + + return _freedom > 1.0 ? double.PositiveInfinity : double.NaN; + } + } + + /// + /// Gets the standard deviation of the Student t-distribution. + /// + public double StdDev + { + get + { + if (double.IsPositiveInfinity(_freedom)) + { + return Math.Sqrt(_scale * _scale); + } + + if (_freedom > 2.0) + { + return Math.Sqrt(_freedom * _scale * _scale / (_freedom - 2.0)); + } + + return _freedom > 1.0 ? double.PositiveInfinity : double.NaN; + } + } + + /// + /// Gets the entropy of the Student t-distribution. + /// + public double Entropy + { + get + { + if (_location != 0 || _scale != 1.0) + { + throw new NotSupportedException(); + } + + return (((_freedom + 1.0) / 2.0) * (SpecialFunctions.DiGamma((1.0 + _freedom) / 2.0) - SpecialFunctions.DiGamma(_freedom / 2.0))) + + Math.Log(Math.Sqrt(_freedom) * SpecialFunctions.Beta(_freedom / 2.0, 1.0 / 2.0)); + } + } + + /// + /// Gets the skewness of the Student t-distribution. + /// + public double Skewness + { + get + { + if (_freedom <= 3) + { + throw new NotSupportedException(); + } + + return 0.0; + } + } + + /// + /// Gets the mode of the Student t-distribution. + /// + public double Mode + { + get { return _location; } + } + + /// + /// Gets the median of the Student t-distribution. + /// + public double Median + { + get { return _location; } + } + + /// + /// Gets the minimum of the Student t-distribution. + /// + public double Minimum + { + get { return double.NegativeInfinity; } + } + + /// + /// Gets the maximum of the Student t-distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + public double Density(double x) + { + return PDF(_location, _scale, _freedom, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + public double DensityLn(double x) + { + return PDFLn(_location, _scale, _freedom, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + return CDF(_location, _scale, _freedom, x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + /// WARNING: currently not an explicit implementation, hence slow and unreliable. + public double InverseCumulativeDistribution(double p) + { + return InvCDF(_location, _scale, _freedom, p); + } + + /// + /// Samples student-t distributed random variables. + /// + /// The algorithm is method 2 in section 5, chapter 9 + /// in L. Devroye's "Non-Uniform Random Variate Generation" + /// The random number generator to use. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// a random number from the standard student-t distribution. + static double SampleUnchecked(System.Random rnd, double location, double scale, double freedom) + { + var gamma = Gamma.SampleUnchecked(rnd, 0.5 * freedom, 0.5); + return Normal.Sample(rnd, location, scale * Math.Sqrt(freedom / gamma)); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double location, double scale, double freedom) + { + Gamma.SamplesUnchecked(rnd, values, 0.5 * freedom, 0.5); + for (int i = 0; i < values.Length; i++) + { + values[i] = Normal.Sample(rnd, location, scale * Math.Sqrt(freedom / values[i])); + } + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double location, double scale, double freedom) + { + while (true) + { + yield return SampleUnchecked(rnd, location, scale, freedom); + } + } + + /// + /// Generates a sample from the Student t-distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _location, _scale, _freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _location, _scale, _freedom); + } + + /// + /// Generates a sequence of samples from the Student t-distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _location, _scale, _freedom); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double location, double scale, double freedom, double x) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + // TODO JVG we can probably do a better job for Cauchy special case + if (freedom >= 1e+8d) + { + return Normal.PDF(location, scale, x); + } + + var d = (x - location) / scale; + return Math.Exp(SpecialFunctions.GammaLn((freedom + 1.0) / 2.0) - SpecialFunctions.GammaLn(freedom / 2.0)) + * Math.Pow(1.0 + (d * d / freedom), -0.5 * (freedom + 1.0)) + / Math.Sqrt(freedom * Math.PI) + / scale; + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double location, double scale, double freedom, double x) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + // TODO JVG we can probably do a better job for Cauchy special case + if (freedom >= 1e+8d) + { + return Normal.PDFLn(location, scale, x); + } + + var d = (x - location) / scale; + return SpecialFunctions.GammaLn((freedom + 1.0) / 2.0) + - (0.5 * ((freedom + 1.0) * Math.Log(1.0 + (d * d / freedom)))) + - SpecialFunctions.GammaLn(freedom / 2.0) + - (0.5 * Math.Log(freedom * Math.PI)) - Math.Log(scale); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double location, double scale, double freedom, double x) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + // TODO JVG we can probably do a better job for Cauchy special case + if (double.IsPositiveInfinity(freedom)) + { + return Normal.CDF(location, scale, x); + } + + var k = (x - location) / scale; + var h = freedom / (freedom + (k * k)); + var ib = 0.5 * SpecialFunctions.BetaRegularized(freedom / 2.0, 0.5, h); + return x <= location ? ib : 1.0 - ib; + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// the inverse cumulative density at . + /// + /// WARNING: currently not an explicit implementation, hence slow and unreliable. + public static double InvCDF(double location, double scale, double freedom, double p) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + // TODO JVG we can probably do a better job for Cauchy special case + if (double.IsPositiveInfinity(freedom)) + { + return Normal.InvCDF(location, scale, p); + } + + if (p == 0.5d) + { + return location; + } + + // TODO PERF: We must implement this explicitly instead of solving for CDF^-1 + return Brent.FindRoot(x => + { + var k = (x - location) / scale; + var h = freedom / (freedom + (k * k)); + var ib = 0.5 * SpecialFunctions.BetaRegularized(freedom / 2.0, 0.5, h); + return x <= location ? ib - p : 1.0 - ib - p; + }, -800, 800, accuracy: 1e-12); + } + + /// + /// Generates a sample from the Student t-distribution. + /// + /// The random number generator to use. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double location, double scale, double freedom) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, location, scale, freedom); + } + + /// + /// Generates a sequence of samples from the Student t-distribution using the Box-Muller algorithm. + /// + /// The random number generator to use. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double location, double scale, double freedom) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, location, scale, freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double location, double scale, double freedom) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, location, scale, freedom); + } + + /// + /// Generates a sample from the Student t-distribution. + /// + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// a sample from the distribution. + public static double Sample(double location, double scale, double freedom) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, location, scale, freedom); + } + + /// + /// Generates a sequence of samples from the Student t-distribution using the Box-Muller algorithm. + /// + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double location, double scale, double freedom) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, location, scale, freedom); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The location (μ) of the distribution. + /// The scale (σ) of the distribution. Range: σ > 0. + /// The degrees of freedom (ν) for the distribution. Range: ν > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double location, double scale, double freedom) + { + if (scale <= 0.0 || freedom <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, location, scale, freedom); + } +} diff --git a/MathNet.Numerics/Distributions/Triangular.cs b/MathNet.Numerics/Distributions/Triangular.cs new file mode 100644 index 0000000..0fa148f --- /dev/null +++ b/MathNet.Numerics/Distributions/Triangular.cs @@ -0,0 +1,573 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Triangular distribution. +/// For details, see Wikipedia - Triangular distribution. +/// +/// The distribution will use the by default. +/// Users can get/set the random number generator by using the property. +/// The statistics classes will check whether all the incoming parameters are in the allowed range. This might involve heavy computation. Optionally, by setting Control.CheckDistributionParameters +/// to false, all parameter checks can be turned off. +public class Triangular : IContinuousDistribution +{ + System.Random _random; + + readonly double _lower; + readonly double _upper; + readonly double _mode; + + /// + /// Initializes a new instance of the Triangular class with the given lower bound, upper bound and mode. + /// + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// If the upper bound is smaller than the mode or if the mode is smaller than the lower bound. + public Triangular(double lower, double upper, double mode) + { + if (!IsValidParameterSet(lower, upper, mode)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _lower = lower; + _upper = upper; + _mode = mode; + } + + /// + /// Initializes a new instance of the Triangular class with the given lower bound, upper bound and mode. + /// + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// The random number generator which is used to draw random samples. + /// If the upper bound is smaller than the mode or if the mode is smaller than the lower bound. + public Triangular(double lower, double upper, double mode, System.Random randomSource) + { + if (!IsValidParameterSet(lower, upper, mode)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _lower = lower; + _upper = upper; + _mode = mode; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Triangular(Lower = " + _lower + ", Upper = " + _upper + ", Mode = " + _mode + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + public static bool IsValidParameterSet(double lower, double upper, double mode) + { + return upper >= mode && mode >= lower && !double.IsInfinity(upper) && !double.IsInfinity(lower) && !double.IsInfinity(mode); + } + + /// + /// Gets the lower bound of the distribution. + /// + public double LowerBound + { + get { return _lower; } + } + + /// + /// Gets the upper bound of the distribution. + /// + public double UpperBound + { + get { return _upper; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return (_lower + _upper + _mode) / 3.0; } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + var a = _lower; + var b = _upper; + var c = _mode; + return (a * a + b * b + c * c - a * b - a * c - b * c) / 18.0; + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the entropy of the distribution. + /// + /// + public double Entropy + { + get { return 0.5 + Math.Log((_upper - _lower) / 2); } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get + { + var a = _lower; + var b = _upper; + var c = _mode; + var q = Math.Sqrt(2) * (a + b - 2 * c) * (2 * a - b - c) * (a - 2 * b + c); + var d = 5 * Math.Pow(a * a + b * b + c * c - a * b - a * c - b * c, 3.0 / 2); + return q / d; + } + } + + /// + /// Gets or sets the mode of the distribution. + /// + public double Mode + { + get { return _mode; } + } + + /// + /// Gets the median of the distribution. + /// + /// + public double Median + { + get + { + var a = _lower; + var b = _upper; + var c = _mode; + return c >= (a + b) / 2 + ? a + Math.Sqrt((b - a) * (c - a) / 2) + : b - Math.Sqrt((b - a) * (b - c) / 2); + } + } + + /// + /// Gets the minimum of the distribution. + /// + public double Minimum + { + get { return _lower; } + } + + /// + /// Gets the maximum of the distribution. + /// + public double Maximum + { + get { return _upper; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return PDF(_lower, _upper, _mode, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return PDFLn(_lower, _upper, _mode, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CDF(_lower, _upper, _mode, x); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return InvCDF(_lower, _upper, _mode, p); + } + + /// + /// Generates a sample from the Triangular distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _lower, _upper, _mode); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _lower, _upper, _mode); + } + + /// + /// Generates a sequence of samples from the Triangular distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _lower, _upper, _mode); + } + + static double SampleUnchecked(System.Random rnd, double lower, double upper, double mode) + { + var u = rnd.NextDouble(); + return u < (mode - lower) / (upper - lower) + ? lower + Math.Sqrt(u * (upper - lower) * (mode - lower)) + : upper - Math.Sqrt((1 - u) * (upper - lower) * (upper - mode)); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double lower, double upper, double mode) + { + double ml = mode - lower, ul = upper - lower, um = upper - mode; + double u = ml / ul, v = ul * ml, w = ul * um; + + return rnd.NextDoubleSequence().Select(x => x < u ? lower + Math.Sqrt(x * v) : upper - Math.Sqrt((1 - x) * w)); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double lower, double upper, double mode) + { + double ml = mode - lower, ul = upper - lower, um = upper - mode; + double u = ml / ul, v = ul * ml, w = ul * um; + + rnd.NextDoubles(values); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = values[i] < u + ? lower + Math.Sqrt(values[i] * v) + : upper - Math.Sqrt((1 - values[i]) * w); + } + }); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double lower, double upper, double mode, double x) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var a = lower; + var b = upper; + var c = mode; + + if (a <= x && x <= c) + { + return 2 * (x - a) / ((b - a) * (c - a)); + } + + if (c < x & x <= b) + { + return 2 * (b - x) / ((b - a) * (b - c)); + } + + return 0; + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double lower, double upper, double mode, double x) + { + return Math.Log(PDF(lower, upper, mode, x)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// the cumulative distribution at location . + /// + public static double CDF(double lower, double upper, double mode, double x) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var a = lower; + var b = upper; + var c = mode; + + if (x < a) + { + return 0; + } + + if (a <= x && x <= c) + { + return (x - a) * (x - a) / ((b - a) * (c - a)); + } + + if (c < x & x <= b) + { + return 1 - (b - x) * (b - x) / ((b - a) * (b - c)); + } + + return 1; + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the quantile or percent point function. + /// + /// The location at which to compute the inverse cumulative density. + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// the inverse cumulative density at . + /// + public static double InvCDF(double lower, double upper, double mode, double p) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + var a = lower; + var b = upper; + var c = mode; + + if (p <= 0) + { + return lower; + } + + // Taken from http://www.ntrand.com/triangular-distribution/ + if (p < (c - a) / (b - a)) + { + return a + Math.Sqrt(p * (c - a) * (b - a)); + } + + if (p < 1) + { + return b - Math.Sqrt((1 - p) * (b - c) * (b - a)); + } + + return upper; + } + + /// + /// Generates a sample from the Triangular distribution. + /// + /// The random number generator to use. + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// a sample from the distribution. + public static double Sample(System.Random rnd, double lower, double upper, double mode) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, lower, upper, mode); + } + + /// + /// Generates a sequence of samples from the Triangular distribution. + /// + /// The random number generator to use. + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double lower, double upper, double mode) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, lower, upper, mode); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double lower, double upper, double mode) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, lower, upper, mode); + } + + /// + /// Generates a sample from the Triangular distribution. + /// + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// a sample from the distribution. + public static double Sample(double lower, double upper, double mode) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, lower, upper, mode); + } + + /// + /// Generates a sequence of samples from the Triangular distribution. + /// + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double lower, double upper, double mode) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, lower, upper, mode); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// Lower bound. Range: lower ≤ mode ≤ upper + /// Upper bound. Range: lower ≤ mode ≤ upper + /// Mode (most frequent value). Range: lower ≤ mode ≤ upper + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double lower, double upper, double mode) + { + if (!(upper >= mode && mode >= lower)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, lower, upper, mode); + } +} diff --git a/MathNet.Numerics/Distributions/TruncatedPareto.cs b/MathNet.Numerics/Distributions/TruncatedPareto.cs new file mode 100644 index 0000000..1c8e57f --- /dev/null +++ b/MathNet.Numerics/Distributions/TruncatedPareto.cs @@ -0,0 +1,475 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2019 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +public class TruncatedPareto : IContinuousDistribution +{ + private System.Random _random; + + /// + /// Initializes a new instance of the TruncatedPareto class. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + /// The random number generator which is used to draw random samples. + /// If or are non-positive or if T ≤ xm. + public TruncatedPareto(double scale, double shape, double truncation, System.Random randomSource = null) + { + if (!IsValidParameterSet(scale, shape, truncation)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + Scale = scale; + Shape = shape; + Truncation = truncation; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Truncated Pareto(Scale = " + Scale + ", Shape = " + Shape + ", Truncation = " + Truncation + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + public static bool IsValidParameterSet(double scale, double shape, double truncation) + { + var allFinite = scale.IsFinite() && shape.IsFinite() && truncation.IsFinite(); + return allFinite && scale > 0.0 && shape > 0.0 && truncation > scale; + } + + /// + /// Gets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the scale (xm) of the distribution. Range: xm > 0. + /// + public double Scale { get; } + + /// + /// Gets the shape (α) of the distribution. Range: α > 0. + /// + public double Shape { get; } + + /// + /// Gets the truncation (T) of the distribution. Range: T > 0. + /// + public double Truncation { get; } + + /// + /// Gets the n-th raw moment of the distribution. + /// + /// The order (n) of the moment. Range: n ≥ 1. + /// the n-th moment of the distribution. + public double GetMoment(int n) + { + double moment; + if (Shape.AlmostEqual(n)) + { + moment = ((Shape * Math.Pow(Scale, n)) / (1 - Math.Pow(Scale / Truncation, Shape))) * Math.Log(Truncation / Scale); + } + else + { + moment = ((Shape * Math.Pow(Scale, n)) / (Shape - n)) * ((1 - Math.Pow((Scale / Truncation), (Shape - n))) / (1 - Math.Pow(Scale / Truncation, Shape))); + } + + return moment; + } + + /// + /// Gets the mean of the truncated Pareto distribution. + /// + public double Mean + { + get + { + return GetMoment(1); + } + } + + /// + /// Gets the variance of the truncated Pareto distribution. + /// + public double Variance + { + get + { + return GetMoment(2) - Math.Pow(GetMoment(1), 2); + } + } + + /// + /// Gets the standard deviation of the truncated Pareto distribution. + /// + public double StdDev + { + get + { + return Math.Sqrt(Variance); + } + } + + /// + /// Gets the mode of the truncated Pareto distribution (not supported). + /// + public double Mode + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the minimum of the truncated Pareto distribution. + /// + public double Minimum + { + get { return Scale; } + } + + /// + /// Gets the maximum of the truncated Pareto distribution. + /// + public double Maximum + { + get { return Truncation; } + } + + /// + /// Gets the entropy of the truncated Pareto distribution (not supported). + /// + public double Entropy + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the skewness of the truncated Pareto distribution. + /// + public double Skewness + { + get + { + var mean = Mean; + var variance = Variance; + var std = StdDev; + return (GetMoment(3) - 3.0 * mean * variance - mean * mean * mean) / (std * std * std); + } + } + + /// + /// Gets the median of the truncated Pareto distribution. + /// + public double Median + { + get + { + return Scale * Math.Pow(1.0 - (1.0 / 2.0) * (1.0 - Math.Pow(Scale / Truncation, Shape)), -(1.0 / Shape)); + } + } + + /// + /// Generates a sample from the truncated Pareto distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, Scale, Shape, Truncation); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, Scale, Shape, Truncation); + } + + /// + /// Generates a sequence of samples from the truncated Pareto distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, Scale, Shape, Truncation); + } + + /// + /// Generates a sample from the truncated Pareto distribution. + /// + /// The random number generator to use. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double scale, double shape, double truncation) + { + if (!IsValidParameterSet(scale, shape, truncation)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, scale, shape, truncation); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + public static void Samples(System.Random rnd, double[] values, double scale, double shape, double truncation) + { + if (!IsValidParameterSet(scale, shape, truncation)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, scale, shape, truncation); + } + + /// + /// Generates a sequence of samples from the truncated Pareto distribution. + /// + /// The random number generator to use. + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double scale, double shape, double truncation) + { + if (!IsValidParameterSet(scale, shape, truncation)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, scale, shape, truncation); + } + + internal static double SampleUnchecked(System.Random rnd, double scale, double shape, double truncation) + { + double uniform = rnd.NextDouble(); + return InvCDFUncheckedImpl(scale, shape, truncation, uniform); + } + + internal static void SamplesUnchecked(System.Random rnd, double[] values, double scale, double shape, double truncation) + { + if (values.Length == 0) + { + return; + } + + double[] uniforms = rnd.NextDoubles(values.Length); + for (var j = 0; j < values.Length; ++j) + { + values[j] = InvCDFUncheckedImpl(scale, shape, truncation, uniforms[j]); + } + } + + internal static IEnumerable SamplesUnchecked(System.Random rnd, double scale, double shape, double truncation) + { + while (true) + { + yield return SampleUnchecked(rnd, scale, shape, truncation); + } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + /// + public double Density(double x) + { + return DensityImpl(Scale, Shape, Truncation, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + /// + public double DensityLn(double x) + { + return DensityLnImpl(Scale, Shape, Truncation, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public double CumulativeDistribution(double x) + { + return CumulativeDistributionImpl(Scale, Shape, Truncation, x); + } + + /// + /// Computes the inverse cumulative distribution (CDF) of the distribution at p, i.e. solving for P(X ≤ x) = p. + /// + /// The location at which to compute the inverse cumulative distribution function. + /// the inverse cumulative distribution at location . + public double InvCDF(double p) + { + return InvCDFUncheckedImpl(Scale, Shape, Truncation, p); + } + + /// + /// Computes the inverse cumulative distribution (CDF) of the distribution at p, i.e. solving for P(X ≤ x) = p. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + /// The location at which to compute the inverse cumulative distribution function. + /// the inverse cumulative distribution at location . + /// + public static double ICDF(double scale, double shape, double truncation, double p) + { + if (!IsValidParameterSet(scale, shape, truncation)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return InvCDFUncheckedImpl(scale, shape, truncation, p); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double scale, double shape, double truncation, double x) + { + if (!IsValidParameterSet(scale, shape, truncation)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return DensityImpl(scale, shape, truncation, x); + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + /// The location at which to compute the log density. + /// the log density at . + /// + public static double PDFLn(double scale, double shape, double truncation, double x) + { + if (!IsValidParameterSet(scale, shape, truncation)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return DensityLnImpl(scale, shape, truncation, x); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The scale (xm) of the distribution. Range: xm > 0. + /// The shape (α) of the distribution. Range: α > 0. + /// The truncation (T) of the distribution. Range: T > xm. + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + /// + public static double CDF(double scale, double shape, double truncation, double x) + { + if (!IsValidParameterSet(scale, shape, truncation)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return CumulativeDistributionImpl(scale, shape, truncation, x); + } + + internal static double DensityImpl(double scale, double shape, double truncation, double x) + { + if (x < scale || x > truncation) + return 0; + else + return (shape * Math.Pow(scale, shape) * Math.Pow(x, -shape - 1)) / (1 - Math.Pow(scale / truncation, shape)); + } + + internal static double DensityLnImpl(double scale, double shape, double truncation, double x) + { + return Math.Log(DensityImpl(scale, shape, truncation, x)); + } + + internal static double CumulativeDistributionImpl(double scale, double shape, double truncation, double x) + { + if (x <= scale) + return 0; + else if (x >= truncation) + return 1; + else + return (1 - Math.Pow(scale, shape) * Math.Pow(x, -shape)) / (1 - Math.Pow(scale / truncation, shape)); + } + + internal static double InvCDFUncheckedImpl(double scale, double shape, double truncation, double p) + { + var numerator = p * Math.Pow(truncation, shape) - p * Math.Pow(scale, shape) - Math.Pow(truncation, shape); + var denominator = Math.Pow(truncation, shape) * Math.Pow(scale, shape); + return Math.Pow(-numerator / denominator, -(1 / shape)); + } +} diff --git a/MathNet.Numerics/Distributions/Weibull.cs b/MathNet.Numerics/Distributions/Weibull.cs new file mode 100644 index 0000000..0e83d7e --- /dev/null +++ b/MathNet.Numerics/Distributions/Weibull.cs @@ -0,0 +1,573 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Distributions; + +/// +/// Continuous Univariate Weibull distribution. +/// For details about this distribution, see +/// Wikipedia - Weibull distribution. +/// +/// +/// The Weibull distribution is parametrized by a shape and scale parameter. +/// +public class Weibull : IContinuousDistribution +{ + System.Random _random; + + readonly double _shape; + readonly double _scale; + + /// + /// Reusable intermediate result 1 / (_scale ^ _shape) + /// + /// + /// By caching this parameter we can get slightly better numerics precision + /// in certain constellations without any additional computations. + /// + readonly double _scalePowShapeInv; + + /// + /// Initializes a new instance of the Weibull class. + /// + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + public Weibull(double shape, double scale) + { + if (!IsValidParameterSet(shape, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _shape = shape; + _scale = scale; + _scalePowShapeInv = Math.Pow(scale, -shape); + } + + /// + /// Initializes a new instance of the Weibull class. + /// + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// The random number generator which is used to draw random samples. + public Weibull(double shape, double scale, System.Random randomSource) + { + if (!IsValidParameterSet(shape, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _shape = shape; + _scale = scale; + _scalePowShapeInv = Math.Pow(scale, -shape); + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Weibull(k = " + _shape + ", λ = " + _scale + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + public static bool IsValidParameterSet(double shape, double scale) + { + return shape > 0.0 && scale > 0.0; + } + + /// + /// Gets the shape (k) of the Weibull distribution. Range: k > 0. + /// + public double Shape + { + get { return _shape; } + } + + /// + /// Gets the scale (λ) of the Weibull distribution. Range: λ > 0. + /// + public double Scale + { + get { return _scale; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the Weibull distribution. + /// + public double Mean + { + get { return _scale * SpecialFunctions.Gamma(1.0 + (1.0 / _shape)); } + } + + /// + /// Gets the variance of the Weibull distribution. + /// + public double Variance + { + get { return (_scale * _scale * SpecialFunctions.Gamma(1.0 + (2.0 / _shape))) - (Mean * Mean); } + } + + /// + /// Gets the standard deviation of the Weibull distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the entropy of the Weibull distribution. + /// + public double Entropy + { + get { return (Constants.EulerMascheroni * (1.0 - (1.0 / _shape))) + Math.Log(_scale / _shape) + 1.0; } + } + + /// + /// Gets the skewness of the Weibull distribution. + /// + public double Skewness + { + get + { + double mu = Mean; + double sigma = StdDev; + double sigma2 = sigma * sigma; + double sigma3 = sigma2 * sigma; + return ((_scale * _scale * _scale * SpecialFunctions.Gamma(1.0 + (3.0 / _shape))) - (3.0 * sigma2 * mu) - (mu * mu * mu)) / sigma3; + } + } + + /// + /// Gets the mode of the Weibull distribution. + /// + public double Mode + { + get + { + if (_shape <= 1.0) + { + return 0.0; + } + + return _scale * Math.Pow((_shape - 1.0) / _shape, 1.0 / _shape); + } + } + + /// + /// Gets the median of the Weibull distribution. + /// + public double Median + { + get { return _scale * Math.Pow(Constants.Ln2, 1.0 / _shape); } + } + + /// + /// Gets the minimum of the Weibull distribution. + /// + public double Minimum + { + get { return 0.0; } + } + + /// + /// Gets the maximum of the Weibull distribution. + /// + public double Maximum + { + get { return double.PositiveInfinity; } + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The location at which to compute the density. + /// the density at . + public double Density(double x) + { + if (x >= 0.0) + { + if (x == 0.0 && _shape == 1.0) + { + return _shape / _scale; + } + + return _shape * Math.Pow(x / _scale, _shape - 1.0) * Math.Exp(-Math.Pow(x, _shape) * _scalePowShapeInv) / _scale; + } + + return 0.0; + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The location at which to compute the log density. + /// the log density at . + public double DensityLn(double x) + { + if (x >= 0.0) + { + if (x == 0.0 && _shape == 1.0) + { + return Math.Log(_shape) - Math.Log(_scale); + } + + return Math.Log(_shape) + ((_shape - 1.0) * Math.Log(x / _scale)) - (Math.Pow(x, _shape) * _scalePowShapeInv) - Math.Log(_scale); + } + + return double.NegativeInfinity; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + if (x < 0.0) + { + return 0.0; + } + + return -SpecialFunctions.ExponentialMinusOne(-Math.Pow(x, _shape) * _scalePowShapeInv); + } + + /// + /// Generates a sample from the Weibull distribution. + /// + /// a sample from the distribution. + public double Sample() + { + return SampleUnchecked(_random, _shape, _scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(double[] values) + { + SamplesUnchecked(_random, values, _shape, _scale); + } + + /// + /// Generates a sequence of samples from the Weibull distribution. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _shape, _scale); + } + + static double SampleUnchecked(System.Random rnd, double shape, double scale) + { + var x = rnd.NextDouble(); + return scale * Math.Pow(-Math.Log(x), 1.0 / shape); + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double shape, double scale) + { + var exponent = 1.0 / shape; + return rnd.NextDoubleSequence().Select(x => scale * Math.Pow(-Math.Log(x), exponent)); + } + + static void SamplesUnchecked(System.Random rnd, double[] values, double shape, double scale) + { + var exponent = 1.0 / shape; + rnd.NextDoubles(values); + CommonParallel.For(0, values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + values[i] = scale * Math.Pow(-Math.Log(values[i]), exponent); + } + }); + } + + /// + /// Computes the probability density of the distribution (PDF) at x, i.e. ∂P(X ≤ x)/∂x. + /// + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// The location at which to compute the density. + /// the density at . + /// + public static double PDF(double shape, double scale, double x) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x >= 0.0) + { + if (x == 0.0 && shape == 1.0) + { + return shape / scale; + } + + return shape + * Math.Pow(x / scale, shape - 1.0) + * Math.Exp(-Math.Pow(x, shape) * Math.Pow(scale, -shape)) + / scale; + } + + return 0.0; + } + + /// + /// Computes the log probability density of the distribution (lnPDF) at x, i.e. ln(∂P(X ≤ x)/∂x). + /// + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// The location at which to compute the density. + /// the log density at . + /// + public static double PDFLn(double shape, double scale, double x) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x >= 0.0) + { + if (x == 0.0 && shape == 1.0) + { + return Math.Log(shape) - Math.Log(scale); + } + + return Math.Log(shape) + + ((shape - 1.0) * Math.Log(x / scale)) + - (Math.Pow(x, shape) * Math.Pow(scale, -shape)) + - Math.Log(scale); + } + + return double.NegativeInfinity; + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// the cumulative distribution at location . + /// + public static double CDF(double shape, double scale, double x) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 0.0) + { + return 0.0; + } + + return -SpecialFunctions.ExponentialMinusOne(-Math.Pow(x, shape) * Math.Pow(scale, -shape)); + } + + /// + /// Implemented according to: Parameter estimation of the Weibull probability distribution, 1994, Hongzhu Qiao, Chris P. Tsokos + /// + /// + /// + /// Returns a Weibull distribution. + public static Weibull Estimate(IEnumerable samples, System.Random randomSource = null) + { + var samp = samples as double[] ?? samples.ToArray(); + double n = samp.Length, s1 = 0, s2 = 0, s3 = 0, previousC = Int32.MinValue, QofC = 0; + + if (n <= 1) + throw new Exception("Observations not sufficient"); + + // Start values + double c = 10; + double b = 0; + + while (Math.Abs(c - previousC) >= 0.0001) + { + s1 = s2 = s3 = 0; + foreach (double x in samp) + { + if (x > 0) + { + s1 += Math.Log(x); + s2 += Math.Pow(x, c); + s3 += Math.Pow(x, c) * Math.Log(x); + } + } + + QofC = n * s2 / (n * s3 - s1 * s2); + + previousC = c; + c = (c + QofC) / 2; + } + + foreach (double x in samp) + { + if (x > 0) + { + b += Math.Pow(x, c); + } + } + + b = Math.Pow(b / n, 1 / c); + + return new Weibull(c, b, randomSource); + } + + /// + /// Generates a sample from the Weibull distribution. + /// + /// The random number generator to use. + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// a sample from the distribution. + public static double Sample(System.Random rnd, double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, shape, scale); + } + + /// + /// Generates a sequence of samples from the Weibull distribution. + /// + /// The random number generator to use. + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(System.Random rnd, double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, shape, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static void Samples(System.Random rnd, double[] values, double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, shape, scale); + } + + /// + /// Generates a sample from the Weibull distribution. + /// + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// a sample from the distribution. + public static double Sample(double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, shape, scale); + } + + /// + /// Generates a sequence of samples from the Weibull distribution. + /// + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, shape, scale); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The shape (k) of the Weibull distribution. Range: k > 0. + /// The scale (λ) of the Weibull distribution. Range: λ > 0. + /// a sequence of samples from the distribution. + public static void Samples(double[] values, double shape, double scale) + { + if (shape <= 0.0 || scale <= 0.0) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, shape, scale); + } +} diff --git a/MathNet.Numerics/Distributions/Wishart.cs b/MathNet.Numerics/Distributions/Wishart.cs new file mode 100644 index 0000000..a4b7295 --- /dev/null +++ b/MathNet.Numerics/Distributions/Wishart.cs @@ -0,0 +1,291 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Double; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; + +namespace MathNet.Numerics.Distributions; + +/// +/// Multivariate Wishart distribution. This distribution is +/// parameterized by the degrees of freedom nu and the scale matrix S. The Wishart distribution +/// is the conjugate prior for the precision (inverse covariance) matrix of the multivariate +/// normal distribution. +/// Wikipedia - Wishart distribution. +/// +public class Wishart : IDistribution +{ + System.Random _random; + + /// + /// The degrees of freedom for the Wishart distribution. + /// + readonly double _degreesOfFreedom; + + /// + /// The scale matrix for the Wishart distribution. + /// + readonly Matrix _scale; + + /// + /// Caches the Cholesky factorization of the scale matrix. + /// + readonly Cholesky _chol; + + /// + /// Initializes a new instance of the class. + /// + /// The degrees of freedom (n) for the Wishart distribution. + /// The scale matrix (V) for the Wishart distribution. + public Wishart(double degreesOfFreedom, Matrix scale) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(degreesOfFreedom, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _degreesOfFreedom = degreesOfFreedom; + _scale = scale; + _chol = _scale.Cholesky(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The degrees of freedom (n) for the Wishart distribution. + /// The scale matrix (V) for the Wishart distribution. + /// The random number generator which is used to draw random samples. + public Wishart(double degreesOfFreedom, Matrix scale, System.Random randomSource) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(degreesOfFreedom, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _degreesOfFreedom = degreesOfFreedom; + _scale = scale; + _chol = _scale.Cholesky(); + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The degrees of freedom (n) for the Wishart distribution. + /// The scale matrix (V) for the Wishart distribution. + public static bool IsValidParameterSet(double degreesOfFreedom, Matrix scale) + { + if (scale.RowCount != scale.ColumnCount) + { + return false; + } + + for (var i = 0; i < scale.RowCount; i++) + { + if (scale.At(i, i) <= 0.0) + { + return false; + } + } + + if (degreesOfFreedom <= 0.0 || double.IsNaN(degreesOfFreedom)) + { + return false; + } + + return true; + } + + /// + /// Gets or sets the degrees of freedom (n) for the Wishart distribution. + /// + public double DegreesOfFreedom + { + get { return _degreesOfFreedom; } + } + + /// + /// Gets or sets the scale matrix (V) for the Wishart distribution. + /// + public Matrix Scale + { + get { return _scale; } + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Wishart(DegreesOfFreedom = " + _degreesOfFreedom + ", Rows = " + _scale.RowCount + ", Columns = " + _scale.ColumnCount + ")"; + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + /// The mean of the distribution. + public Matrix Mean + { + get { return _degreesOfFreedom * _scale; } + } + + /// + /// Gets the mode of the distribution. + /// + /// The mode of the distribution. + public Matrix Mode + { + get { return (_degreesOfFreedom - _scale.RowCount - 1.0) * _scale; } + } + + /// + /// Gets the variance of the distribution. + /// + /// The variance of the distribution. + public Matrix Variance + { + get + { + return Matrix.Build.Dense(_scale.RowCount, _scale.ColumnCount, + (i, j) => _degreesOfFreedom * ((_scale.At(i, j) * _scale.At(i, j)) + (_scale.At(i, i) * _scale.At(j, j)))); + } + } + + /// + /// Evaluates the probability density function for the Wishart distribution. + /// + /// The matrix at which to evaluate the density at. + /// If the argument does not have the same dimensions as the scale matrix. + /// the density at . + public double Density(Matrix x) + { + var p = _scale.RowCount; + + if (x.RowCount != p || x.ColumnCount != p) + { + throw Matrix.DimensionsDontMatch(x, _scale, "x"); + } + + var dX = x.Determinant(); + var siX = _chol.Solve(x); + + // Compute the multivariate Gamma function. + var gp = Math.Pow(Constants.Pi, p * (p - 1.0) / 4.0); + for (var j = 1; j <= p; j++) + { + gp *= SpecialFunctions.Gamma((_degreesOfFreedom + 1.0 - j) / 2.0); + } + + return Math.Pow(dX, (_degreesOfFreedom - p - 1.0) / 2.0) + * Math.Exp(-0.5 * siX.Trace()) + / Math.Pow(2.0, _degreesOfFreedom * p / 2.0) + / Math.Pow(_chol.Determinant, _degreesOfFreedom / 2.0) + / gp; + } + + /// + /// Samples a Wishart distributed random variable using the method + /// Algorithm AS 53: Wishart Variate Generator + /// W. B. Smith and R. R. Hocking + /// Applied Statistics, Vol. 21, No. 3 (1972), pp. 341-345 + /// + /// A random number from this distribution. + public Matrix Sample() + { + return DoSample(RandomSource, _degreesOfFreedom, _scale, _chol); + } + + /// + /// Samples a Wishart distributed random variable using the method + /// Algorithm AS 53: Wishart Variate Generator + /// W. B. Smith and R. R. Hocking + /// Applied Statistics, Vol. 21, No. 3 (1972), pp. 341-345 + /// + /// The random number generator to use. + /// The degrees of freedom (n) for the Wishart distribution. + /// The scale matrix (V) for the Wishart distribution. + /// a sequence of samples from the distribution. + public static Matrix Sample(System.Random rnd, double degreesOfFreedom, Matrix scale) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(degreesOfFreedom, scale)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return DoSample(rnd, degreesOfFreedom, scale, scale.Cholesky()); + } + + /// + /// Samples the distribution. + /// + /// The random number generator to use. + /// The degrees of freedom (n) for the Wishart distribution. + /// The scale matrix (V) for the Wishart distribution. + /// The cholesky decomposition to use. + /// a random number from the distribution. + static Matrix DoSample(System.Random rnd, double degreesOfFreedom, Matrix scale, Cholesky chol) + { + var count = scale.RowCount; + + // First generate a lower triangular matrix with Sqrt(Chi-Squares) on the diagonal + // and normal distributed variables in the lower triangle. + var a = new DenseMatrix(count, count); + for (var d = 0; d < count; d++) + { + a.At(d, d, Math.Sqrt(Gamma.Sample(rnd, (degreesOfFreedom - d) / 2.0, 0.5))); + } + + for (var i = 1; i < count; i++) + { + for (var j = 0; j < i; j++) + { + a.At(i, j, Normal.Sample(rnd, 0.0, 1.0)); + } + } + + var factor = chol.Factor; + return factor * a * a.Transpose() * factor.Transpose(); + } +} diff --git a/MathNet.Numerics/Distributions/Zipf.cs b/MathNet.Numerics/Distributions/Zipf.cs new file mode 100644 index 0000000..5807f99 --- /dev/null +++ b/MathNet.Numerics/Distributions/Zipf.cs @@ -0,0 +1,496 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Distributions; + +/// +/// Discrete Univariate Zipf distribution. +/// Zipf's law, an empirical law formulated using mathematical statistics, refers to the fact +/// that many types of data studied in the physical and social sciences can be approximated with +/// a Zipfian distribution, one of a family of related discrete power law probability distributions. +/// For details about this distribution, see +/// Wikipedia - Zipf distribution. +/// +public class Zipf : IDiscreteDistribution +{ + System.Random _random; + + /// + /// The s parameter of the distribution. + /// + readonly double _s; + + /// + /// The n parameter of the distribution. + /// + readonly int _n; + + /// + /// Initializes a new instance of the class. + /// + /// The s parameter of the distribution. + /// The n parameter of the distribution. + public Zipf(double s, int n) + { + if (!IsValidParameterSet(s, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = SystemRandomSource.Default; + _s = s; + _n = n; + } + + /// + /// Initializes a new instance of the class. + /// + /// The s parameter of the distribution. + /// The n parameter of the distribution. + /// The random number generator which is used to draw random samples. + public Zipf(double s, int n, System.Random randomSource) + { + if (!IsValidParameterSet(s, n)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + _random = randomSource ?? SystemRandomSource.Default; + _s = s; + _n = n; + } + + /// + /// A string representation of the distribution. + /// + /// a string representation of the distribution. + public override string ToString() + { + return "Zipf(S = " + _s + ", N = " + _n + ")"; + } + + /// + /// Tests whether the provided values are valid parameters for this distribution. + /// + /// The s parameter of the distribution. + /// The n parameter of the distribution. + public static bool IsValidParameterSet(double s, int n) + { + return n > 0 && s > 0.0; + } + + /// + /// Gets or sets the s parameter of the distribution. + /// + public double S + { + get { return _s; } + } + + /// + /// Gets or sets the n parameter of the distribution. + /// + public int N + { + get { return _n; } + } + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public System.Random RandomSource + { + get { return _random; } + set { _random = value ?? SystemRandomSource.Default; } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get { return SpecialFunctions.GeneralHarmonic(_n, _s - 1.0) / SpecialFunctions.GeneralHarmonic(_n, _s); } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + if (_s <= 3) + { + throw new NotSupportedException(); + } + + var ghns = SpecialFunctions.GeneralHarmonic(_n, _s); + return (SpecialFunctions.GeneralHarmonic(_n, _s - 2) * SpecialFunctions.GeneralHarmonic(_n, _s)) + - (Math.Pow(SpecialFunctions.GeneralHarmonic(_n, _s - 1), 2) / (ghns * ghns)); + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get { return Math.Sqrt(Variance); } + } + + /// + /// Gets the entropy of the distribution. + /// + public double Entropy + { + get + { + double sum = 0; + for (var i = 0; i < _n; i++) + { + sum += Math.Log(i + 1) / Math.Pow(i + 1, _s); + } + + return ((_s / SpecialFunctions.GeneralHarmonic(_n, _s)) * sum) + Math.Log(SpecialFunctions.GeneralHarmonic(_n, _s)); + } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get + { + if (_s <= 4) + { + throw new NotSupportedException(); + } + + return ((SpecialFunctions.GeneralHarmonic(_n, _s - 3) * Math.Pow(SpecialFunctions.GeneralHarmonic(_n, _s), 2)) - (SpecialFunctions.GeneralHarmonic(_n, _s - 1) * ((3 * SpecialFunctions.GeneralHarmonic(_n, _s - 2) * SpecialFunctions.GeneralHarmonic(_n, _s)) - Math.Pow(SpecialFunctions.GeneralHarmonic(_n, _s - 1), 2)))) / Math.Pow((SpecialFunctions.GeneralHarmonic(_n, _s - 2) * SpecialFunctions.GeneralHarmonic(_n, _s)) - Math.Pow(SpecialFunctions.GeneralHarmonic(_n, _s - 1), 2), 1.5); + } + } + + /// + /// Gets the mode of the distribution. + /// + public int Mode + { + get { return 1; } + } + + /// + /// Gets the median of the distribution. + /// + public double Median + { + get { throw new NotSupportedException(); } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get { return 1; } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get { return _n; } + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + return (1.0 / Math.Pow(k, _s)) / SpecialFunctions.GeneralHarmonic(_n, _s); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + return Math.Log(Probability(k)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// the cumulative distribution at location . + public double CumulativeDistribution(double x) + { + if (x < 1) + { + return 0.0; + } + + return SpecialFunctions.GeneralHarmonic((int)x, _s) / SpecialFunctions.GeneralHarmonic(_n, _s); + } + + /// + /// Computes the probability mass (PMF) at k, i.e. P(X = k). + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// The s parameter of the distribution. + /// The n parameter of the distribution. + /// the probability mass at location . + public static double PMF(double s, int n, int k) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return (1.0 / Math.Pow(k, s)) / SpecialFunctions.GeneralHarmonic(n, s); + } + + /// + /// Computes the log probability mass (lnPMF) at k, i.e. ln(P(X = k)). + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// The s parameter of the distribution. + /// The n parameter of the distribution. + /// the log probability mass at location . + public static double PMFLn(double s, int n, int k) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return Math.Log(PMF(s, n, k)); + } + + /// + /// Computes the cumulative distribution (CDF) of the distribution at x, i.e. P(X ≤ x). + /// + /// The location at which to compute the cumulative distribution function. + /// The s parameter of the distribution. + /// The n parameter of the distribution. + /// the cumulative distribution at location . + /// + public static double CDF(double s, int n, double x) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + if (x < 1) + { + return 0.0; + } + + return SpecialFunctions.GeneralHarmonic((int)x, s) / SpecialFunctions.GeneralHarmonic(n, s); + } + + /// + /// Generates a sample from the Zipf distribution without doing parameter checking. + /// + /// The random number generator to use. + /// The s parameter of the distribution. + /// The n parameter of the distribution. + /// a random number from the Zipf distribution. + static int SampleUnchecked(System.Random rnd, double s, int n) + { + var r = 0.0; + while (r == 0.0) + { + r = rnd.NextDouble(); + } + + var p = 1.0 / SpecialFunctions.GeneralHarmonic(n, s); + int i; + var sum = 0.0; + for (i = 1; i <= n; i++) + { + sum += p / Math.Pow(i, s); + if (sum >= r) + { + break; + } + } + + return i; + } + + static void SamplesUnchecked(System.Random rnd, int[] values, double s, int n) + { + for (int i = 0; i < values.Length; i++) + { + values[i] = SampleUnchecked(rnd, s, n); + } + } + + static IEnumerable SamplesUnchecked(System.Random rnd, double s, int n) + { + while (true) + { + yield return SampleUnchecked(rnd, s, n); + } + } + + /// + /// Draws a random sample from the distribution. + /// + /// a sample from the distribution. + public int Sample() + { + return SampleUnchecked(_random, _s, _n); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + public void Samples(int[] values) + { + SamplesUnchecked(_random, values, _s, _n); + } + + /// + /// Samples an array of zipf distributed random variables. + /// + /// a sequence of samples from the distribution. + public IEnumerable Samples() + { + return SamplesUnchecked(_random, _s, _n); + } + + /// + /// Samples a random variable. + /// + /// The random number generator to use. + /// The s parameter of the distribution. + /// The n parameter of the distribution. + public static int Sample(System.Random rnd, double s, int n) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(rnd, s, n); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The random number generator to use. + /// The s parameter of the distribution. + /// The n parameter of the distribution. + public static IEnumerable Samples(System.Random rnd, double s, int n) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(rnd, s, n); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The random number generator to use. + /// The array to fill with the samples. + /// The s parameter of the distribution. + /// The n parameter of the distribution. + public static void Samples(System.Random rnd, int[] values, double s, int n) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(rnd, values, s, n); + } + + /// + /// Samples a random variable. + /// + /// The s parameter of the distribution. + /// The n parameter of the distribution. + public static int Sample(double s, int n) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SampleUnchecked(SystemRandomSource.Default, s, n); + } + + /// + /// Samples a sequence of this random variable. + /// + /// The s parameter of the distribution. + /// The n parameter of the distribution. + public static IEnumerable Samples(double s, int n) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + return SamplesUnchecked(SystemRandomSource.Default, s, n); + } + + /// + /// Fills an array with samples generated from the distribution. + /// + /// The array to fill with the samples. + /// The s parameter of the distribution. + /// The n parameter of the distribution. + public static void Samples(int[] values, double s, int n) + { + if (!(n > 0 && s > 0.0)) + { + throw new ArgumentException(Resources.InvalidDistributionParameters); + } + + SamplesUnchecked(SystemRandomSource.Default, values, s, n); + } +} diff --git a/MathNet.Numerics/Euclid.cs b/MathNet.Numerics/Euclid.cs new file mode 100644 index 0000000..1b95a1b --- /dev/null +++ b/MathNet.Numerics/Euclid.cs @@ -0,0 +1,707 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +using BigInteger = System.Numerics.BigInteger; + +namespace MathNet.Numerics; + +/// +/// Integer number theory functions. +/// +public static class Euclid +{ + /// + /// Canonical Modulus. The result has the sign of the divisor. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static double Modulus(double dividend, double divisor) + { + return ((dividend % divisor) + divisor) % divisor; + } + + /// + /// Canonical Modulus. The result has the sign of the divisor. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static float Modulus(float dividend, float divisor) + { + return ((dividend % divisor) + divisor) % divisor; + } + + /// + /// Canonical Modulus. The result has the sign of the divisor. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static int Modulus(int dividend, int divisor) + { + return ((dividend % divisor) + divisor) % divisor; + } + + /// + /// Canonical Modulus. The result has the sign of the divisor. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static long Modulus(long dividend, long divisor) + { + return ((dividend % divisor) + divisor) % divisor; + } + + /// + /// Canonical Modulus. The result has the sign of the divisor. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static BigInteger Modulus(BigInteger dividend, BigInteger divisor) + { + return ((dividend % divisor) + divisor) % divisor; + } + + /// + /// Remainder (% operator). The result has the sign of the dividend. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static double Remainder(double dividend, double divisor) + { + return dividend % divisor; + } + + /// + /// Remainder (% operator). The result has the sign of the dividend. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static float Remainder(float dividend, float divisor) + { + return dividend % divisor; + } + + /// + /// Remainder (% operator). The result has the sign of the dividend. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static int Remainder(int dividend, int divisor) + { + return dividend % divisor; + } + + /// + /// Remainder (% operator). The result has the sign of the dividend. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static long Remainder(long dividend, long divisor) + { + return dividend % divisor; + } + + /// + /// Remainder (% operator). The result has the sign of the dividend. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static BigInteger Remainder(BigInteger dividend, BigInteger divisor) + { + return dividend % divisor; + } + + /// + /// Find out whether the provided 32 bit integer is an even number. + /// + /// The number to very whether it's even. + /// True if and only if it is an even number. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static bool IsEven(this int number) + { + return (number & 0x1) == 0x0; + } + + /// + /// Find out whether the provided 64 bit integer is an even number. + /// + /// The number to very whether it's even. + /// True if and only if it is an even number. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static bool IsEven(this long number) + { + return (number & 0x1) == 0x0; + } + + /// + /// Find out whether the provided 32 bit integer is an odd number. + /// + /// The number to very whether it's odd. + /// True if and only if it is an odd number. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static bool IsOdd(this int number) + { + return (number & 0x1) == 0x1; + } + + /// + /// Find out whether the provided 64 bit integer is an odd number. + /// + /// The number to very whether it's odd. + /// True if and only if it is an odd number. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static bool IsOdd(this long number) + { + return (number & 0x1) == 0x1; + } + + /// + /// Find out whether the provided 32 bit integer is a perfect power of two. + /// + /// The number to very whether it's a power of two. + /// True if and only if it is a power of two. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static bool IsPowerOfTwo(this int number) + { + return number > 0 && (number & (number - 1)) == 0x0; + } + + /// + /// Find out whether the provided 64 bit integer is a perfect power of two. + /// + /// The number to very whether it's a power of two. + /// True if and only if it is a power of two. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static bool IsPowerOfTwo(this long number) + { + return number > 0 && (number & (number - 1)) == 0x0; + } + + /// + /// Find out whether the provided 32 bit integer is a perfect square, i.e. a square of an integer. + /// + /// The number to very whether it's a perfect square. + /// True if and only if it is a perfect square. + public static bool IsPerfectSquare(this int number) + { + if (number < 0) + { + return false; + } + + int lastHexDigit = number & 0xF; + if (lastHexDigit > 9) + { + return false; // return immediately in 6 cases out of 16. + } + + if (lastHexDigit == 0 || lastHexDigit == 1 || lastHexDigit == 4 || lastHexDigit == 9) + { + int t = (int)Math.Floor(Math.Sqrt(number) + 0.5); + return (t * t) == number; + } + + return false; + } + + /// + /// Find out whether the provided 64 bit integer is a perfect square, i.e. a square of an integer. + /// + /// The number to very whether it's a perfect square. + /// True if and only if it is a perfect square. + public static bool IsPerfectSquare(this long number) + { + if (number < 0) + { + return false; + } + + int lastHexDigit = (int)(number & 0xF); + if (lastHexDigit > 9) + { + return false; // return immediately in 6 cases out of 16. + } + + if (lastHexDigit == 0 || lastHexDigit == 1 || lastHexDigit == 4 || lastHexDigit == 9) + { + long t = (long)Math.Floor(Math.Sqrt(number) + 0.5); + return (t * t) == number; + } + + return false; + } + + /// + /// Raises 2 to the provided integer exponent (0 <= exponent < 31). + /// + /// The exponent to raise 2 up to. + /// 2 ^ exponent. + /// + public static int PowerOfTwo(this int exponent) + { + if (exponent < 0 || exponent >= 31) + { + throw new ArgumentOutOfRangeException(nameof(exponent)); + } + + return 1 << exponent; + } + + /// + /// Raises 2 to the provided integer exponent (0 <= exponent < 63). + /// + /// The exponent to raise 2 up to. + /// 2 ^ exponent. + /// + public static long PowerOfTwo(this long exponent) + { + if (exponent < 0 || exponent >= 63) + { + throw new ArgumentOutOfRangeException(nameof(exponent)); + } + + return ((long)1) << (int)exponent; + } + + /// + /// Evaluate the binary logarithm of an integer number. + /// + /// Two-step method using a De Bruijn-like sequence table lookup. + public static int Log2(this int number) + { + number |= number >> 1; + number |= number >> 2; + number |= number >> 4; + number |= number >> 8; + number |= number >> 16; + + return MultiplyDeBruijnBitPosition[(uint)(number * 0x07C4ACDDU) >> 27]; + } + + static readonly int[] MultiplyDeBruijnBitPosition = new int[32] + { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + /// + /// Find the closest perfect power of two that is larger or equal to the provided + /// 32 bit integer. + /// + /// The number of which to find the closest upper power of two. + /// A power of two. + /// + public static int CeilingToPowerOfTwo(this int number) + { + if (number == Int32.MinValue) + { + return 0; + } + + const int maxPowerOfTwo = 0x40000000; + if (number > maxPowerOfTwo) + { + throw new ArgumentOutOfRangeException(nameof(number)); + } + + number--; + number |= number >> 1; + number |= number >> 2; + number |= number >> 4; + number |= number >> 8; + number |= number >> 16; + return number + 1; + } + + /// + /// Find the closest perfect power of two that is larger or equal to the provided + /// 64 bit integer. + /// + /// The number of which to find the closest upper power of two. + /// A power of two. + /// + public static long CeilingToPowerOfTwo(this long number) + { + if (number == Int64.MinValue) + { + return 0; + } + + const long maxPowerOfTwo = 0x4000000000000000; + if (number > maxPowerOfTwo) + { + throw new ArgumentOutOfRangeException(nameof(number)); + } + + number--; + number |= number >> 1; + number |= number >> 2; + number |= number >> 4; + number |= number >> 8; + number |= number >> 16; + number |= number >> 32; + return number + 1; + } + + /// + /// Returns the greatest common divisor (gcd) of two integers using Euclid's algorithm. + /// + /// First Integer: a. + /// Second Integer: b. + /// Greatest common divisor gcd(a,b) + public static long GreatestCommonDivisor(long a, long b) + { + while (b != 0) + { + var remainder = a % b; + a = b; + b = remainder; + } + + return Math.Abs(a); + } + + /// + /// Returns the greatest common divisor (gcd) of a set of integers using Euclid's + /// algorithm. + /// + /// List of Integers. + /// Greatest common divisor gcd(list of integers) + public static long GreatestCommonDivisor(IList integers) + { + if (null == integers) + { + throw new ArgumentNullException(nameof(integers)); + } + + if (integers.Count == 0) + { + return 0; + } + + var gcd = Math.Abs(integers[0]); + + for (var i = 1; (i < integers.Count) && (gcd > 1); i++) + { + gcd = GreatestCommonDivisor(gcd, integers[i]); + } + + return gcd; + } + + /// + /// Returns the greatest common divisor (gcd) of a set of integers using Euclid's algorithm. + /// + /// List of Integers. + /// Greatest common divisor gcd(list of integers) + public static long GreatestCommonDivisor(params long[] integers) + { + return GreatestCommonDivisor((IList)integers); + } + + /// + /// Computes the extended greatest common divisor, such that a*x + b*y = gcd(a,b). + /// + /// First Integer: a. + /// Second Integer: b. + /// Resulting x, such that a*x + b*y = gcd(a,b). + /// Resulting y, such that a*x + b*y = gcd(a,b) + /// Greatest common divisor gcd(a,b) + /// + /// + /// long x,y,d; + /// d = Fn.GreatestCommonDivisor(45,18,out x, out y); + /// -> d == 9 && x == 1 && y == -2 + /// + /// The gcd of 45 and 18 is 9: 18 = 2*9, 45 = 5*9. 9 = 1*45 -2*18, therefore x=1 and y=-2. + /// + public static long ExtendedGreatestCommonDivisor(long a, long b, out long x, out long y) + { + long mp = 1, np = 0, m = 0, n = 1; + + while (b != 0) + { + long rem; +#if NETSTANDARD1_3 + rem = a % b; + var quot = a / b; +#else + long quot = Math.DivRem(a, b, out rem); +#endif + a = b; + b = rem; + + var tmp = m; + m = mp - (quot * m); + mp = tmp; + + tmp = n; + n = np - (quot * n); + np = tmp; + } + + if (a >= 0) + { + x = mp; + y = np; + return a; + } + + x = -mp; + y = -np; + return -a; + } + + /// + /// Returns the least common multiple (lcm) of two integers using Euclid's algorithm. + /// + /// First Integer: a. + /// Second Integer: b. + /// Least common multiple lcm(a,b) + public static long LeastCommonMultiple(long a, long b) + { + if ((a == 0) || (b == 0)) + { + return 0; + } + + return Math.Abs((a / GreatestCommonDivisor(a, b)) * b); + } + + /// + /// Returns the least common multiple (lcm) of a set of integers using Euclid's algorithm. + /// + /// List of Integers. + /// Least common multiple lcm(list of integers) + public static long LeastCommonMultiple(IList integers) + { + if (null == integers) + { + throw new ArgumentNullException(nameof(integers)); + } + + if (integers.Count == 0) + { + return 1; + } + + var lcm = Math.Abs(integers[0]); + + for (var i = 1; i < integers.Count; i++) + { + lcm = LeastCommonMultiple(lcm, integers[i]); + } + + return lcm; + } + + /// + /// Returns the least common multiple (lcm) of a set of integers using Euclid's algorithm. + /// + /// List of Integers. + /// Least common multiple lcm(list of integers) + public static long LeastCommonMultiple(params long[] integers) + { + return LeastCommonMultiple((IList)integers); + } + + /// + /// Returns the greatest common divisor (gcd) of two big integers. + /// + /// First Integer: a. + /// Second Integer: b. + /// Greatest common divisor gcd(a,b) + public static BigInteger GreatestCommonDivisor(BigInteger a, BigInteger b) + { + return BigInteger.GreatestCommonDivisor(a, b); + } + + /// + /// Returns the greatest common divisor (gcd) of a set of big integers. + /// + /// List of Integers. + /// Greatest common divisor gcd(list of integers) + public static BigInteger GreatestCommonDivisor(IList integers) + { + if (null == integers) + { + throw new ArgumentNullException(nameof(integers)); + } + + if (integers.Count == 0) + { + return 0; + } + + var gcd = BigInteger.Abs(integers[0]); + + for (int i = 1; (i < integers.Count) && (gcd > BigInteger.One); i++) + { + gcd = GreatestCommonDivisor(gcd, integers[i]); + } + + return gcd; + } + + /// + /// Returns the greatest common divisor (gcd) of a set of big integers. + /// + /// List of Integers. + /// Greatest common divisor gcd(list of integers) + public static BigInteger GreatestCommonDivisor(params BigInteger[] integers) + { + return GreatestCommonDivisor((IList)integers); + } + + /// + /// Computes the extended greatest common divisor, such that a*x + b*y = gcd(a,b). + /// + /// First Integer: a. + /// Second Integer: b. + /// Resulting x, such that a*x + b*y = gcd(a,b). + /// Resulting y, such that a*x + b*y = gcd(a,b) + /// Greatest common divisor gcd(a,b) + /// + /// + /// long x,y,d; + /// d = Fn.GreatestCommonDivisor(45,18,out x, out y); + /// -> d == 9 && x == 1 && y == -2 + /// + /// The gcd of 45 and 18 is 9: 18 = 2*9, 45 = 5*9. 9 = 1*45 -2*18, therefore x=1 and y=-2. + /// + public static BigInteger ExtendedGreatestCommonDivisor(BigInteger a, BigInteger b, out BigInteger x, out BigInteger y) + { + BigInteger mp = BigInteger.One, np = BigInteger.Zero, m = BigInteger.Zero, n = BigInteger.One; + + while (!b.IsZero) + { + BigInteger rem; + BigInteger quot = BigInteger.DivRem(a, b, out rem); + a = b; + b = rem; + + BigInteger tmp = m; + m = mp - (quot * m); + mp = tmp; + + tmp = n; + n = np - (quot * n); + np = tmp; + } + + if (a >= BigInteger.Zero) + { + x = mp; + y = np; + return a; + } + + x = -mp; + y = -np; + return -a; + } + + /// + /// Returns the least common multiple (lcm) of two big integers. + /// + /// First Integer: a. + /// Second Integer: b. + /// Least common multiple lcm(a,b) + public static BigInteger LeastCommonMultiple(BigInteger a, BigInteger b) + { + if (a.IsZero || b.IsZero) + { + return BigInteger.Zero; + } + + return BigInteger.Abs((a / BigInteger.GreatestCommonDivisor(a, b)) * b); + } + + /// + /// Returns the least common multiple (lcm) of a set of big integers. + /// + /// List of Integers. + /// Least common multiple lcm(list of integers) + public static BigInteger LeastCommonMultiple(IList integers) + { + if (null == integers) + { + throw new ArgumentNullException(nameof(integers)); + } + + if (integers.Count == 0) + { + return 1; + } + + var lcm = BigInteger.Abs(integers[0]); + + for (int i = 1; i < integers.Count; i++) + { + lcm = LeastCommonMultiple(lcm, integers[i]); + } + + return lcm; + } + + /// + /// Returns the least common multiple (lcm) of a set of big integers. + /// + /// List of Integers. + /// Least common multiple lcm(list of integers) + public static BigInteger LeastCommonMultiple(params BigInteger[] integers) + { + return LeastCommonMultiple((IList)integers); + } +} diff --git a/MathNet.Numerics/ExcelFunctions.cs b/MathNet.Numerics/ExcelFunctions.cs new file mode 100644 index 0000000..9316f8c --- /dev/null +++ b/MathNet.Numerics/ExcelFunctions.cs @@ -0,0 +1,164 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.Statistics; + +#if !NETSTANDARD1_3 +using System.Runtime; +#endif + +// ReSharper disable InconsistentNaming + +namespace MathNet.Numerics; + +/// +/// Collection of functions equivalent to those provided by Microsoft Excel +/// but backed instead by Math.NET Numerics. +/// We do not recommend to use them except in an intermediate phase when +/// porting over solutions previously implemented in Excel. +/// +public static class ExcelFunctions +{ + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double NormSDist(double z) + { + return Normal.CDF(0d, 1d, z); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double NormSInv(double probability) + { + return Normal.InvCDF(0d, 1d, probability); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double NormDist(double x, double mean, double standardDev, bool cumulative) + { + return cumulative ? Normal.CDF(mean, standardDev, x) : Normal.PDF(mean, standardDev, x); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double NormInv(double probability, double mean, double standardDev) + { + return Normal.InvCDF(mean, standardDev, probability); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double TDist(double x, int degreesFreedom, int tails) + { + switch (tails) + { + case 1: + return 1d - StudentT.CDF(0d, 1d, degreesFreedom, x); + case 2: + return 1d - StudentT.CDF(0d, 1d, degreesFreedom, x) + StudentT.CDF(0d, 1d, degreesFreedom, -x); + default: + throw new ArgumentOutOfRangeException(nameof(tails)); + } + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double TInv(double probability, int degreesFreedom) + { + return -StudentT.InvCDF(0d, 1d, degreesFreedom, probability / 2); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double FDist(double x, int degreesFreedom1, int degreesFreedom2) + { + return 1d - FisherSnedecor.CDF(degreesFreedom1, degreesFreedom2, x); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double FInv(double probability, int degreesFreedom1, int degreesFreedom2) + { + return FisherSnedecor.InvCDF(degreesFreedom1, degreesFreedom2, 1d - probability); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double BetaDist(double x, double alpha, double beta) + { + return Beta.CDF(alpha, beta, x); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double BetaInv(double probability, double alpha, double beta) + { + return Beta.InvCDF(alpha, beta, probability); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double GammaDist(double x, double alpha, double beta, bool cumulative) + { + return cumulative ? Gamma.CDF(alpha, 1 / beta, x) : Gamma.PDF(alpha, 1 / beta, x); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double GammaInv(double probability, double alpha, double beta) + { + return Gamma.InvCDF(alpha, 1 / beta, probability); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double Quartile(double[] array, int quant) + { + switch (quant) + { + case 0: + return ArrayStatistics.Minimum(array); + case 1: + return array.QuantileCustom(0.25, QuantileDefinition.Excel); + case 2: + return array.QuantileCustom(0.5, QuantileDefinition.Excel); + case 3: + return array.QuantileCustom(0.75, QuantileDefinition.Excel); + case 4: + return ArrayStatistics.Maximum(array); + default: + throw new ArgumentOutOfRangeException(nameof(quant)); + } + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double Percentile(double[] array, double k) + { + return array.QuantileCustom(k, QuantileDefinition.Excel); + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double PercentRank(double[] array, double x) + { + return array.QuantileRank(x, RankDefinition.Min); + } +} + +// ReSharper restore InconsistentNaming diff --git a/MathNet.Numerics/Exceptions.cs b/MathNet.Numerics/Exceptions.cs new file mode 100644 index 0000000..6741de8 --- /dev/null +++ b/MathNet.Numerics/Exceptions.cs @@ -0,0 +1,164 @@ +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics; + +/// +/// An algorithm failed to converge. +/// +[Serializable] +public class NonConvergenceException : Exception +{ + public NonConvergenceException() : base(Resources.ConvergenceFailed) + { + } + + public NonConvergenceException(string message) : base(message) + { + } + + public NonConvergenceException(string message, Exception innerException) : base(message, innerException) + { + } +#if !NETSTANDARD1_3 + protected NonConvergenceException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#endif +} + +/// +/// An algorithm failed to converge due to a numerical breakdown. +/// +[Serializable] +public class NumericalBreakdownException : NonConvergenceException +{ + public NumericalBreakdownException() + : base(Resources.NumericalBreakdown) + { + } + + public NumericalBreakdownException(string message) + : base(message) + { + } + + public NumericalBreakdownException(string message, Exception innerException) + : base(message, innerException) + { + } +#if !NETSTANDARD1_3 + protected NumericalBreakdownException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#endif +} + +/// +/// An error occurred calling native provider function. +/// +[Serializable] +public abstract class NativeInterfaceException : Exception +{ + protected NativeInterfaceException() + { + } + + protected NativeInterfaceException(string message) + : base(message) + { + } + + protected NativeInterfaceException(string message, Exception innerException) + : base(message, innerException) + { + } +#if !NETSTANDARD1_3 + protected NativeInterfaceException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#endif +} + +/// +/// An error occurred calling native provider function. +/// +[Serializable] +public class InvalidParameterException : NativeInterfaceException +{ + public InvalidParameterException() + : base(Resources.InvalidParameter) + { + } + + public InvalidParameterException(int parameter) + : base(string.Format(Resources.InvalidParameterWithNumber, parameter)) + { + } + + public InvalidParameterException(int parameter, Exception innerException) + : base(string.Format(Resources.InvalidParameterWithNumber, parameter), innerException) + { + } +#if !NETSTANDARD1_3 + protected InvalidParameterException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#endif +} + +/// +/// Native provider was unable to allocate sufficient memory. +/// +[Serializable] +public class MemoryAllocationException : NativeInterfaceException +{ + public MemoryAllocationException() + : base(Resources.MemoryAllocation) + { + } + + public MemoryAllocationException(Exception innerException) + : base(Resources.MemoryAllocation, innerException) + { + } +#if !NETSTANDARD1_3 + protected MemoryAllocationException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#endif +} + +/// +/// Native provider failed LU inversion do to a singular U matrix. +/// +[Serializable] +public class SingularUMatrixException : NativeInterfaceException +{ + public SingularUMatrixException() + : base(Resources.SingularUMatrix) + { + } + + public SingularUMatrixException(int element) + : base(string.Format(Resources.SingularUMatrixWithElement, element)) + { + } + + public SingularUMatrixException(int element, Exception innerException) + : base(string.Format(Resources.SingularUMatrixWithElement, element), innerException) + { + } +#if !NETSTANDARD1_3 + protected SingularUMatrixException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } +#endif +} diff --git a/MathNet.Numerics/Financial/AbsoluteReturnMeasures.cs b/MathNet.Numerics/Financial/AbsoluteReturnMeasures.cs new file mode 100644 index 0000000..9dac71f --- /dev/null +++ b/MathNet.Numerics/Financial/AbsoluteReturnMeasures.cs @@ -0,0 +1,92 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Statistics; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Financial; + +public static class AbsoluteReturnMeasures +{ + /// + /// Compound Monthly Return or Geometric Return or Annualized Return + /// + public static double CompoundReturn(this IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + int count = 0; + double compoundReturn = 1.0; + foreach (var item in data) + { + count++; + compoundReturn *= 1 + item; + } + + return count == 0 ? double.NaN : Math.Pow(compoundReturn, 1.0 / count) - 1.0; + } + + /// + /// Average Gain or Gain Mean + /// This is a simple average (arithmetic mean) of the periods with a gain. It is calculated by summing the returns for gain periods (return 0) + /// and then dividing the total by the number of gain periods. + /// + /// http://www.offshore-library.com/kb/statistics.php + public static double GainMean(this IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + return data.Where(x => x >= 0).Mean(); + } + + /// + /// Average Loss or LossMean + /// This is a simple average (arithmetic mean) of the periods with a loss. It is calculated by summing the returns for loss periods (return < 0) + /// and then dividing the total by the number of loss periods. + /// + /// http://www.offshore-library.com/kb/statistics.php + public static double LossMean(this IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + return data.Where(x => x < 0).Mean(); + } +} diff --git a/MathNet.Numerics/Financial/AbsoluteRiskMeasures.cs b/MathNet.Numerics/Financial/AbsoluteRiskMeasures.cs new file mode 100644 index 0000000..8b997cd --- /dev/null +++ b/MathNet.Numerics/Financial/AbsoluteRiskMeasures.cs @@ -0,0 +1,124 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Statistics; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Financial; + +public static class AbsoluteRiskMeasures +{ + // Note: The following statistics would be considered an absolute risk statistic in the finance realm as well. + // Standard Deviation + // Annualized Standard Deviation = Math.Sqrt(Monthly Standard Deviation x ( 12 )) + // Skewness + // Kurtosis + + /// + /// Calculation is similar to Standard Deviation , except it calculates an average (mean) return only for periods with a gain + /// and measures the variation of only the gain periods around the gain mean. Measures the volatility of upside performance. + /// © Copyright 1996, 1999 Gary L.Gastineau. First Edition. © 1992 Swiss Bank Corporation. + /// + public static double GainStandardDeviation(this IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + return data.Where(x => x >= 0).StandardDeviation(); + } + + /// + /// Similar to standard deviation, except this statistic calculates an average (mean) return for only the periods with a loss and then + /// measures the variation of only the losing periods around this loss mean. This statistic measures the volatility of downside performance. + /// + /// http://www.offshore-library.com/kb/statistics.php + public static double LossStandardDeviation(this IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + return data.Where(x => x < 0).StandardDeviation(); + } + + /// + /// This measure is similar to the loss standard deviation except the downside deviation + /// considers only returns that fall below a defined minimum acceptable return (MAR) rather than the arithmetic mean. + /// For example, if the MAR is 7%, the downside deviation would measure the variation of each period that falls below + /// 7%. (The loss standard deviation, on the other hand, would take only losing periods, calculate an average return for + /// the losing periods, and then measure the variation between each losing return and the losing return average). + /// + public static double DownsideDeviation(this IEnumerable data, double minimalAcceptableReturn) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + return data.Where(x => x < minimalAcceptableReturn).StandardDeviation(); + } + + /// + /// A measure of volatility in returns below the mean. It's similar to standard deviation, but it only + /// looks at periods where the investment return was less than average return. + /// + public static double SemiDeviation(this IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var mean = data.Mean(); + var belowMeanData = data.Where(x => x < mean); + return belowMeanData.StandardDeviation(); + } + + /// + /// Measures a fund’s average gain in a gain period divided by the fund’s average loss in a losing + /// period. Periods can be monthly or quarterly depending on the data frequency. + /// + public static double GainLossRatio(this IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var gains = data.Where(x => x >= 0); + var losses = data.Where(x => x < 0); + return Math.Abs(gains.Mean() / losses.Mean()); + } +} diff --git a/MathNet.Numerics/FindMinimum.cs b/MathNet.Numerics/FindMinimum.cs new file mode 100644 index 0000000..dd6a253 --- /dev/null +++ b/MathNet.Numerics/FindMinimum.cs @@ -0,0 +1,179 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization; + +using System; + +namespace MathNet.Numerics; + +public static class FindMinimum +{ + /// + /// Find value x that minimizes the scalar function f(x), constrained within bounds, using the Golden Section algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static double OfScalarFunctionConstrained(Func function, double lowerBound, double upperBound, double tolerance = 1e-5, int maxIterations = 1000) + { + var objective = ObjectiveFunction.ScalarValue(function); + var result = GoldenSectionMinimizer.Minimum(objective, lowerBound, upperBound, tolerance, maxIterations); + return result.MinimizingPoint; + } + + /// + /// Find vector x that minimizes the function f(x) using the Nelder-Mead Simplex algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static double OfScalarFunction(Func function, double initialGuess, double tolerance = 1e-8, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Value(v => function(v[0])); + var result = NelderMeadSimplex.Minimum(objective, CreateVector.Dense(new[] { initialGuess }), tolerance, maxIterations); + return result.MinimizingPoint[0]; + } + + /// + /// Find vector x that minimizes the function f(x) using the Nelder-Mead Simplex algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static Tuple OfFunction(Func function, double initialGuess0, double initialGuess1, double tolerance = 1e-8, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Value(v => function(v[0], v[1])); + var result = NelderMeadSimplex.Minimum(objective, CreateVector.Dense(new[] { initialGuess0, initialGuess1 }), tolerance, maxIterations); + return Tuple.Create(result.MinimizingPoint[0], result.MinimizingPoint[1]); + } + + /// + /// Find vector x that minimizes the function f(x) using the Nelder-Mead Simplex algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static Tuple OfFunction(Func function, double initialGuess0, double initialGuess1, double initialGuess2, double tolerance = 1e-8, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Value(v => function(v[0], v[1], v[2])); + var result = NelderMeadSimplex.Minimum(objective, CreateVector.Dense(new[] { initialGuess0, initialGuess1, initialGuess2 }), tolerance, maxIterations); + return Tuple.Create(result.MinimizingPoint[0], result.MinimizingPoint[1], result.MinimizingPoint[2]); + } + + /// + /// Find vector x that minimizes the function f(x) using the Nelder-Mead Simplex algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static Vector OfFunction(Func, double> function, Vector initialGuess, double tolerance = 1e-8, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Value(function); + var result = NelderMeadSimplex.Minimum(objective, initialGuess, tolerance, maxIterations); + return result.MinimizingPoint; + } + + /// + /// Find vector x that minimizes the function f(x), constrained within bounds, using the Broyden–Fletcher–Goldfarb–Shanno Bounded (BFGS-B) algorithm. + /// The missing gradient is evaluated numerically (forward difference). + /// For more options and diagnostics consider to use directly. + /// + public static Vector OfFunctionConstrained(Func, double> function, Vector lowerBound, Vector upperBound, Vector initialGuess, double gradientTolerance = 1e-5, double parameterTolerance = 1e-5, double functionProgressTolerance = 1e-5, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Value(function); + var objectiveWithGradient = new Optimization.ObjectiveFunctions.ForwardDifferenceGradientObjectiveFunction(objective, lowerBound, upperBound); + var algorithm = new BfgsBMinimizer(gradientTolerance, parameterTolerance, functionProgressTolerance, maxIterations); + var result = algorithm.FindMinimum(objectiveWithGradient, lowerBound, upperBound, initialGuess); + return result.MinimizingPoint; + } + + /// + /// Find vector x that minimizes the function f(x) using the Broyden–Fletcher–Goldfarb–Shanno (BFGS) algorithm. + /// For more options and diagnostics consider to use directly. + /// An alternative routine using conjugate gradients (CG) is available in . + /// + public static Vector OfFunctionGradient(Func, double> function, Func, Vector> gradient, Vector initialGuess, double gradientTolerance = 1e-5, double parameterTolerance = 1e-5, double functionProgressTolerance = 1e-5, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Gradient(function, gradient); + var algorithm = new BfgsMinimizer(gradientTolerance, parameterTolerance, functionProgressTolerance, maxIterations); + var result = algorithm.FindMinimum(objective, initialGuess); + return result.MinimizingPoint; + } + + /// + /// Find vector x that minimizes the function f(x) using the Broyden–Fletcher–Goldfarb–Shanno (BFGS) algorithm. + /// For more options and diagnostics consider to use directly. + /// An alternative routine using conjugate gradients (CG) is available in . + /// + public static Vector OfFunctionGradient(Func, Tuple>> functionGradient, Vector initialGuess, double gradientTolerance = 1e-5, double parameterTolerance = 1e-5, double functionProgressTolerance = 1e-5, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Gradient(functionGradient); + var algorithm = new BfgsMinimizer(gradientTolerance, parameterTolerance, functionProgressTolerance, maxIterations); + var result = algorithm.FindMinimum(objective, initialGuess); + return result.MinimizingPoint; + } + + /// + /// Find vector x that minimizes the function f(x), constrained within bounds, using the Broyden–Fletcher–Goldfarb–Shanno Bounded (BFGS-B) algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static Vector OfFunctionGradientConstrained(Func, double> function, Func, Vector> gradient, Vector lowerBound, Vector upperBound, Vector initialGuess, double gradientTolerance = 1e-5, double parameterTolerance = 1e-5, double functionProgressTolerance = 1e-5, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Gradient(function, gradient); + var algorithm = new BfgsBMinimizer(gradientTolerance, parameterTolerance, functionProgressTolerance, maxIterations); + var result = algorithm.FindMinimum(objective, lowerBound, upperBound, initialGuess); + return result.MinimizingPoint; + } + + /// + /// Find vector x that minimizes the function f(x), constrained within bounds, using the Broyden–Fletcher–Goldfarb–Shanno Bounded (BFGS-B) algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static Vector OfFunctionGradientConstrained(Func, Tuple>> functionGradient, Vector lowerBound, Vector upperBound, Vector initialGuess, double gradientTolerance = 1e-5, double parameterTolerance = 1e-5, double functionProgressTolerance = 1e-5, int maxIterations = 1000) + { + var objective = ObjectiveFunction.Gradient(functionGradient); + var algorithm = new BfgsBMinimizer(gradientTolerance, parameterTolerance, functionProgressTolerance, maxIterations); + var result = algorithm.FindMinimum(objective, lowerBound, upperBound, initialGuess); + return result.MinimizingPoint; + } + + /// + /// Find vector x that minimizes the function f(x) using the Newton algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static Vector OfFunctionGradientHessian(Func, double> function, Func, Vector> gradient, Func, Matrix> hessian, Vector initialGuess, double gradientTolerance = 1e-8, int maxIterations = 1000) + { + var objective = ObjectiveFunction.GradientHessian(function, gradient, hessian); + var result = NewtonMinimizer.Minimum(objective, initialGuess, gradientTolerance, maxIterations); + return result.MinimizingPoint; + } + + /// + /// Find vector x that minimizes the function f(x) using the Newton algorithm. + /// For more options and diagnostics consider to use directly. + /// + public static Vector OfFunctionGradientHessian(Func, Tuple, Matrix>> functionGradientHessian, Vector initialGuess, double gradientTolerance = 1e-8, int maxIterations = 1000) + { + var objective = ObjectiveFunction.GradientHessian(functionGradientHessian); + var result = NewtonMinimizer.Minimum(objective, initialGuess, gradientTolerance, maxIterations); + return result.MinimizingPoint; + } +} diff --git a/MathNet.Numerics/FindRoots.cs b/MathNet.Numerics/FindRoots.cs new file mode 100644 index 0000000..541721f --- /dev/null +++ b/MathNet.Numerics/FindRoots.cs @@ -0,0 +1,195 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.RootFinding; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +public static class FindRoots +{ + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The low value of the range where the root is supposed to be. + /// The high value of the range where the root is supposed to be. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Example: 1e-14. + /// Maximum number of iterations. Example: 100. + public static double OfFunction(Func f, double lowerBound, double upperBound, double accuracy = 1e-8, int maxIterations = 100) + { + double root; + + if (!ZeroCrossingBracketing.ExpandReduce(f, ref lowerBound, ref upperBound, 1.6, maxIterations, maxIterations * 10)) + { + throw new NonConvergenceException(Resources.RootFindingFailed); + } + + if (Brent.TryFindRoot(f, lowerBound, upperBound, accuracy, maxIterations, out root)) + { + return root; + } + + if (Bisection.TryFindRoot(f, lowerBound, upperBound, accuracy, maxIterations, out root)) + { + return root; + } + + throw new NonConvergenceException(Resources.RootFindingFailed); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The first derivative of the function to find roots from. + /// The low value of the range where the root is supposed to be. + /// The high value of the range where the root is supposed to be. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Example: 1e-14. + /// Maximum number of iterations. Example: 100. + public static double OfFunctionDerivative(Func f, Func df, double lowerBound, double upperBound, double accuracy = 1e-8, int maxIterations = 100) + { + double root; + + if (RobustNewtonRaphson.TryFindRoot(f, df, lowerBound, upperBound, accuracy, maxIterations, 20, out root)) + { + return root; + } + + return OfFunction(f, lowerBound, upperBound, accuracy, maxIterations); + } + + /// + /// Find both complex roots of the quadratic equation c + b*x + a*x^2 = 0. + /// Note the special coefficient order ascending by exponent (consistent with polynomials). + /// + public static Tuple Quadratic(double c, double b, double a) + { + if (b == 0d) + { + var t = new Complex(-c / a, 0d).SquareRoot(); + return new Tuple(t, -t); + } + + var q = b > 0d + ? -0.5 * (b + new Complex(b * b - 4 * a * c, 0d).SquareRoot()) + : -0.5 * (b - new Complex(b * b - 4 * a * c, 0d).SquareRoot()); + + return new Tuple(q / a, c / q); + } + + /// + /// Find all three complex roots of the cubic equation d + c*x + b*x^2 + a*x^3 = 0. + /// Note the special coefficient order ascending by exponent (consistent with polynomials). + /// + public static Tuple Cubic(double d, double c, double b, double a) + { + return RootFinding.Cubic.Roots(d, c, b, a); + } + + /// + /// Find all roots of a polynomial by calculating the characteristic polynomial of the companion matrix + /// + /// The coefficients of the polynomial in ascending order, e.g. new double[] {5, 0, 2} = "5 + 0 x^1 + 2 x^2" + /// The roots of the polynomial + public static Complex[] Polynomial(double[] coefficients) + { + return new Polynomial(coefficients).Roots(); + } + + /// + /// Find all roots of a polynomial by calculating the characteristic polynomial of the companion matrix + /// + /// The polynomial. + /// The roots of the polynomial + public static Complex[] Polynomial(Polynomial polynomial) + { + return polynomial.Roots(); + } + + /// + /// Find all roots of the Chebychev polynomial of the first kind. + /// + /// The polynomial order and therefore the number of roots. + /// The real domain interval begin where to start sampling. + /// The real domain interval end where to stop sampling. + /// Samples in [a,b] at (b+a)/2+(b-1)/2*cos(pi*(2i-1)/(2n)) + public static double[] ChebychevPolynomialFirstKind(int degree, double intervalBegin = -1d, double intervalEnd = 1d) + { + if (degree < 1) + { + return new double[0]; + } + + // transform to map to [-1..1] interval + double location = 0.5 * (intervalBegin + intervalEnd); + double scale = 0.5 * (intervalEnd - intervalBegin); + + // evaluate first kind chebychev nodes + double angleFactor = Constants.Pi / (2 * degree); + + var samples = new double[degree]; + for (int i = 0; i < samples.Length; i++) + { + samples[i] = location + scale * Math.Cos(((2 * i) + 1) * angleFactor); + } + + return samples; + } + + /// + /// Find all roots of the Chebychev polynomial of the second kind. + /// + /// The polynomial order and therefore the number of roots. + /// The real domain interval begin where to start sampling. + /// The real domain interval end where to stop sampling. + /// Samples in [a,b] at (b+a)/2+(b-1)/2*cos(pi*i/(n-1)) + public static double[] ChebychevPolynomialSecondKind(int degree, double intervalBegin = -1d, double intervalEnd = 1d) + { + if (degree < 1) + { + return new double[0]; + } + + // transform to map to [-1..1] interval + double location = 0.5 * (intervalBegin + intervalEnd); + double scale = 0.5 * (intervalEnd - intervalBegin); + + // evaluate second kind chebychev nodes + double angleFactor = Constants.Pi / (degree + 1); + + var samples = new double[degree]; + for (int i = 0; i < samples.Length; i++) + { + samples[i] = location + scale * Math.Cos((i + 1) * angleFactor); + } + + return samples; + } +} diff --git a/MathNet.Numerics/Fit.cs b/MathNet.Numerics/Fit.cs new file mode 100644 index 0000000..f9d56c4 --- /dev/null +++ b/MathNet.Numerics/Fit.cs @@ -0,0 +1,395 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearRegression; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; +using System.Linq; + +namespace MathNet.Numerics; + +/// +/// Least-Squares Curve Fitting Routines +/// +public static class Fit +{ + /// + /// Least-Squares fitting the points (x,y) to a line y : x -> a+b*x, + /// returning its best fitting parameters as [a, b] array, + /// where a is the intercept and b the slope. + /// + public static Tuple Line(double[] x, double[] y) + { + return SimpleRegression.Fit(x, y); + } + + /// + /// Least-Squares fitting the points (x,y) to a line y : x -> a+b*x, + /// returning a function y' for the best fitting line. + /// + public static Func LineFunc(double[] x, double[] y) + { + var parameters = SimpleRegression.Fit(x, y); + double intercept = parameters.Item1, slope = parameters.Item2; + return z => intercept + slope * z; + } + + /// + /// Least-Squares fitting the points (x,y) to a line through origin y : x -> b*x, + /// returning its best fitting parameter b, + /// where the intercept is zero and b the slope. + /// + public static double LineThroughOrigin(double[] x, double[] y) + { + return SimpleRegression.FitThroughOrigin(x, y); + } + + /// + /// Least-Squares fitting the points (x,y) to a line through origin y : x -> b*x, + /// returning a function y' for the best fitting line. + /// + public static Func LineThroughOriginFunc(double[] x, double[] y) + { + double slope = SimpleRegression.FitThroughOrigin(x, y); + return z => slope * z; + } + + /// + /// Least-Squares fitting the points (x,y) to an exponential y : x -> a*exp(r*x), + /// returning its best fitting parameters as (a, r) tuple. + /// + public static Tuple Exponential(double[] x, double[] y, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + // Transformation: y_h := ln(y) ~> y_h : x -> ln(a) + r*x; + double[] lny = Generate.Map(y, Math.Log); + double[] p = LinearCombination(x, lny, method, t => 1.0, t => t); + return Tuple.Create(Math.Exp(p[0]), p[1]); + } + + /// + /// Least-Squares fitting the points (x,y) to an exponential y : x -> a*exp(r*x), + /// returning a function y' for the best fitting line. + /// + public static Func ExponentialFunc(double[] x, double[] y, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + var parameters = Exponential(x, y, method); + var a = parameters.Item1; + var r = parameters.Item2; + return z => a * Math.Exp(r * z); + } + + /// + /// Least-Squares fitting the points (x,y) to a logarithm y : x -> a + b*ln(x), + /// returning its best fitting parameters as (a, b) tuple. + /// + public static Tuple Logarithm(double[] x, double[] y, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + double[] lnx = Generate.Map(x, Math.Log); + double[] p = LinearCombination(lnx, y, method, t => 1.0, t => t); + return Tuple.Create(p[0], p[1]); + } + + /// + /// Least-Squares fitting the points (x,y) to a logarithm y : x -> a + b*ln(x), + /// returning a function y' for the best fitting line. + /// + public static Func LogarithmFunc(double[] x, double[] y, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + var parameters = Logarithm(x, y, method); + var a = parameters.Item1; + var b = parameters.Item2; + return z => a + b * Math.Log(z); + } + + /// + /// Least-Squares fitting the points (x,y) to a power y : x -> a*x^b, + /// returning its best fitting parameters as (a, b) tuple. + /// + public static Tuple Power(double[] x, double[] y, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + // Transformation: y_h := ln(y) ~> y_h : x -> ln(a) + b*ln(x); + double[] lny = Generate.Map(y, Math.Log); + double[] p = LinearCombination(x, lny, method, t => 1.0, Math.Log); + return Tuple.Create(Math.Exp(p[0]), p[1]); + } + + /// + /// Least-Squares fitting the points (x,y) to a power y : x -> a*x^b, + /// returning a function y' for the best fitting line. + /// + public static Func PowerFunc(double[] x, double[] y, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + var parameters = Power(x, y, method); + var a = parameters.Item1; + var b = parameters.Item2; + return z => a * Math.Pow(z, b); + } + + /// + /// Least-Squares fitting the points (x,y) to a k-order polynomial y : x -> p0 + p1*x + p2*x^2 + ... + pk*x^k, + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array, compatible with Polynomial.Evaluate. + /// A polynomial with order/degree k has (k+1) coefficients and thus requires at least (k+1) samples. + /// + public static double[] Polynomial(double[] x, double[] y, int order, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + var design = Matrix.Build.Dense(x.Length, order + 1, (i, j) => Math.Pow(x[i], j)); + return MultipleRegression.DirectMethod(design, Vector.Build.Dense(y), method).ToArray(); + } + + /// + /// Least-Squares fitting the points (x,y) to a k-order polynomial y : x -> p0 + p1*x + p2*x^2 + ... + pk*x^k, + /// returning a function y' for the best fitting polynomial. + /// A polynomial with order/degree k has (k+1) coefficients and thus requires at least (k+1) samples. + /// + public static Func PolynomialFunc(double[] x, double[] y, int order, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + var parameters = Polynomial(x, y, order, method); + return z => Numerics.Polynomial.Evaluate(z, parameters); + } + + /// + /// Weighted Least-Squares fitting the points (x,y) and weights w to a k-order polynomial y : x -> p0 + p1*x + p2*x^2 + ... + pk*x^k, + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array, compatible with Polynomial.Evaluate. + /// A polynomial with order/degree k has (k+1) coefficients and thus requires at least (k+1) samples. + /// + public static double[] PolynomialWeighted(double[] x, double[] y, double[] w, int order) + { + var design = Matrix.Build.Dense(x.Length, order + 1, (i, j) => Math.Pow(x[i], j)); + return WeightedRegression.Weighted(design, Vector.Build.Dense(y), Matrix.Build.Diagonal(w)).ToArray(); + } + + /// + /// Least-Squares fitting the points (x,y) to an arbitrary linear combination y : x -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x), + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. + /// + public static double[] LinearCombination(double[] x, double[] y, params Func[] functions) + { + var design = Matrix.Build.Dense(x.Length, functions.Length, (i, j) => functions[j](x[i])); + return MultipleRegression.QR(design, Vector.Build.Dense(y)).ToArray(); + } + + /// + /// Least-Squares fitting the points (x,y) to an arbitrary linear combination y : x -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x), + /// returning a function y' for the best fitting combination. + /// + public static Func LinearCombinationFunc(double[] x, double[] y, params Func[] functions) + { + var parameters = LinearCombination(x, y, functions); + return z => functions.Zip(parameters, (f, p) => p * f(z)).Sum(); + } + + /// + /// Least-Squares fitting the points (x,y) to an arbitrary linear combination y : x -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x), + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. + /// + public static double[] LinearCombination(double[] x, double[] y, DirectRegressionMethod method, params Func[] functions) + { + var design = Matrix.Build.Dense(x.Length, functions.Length, (i, j) => functions[j](x[i])); + return MultipleRegression.DirectMethod(design, Vector.Build.Dense(y), method).ToArray(); + } + + /// + /// Least-Squares fitting the points (x,y) to an arbitrary linear combination y : x -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x), + /// returning a function y' for the best fitting combination. + /// + public static Func LinearCombinationFunc(double[] x, double[] y, DirectRegressionMethod method, params Func[] functions) + { + var parameters = LinearCombination(x, y, method, functions); + return z => functions.Zip(parameters, (f, p) => p * f(z)).Sum(); + } + + /// + /// Least-Squares fitting the points (X,y) = ((x0,x1,..,xk),y) to a linear surface y : X -> p0*x0 + p1*x1 + ... + pk*xk, + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. + /// If an intercept is added, its coefficient will be prepended to the resulting parameters. + /// + public static double[] MultiDim(double[][] x, double[] y, bool intercept = false, DirectRegressionMethod method = DirectRegressionMethod.NormalEquations) + { + return MultipleRegression.DirectMethod(x, y, intercept, method); + } + + /// + /// Least-Squares fitting the points (X,y) = ((x0,x1,..,xk),y) to a linear surface y : X -> p0*x0 + p1*x1 + ... + pk*xk, + /// returning a function y' for the best fitting combination. + /// If an intercept is added, its coefficient will be prepended to the resulting parameters. + /// + public static Func MultiDimFunc(double[][] x, double[] y, bool intercept = false, DirectRegressionMethod method = DirectRegressionMethod.NormalEquations) + { + var parameters = MultipleRegression.DirectMethod(x, y, intercept, method); + return z => LinearAlgebraControl.Provider.DotProduct(parameters, z); + } + + /// + /// Weighted Least-Squares fitting the points (X,y) = ((x0,x1,..,xk),y) and weights w to a linear surface y : X -> p0*x0 + p1*x1 + ... + pk*xk, + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. + /// + public static double[] MultiDimWeighted(double[][] x, double[] y, double[] w) + { + return WeightedRegression.Weighted(x, y, w); + } + + /// + /// Least-Squares fitting the points (X,y) = ((x0,x1,..,xk),y) to an arbitrary linear combination y : X -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x), + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. + /// + public static double[] LinearMultiDim(double[][] x, double[] y, params Func[] functions) + { + var design = Matrix.Build.Dense(x.Length, functions.Length, (i, j) => functions[j](x[i])); + return MultipleRegression.QR(design, Vector.Build.Dense(y)).ToArray(); + } + + /// + /// Least-Squares fitting the points (X,y) = ((x0,x1,..,xk),y) to an arbitrary linear combination y : X -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x), + /// returning a function y' for the best fitting combination. + /// + public static Func LinearMultiDimFunc(double[][] x, double[] y, params Func[] functions) + { + var parameters = LinearMultiDim(x, y, functions); + return z => functions.Zip(parameters, (f, p) => p * f(z)).Sum(); + } + + /// + /// Least-Squares fitting the points (X,y) = ((x0,x1,..,xk),y) to an arbitrary linear combination y : X -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x), + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. + /// + public static double[] LinearMultiDim(double[][] x, double[] y, DirectRegressionMethod method, params Func[] functions) + { + var design = Matrix.Build.Dense(x.Length, functions.Length, (i, j) => functions[j](x[i])); + return MultipleRegression.DirectMethod(design, Vector.Build.Dense(y), method).ToArray(); + } + + /// + /// Least-Squares fitting the points (X,y) = ((x0,x1,..,xk),y) to an arbitrary linear combination y : X -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x), + /// returning a function y' for the best fitting combination. + /// + public static Func LinearMultiDimFunc(double[][] x, double[] y, DirectRegressionMethod method, params Func[] functions) + { + var parameters = LinearMultiDim(x, y, method, functions); + return z => functions.Zip(parameters, (f, p) => p * f(z)).Sum(); + } + + /// + /// Least-Squares fitting the points (T,y) = (T,y) to an arbitrary linear combination y : X -> p0*f0(T) + p1*f1(T) + ... + pk*fk(T), + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. + /// + public static double[] LinearGeneric(T[] x, double[] y, params Func[] functions) + { + var design = Matrix.Build.Dense(x.Length, functions.Length, (i, j) => functions[j](x[i])); + return MultipleRegression.QR(design, Vector.Build.Dense(y)).ToArray(); + } + + /// + /// Least-Squares fitting the points (T,y) = (T,y) to an arbitrary linear combination y : X -> p0*f0(T) + p1*f1(T) + ... + pk*fk(T), + /// returning a function y' for the best fitting combination. + /// + public static Func LinearGenericFunc(T[] x, double[] y, params Func[] functions) + { + var parameters = LinearGeneric(x, y, functions); + return z => functions.Zip(parameters, (f, p) => p * f(z)).Sum(); + } + + /// + /// Least-Squares fitting the points (T,y) = (T,y) to an arbitrary linear combination y : X -> p0*f0(T) + p1*f1(T) + ... + pk*fk(T), + /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array. + /// + public static double[] LinearGeneric(T[] x, double[] y, DirectRegressionMethod method, params Func[] functions) + { + var design = Matrix.Build.Dense(x.Length, functions.Length, (i, j) => functions[j](x[i])); + return MultipleRegression.DirectMethod(design, Vector.Build.Dense(y), method).ToArray(); + } + + /// + /// Least-Squares fitting the points (T,y) = (T,y) to an arbitrary linear combination y : X -> p0*f0(T) + p1*f1(T) + ... + pk*fk(T), + /// returning a function y' for the best fitting combination. + /// + public static Func LinearGenericFunc(T[] x, double[] y, DirectRegressionMethod method, params Func[] functions) + { + var parameters = LinearGeneric(x, y, method, functions); + return z => functions.Zip(parameters, (f, p) => p * f(z)).Sum(); + } + + /// + /// Non-linear least-squares fitting the points (x,y) to an arbitrary function y : x -> f(p, x), + /// returning its best fitting parameter p. + /// + public static double Curve(double[] x, double[] y, Func f, double initialGuess, double tolerance = 1e-8, int maxIterations = 1000) + { + return FindMinimum.OfScalarFunction(p => Distance.Euclidean(Generate.Map(x, t => f(p, t)), y), initialGuess, tolerance, maxIterations); + } + + /// + /// Non-linear least-squares fitting the points (x,y) to an arbitrary function y : x -> f(p0, p1, x), + /// returning its best fitting parameter p0 and p1. + /// + public static Tuple Curve(double[] x, double[] y, Func f, double initialGuess0, double initialGuess1, double tolerance = 1e-8, int maxIterations = 1000) + { + return FindMinimum.OfFunction((p0, p1) => Distance.Euclidean(Generate.Map(x, t => f(p0, p1, t)), y), initialGuess0, initialGuess1, tolerance, maxIterations); + } + + /// + /// Non-linear least-squares fitting the points (x,y) to an arbitrary function y : x -> f(p0, p1, p2, x), + /// returning its best fitting parameter p0, p1 and p2. + /// + public static Tuple Curve(double[] x, double[] y, Func f, double initialGuess0, double initialGuess1, double initialGuess2, double tolerance = 1e-8, int maxIterations = 1000) + { + return FindMinimum.OfFunction((p0, p1, p2) => Distance.Euclidean(Generate.Map(x, t => f(p0, p1, p2, t)), y), initialGuess0, initialGuess1, initialGuess2, tolerance, maxIterations); + } + + /// + /// Non-linear least-squares fitting the points (x,y) to an arbitrary function y : x -> f(p, x), + /// returning a function y' for the best fitting curve. + /// + public static Func CurveFunc(double[] x, double[] y, Func f, double initialGuess, double tolerance = 1e-8, int maxIterations = 1000) + { + var parameters = Curve(x, y, f, initialGuess, tolerance, maxIterations); + return z => f(parameters, z); + } + + /// + /// Non-linear least-squares fitting the points (x,y) to an arbitrary function y : x -> f(p0, p1, x), + /// returning a function y' for the best fitting curve. + /// + public static Func CurveFunc(double[] x, double[] y, Func f, double initialGuess0, double initialGuess1, double tolerance = 1e-8, int maxIterations = 1000) + { + var parameters = Curve(x, y, f, initialGuess0, initialGuess1, tolerance, maxIterations); + return z => f(parameters.Item1, parameters.Item2, z); + } + + /// + /// Non-linear least-squares fitting the points (x,y) to an arbitrary function y : x -> f(p0, p1, p2, x), + /// returning a function y' for the best fitting curve. + /// + public static Func CurveFunc(double[] x, double[] y, Func f, double initialGuess0, double initialGuess1, double initialGuess2, double tolerance = 1e-8, int maxIterations = 1000) + { + var parameters = Curve(x, y, f, initialGuess0, initialGuess1, initialGuess2, tolerance, maxIterations); + return z => f(parameters.Item1, parameters.Item2, parameters.Item3, z); + } +} diff --git a/MathNet.Numerics/Generate.cs b/MathNet.Numerics/Generate.cs new file mode 100644 index 0000000..8dc7f67 --- /dev/null +++ b/MathNet.Numerics/Generate.cs @@ -0,0 +1,1132 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Random; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +using BigInteger = System.Numerics.BigInteger; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +public static class Generate +{ + /// + /// Generate samples by sampling a function at the provided points. + /// + public static T[] Map(TA[] points, Func map) + { + var res = new T[points.Length]; + for (int i = 0; i < points.Length; i++) + { + res[i] = map(points[i]); + } + + return res; + } + + /// + /// Generate a sample sequence by sampling a function at the provided point sequence. + /// + public static IEnumerable MapSequence(IEnumerable points, Func map) + { + return points.Select(map); + } + + /// + /// Generate samples by sampling a function at the provided points. + /// + public static T[] Map2(TA[] pointsA, TB[] pointsB, Func map) + { + if (pointsA.Length != pointsB.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(pointsB)); + } + + var res = new T[pointsA.Length]; + for (int i = 0; i < res.Length; i++) + { + res[i] = map(pointsA[i], pointsB[i]); + } + + return res; + } + + /// + /// Generate a sample sequence by sampling a function at the provided point sequence. + /// + public static IEnumerable Map2Sequence(IEnumerable pointsA, IEnumerable pointsB, Func map) + { + return pointsA.Zip(pointsB, map); + } + + /// + /// Generate a linearly spaced sample vector of the given length between the specified values (inclusive). + /// Equivalent to MATLAB linspace but with the length as first instead of last argument. + /// + public static double[] LinearSpaced(int length, double start, double stop) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + if (length == 0) + return new double[0]; + if (length == 1) + return new[] { stop }; + + double step = (stop - start) / (length - 1); + + var data = new double[length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = start + i * step; + } + + data[data.Length - 1] = stop; + return data; + } + + /// + /// Generate samples by sampling a function at linearly spaced points between the specified values (inclusive). + /// + public static T[] LinearSpacedMap(int length, double start, double stop, Func map) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + if (length == 0) + return new T[0]; + if (length == 1) + return new[] { map(stop) }; + + double step = (stop - start) / (length - 1); + + var data = new T[length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = map(start + i * step); + } + + data[data.Length - 1] = map(stop); + return data; + } + + /// + /// Generate a base 10 logarithmically spaced sample vector of the given length between the specified decade exponents (inclusive). + /// Equivalent to MATLAB logspace but with the length as first instead of last argument. + /// + public static double[] LogSpaced(int length, double startExponent, double stopExponent) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + if (length == 0) + return new double[0]; + if (length == 1) + return new[] { Math.Pow(10, stopExponent) }; + + double step = (stopExponent - startExponent) / (length - 1); + + var data = new double[length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = Math.Pow(10, startExponent + i * step); + } + + data[data.Length - 1] = Math.Pow(10, stopExponent); + return data; + } + + /// + /// Generate samples by sampling a function at base 10 logarithmically spaced points between the specified decade exponents (inclusive). + /// + public static T[] LogSpacedMap(int length, double startExponent, double stopExponent, Func map) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + if (length == 0) + return new T[0]; + if (length == 1) + return new[] { map(Math.Pow(10, stopExponent)) }; + + double step = (stopExponent - startExponent) / (length - 1); + + var data = new T[length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = map(Math.Pow(10, startExponent + i * step)); + } + + data[data.Length - 1] = map(Math.Pow(10, stopExponent)); + return data; + } + + /// + /// Generate a linearly spaced sample vector within the inclusive interval (start, stop) and step 1. + /// Equivalent to MATLAB colon operator (:). + /// + public static double[] LinearRange(int start, int stop) + { + if (start == stop) + return new double[] { start }; + if (start < stop) + { + var data = new double[stop - start + 1]; + for (int i = 0; i < data.Length; i++) + { + data[i] = start + i; + } + + return data; + } + else + { + var data = new double[start - stop + 1]; + for (int i = 0; i < data.Length; i++) + { + data[i] = start - i; + } + + return data; + } + } + + /// + /// Generate a linearly spaced sample vector within the inclusive interval (start, stop) and step 1. + /// Equivalent to MATLAB colon operator (:). + /// + public static int[] LinearRangeInt32(int start, int stop) + { + if (start == stop) + return new int[] { start }; + if (start < stop) + { + var data = new int[stop - start + 1]; + for (int i = 0; i < data.Length; i++) + { + data[i] = start + i; + } + + return data; + } + else + { + var data = new int[start - stop + 1]; + for (int i = 0; i < data.Length; i++) + { + data[i] = start - i; + } + + return data; + } + } + + /// + /// Generate a linearly spaced sample vector within the inclusive interval (start, stop) and the provided step. + /// The start value is aways included as first value, but stop is only included if it stop-start is a multiple of step. + /// Equivalent to MATLAB double colon operator (::). + /// + public static double[] LinearRange(int start, int step, int stop) + { + if (start == stop) + return new double[] { start }; + if (start < stop && step < 0 || start > stop && step > 0 || step == 0d) + { + return new double[0]; + } + + var data = new double[(stop - start) / step + 1]; + for (int i = 0; i < data.Length; i++) + { + data[i] = start + i * step; + } + + return data; + } + + /// + /// Generate a linearly spaced sample vector within the inclusive interval (start, stop) and the provided step. + /// The start value is aways included as first value, but stop is only included if it stop-start is a multiple of step. + /// Equivalent to MATLAB double colon operator (::). + /// + public static int[] LinearRangeInt32(int start, int step, int stop) + { + if (start == stop) + return new int[] { start }; + if (start < stop && step < 0 || start > stop && step > 0 || step == 0d) + { + return new int[0]; + } + + var data = new int[(stop - start) / step + 1]; + for (int i = 0; i < data.Length; i++) + { + data[i] = start + i * step; + } + + return data; + } + + /// + /// Generate a linearly spaced sample vector within the inclusive interval (start, stop) and the provide step. + /// The start value is aways included as first value, but stop is only included if it stop-start is a multiple of step. + /// Equivalent to MATLAB double colon operator (::). + /// + public static double[] LinearRange(double start, double step, double stop) + { + if (start == stop) + return new[] { start }; + if (start < stop && step < 0 || start > stop && step > 0 || step == 0d) + { + return new double[0]; + } + + var data = new double[(int)Math.Floor((stop - start) / step + 1d)]; + for (int i = 0; i < data.Length; i++) + { + data[i] = start + i * step; + } + + return data; + } + + /// + /// Generate samples by sampling a function at linearly spaced points within the inclusive interval (start, stop) and the provide step. + /// The start value is aways included as first value, but stop is only included if it stop-start is a multiple of step. + /// + public static T[] LinearRangeMap(double start, double step, double stop, Func map) + { + if (start == stop) + return new[] { map(start) }; + if (start < stop && step < 0 || start > stop && step > 0 || step == 0d) + { + return new T[0]; + } + + var data = new T[(int)Math.Floor((stop - start) / step + 1d)]; + for (int i = 0; i < data.Length; i++) + { + data[i] = map(start + i * step); + } + + return data; + } + + /// + /// Create a periodic wave. + /// + /// The number of samples to generate. + /// Samples per time unit (Hz). Must be larger than twice the frequency to satisfy the Nyquist criterion. + /// Frequency in periods per time unit (Hz). + /// The length of the period when sampled at one sample per time unit. This is the interval of the periodic domain, a typical value is 1.0, or 2*Pi for angular functions. + /// Optional phase offset. + /// Optional delay, relative to the phase. + public static double[] Periodic(int length, double samplingRate, double frequency, double amplitude = 1.0, double phase = 0.0, int delay = 0) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + double step = frequency / samplingRate * amplitude; + phase = Euclid.Modulus(phase - delay * step, amplitude); + + var data = new double[length]; + for (int i = 0, k = 0; i < data.Length; i++, k++) + { + var x = phase + k * step; + if (x >= amplitude) + { + x %= amplitude; + phase = x; + k = 0; + } + + data[i] = x; + } + + return data; + } + + /// + /// Create a periodic wave. + /// + /// The number of samples to generate. + /// The function to apply to each of the values and evaluate the resulting sample. + /// Samples per time unit (Hz). Must be larger than twice the frequency to satisfy the Nyquist criterion. + /// Frequency in periods per time unit (Hz). + /// The length of the period when sampled at one sample per time unit. This is the interval of the periodic domain, a typical value is 1.0, or 2*Pi for angular functions. + /// Optional phase offset. + /// Optional delay, relative to the phase. + public static T[] PeriodicMap(int length, Func map, double samplingRate, double frequency, double amplitude = 1.0, double phase = 0.0, int delay = 0) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + double step = frequency / samplingRate * amplitude; + phase = Euclid.Modulus(phase - delay * step, amplitude); + + var data = new T[length]; + for (int i = 0, k = 0; i < data.Length; i++, k++) + { + var x = phase + k * step; + if (x >= amplitude) + { + x %= amplitude; + phase = x; + k = 0; + } + + data[i] = map(x); + } + + return data; + } + + /// + /// Create an infinite periodic wave sequence. + /// + /// Samples per time unit (Hz). Must be larger than twice the frequency to satisfy the Nyquist criterion. + /// Frequency in periods per time unit (Hz). + /// The length of the period when sampled at one sample per time unit. This is the interval of the periodic domain, a typical value is 1.0, or 2*Pi for angular functions. + /// Optional phase offset. + /// Optional delay, relative to the phase. + public static IEnumerable PeriodicSequence(double samplingRate, double frequency, double amplitude = 1.0, double phase = 0.0, int delay = 0) + { + double step = frequency / samplingRate * amplitude; + phase = Euclid.Modulus(phase - delay * step, amplitude); + + int k = 0; + while (true) + { + var x = phase + (k++) * step; + if (x >= amplitude) + { + x %= amplitude; + phase = x; + k = 1; + } + + yield return x; + } + } + + /// + /// Create an infinite periodic wave sequence. + /// + /// The function to apply to each of the values and evaluate the resulting sample. + /// Samples per time unit (Hz). Must be larger than twice the frequency to satisfy the Nyquist criterion. + /// Frequency in periods per time unit (Hz). + /// The length of the period when sampled at one sample per time unit. This is the interval of the periodic domain, a typical value is 1.0, or 2*Pi for angular functions. + /// Optional phase offset. + /// Optional delay, relative to the phase. + public static IEnumerable PeriodicMapSequence(Func map, double samplingRate, double frequency, double amplitude = 1.0, double phase = 0.0, int delay = 0) + { + double step = frequency / samplingRate * amplitude; + phase = Euclid.Modulus(phase - delay * step, amplitude); + + int k = 0; + while (true) + { + var x = phase + (k++) * step; + if (x >= amplitude) + { + x %= amplitude; + phase = x; + k = 1; + } + + yield return map(x); + } + } + + /// + /// Create a Sine wave. + /// + /// The number of samples to generate. + /// Samples per time unit (Hz). Must be larger than twice the frequency to satisfy the Nyquist criterion. + /// Frequency in periods per time unit (Hz). + /// The maximal reached peak. + /// The mean, or DC part, of the signal. + /// Optional phase offset. + /// Optional delay, relative to the phase. + public static double[] Sinusoidal(int length, double samplingRate, double frequency, double amplitude, double mean = 0.0, double phase = 0.0, int delay = 0) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + double step = frequency / samplingRate * Constants.Pi2; + phase = (phase - delay * step) % Constants.Pi2; + + var data = new double[length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = mean + amplitude * Math.Sin(phase + i * step); + } + + return data; + } + + /// + /// Create an infinite Sine wave sequence. + /// + /// Samples per unit. + /// Frequency in samples per unit. + /// The maximal reached peak. + /// The mean, or DC part, of the signal. + /// Optional phase offset. + /// Optional delay, relative to the phase. + public static IEnumerable SinusoidalSequence(double samplingRate, double frequency, double amplitude, double mean = 0.0, double phase = 0.0, int delay = 0) + { + double step = frequency / samplingRate * Constants.Pi2; + phase = (phase - delay * step) % Constants.Pi2; + + while (true) + { + for (int i = 0; i < 1000; i++) + { + yield return mean + amplitude * Math.Sin(phase + i * step); + } + + phase = (phase + 1000 * step) % Constants.Pi2; + } + } + + /// + /// Create a periodic square wave, starting with the high phase. + /// + /// The number of samples to generate. + /// Number of samples of the high phase. + /// Number of samples of the low phase. + /// Sample value to be emitted during the low phase. + /// Sample value to be emitted during the high phase. + /// Optional delay. + public static double[] Square(int length, int highDuration, int lowDuration, double lowValue, double highValue, int delay = 0) + { + var duration = highDuration + lowDuration; + return PeriodicMap(length, x => x < highDuration ? highValue : lowValue, 1.0, 1.0 / duration, duration, 0.0, delay); + } + + /// + /// Create an infinite periodic square wave sequence, starting with the high phase. + /// + /// Number of samples of the high phase. + /// Number of samples of the low phase. + /// Sample value to be emitted during the low phase. + /// Sample value to be emitted during the high phase. + /// Optional delay. + public static IEnumerable SquareSequence(int highDuration, int lowDuration, double lowValue, double highValue, int delay = 0) + { + var duration = highDuration + lowDuration; + return PeriodicMapSequence(x => x < highDuration ? highValue : lowValue, 1.0, 1.0 / duration, duration, 0.0, delay); + } + + /// + /// Create a periodic triangle wave, starting with the raise phase from the lowest sample. + /// + /// The number of samples to generate. + /// Number of samples of the raise phase. + /// Number of samples of the fall phase. + /// Lowest sample value. + /// Highest sample value. + /// Optional delay. + public static double[] Triangle(int length, int raiseDuration, int fallDuration, double lowValue, double highValue, int delay = 0) + { + var duration = raiseDuration + fallDuration; + var height = highValue - lowValue; + var raise = height / raiseDuration; + var fall = height / fallDuration; + return PeriodicMap(length, x => x < raiseDuration ? lowValue + x * raise : highValue - (x - raiseDuration) * fall, 1.0, 1.0 / duration, duration, 0.0, delay); + } + + /// + /// Create an infinite periodic triangle wave sequence, starting with the raise phase from the lowest sample. + /// + /// Number of samples of the raise phase. + /// Number of samples of the fall phase. + /// Lowest sample value. + /// Highest sample value. + /// Optional delay. + public static IEnumerable TriangleSequence(int raiseDuration, int fallDuration, double lowValue, double highValue, int delay = 0) + { + var duration = raiseDuration + fallDuration; + var height = highValue - lowValue; + var raise = height / raiseDuration; + var fall = height / fallDuration; + return PeriodicMapSequence(x => x < raiseDuration ? lowValue + x * raise : highValue - (x - raiseDuration) * fall, 1.0, 1.0 / duration, duration, 0.0, delay); + } + + /// + /// Create a periodic sawtooth wave, starting with the lowest sample. + /// + /// The number of samples to generate. + /// Number of samples a full sawtooth period. + /// Lowest sample value. + /// Highest sample value. + /// Optional delay. + public static double[] Sawtooth(int length, int period, double lowValue, double highValue, int delay = 0) + { + var height = highValue - lowValue; + return PeriodicMap(length, x => x + lowValue, 1.0, 1.0 / period, height * period / (period - 1), 0.0, delay); + } + + /// + /// Create an infinite periodic sawtooth wave sequence, starting with the lowest sample. + /// + /// Number of samples a full sawtooth period. + /// Lowest sample value. + /// Highest sample value. + /// Optional delay. + public static IEnumerable SawtoothSequence(int period, double lowValue, double highValue, int delay = 0) + { + var height = highValue - lowValue; + return PeriodicMapSequence(x => x + lowValue, 1.0, 1.0 / period, height * period / (period - 1), 0.0, delay); + } + + /// + /// Create an array with each field set to the same value. + /// + /// The number of samples to generate. + /// The value that each field should be set to. + public static T[] Repeat(int length, T value) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var data = new T[length]; + CommonParallel.For(0, data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + data[i] = value; + } + }); + return data; + } + + /// + /// Create an infinite sequence where each element has the same value. + /// + /// The value that each element should be set to. + public static IEnumerable RepeatSequence(T value) + { + while (true) + { + yield return value; + } + } + + /// + /// Create a Heaviside Step sample vector. + /// + /// The number of samples to generate. + /// The maximal reached peak. + /// Offset to the time axis. + public static double[] Step(int length, double amplitude, int delay) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var data = new double[length]; + for (int i = Math.Max(0, delay); i < data.Length; i++) + { + data[i] = amplitude; + } + + return data; + } + + /// + /// Create an infinite Heaviside Step sample sequence. + /// + /// The maximal reached peak. + /// Offset to the time axis. + public static IEnumerable StepSequence(double amplitude, int delay) + { + for (int i = 0; i < delay; i++) + { + yield return 0d; + } + + while (true) + { + yield return amplitude; + } + } + + /// + /// Create a Kronecker Delta impulse sample vector. + /// + /// The number of samples to generate. + /// The maximal reached peak. + /// Offset to the time axis. Zero or positive. + public static double[] Impulse(int length, double amplitude, int delay) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var data = new double[length]; + if (delay >= 0 && delay < length) + { + data[delay] = amplitude; + } + + return data; + } + + /// + /// Create a Kronecker Delta impulse sample vector. + /// + /// The maximal reached peak. + /// Offset to the time axis, hence the sample index of the impulse. + public static IEnumerable ImpulseSequence(double amplitude, int delay) + { + if (delay >= 0) + { + for (int i = 0; i < delay; i++) + { + yield return 0d; + } + + yield return amplitude; + } + + while (true) + { + yield return 0d; + } + } + + /// + /// Create a periodic Kronecker Delta impulse sample vector. + /// + /// The number of samples to generate. + /// impulse sequence period. + /// The maximal reached peak. + /// Offset to the time axis. Zero or positive. + public static double[] PeriodicImpulse(int length, int period, double amplitude, int delay) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var data = new double[length]; + delay = Euclid.Modulus(delay, period); + while (delay < length) + { + data[delay] = amplitude; + delay += period; + } + + return data; + } + + /// + /// Create a Kronecker Delta impulse sample vector. + /// + /// impulse sequence period. + /// The maximal reached peak. + /// Offset to the time axis. Zero or positive. + public static IEnumerable PeriodicImpulseSequence(int period, double amplitude, int delay) + { + delay = Euclid.Modulus(delay, period); + + for (int i = 0; i < delay; i++) + { + yield return 0d; + } + + while (true) + { + yield return amplitude; + + for (int i = 1; i < period; i++) + { + yield return 0d; + } + } + } + + /// + /// Generate samples generated by the given computation. + /// + public static T[] Unfold(int length, Func> f, TState state) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var data = new T[length]; + for (int i = 0; i < data.Length; i++) + { + Tuple next = f(state); + data[i] = next.Item1; + state = next.Item2; + } + + return data; + } + + /// + /// Generate an infinite sequence generated by the given computation. + /// + public static IEnumerable UnfoldSequence(Func> f, TState state) + { + while (true) + { + Tuple next = f(state); + state = next.Item2; + yield return next.Item1; + } + } + + /// + /// Generate a Fibonacci sequence, including zero as first value. + /// + public static BigInteger[] Fibonacci(int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var data = new BigInteger[length]; + if (data.Length > 0) + { + data[0] = BigInteger.Zero; + } + + if (data.Length > 1) + { + data[1] = BigInteger.One; + } + + for (int i = 2; i < data.Length; i++) + { + data[i] = data[i - 1] + data[i - 2]; + } + + return data; + } + + /// + /// Generate an infinite Fibonacci sequence, including zero as first value. + /// + public static IEnumerable FibonacciSequence() + { + BigInteger a = BigInteger.Zero; + yield return a; + + BigInteger b = BigInteger.One; + yield return b; + + while (true) + { + a = a + b; + yield return a; + + b = a + b; + yield return b; + } + } + + /// + /// Create random samples, uniform between 0 and 1. + /// Faster than other methods but with reduced guarantees on randomness. + /// + public static double[] Uniform(int length) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return SystemRandomSource.FastDoubles(length); + } + + /// + /// Create an infinite random sample sequence, uniform between 0 and 1. + /// Faster than other methods but with reduced guarantees on randomness. + /// + public static IEnumerable UniformSequence() + { + return SystemRandomSource.DoubleSequence(); + } + + /// + /// Generate samples by sampling a function at samples from a probability distribution, uniform between 0 and 1. + /// Faster than other methods but with reduced guarantees on randomness. + /// + public static T[] UniformMap(int length, Func map) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var samples = SystemRandomSource.FastDoubles(length); + return Map(samples, map); + } + + /// + /// Generate a sample sequence by sampling a function at samples from a probability distribution, uniform between 0 and 1. + /// Faster than other methods but with reduced guarantees on randomness. + /// + public static IEnumerable UniformMapSequence(Func map) + { + return SystemRandomSource.DoubleSequence().Select(map); + } + + /// + /// Generate samples by sampling a function at sample pairs from a probability distribution, uniform between 0 and 1. + /// Faster than other methods but with reduced guarantees on randomness. + /// + public static T[] UniformMap2(int length, Func map) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var samples1 = SystemRandomSource.FastDoubles(length); + var samples2 = SystemRandomSource.FastDoubles(length); + return Map2(samples1, samples2, map); + } + + /// + /// Generate a sample sequence by sampling a function at sample pairs from a probability distribution, uniform between 0 and 1. + /// Faster than other methods but with reduced guarantees on randomness. + /// + public static IEnumerable UniformMap2Sequence(Func map) + { + var rnd1 = SystemRandomSource.Default; + for (int i = 0; i < 128; i++) + { + yield return map(rnd1.NextDouble(), rnd1.NextDouble()); + } + + var rnd2 = new System.Random(RandomSeed.Robust()); + while (true) + { + yield return map(rnd2.NextDouble(), rnd2.NextDouble()); + } + } + + /// + /// Create samples with independent amplitudes of standard distribution. + /// + public static double[] Standard(int length) + { + return Normal(length, 0.0, 1.0); + } + + /// + /// Create an infinite sample sequence with independent amplitudes of standard distribution. + /// + public static IEnumerable StandardSequence() + { + return NormalSequence(0.0, 1.0); + } + + /// + /// Create samples with independent amplitudes of normal distribution and a flat spectral density. + /// + public static double[] Normal(int length, double mean, double standardDeviation) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var samples = new double[length]; + Distributions.Normal.Samples(SystemRandomSource.Default, samples, mean, standardDeviation); + return samples; + } + + /// + /// Create an infinite sample sequence with independent amplitudes of normal distribution and a flat spectral density. + /// + public static IEnumerable NormalSequence(double mean, double standardDeviation) + { + return Distributions.Normal.Samples(SystemRandomSource.Default, mean, standardDeviation); + } + + /// + /// Create random samples. + /// + public static double[] Random(int length, IContinuousDistribution distribution) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var samples = new double[length]; + distribution.Samples(samples); + return samples; + } + + /// + /// Create an infinite random sample sequence. + /// + public static IEnumerable Random(IContinuousDistribution distribution) + { + return distribution.Samples(); + } + + /// + /// Create random samples. + /// + public static float[] RandomSingle(int length, IContinuousDistribution distribution) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var samples = new double[length]; + distribution.Samples(samples); + return Map(samples, v => (float)v); + } + + /// + /// Create an infinite random sample sequence. + /// + public static IEnumerable RandomSingle(IContinuousDistribution distribution) + { + return distribution.Samples().Select(v => (float)v); + } + + /// + /// Create random samples. + /// + public static Complex[] RandomComplex(int length, IContinuousDistribution distribution) + { + return RandomMap2(length, distribution, (r, i) => new Complex(r, i)); + } + + /// + /// Create an infinite random sample sequence. + /// + public static IEnumerable RandomComplex(IContinuousDistribution distribution) + { + return RandomMap2Sequence(distribution, (r, i) => new Complex(r, i)); + } + + /// + /// Create random samples. + /// + public static Complex32[] RandomComplex32(int length, IContinuousDistribution distribution) + { + return RandomMap2(length, distribution, (r, i) => new Complex32((float)r, (float)i)); + } + + /// + /// Create an infinite random sample sequence. + /// + public static IEnumerable RandomComplex32(IContinuousDistribution distribution) + { + return RandomMap2Sequence(distribution, (r, i) => new Complex32((float)r, (float)i)); + } + + /// + /// Generate samples by sampling a function at samples from a probability distribution. + /// + public static T[] RandomMap(int length, IContinuousDistribution distribution, Func map) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var samples = new double[length]; + distribution.Samples(samples); + return Map(samples, map); + } + + /// + /// Generate a sample sequence by sampling a function at samples from a probability distribution. + /// + public static IEnumerable RandomMapSequence(IContinuousDistribution distribution, Func map) + { + return distribution.Samples().Select(map); + } + + /// + /// Generate samples by sampling a function at sample pairs from a probability distribution. + /// + public static T[] RandomMap2(int length, IContinuousDistribution distribution, Func map) + { + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var samples1 = new double[length]; + var samples2 = new double[length]; + distribution.Samples(samples1); + distribution.Samples(samples2); + return Map2(samples1, samples2, map); + } + + /// + /// Generate a sample sequence by sampling a function at sample pairs from a probability distribution. + /// + public static IEnumerable RandomMap2Sequence(IContinuousDistribution distribution, Func map) + { + return distribution.Samples().Zip(distribution.Samples(), map); + } +} diff --git a/MathNet.Numerics/GlobalizationHelper.cs b/MathNet.Numerics/GlobalizationHelper.cs new file mode 100644 index 0000000..0cd2fe9 --- /dev/null +++ b/MathNet.Numerics/GlobalizationHelper.cs @@ -0,0 +1,270 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace MathNet.Numerics; + +/// +/// Globalized string Handling Helpers +/// +internal static class GlobalizationHelper +{ + /// + /// Tries to get a from the format provider, + /// returning the current culture if it fails. + /// + /// + /// An that supplies culture-specific + /// formatting information. + /// + /// A instance. + internal static CultureInfo GetCultureInfo(this IFormatProvider formatProvider) + { + if (formatProvider == null) + { + return CultureInfo.CurrentCulture; + } + + return (formatProvider as CultureInfo) + ?? (formatProvider.GetFormat(typeof(CultureInfo)) as CultureInfo) + ?? CultureInfo.CurrentCulture; + } + + /// + /// Tries to get a from the format + /// provider, returning the current culture if it fails. + /// + /// + /// An that supplies culture-specific + /// formatting information. + /// + /// A instance. + internal static NumberFormatInfo GetNumberFormatInfo(this IFormatProvider formatProvider) + { + return NumberFormatInfo.GetInstance(formatProvider); + } + + /// + /// Tries to get a from the format provider, returning the current culture if it fails. + /// + /// + /// An that supplies culture-specific + /// formatting information. + /// + /// A instance. + internal static TextInfo GetTextInfo(this IFormatProvider formatProvider) + { + if (formatProvider == null) + { + return CultureInfo.CurrentCulture.TextInfo; + } + + return (formatProvider.GetFormat(typeof(TextInfo)) as TextInfo) + ?? GetCultureInfo(formatProvider).TextInfo; + } + + /// + /// Globalized Parsing: Tokenize a node by splitting it into several nodes. + /// + /// Node that contains the trimmed string to be tokenized. + /// List of keywords to tokenize by. + /// keywords to skip looking for (because they've already been handled). + internal static void Tokenize(LinkedListNode node, string[] keywords, int skip) + { + for (int i = skip; i < keywords.Length; i++) + { + var keyword = keywords[i]; + int indexOfKeyword; + while ((indexOfKeyword = node.Value.IndexOf(keyword, StringComparison.Ordinal)) >= 0) + { + if (indexOfKeyword != 0) + { + // separate part before the token, process recursively + string partBeforeKeyword = node.Value.Substring(0, indexOfKeyword).Trim(); + Tokenize(node.List.AddBefore(node, partBeforeKeyword), keywords, i + 1); + + // continue processing the rest iteratively + node.Value = node.Value.Substring(indexOfKeyword); + } + + if (keyword.Length == node.Value.Length) + { + return; + } + + // separate the token, done + string partAfterKeyword = node.Value.Substring(keyword.Length).Trim(); + node.List.AddBefore(node, keyword); + + // continue processing the rest on the right iteratively + node.Value = partAfterKeyword; + } + } + } + +#if NETSTANDARD1_3 + /// + /// Globalized Parsing: Parse a double number + /// + /// First token of the number. + /// The parsed double number using the current culture information. + /// + internal static double ParseDouble(ref LinkedListNode token) + { + // in case the + and - in scientific notation are separated, join them back together. + if (token.Value.EndsWith("e", StringComparison.CurrentCultureIgnoreCase)) + { + if (token.Next == null || token.Next.Next == null) + { + throw new FormatException(); + } + + token.Value = token.Value + token.Next.Value + token.Next.Next.Value; + + var list = token.List; + list.Remove(token.Next.Next); + list.Remove(token.Next); + } + + double value; + if (!double.TryParse(token.Value, NumberStyles.Any, CultureInfo.CurrentCulture, out value)) + { + throw new FormatException(); + } + + token = token.Next; + return value; + } + + /// + /// Globalized Parsing: Parse a float number + /// + /// First token of the number. + /// The parsed float number using the current culture information. + /// + internal static float ParseSingle(ref LinkedListNode token) + { + // in case the + and - in scientific notation are separated, join them back together. + if (token.Value.EndsWith("e", StringComparison.CurrentCultureIgnoreCase)) + { + if (token.Next == null || token.Next.Next == null) + { + throw new FormatException(); + } + + token.Value = token.Value + token.Next.Value + token.Next.Next.Value; + + var list = token.List; + list.Remove(token.Next.Next); + list.Remove(token.Next); + } + + float value; + if (!Single.TryParse(token.Value, NumberStyles.Any, CultureInfo.CurrentCulture, out value)) + { + throw new FormatException(); + } + + token = token.Next; + return value; + } + +#else + /// + /// Globalized Parsing: Parse a double number + /// + /// First token of the number. + /// Culture Info. + /// The parsed double number using the given culture information. + /// + internal static double ParseDouble(ref LinkedListNode token, CultureInfo culture) + { + // in case the + and - in scientific notation are separated, join them back together. + if (token.Value.EndsWith("e", true, culture)) + { + if (token.Next == null || token.Next.Next == null) + { + throw new FormatException(); + } + + token.Value = token.Value + token.Next.Value + token.Next.Next.Value; + + var list = token.List; + list.Remove(token.Next.Next); + list.Remove(token.Next); + } + + double value; + if (!double.TryParse(token.Value, NumberStyles.Any, culture, out value)) + { + throw new FormatException(); + } + + token = token.Next; + return value; + } + + /// + /// Globalized Parsing: Parse a float number + /// + /// First token of the number. + /// Culture Info. + /// The parsed float number using the given culture information. + /// + internal static float ParseSingle(ref LinkedListNode token, CultureInfo culture) + { + // in case the + and - in scientific notation are separated, join them back together. + if (token.Value.EndsWith("e", true, culture)) + { + if (token.Next == null || token.Next.Next == null) + { + throw new FormatException(); + } + + token.Value = token.Value + token.Next.Value + token.Next.Next.Value; + + var list = token.List; + list.Remove(token.Next.Next); + list.Remove(token.Next); + } + + float value; + if (!Single.TryParse(token.Value, NumberStyles.Any, culture, out value)) + { + throw new FormatException(); + } + + token = token.Next; + return value; + } +#endif +} diff --git a/MathNet.Numerics/GoodnessOfFit.cs b/MathNet.Numerics/GoodnessOfFit.cs new file mode 100644 index 0000000..1952bf3 --- /dev/null +++ b/MathNet.Numerics/GoodnessOfFit.cs @@ -0,0 +1,177 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Statistics; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics; + +public static class GoodnessOfFit +{ + /// + /// Calculates r^2, the square of the sample correlation coefficient between + /// the observed outcomes and the observed predictor values. + /// Not to be confused with R^2, the coefficient of determination, see . + /// + /// The modelled/predicted values + /// The observed/actual values + /// Squared Person product-momentum correlation coefficient. + public static double RSquared(IEnumerable modelledValues, IEnumerable observedValues) + { + var corr = Correlation.Pearson(modelledValues, observedValues); + return corr * corr; + } + + /// + /// Calculates r, the sample correlation coefficient between the observed outcomes + /// and the observed predictor values. + /// + /// The modelled/predicted values + /// The observed/actual values + /// Person product-momentum correlation coefficient. + public static double R(IEnumerable modelledValues, IEnumerable observedValues) + { + return Correlation.Pearson(modelledValues, observedValues); + } + + /// + /// Calculates the Standard Error of the regression, given a sequence of + /// modeled/predicted values, and a sequence of actual/observed values + /// + /// The modelled/predicted values + /// The observed/actual values + /// The Standard Error of the regression + public static double PopulationStandardError(IEnumerable modelledValues, IEnumerable observedValues) + { + return StandardError(modelledValues, observedValues, 0); + } + + /// + /// Calculates the Standard Error of the regression, given a sequence of + /// modeled/predicted values, and a sequence of actual/observed values + /// + /// The modelled/predicted values + /// The observed/actual values + /// The degrees of freedom by which the + /// number of samples is reduced for performing the Standard Error calculation + /// The Standard Error of the regression + public static double StandardError(IEnumerable modelledValues, IEnumerable observedValues, int degreesOfFreedom) + { + using (IEnumerator ieM = modelledValues.GetEnumerator()) + using (IEnumerator ieO = observedValues.GetEnumerator()) + { + double n = 0; + double accumulator = 0; + while (ieM.MoveNext()) + { + if (!ieO.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(modelledValues), Resources.ArgumentArraysSameLength); + } + + double currentM = ieM.Current; + double currentO = ieO.Current; + var diff = currentM - currentO; + accumulator += diff * diff; + n++; + } + + if (degreesOfFreedom >= n) + { + throw new ArgumentOutOfRangeException(nameof(degreesOfFreedom), Resources.DegreesOfFreedomMustBeLessThanSampleSize); + } + + return Math.Sqrt(accumulator / (n - degreesOfFreedom)); + } + } + + /// + /// Calculates the R-Squared value, also known as coefficient of determination, + /// given some modelled and observed values. + /// + /// The values expected from the model. + /// The actual values obtained. + /// Coefficient of determination. + public static double CoefficientOfDetermination(IEnumerable modelledValues, IEnumerable observedValues) + { + var y = observedValues; + var f = modelledValues; + int n = 0; + + double meanY = 0; + double ssTot = 0; + double ssRes = 0; + + using (IEnumerator ieY = y.GetEnumerator()) + using (IEnumerator ieF = f.GetEnumerator()) + { + while (ieY.MoveNext()) + { + if (!ieF.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(modelledValues), Resources.ArgumentArraysSameLength); + } + + double currentY = ieY.Current; + double currentF = ieF.Current; + + // If a large constant C is added to every y value, + // then each new y have an error of about C*eps, + // thus each new deltaY will change by about C*eps (compared to the old deltaY), + // and thus ssTot will change by only C*eps*deltaY on each step + // thus C*eps*deltaY*n in total. + // (This error cannot be eliminated by a Kahan algorithm, + // because it is introduced when C is added to the old Y value). + // + // This is better than summing the square of y values + // and then substracting the correct multiple of the square of the sum of y values, + // in this latter case ssTot will change by eps*n*(C^2+2*C*meanY) in total. + double deltaY = currentY - meanY; + double scaleDeltaY = deltaY / ++n; + + meanY += scaleDeltaY; + ssTot += scaleDeltaY * deltaY * (n - 1); + + // This calculation is as safe as ssTot + // in the case when a constant is added to both y and f. + ssRes += (currentY - currentF) * (currentY - currentF); + } + + if (ieF.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(observedValues), Resources.ArgumentArraysSameLength); + } + } + + return 1 - ssRes / ssTot; + } +} diff --git a/MathNet.Numerics/IntegralTransforms/Fourier.cs b/MathNet.Numerics/IntegralTransforms/Fourier.cs new file mode 100644 index 0000000..c54df4b --- /dev/null +++ b/MathNet.Numerics/IntegralTransforms/Fourier.cs @@ -0,0 +1,931 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.FourierTransform; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.IntegralTransforms; + +/// +/// Complex Fast (FFT) Implementation of the Discrete Fourier Transform (DFT). +/// +public static partial class Fourier +{ + /// + /// Applies the forward Fast Fourier Transform (FFT) to arbitrary-length sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + public static void Forward(Complex32[] samples) + { + FourierTransformControl.Provider.Forward(samples, FourierTransformScaling.SymmetricScaling); + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to arbitrary-length sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + public static void Forward(Complex[] samples) + { + FourierTransformControl.Provider.Forward(samples, FourierTransformScaling.SymmetricScaling); + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to arbitrary-length sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + public static void Forward(Complex32[] samples, FourierOptions options) + { + switch (options) + { + case FourierOptions.NoScaling: + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.Forward(samples, FourierTransformScaling.NoScaling); + break; + case FourierOptions.InverseExponent: + FourierTransformControl.Provider.Backward(samples, FourierTransformScaling.SymmetricScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.NoScaling: + case FourierOptions.InverseExponent | FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.Backward(samples, FourierTransformScaling.NoScaling); + break; + default: + FourierTransformControl.Provider.Forward(samples, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to arbitrary-length sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + public static void Forward(Complex[] samples, FourierOptions options) + { + switch (options) + { + case FourierOptions.NoScaling: + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.Forward(samples, FourierTransformScaling.NoScaling); + break; + case FourierOptions.InverseExponent: + FourierTransformControl.Provider.Backward(samples, FourierTransformScaling.SymmetricScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.NoScaling: + case FourierOptions.InverseExponent | FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.Backward(samples, FourierTransformScaling.NoScaling); + break; + default: + FourierTransformControl.Provider.Forward(samples, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to arbitrary-length sample vectors. + /// + /// Real part of the sample vector, where the FFT is evaluated in place. + /// Imaginary part of the sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + public static void Forward(float[] real, float[] imaginary, FourierOptions options = FourierOptions.Default) + { + if (real.Length != imaginary.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + // TODO: consider to support this natively by the provider, without the need for copying + // TODO: otherwise, consider ArrayPool + + Complex32[] data = new Complex32[real.Length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = new Complex32(real[i], imaginary[i]); + } + + Forward(data, options); + + for (int i = 0; i < data.Length; i++) + { + real[i] = data[i].Real; + imaginary[i] = data[i].Imaginary; + } + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to arbitrary-length sample vectors. + /// + /// Real part of the sample vector, where the FFT is evaluated in place. + /// Imaginary part of the sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + public static void Forward(double[] real, double[] imaginary, FourierOptions options = FourierOptions.Default) + { + if (real.Length != imaginary.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + // TODO: consider to support this natively by the provider, without the need for copying + // TODO: otherwise, consider ArrayPool + + Complex[] data = new Complex[real.Length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = new Complex(real[i], imaginary[i]); + } + + Forward(data, options); + + for (int i = 0; i < data.Length; i++) + { + real[i] = data[i].Real; + imaginary[i] = data[i].Imaginary; + } + } + + /// + /// Packed Real-Complex forward Fast Fourier Transform (FFT) to arbitrary-length sample vectors. + /// Since for real-valued time samples the complex spectrum is conjugate-even (symmetry), + /// the spectrum can be fully reconstructed from the positive frequencies only (first half). + /// The data array needs to be N+2 (if N is even) or N+1 (if N is odd) long in order to support such a packed spectrum. + /// + /// Data array of length N+2 (if N is even) or N+1 (if N is odd). + /// The number of samples. + /// Fourier Transform Convention Options. + public static void ForwardReal(float[] data, int n, FourierOptions options = FourierOptions.Default) + { + int length = n.IsEven() ? n + 2 : n + 1; + if (data.Length < length) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, length)); + } + + if ((options & FourierOptions.InverseExponent) == FourierOptions.InverseExponent) + { + throw new NotSupportedException(); + } + + switch (options) + { + case FourierOptions.NoScaling: + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.ForwardReal(data, n, FourierTransformScaling.NoScaling); + break; + default: + FourierTransformControl.Provider.ForwardReal(data, n, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Packed Real-Complex forward Fast Fourier Transform (FFT) to arbitrary-length sample vectors. + /// Since for real-valued time samples the complex spectrum is conjugate-even (symmetry), + /// the spectrum can be fully reconstructed form the positive frequencies only (first half). + /// The data array needs to be N+2 (if N is even) or N+1 (if N is odd) long in order to support such a packed spectrum. + /// + /// Data array of length N+2 (if N is even) or N+1 (if N is odd). + /// The number of samples. + /// Fourier Transform Convention Options. + public static void ForwardReal(double[] data, int n, FourierOptions options = FourierOptions.Default) + { + int length = n.IsEven() ? n + 2 : n + 1; + if (data.Length < length) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, length)); + } + + if ((options & FourierOptions.InverseExponent) == FourierOptions.InverseExponent) + { + throw new NotSupportedException(); + } + + switch (options) + { + case FourierOptions.NoScaling: + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.ForwardReal(data, n, FourierTransformScaling.NoScaling); + break; + default: + FourierTransformControl.Provider.ForwardReal(data, n, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to multiple dimensional sample data. + /// + /// Sample data, where the FFT is evaluated in place. + /// + /// The data size per dimension. The first dimension is the major one. + /// For example, with two dimensions "rows" and "columns" the samples are assumed to be organized row by row. + /// + /// Fourier Transform Convention Options. + public static void ForwardMultiDim(Complex32[] samples, int[] dimensions, FourierOptions options = FourierOptions.Default) + { + switch (options) + { + case FourierOptions.NoScaling: + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.ForwardMultidim(samples, dimensions, FourierTransformScaling.NoScaling); + break; + case FourierOptions.InverseExponent: + FourierTransformControl.Provider.BackwardMultidim(samples, dimensions, FourierTransformScaling.SymmetricScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.NoScaling: + case FourierOptions.InverseExponent | FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.BackwardMultidim(samples, dimensions, FourierTransformScaling.NoScaling); + break; + default: + FourierTransformControl.Provider.ForwardMultidim(samples, dimensions, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to multiple dimensional sample data. + /// + /// Sample data, where the FFT is evaluated in place. + /// + /// The data size per dimension. The first dimension is the major one. + /// For example, with two dimensions "rows" and "columns" the samples are assumed to be organized row by row. + /// + /// Fourier Transform Convention Options. + public static void ForwardMultiDim(Complex[] samples, int[] dimensions, FourierOptions options = FourierOptions.Default) + { + switch (options) + { + case FourierOptions.NoScaling: + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.ForwardMultidim(samples, dimensions, FourierTransformScaling.NoScaling); + break; + case FourierOptions.InverseExponent: + FourierTransformControl.Provider.BackwardMultidim(samples, dimensions, FourierTransformScaling.SymmetricScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.NoScaling: + case FourierOptions.InverseExponent | FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.BackwardMultidim(samples, dimensions, FourierTransformScaling.NoScaling); + break; + default: + FourierTransformControl.Provider.ForwardMultidim(samples, dimensions, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to two dimensional sample data. + /// + /// Sample data, organized row by row, where the FFT is evaluated in place + /// The number of rows. + /// The number of columns. + /// Data available organized column by column instead of row by row can be processed directly by swapping the rows and columns arguments. + /// Fourier Transform Convention Options. + public static void Forward2D(Complex32[] samplesRowWise, int rows, int columns, FourierOptions options = FourierOptions.Default) + { + ForwardMultiDim(samplesRowWise, new[] { rows, columns }, options); + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to two dimensional sample data. + /// + /// Sample data, organized row by row, where the FFT is evaluated in place + /// The number of rows. + /// The number of columns. + /// Data available organized column by column instead of row by row can be processed directly by swapping the rows and columns arguments. + /// Fourier Transform Convention Options. + public static void Forward2D(Complex[] samplesRowWise, int rows, int columns, FourierOptions options = FourierOptions.Default) + { + ForwardMultiDim(samplesRowWise, new[] { rows, columns }, options); + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to a two dimensional data in form of a matrix. + /// + /// Sample matrix, where the FFT is evaluated in place + /// Fourier Transform Convention Options. + public static void Forward2D(Matrix samples, FourierOptions options = FourierOptions.Default) + { + var rowMajorArray = samples.AsRowMajorArray(); + if (rowMajorArray != null) + { + ForwardMultiDim(rowMajorArray, new[] { samples.RowCount, samples.ColumnCount }, options); + return; + } + + var columnMajorArray = samples.AsColumnMajorArray(); + if (columnMajorArray != null) + { + ForwardMultiDim(columnMajorArray, new[] { samples.ColumnCount, samples.RowCount }, options); + return; + } + + // Fall Back + columnMajorArray = samples.ToColumnMajorArray(); + ForwardMultiDim(columnMajorArray, new[] { samples.ColumnCount, samples.RowCount }, options); + var denseStorage = new DenseColumnMajorMatrixStorage(samples.RowCount, samples.ColumnCount, columnMajorArray); + denseStorage.CopyToUnchecked(samples.Storage, ExistingData.Clear); + } + + /// + /// Applies the forward Fast Fourier Transform (FFT) to a two dimensional data in form of a matrix. + /// + /// Sample matrix, where the FFT is evaluated in place + /// Fourier Transform Convention Options. + public static void Forward2D(Matrix samples, FourierOptions options = FourierOptions.Default) + { + var rowMajorArray = samples.AsRowMajorArray(); + if (rowMajorArray != null) + { + ForwardMultiDim(rowMajorArray, new[] { samples.RowCount, samples.ColumnCount }, options); + return; + } + + var columnMajorArray = samples.AsColumnMajorArray(); + if (columnMajorArray != null) + { + ForwardMultiDim(columnMajorArray, new[] { samples.ColumnCount, samples.RowCount }, options); + return; + } + + // Fall Back + columnMajorArray = samples.ToColumnMajorArray(); + ForwardMultiDim(columnMajorArray, new[] { samples.ColumnCount, samples.RowCount }, options); + var denseStorage = new DenseColumnMajorMatrixStorage(samples.RowCount, samples.ColumnCount, columnMajorArray); + denseStorage.CopyToUnchecked(samples.Storage, ExistingData.Clear); + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to arbitrary-length sample vectors. + /// + /// Spectrum data, where the iFFT is evaluated in place. + public static void Inverse(Complex32[] spectrum) + { + FourierTransformControl.Provider.Backward(spectrum, FourierTransformScaling.SymmetricScaling); + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to arbitrary-length sample vectors. + /// + /// Spectrum data, where the iFFT is evaluated in place. + public static void Inverse(Complex[] spectrum) + { + FourierTransformControl.Provider.Backward(spectrum, FourierTransformScaling.SymmetricScaling); + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to arbitrary-length sample vectors. + /// + /// Spectrum data, where the iFFT is evaluated in place. + /// Fourier Transform Convention Options. + public static void Inverse(Complex32[] spectrum, FourierOptions options) + { + switch (options) + { + case FourierOptions.NoScaling: + FourierTransformControl.Provider.Backward(spectrum, FourierTransformScaling.NoScaling); + break; + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.Backward(spectrum, FourierTransformScaling.BackwardScaling); + break; + case FourierOptions.InverseExponent: + FourierTransformControl.Provider.Forward(spectrum, FourierTransformScaling.SymmetricScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.NoScaling: + FourierTransformControl.Provider.Forward(spectrum, FourierTransformScaling.NoScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.Forward(spectrum, FourierTransformScaling.ForwardScaling); + break; + default: + FourierTransformControl.Provider.Backward(spectrum, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to arbitrary-length sample vectors. + /// + /// Spectrum data, where the iFFT is evaluated in place. + /// Fourier Transform Convention Options. + public static void Inverse(Complex[] spectrum, FourierOptions options) + { + switch (options) + { + case FourierOptions.NoScaling: + FourierTransformControl.Provider.Backward(spectrum, FourierTransformScaling.NoScaling); + break; + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.Backward(spectrum, FourierTransformScaling.BackwardScaling); + break; + case FourierOptions.InverseExponent: + FourierTransformControl.Provider.Forward(spectrum, FourierTransformScaling.SymmetricScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.NoScaling: + FourierTransformControl.Provider.Forward(spectrum, FourierTransformScaling.NoScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.Forward(spectrum, FourierTransformScaling.ForwardScaling); + break; + default: + FourierTransformControl.Provider.Backward(spectrum, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to arbitrary-length sample vectors. + /// + /// Real part of the sample vector, where the iFFT is evaluated in place. + /// Imaginary part of the sample vector, where the iFFT is evaluated in place. + /// Fourier Transform Convention Options. + public static void Inverse(float[] real, float[] imaginary, FourierOptions options = FourierOptions.Default) + { + if (real.Length != imaginary.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + // TODO: consider to support this natively by the provider, without the need for copying + // TODO: otherwise, consider ArrayPool + + Complex32[] data = new Complex32[real.Length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = new Complex32(real[i], imaginary[i]); + } + + Inverse(data, options); + + for (int i = 0; i < data.Length; i++) + { + real[i] = data[i].Real; + imaginary[i] = data[i].Imaginary; + } + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to arbitrary-length sample vectors. + /// + /// Real part of the sample vector, where the iFFT is evaluated in place. + /// Imaginary part of the sample vector, where the iFFT is evaluated in place. + /// Fourier Transform Convention Options. + public static void Inverse(double[] real, double[] imaginary, FourierOptions options = FourierOptions.Default) + { + if (real.Length != imaginary.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + // TODO: consider to support this natively by the provider, without the need for copying + // TODO: otherwise, consider ArrayPool + + Complex[] data = new Complex[real.Length]; + for (int i = 0; i < data.Length; i++) + { + data[i] = new Complex(real[i], imaginary[i]); + } + + Inverse(data, options); + + for (int i = 0; i < data.Length; i++) + { + real[i] = data[i].Real; + imaginary[i] = data[i].Imaginary; + } + } + + /// + /// Packed Real-Complex inverse Fast Fourier Transform (iFFT) to arbitrary-length sample vectors. + /// Since for real-valued time samples the complex spectrum is conjugate-even (symmetry), + /// the spectrum can be fully reconstructed form the positive frequencies only (first half). + /// The data array needs to be N+2 (if N is even) or N+1 (if N is odd) long in order to support such a packed spectrum. + /// + /// Data array of length N+2 (if N is even) or N+1 (if N is odd). + /// The number of samples. + /// Fourier Transform Convention Options. + public static void InverseReal(float[] data, int n, FourierOptions options = FourierOptions.Default) + { + int length = n.IsEven() ? n + 2 : n + 1; + if (data.Length < length) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, length)); + } + + if ((options & FourierOptions.InverseExponent) == FourierOptions.InverseExponent) + { + throw new NotSupportedException(); + } + + switch (options) + { + case FourierOptions.NoScaling: + FourierTransformControl.Provider.BackwardReal(data, n, FourierTransformScaling.NoScaling); + break; + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.BackwardReal(data, n, FourierTransformScaling.BackwardScaling); + break; + default: + FourierTransformControl.Provider.BackwardReal(data, n, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Packed Real-Complex inverse Fast Fourier Transform (iFFT) to arbitrary-length sample vectors. + /// Since for real-valued time samples the complex spectrum is conjugate-even (symmetry), + /// the spectrum can be fully reconstructed form the positive frequencies only (first half). + /// The data array needs to be N+2 (if N is even) or N+1 (if N is odd) long in order to support such a packed spectrum. + /// + /// Data array of length N+2 (if N is even) or N+1 (if N is odd). + /// The number of samples. + /// Fourier Transform Convention Options. + public static void InverseReal(double[] data, int n, FourierOptions options = FourierOptions.Default) + { + int length = n.IsEven() ? n + 2 : n + 1; + if (data.Length < length) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, length)); + } + + if ((options & FourierOptions.InverseExponent) == FourierOptions.InverseExponent) + { + throw new NotSupportedException(); + } + + switch (options) + { + case FourierOptions.NoScaling: + FourierTransformControl.Provider.BackwardReal(data, n, FourierTransformScaling.NoScaling); + break; + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.BackwardReal(data, n, FourierTransformScaling.BackwardScaling); + break; + default: + FourierTransformControl.Provider.BackwardReal(data, n, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to multiple dimensional sample data. + /// + /// Spectrum data, where the iFFT is evaluated in place. + /// + /// The data size per dimension. The first dimension is the major one. + /// For example, with two dimensions "rows" and "columns" the samples are assumed to be organized row by row. + /// + /// Fourier Transform Convention Options. + public static void InverseMultiDim(Complex32[] spectrum, int[] dimensions, FourierOptions options = FourierOptions.Default) + { + switch (options) + { + case FourierOptions.NoScaling: + FourierTransformControl.Provider.BackwardMultidim(spectrum, dimensions, FourierTransformScaling.NoScaling); + break; + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.BackwardMultidim(spectrum, dimensions, FourierTransformScaling.BackwardScaling); + break; + case FourierOptions.InverseExponent: + FourierTransformControl.Provider.ForwardMultidim(spectrum, dimensions, FourierTransformScaling.SymmetricScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.NoScaling: + FourierTransformControl.Provider.ForwardMultidim(spectrum, dimensions, FourierTransformScaling.NoScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.ForwardMultidim(spectrum, dimensions, FourierTransformScaling.ForwardScaling); + break; + default: + FourierTransformControl.Provider.BackwardMultidim(spectrum, dimensions, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to multiple dimensional sample data. + /// + /// Spectrum data, where the iFFT is evaluated in place. + /// + /// The data size per dimension. The first dimension is the major one. + /// For example, with two dimensions "rows" and "columns" the samples are assumed to be organized row by row. + /// + /// Fourier Transform Convention Options. + public static void InverseMultiDim(Complex[] spectrum, int[] dimensions, FourierOptions options = FourierOptions.Default) + { + switch (options) + { + case FourierOptions.NoScaling: + FourierTransformControl.Provider.BackwardMultidim(spectrum, dimensions, FourierTransformScaling.NoScaling); + break; + case FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.BackwardMultidim(spectrum, dimensions, FourierTransformScaling.BackwardScaling); + break; + case FourierOptions.InverseExponent: + FourierTransformControl.Provider.ForwardMultidim(spectrum, dimensions, FourierTransformScaling.SymmetricScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.NoScaling: + FourierTransformControl.Provider.ForwardMultidim(spectrum, dimensions, FourierTransformScaling.NoScaling); + break; + case FourierOptions.InverseExponent | FourierOptions.AsymmetricScaling: + FourierTransformControl.Provider.ForwardMultidim(spectrum, dimensions, FourierTransformScaling.ForwardScaling); + break; + default: + FourierTransformControl.Provider.BackwardMultidim(spectrum, dimensions, FourierTransformScaling.SymmetricScaling); + break; + } + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to two dimensional sample data. + /// + /// Sample data, organized row by row, where the iFFT is evaluated in place + /// The number of rows. + /// The number of columns. + /// Data available organized column by column instead of row by row can be processed directly by swapping the rows and columns arguments. + /// Fourier Transform Convention Options. + public static void Inverse2D(Complex32[] spectrumRowWise, int rows, int columns, FourierOptions options = FourierOptions.Default) + { + InverseMultiDim(spectrumRowWise, new[] { rows, columns }, options); + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to two dimensional sample data. + /// + /// Sample data, organized row by row, where the iFFT is evaluated in place + /// The number of rows. + /// The number of columns. + /// Data available organized column by column instead of row by row can be processed directly by swapping the rows and columns arguments. + /// Fourier Transform Convention Options. + public static void Inverse2D(Complex[] spectrumRowWise, int rows, int columns, FourierOptions options = FourierOptions.Default) + { + InverseMultiDim(spectrumRowWise, new[] { rows, columns }, options); + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to a two dimensional data in form of a matrix. + /// + /// Sample matrix, where the iFFT is evaluated in place + /// Fourier Transform Convention Options. + public static void Inverse2D(Matrix spectrum, FourierOptions options = FourierOptions.Default) + { + var rowMajorArray = spectrum.AsRowMajorArray(); + if (rowMajorArray != null) + { + InverseMultiDim(rowMajorArray, new[] { spectrum.RowCount, spectrum.ColumnCount }, options); + return; + } + + var columnMajorArray = spectrum.AsColumnMajorArray(); + if (columnMajorArray != null) + { + InverseMultiDim(columnMajorArray, new[] { spectrum.ColumnCount, spectrum.RowCount }, options); + return; + } + + // Fall Back + columnMajorArray = spectrum.ToColumnMajorArray(); + InverseMultiDim(columnMajorArray, new[] { spectrum.ColumnCount, spectrum.RowCount }, options); + var denseStorage = new DenseColumnMajorMatrixStorage(spectrum.RowCount, spectrum.ColumnCount, columnMajorArray); + denseStorage.CopyToUnchecked(spectrum.Storage, ExistingData.Clear); + } + + /// + /// Applies the inverse Fast Fourier Transform (iFFT) to a two dimensional data in form of a matrix. + /// + /// Sample matrix, where the iFFT is evaluated in place + /// Fourier Transform Convention Options. + public static void Inverse2D(Matrix spectrum, FourierOptions options = FourierOptions.Default) + { + var rowMajorArray = spectrum.AsRowMajorArray(); + if (rowMajorArray != null) + { + InverseMultiDim(rowMajorArray, new[] { spectrum.RowCount, spectrum.ColumnCount }, options); + return; + } + + var columnMajorArray = spectrum.AsColumnMajorArray(); + if (columnMajorArray != null) + { + InverseMultiDim(columnMajorArray, new[] { spectrum.ColumnCount, spectrum.RowCount }, options); + return; + } + + // Fall Back + columnMajorArray = spectrum.ToColumnMajorArray(); + InverseMultiDim(columnMajorArray, new[] { spectrum.ColumnCount, spectrum.RowCount }, options); + var denseStorage = new DenseColumnMajorMatrixStorage(spectrum.RowCount, spectrum.ColumnCount, columnMajorArray); + denseStorage.CopyToUnchecked(spectrum.Storage, ExistingData.Clear); + } + + /// + /// Naive forward DFT, useful e.g. to verify faster algorithms. + /// + /// Time-space sample vector. + /// Fourier Transform Convention Options. + /// Corresponding frequency-space vector. + [Obsolete("Use Forward instead. Will be dropped in version 5.0 and behave like Forward until then.")] + public static Complex32[] NaiveForward(Complex32[] samples, FourierOptions options = FourierOptions.Default) + { + var result = new Complex32[samples.Length]; + samples.Copy(result); + Forward(result, options); + return result; + } + + /// + /// Naive forward DFT, useful e.g. to verify faster algorithms. + /// + /// Time-space sample vector. + /// Fourier Transform Convention Options. + /// Corresponding frequency-space vector. + [Obsolete("Use Forward instead. Will be dropped in version 5.0 and behave like Forward until then.")] + public static Complex[] NaiveForward(Complex[] samples, FourierOptions options = FourierOptions.Default) + { + var result = new Complex[samples.Length]; + samples.Copy(result); + Forward(result, options); + return result; + } + + /// + /// Naive inverse DFT, useful e.g. to verify faster algorithms. + /// + /// Frequency-space sample vector. + /// Fourier Transform Convention Options. + /// Corresponding time-space vector. + [Obsolete("Use Inverse instead. Will be dropped in version 5.0 and behave like Inverse until then.")] + public static Complex32[] NaiveInverse(Complex32[] spectrum, FourierOptions options = FourierOptions.Default) + { + var result = new Complex32[spectrum.Length]; + spectrum.Copy(result); + Inverse(result, options); + return result; + } + + /// + /// Naive inverse DFT, useful e.g. to verify faster algorithms. + /// + /// Frequency-space sample vector. + /// Fourier Transform Convention Options. + /// Corresponding time-space vector. + [Obsolete("Use Inverse instead. Will be dropped in version 5.0 and behave like Inverse until then.")] + public static Complex[] NaiveInverse(Complex[] spectrum, FourierOptions options = FourierOptions.Default) + { + var result = new Complex[spectrum.Length]; + spectrum.Copy(result); + Inverse(result, options); + return result; + } + + /// + /// Radix-2 forward FFT for power-of-two sized sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + /// + [Obsolete("Use Forward instead. Will be dropped in version 5.0 and behave like Forward until then.")] + public static void Radix2Forward(Complex32[] samples, FourierOptions options = FourierOptions.Default) + { + Forward(samples, options); + } + + /// + /// Radix-2 forward FFT for power-of-two sized sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + /// + [Obsolete("Use Forward instead. Will be dropped in version 5.0 and behave like Forward until then.")] + public static void Radix2Forward(Complex[] samples, FourierOptions options = FourierOptions.Default) + { + Forward(samples, options); + } + + /// + /// Radix-2 inverse FFT for power-of-two sized sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + /// + [Obsolete("Use Inverse instead. Will be dropped in version 5.0 and behave like Inverse until then.")] + public static void Radix2Inverse(Complex32[] spectrum, FourierOptions options = FourierOptions.Default) + { + Inverse(spectrum, options); + } + + /// + /// Radix-2 inverse FFT for power-of-two sized sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + /// + [Obsolete("Use Inverse instead. Will be dropped in version 5.0 and behave like Inverse until then.")] + public static void Radix2Inverse(Complex[] spectrum, FourierOptions options = FourierOptions.Default) + { + Inverse(spectrum, options); + } + + /// + /// Bluestein forward FFT for arbitrary sized sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + [Obsolete("Use Forward instead. Will be dropped in version 5.0 and behave like Forward until then.")] + public static void BluesteinForward(Complex32[] samples, FourierOptions options = FourierOptions.Default) + { + Forward(samples, options); + } + + /// + /// Bluestein forward FFT for arbitrary sized sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + [Obsolete("Use Forward instead. Will be dropped in version 5.0 and behave like Forward until then.")] + public static void BluesteinForward(Complex[] samples, FourierOptions options = FourierOptions.Default) + { + Forward(samples, options); + } + + /// + /// Bluestein inverse FFT for arbitrary sized sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + [Obsolete("Use Inverse instead. Will be dropped in version 5.0 and behave like Inverse until then.")] + public static void BluesteinInverse(Complex32[] spectrum, FourierOptions options = FourierOptions.Default) + { + Inverse(spectrum, options); + } + + /// + /// Bluestein inverse FFT for arbitrary sized sample vectors. + /// + /// Sample vector, where the FFT is evaluated in place. + /// Fourier Transform Convention Options. + [Obsolete("Use Inverse instead. Will be dropped in version 5.0 and behave like Inverse until then.")] + public static void BluesteinInverse(Complex[] spectrum, FourierOptions options = FourierOptions.Default) + { + Inverse(spectrum, options); + } + + /// + /// Generate the frequencies corresponding to each index in frequency space. + /// The frequency space has a resolution of sampleRate/N. + /// Index 0 corresponds to the DC part, the following indices correspond to + /// the positive frequencies up to the Nyquist frequency (sampleRate/2), + /// followed by the negative frequencies wrapped around. + /// + /// Number of samples. + /// The sampling rate of the time-space data. + public static double[] FrequencyScale(int length, double sampleRate) + { + double[] scale = new double[length]; + double f = 0, step = sampleRate / length; + int secondHalf = (length >> 1) + 1; + for (int i = 0; i < secondHalf; i++) + { + scale[i] = f; + f += step; + } + + f = -step * (secondHalf - 2); + for (int i = secondHalf; i < length; i++) + { + scale[i] = f; + f += step; + } + + return scale; + } +} diff --git a/MathNet.Numerics/IntegralTransforms/FourierOptions.cs b/MathNet.Numerics/IntegralTransforms/FourierOptions.cs new file mode 100644 index 0000000..2d72e13 --- /dev/null +++ b/MathNet.Numerics/IntegralTransforms/FourierOptions.cs @@ -0,0 +1,73 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.IntegralTransforms; + +/// +/// Fourier Transform Convention +/// +[Flags] +public enum FourierOptions +{ + // FLAGS: + + /// + /// Inverse integrand exponent (forward: positive sign; inverse: negative sign). + /// + InverseExponent = 0x01, + + /// + /// Only scale by 1/N in the inverse direction; No scaling in forward direction. + /// + AsymmetricScaling = 0x02, + + /// + /// Don't scale at all (neither on forward nor on inverse transformation). + /// + NoScaling = 0x04, + + // USABILITY POINTERS: + + /// + /// Universal; Symmetric scaling and common exponent (used in Maple). + /// + Default = 0, + + /// + /// Only scale by 1/N in the inverse direction; No scaling in forward direction (used in Matlab). [= AsymmetricScaling] + /// + Matlab = AsymmetricScaling, + + /// + /// Inverse integrand exponent; No scaling at all (used in all Numerical Recipes based implementations). [= InverseExponent | NoScaling] + /// + NumericalRecipes = InverseExponent | NoScaling +} diff --git a/MathNet.Numerics/IntegralTransforms/Hartley.Naive.cs b/MathNet.Numerics/IntegralTransforms/Hartley.Naive.cs new file mode 100644 index 0000000..a1ec77c --- /dev/null +++ b/MathNet.Numerics/IntegralTransforms/Hartley.Naive.cs @@ -0,0 +1,69 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.IntegralTransforms; + +/// +/// Fast (FHT) Implementation of the Discrete Hartley Transform (DHT). +/// +public static partial class Hartley +{ + /// + /// Naive generic DHT, useful e.g. to verify faster algorithms. + /// + /// Time-space sample vector. + /// Corresponding frequency-space vector. + internal static double[] Naive(double[] samples) + { + var w0 = Constants.Pi2 / samples.Length; + var spectrum = new double[samples.Length]; + + CommonParallel.For(0, samples.Length, (u, v) => + { + for (int i = u; i < v; i++) + { + var wk = w0 * i; + var sum = 0.0; + for (var n = 0; n < samples.Length; n++) + { + var w = n * wk; + sum += samples[n] * Constants.Sqrt2 * Math.Cos(w - Constants.PiOver4); + } + + spectrum[i] = sum; + } + }); + + return spectrum; + } +} diff --git a/MathNet.Numerics/IntegralTransforms/Hartley.cs b/MathNet.Numerics/IntegralTransforms/Hartley.cs new file mode 100644 index 0000000..8cfa449 --- /dev/null +++ b/MathNet.Numerics/IntegralTransforms/Hartley.cs @@ -0,0 +1,108 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.IntegralTransforms; + +/// +/// Fast (FHT) Implementation of the Discrete Hartley Transform (DHT). +/// +public static partial class Hartley +{ + /// + /// Naive forward DHT, useful e.g. to verify faster algorithms. + /// + /// Time-space sample vector. + /// Hartley Transform Convention Options. + /// Corresponding frequency-space vector. + public static double[] NaiveForward(double[] timeSpace, HartleyOptions options) + { + var frequencySpace = Naive(timeSpace); + ForwardScaleByOptions(options, frequencySpace); + return frequencySpace; + } + + /// + /// Naive inverse DHT, useful e.g. to verify faster algorithms. + /// + /// Frequency-space sample vector. + /// Hartley Transform Convention Options. + /// Corresponding time-space vector. + public static double[] NaiveInverse(double[] frequencySpace, HartleyOptions options) + { + var timeSpace = Naive(frequencySpace); + InverseScaleByOptions(options, timeSpace); + return timeSpace; + } + + /// + /// Rescale FFT-the resulting vector according to the provided convention options. + /// + /// Fourier Transform Convention Options. + /// Sample Vector. + static void ForwardScaleByOptions(HartleyOptions options, double[] samples) + { + if ((options & HartleyOptions.NoScaling) == HartleyOptions.NoScaling || + (options & HartleyOptions.AsymmetricScaling) == HartleyOptions.AsymmetricScaling) + { + return; + } + + var scalingFactor = Math.Sqrt(1.0 / samples.Length); + for (int i = 0; i < samples.Length; i++) + { + samples[i] *= scalingFactor; + } + } + + /// + /// Rescale the iFFT-resulting vector according to the provided convention options. + /// + /// Fourier Transform Convention Options. + /// Sample Vector. + static void InverseScaleByOptions(HartleyOptions options, double[] samples) + { + if ((options & HartleyOptions.NoScaling) == HartleyOptions.NoScaling) + { + return; + } + + var scalingFactor = 1.0 / samples.Length; + if ((options & HartleyOptions.AsymmetricScaling) != HartleyOptions.AsymmetricScaling) + { + scalingFactor = Math.Sqrt(scalingFactor); + } + + for (int i = 0; i < samples.Length; i++) + { + samples[i] *= scalingFactor; + } + } +} diff --git a/MathNet.Numerics/IntegralTransforms/HartleyOptions.cs b/MathNet.Numerics/IntegralTransforms/HartleyOptions.cs new file mode 100644 index 0000000..ae29580 --- /dev/null +++ b/MathNet.Numerics/IntegralTransforms/HartleyOptions.cs @@ -0,0 +1,58 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.IntegralTransforms; + +using System; + +/// +/// Hartley Transform Convention +/// +[Flags] +public enum HartleyOptions +{ + // FLAGS: + + /// + /// Only scale by 1/N in the inverse direction; No scaling in forward direction. + /// + AsymmetricScaling = 0x02, + + /// + /// Don't scale at all (neither on forward nor on inverse transformation). + /// + NoScaling = 0x04, + + // USABILITY POINTERS: + + /// + /// Universal; Symmetric scaling. + /// + Default = 0, +} diff --git a/MathNet.Numerics/Integrate.cs b/MathNet.Numerics/Integrate.cs new file mode 100644 index 0000000..d4c0e75 --- /dev/null +++ b/MathNet.Numerics/Integrate.cs @@ -0,0 +1,451 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Integration; + +using System; +using System.Numerics; + +namespace MathNet.Numerics; + +/// +/// Numerical Integration (Quadrature). +/// +public static class Integrate +{ + /// + /// Approximation of the definite integral of an analytic smooth function on a closed interval. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static double OnClosedInterval(Func f, double intervalBegin, double intervalEnd, double targetAbsoluteError) + { + return DoubleExponentialTransformation.Integrate(f, intervalBegin, intervalEnd, targetAbsoluteError); + } + + /// + /// Approximation of the definite integral of an analytic smooth function on a closed interval. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Approximation of the finite integral in the given interval. + public static double OnClosedInterval(Func f, double intervalBegin, double intervalEnd) + { + return DoubleExponentialTransformation.Integrate(f, intervalBegin, intervalEnd, 1e-8); + } + + /// + /// Approximates a 2-dimensional definite integral using an Nth order Gauss-Legendre rule over the rectangle [a,b] x [c,d]. + /// + /// The 2-dimensional analytic smooth function to integrate. + /// Where the interval starts for the first (inside) integral, exclusive and finite. + /// Where the interval ends for the first (inside) integral, exclusive and finite. + /// Where the interval starts for the second (outside) integral, exclusive and finite. + /// /// Where the interval ends for the second (outside) integral, exclusive and finite. + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Approximation of the finite integral in the given interval. + public static double OnRectangle(Func f, double invervalBeginA, double invervalEndA, double invervalBeginB, double invervalEndB, int order) + { + return GaussLegendreRule.Integrate(f, invervalBeginA, invervalEndA, invervalBeginB, invervalEndB, order); + } + + /// + /// Approximates a 2-dimensional definite integral using an Nth order Gauss-Legendre rule over the rectangle [a,b] x [c,d]. + /// + /// The 2-dimensional analytic smooth function to integrate. + /// Where the interval starts for the first (inside) integral, exclusive and finite. + /// Where the interval ends for the first (inside) integral, exclusive and finite. + /// Where the interval starts for the second (outside) integral, exclusive and finite. + /// /// Where the interval ends for the second (outside) integral, exclusive and finite. + /// Approximation of the finite integral in the given interval. + public static double OnRectangle(Func f, double invervalBeginA, double invervalEndA, double invervalBeginB, double invervalEndB) + { + return GaussLegendreRule.Integrate(f, invervalBeginA, invervalEndA, invervalBeginB, invervalEndB, 32); + } + + /// + /// Approximation of the definite integral of an analytic smooth function by double-exponential quadrature. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts. + /// Where the interval stops. + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static double DoubleExponential(Func f, double intervalBegin, double intervalEnd, double targetAbsoluteError = 1E-8) + { + // Reference: + // Formula used for variable subsitution from + // 1. Shampine, L. F. (2008). Vectorized adaptive quadrature in MATLAB. Journal of Computational and Applied Mathematics, 211(2), 131-140. + // 2. quadgk.m, GNU Octave + + if (intervalBegin > intervalEnd) + { + return -DoubleExponential(f, intervalEnd, intervalBegin, targetAbsoluteError); + } + + // (-oo, oo) => [-1, 1] + // + // integral_(-oo)^(oo) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = t / (1 - t^2) + // g'(t) = (1 + t^2) / (1 - t^2)^2 + if (double.IsInfinity(intervalBegin) && double.IsInfinity(intervalEnd)) + { + Func u = (t) => + { + return f(t / (1 - t * t)) * (1 + t * t) / ((1 - t * t) * (1 - t * t)); + }; + return DoubleExponentialTransformation.Integrate(u, -1, 1, targetAbsoluteError); + } + // [a, oo) => [0, 1] + // + // integral_(a)^(oo) f(x) dx = integral_(0)^(oo) f(a + t^2) 2 t dt + // = integral_(0)^(1) f(a + g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 - s) + // g'(s) = 1 / (1 - s)^2 + else if (double.IsInfinity(intervalEnd)) + { + Func u = (s) => + { + return 2 * s * f(intervalBegin + (s / (1 - s)) * (s / (1 - s))) / ((1 - s) * (1 - s) * (1 - s)); + }; + return DoubleExponentialTransformation.Integrate(u, 0, 1, targetAbsoluteError); + } + // (-oo, b] => [-1, 0] + // + // integral_(-oo)^(b) f(x) dx = -integral_(-oo)^(0) f(b - t^2) 2 t dt + // = -integral_(-1)^(0) f(b - g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 + s) + // g'(s) = 1 / (1 + s)^2 + else if (double.IsInfinity(intervalBegin)) + { + Func u = (s) => + { + return -2 * s * f(intervalEnd - s / (1 + s) * (s / (1 + s))) / ((1 + s) * (1 + s) * (1 + s)); + }; + return DoubleExponentialTransformation.Integrate(u, -1, 0, targetAbsoluteError); + } + else + { + return DoubleExponentialTransformation.Integrate(f, intervalBegin, intervalEnd, targetAbsoluteError); + } + } + + /// + /// Approximation of the definite integral of an analytic smooth function by Gauss-Legendre quadrature. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts. + /// Where the interval stops. + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Approximation of the finite integral in the given interval. + public static double GaussLegendre(Func f, double intervalBegin, double intervalEnd, int order = 128) + { + // Reference: + // Formula used for variable subsitution from + // 1. Shampine, L. F. (2008). Vectorized adaptive quadrature in MATLAB. Journal of Computational and Applied Mathematics, 211(2), 131-140. + // 2. quadgk.m, GNU Octave + + if (intervalBegin > intervalEnd) + { + return -GaussLegendre(f, intervalEnd, intervalBegin, order); + } + + // (-oo, oo) => [-1, 1] + // + // integral_(-oo)^(oo) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = t / (1 - t^2) + // g'(t) = (1 + t^2) / (1 - t^2)^2 + if (double.IsInfinity(intervalBegin) && double.IsInfinity(intervalEnd)) + { + Func u = (t) => + { + return f(t / (1 - t * t)) * (1 + t * t) / ((1 - t * t) * (1 - t * t)); + }; + return GaussLegendreRule.Integrate(u, -1, 1, order); + } + // [a, oo) => [0, 1] + // + // integral_(a)^(oo) f(x) dx = integral_(0)^(oo) f(a + t^2) 2 t dt + // = integral_(0)^(1) f(a + g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 - s) + // g'(s) = 1 / (1 - s)^2 + else if (double.IsInfinity(intervalEnd)) + { + Func u = (s) => + { + return 2 * s * f(intervalBegin + (s / (1 - s)) * (s / (1 - s))) / ((1 - s) * (1 - s) * (1 - s)); + }; + return GaussLegendreRule.Integrate(u, 0, 1, order); + } + // (-oo, b] => [-1, 0] + // + // integral_(-oo)^(b) f(x) dx = -integral_(-oo)^(0) f(b - t^2) 2 t dt + // = -integral_(-1)^(0) f(b - g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 + s) + // g'(s) = 1 / (1 + s)^2 + else if (double.IsInfinity(intervalBegin)) + { + Func u = (s) => + { + return -2 * s * f(intervalEnd - s / (1 + s) * (s / (1 + s))) / ((1 + s) * (1 + s) * (1 + s)); + }; + return GaussLegendreRule.Integrate(u, -1, 0, order); + } + // [a, b] => [-1, 1] + // + // integral_(a)^(b) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = (b - a) * t * (3 - t^2) / 4 + (b + a) / 2 + // g'(t) = 3 / 4 * (b - a) * (1 - t^2) + else + { + Func u = (t) => + { + return f((intervalEnd - intervalBegin) / 4 * t * (3 - t * t) + (intervalEnd + intervalBegin) / 2) * 3 * (intervalEnd - intervalBegin) / 4 * (1 - t * t); + }; + return GaussLegendreRule.Integrate(u, -1, 1, order); + } + } + + /// + /// Approximation of the definite integral of an analytic smooth function by Gauss-Kronrod quadrature. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts. + /// Where the interval stops. + /// The expected relative accuracy of the approximation. + /// The maximum number of interval splittings permitted before stopping. + /// The number of Gauss-Kronrod points. Pre-computed for 15, 31, 41, 51 and 61 points. + /// Approximation of the finite integral in the given interval. + public static double GaussKronrod(Func f, double intervalBegin, double intervalEnd, double targetRelativeError = 1E-8, int maximumDepth = 15, int order = 15) + { + return GaussKronrodRule.Integrate(f, intervalBegin, intervalEnd, out _, out _, targetRelativeError: targetRelativeError, maximumDepth: maximumDepth, order: order); + } + + /// + /// Approximation of the definite integral of an analytic smooth function by Gauss-Kronrod quadrature. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts. + /// Where the interval stops. + /// The difference between the (N-1)/2 point Gauss approximation and the N-point Gauss-Kronrod approximation + /// The L1 norm of the result, if there is a significant difference between this and the returned value, then the result is likely to be ill-conditioned. + /// The expected relative accuracy of the approximation. + /// The maximum number of interval splittings permitted before stopping + /// The number of Gauss-Kronrod points. Pre-computed for 15, 21, 31, 41, 51 and 61 points + /// Approximation of the finite integral in the given interval. + public static double GaussKronrod(Func f, double intervalBegin, double intervalEnd, out double error, out double L1Norm, double targetRelativeError = 1E-8, int maximumDepth = 15, int order = 15) + { + return GaussKronrodRule.Integrate(f, intervalBegin, intervalEnd, out error, out L1Norm, targetRelativeError: targetRelativeError, maximumDepth: maximumDepth, order: order); + } +} + +/// +/// Numerical Contour Integration of a complex-valued function over a real variable,. +/// +public static class ContourIntegrate +{ + /// + /// Approximation of the definite integral of an analytic smooth complex function by double-exponential quadrature. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts. + /// Where the interval stops. + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static Complex DoubleExponential(Func f, double intervalBegin, double intervalEnd, double targetAbsoluteError = 1E-8) + { + // Reference: + // Formula used for variable subsitution from + // 1. Shampine, L. F. (2008). Vectorized adaptive quadrature in MATLAB. Journal of Computational and Applied Mathematics, 211(2), 131-140. + // 2. quadgk.m, GNU Octave + + if (intervalBegin > intervalEnd) + { + return -DoubleExponential(f, intervalEnd, intervalBegin, targetAbsoluteError); + } + + // (-oo, oo) => [-1, 1] + // + // integral_(-oo)^(oo) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = t / (1 - t^2) + // g'(t) = (1 + t^2) / (1 - t^2)^2 + if (double.IsInfinity(intervalBegin) && double.IsInfinity(intervalEnd)) + { + Func u = (t) => + { + return f(t / (1 - t * t)) * (1 + t * t) / ((1 - t * t) * (1 - t * t)); + }; + return DoubleExponentialTransformation.ContourIntegrate(u, -1, 1, targetAbsoluteError); + } + // [a, oo) => [0, 1] + // + // integral_(a)^(oo) f(x) dx = integral_(0)^(oo) f(a + t^2) 2 t dt + // = integral_(0)^(1) f(a + g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 - s) + // g'(s) = 1 / (1 - s)^2 + else if (double.IsInfinity(intervalEnd)) + { + Func u = (s) => + { + return 2 * s * f(intervalBegin + (s / (1 - s)) * (s / (1 - s))) / ((1 - s) * (1 - s) * (1 - s)); + }; + return DoubleExponentialTransformation.ContourIntegrate(u, 0, 1, targetAbsoluteError); + } + // (-oo, b] => [-1, 0] + // + // integral_(-oo)^(b) f(x) dx = -integral_(-oo)^(0) f(b - t^2) 2 t dt + // = -integral_(-1)^(0) f(b - g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 + s) + // g'(s) = 1 / (1 + s)^2 + else if (double.IsInfinity(intervalBegin)) + { + Func u = (s) => + { + return -2 * s * f(intervalEnd - s / (1 + s) * (s / (1 + s))) / ((1 + s) * (1 + s) * (1 + s)); + }; + return DoubleExponentialTransformation.ContourIntegrate(u, -1, 0, targetAbsoluteError); + } + else + { + return DoubleExponentialTransformation.ContourIntegrate(f, intervalBegin, intervalEnd, targetAbsoluteError); + } + } + + /// + /// Approximation of the definite integral of an analytic smooth complex function by double-exponential quadrature. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts. + /// Where the interval stops. + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Approximation of the finite integral in the given interval. + public static Complex GaussLegendre(Func f, double intervalBegin, double intervalEnd, int order = 128) + { + // Reference: + // Formula used for variable subsitution from + // 1. Shampine, L. F. (2008). Vectorized adaptive quadrature in MATLAB. Journal of Computational and Applied Mathematics, 211(2), 131-140. + // 2. quadgk.m, GNU Octave + + if (intervalBegin > intervalEnd) + { + return -GaussLegendre(f, intervalEnd, intervalBegin, order); + } + + // (-oo, oo) => [-1, 1] + // + // integral_(-oo)^(oo) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = t / (1 - t^2) + // g'(t) = (1 + t^2) / (1 - t^2)^2 + if (double.IsInfinity(intervalBegin) && double.IsInfinity(intervalEnd)) + { + Func u = (t) => + { + return f(t / (1 - t * t)) * (1 + t * t) / ((1 - t * t) * (1 - t * t)); + }; + return GaussLegendreRule.ContourIntegrate(u, -1, 1, order); + } + // [a, oo) => [0, 1] + // + // integral_(a)^(oo) f(x) dx = integral_(0)^(oo) f(a + t^2) 2 t dt + // = integral_(0)^(1) f(a + g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 - s) + // g'(s) = 1 / (1 - s)^2 + else if (double.IsInfinity(intervalEnd)) + { + Func u = (s) => + { + return 2 * s * f(intervalBegin + (s / (1 - s)) * (s / (1 - s))) / ((1 - s) * (1 - s) * (1 - s)); + }; + return GaussLegendreRule.ContourIntegrate(u, 0, 1, order); + } + // (-oo, b] => [-1, 0] + // + // integral_(-oo)^(b) f(x) dx = -integral_(-oo)^(0) f(b - t^2) 2 t dt + // = -integral_(-1)^(0) f(b - g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 + s) + // g'(s) = 1 / (1 + s)^2 + else if (double.IsInfinity(intervalBegin)) + { + Func u = (s) => + { + return -2 * s * f(intervalEnd - s / (1 + s) * (s / (1 + s))) / ((1 + s) * (1 + s) * (1 + s)); + }; + return GaussLegendreRule.ContourIntegrate(u, -1, 0, order); + } + // [a, b] => [-1, 1] + // + // integral_(a)^(b) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = (b - a) * t * (3 - t^2) / 4 + (b + a) / 2 + // g'(t) = 3 / 4 * (b - a) * (1 - t^2) + else + { + Func u = (t) => + { + return f((intervalEnd - intervalBegin) / 4 * t * (3 - t * t) + (intervalEnd + intervalBegin) / 2) * 3 * (intervalEnd - intervalBegin) / 4 * (1 - t * t); + }; + return GaussLegendreRule.ContourIntegrate(u, -1, 1, order); + } + } + + /// + /// Approximation of the definite integral of an analytic smooth function by Gauss-Kronrod quadrature. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts. + /// Where the interval stops. + /// The expected relative accuracy of the approximation. + /// The maximum number of interval splittings permitted before stopping + /// The number of Gauss-Kronrod points. Pre-computed for 15, 21, 31, 41, 51 and 61 points + /// Approximation of the finite integral in the given interval. + public static Complex GaussKronrod(Func f, double intervalBegin, double intervalEnd, double targetRelativeError = 1E-8, int maximumDepth = 15, int order = 15) + { + return GaussKronrodRule.ContourIntegrate(f, intervalBegin, intervalEnd, out _, out _, targetRelativeError: targetRelativeError, maximumDepth: maximumDepth, order: order); + } + + /// + /// Approximation of the definite integral of an analytic smooth function by Gauss-Kronrod quadrature. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts. + /// Where the interval stops. + /// The difference between the (N-1)/2 point Gauss approximation and the N-point Gauss-Kronrod approximation + /// The L1 norm of the result, if there is a significant difference between this and the returned value, then the result is likely to be ill-conditioned. + /// The expected relative accuracy of the approximation. + /// The maximum number of interval splittings permitted before stopping + /// The number of Gauss-Kronrod points. Pre-computed for 15, 21, 31, 41, 51 and 61 points + /// Approximation of the finite integral in the given interval. + public static Complex GaussKronrod(Func f, double intervalBegin, double intervalEnd, out double error, out double L1Norm, double targetRelativeError = 1E-8, int maximumDepth = 15, int order = 15) + { + return GaussKronrodRule.ContourIntegrate(f, intervalBegin, intervalEnd, out error, out L1Norm, targetRelativeError: targetRelativeError, maximumDepth: maximumDepth, order: order); + } +} diff --git a/MathNet.Numerics/Integration/DoubleExponentialTransformation.cs b/MathNet.Numerics/Integration/DoubleExponentialTransformation.cs new file mode 100644 index 0000000..ff49bb2 --- /dev/null +++ b/MathNet.Numerics/Integration/DoubleExponentialTransformation.cs @@ -0,0 +1,591 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Linq; +using System.Numerics; + +namespace MathNet.Numerics.Integration; + +/// +/// Analytic integration algorithm for smooth functions with no discontinuities +/// or derivative discontinuities and no poles inside the interval. +/// +public static class DoubleExponentialTransformation +{ + /// + /// Maximum number of iterations, until the asked + /// maximum error is (likely to be) satisfied. + /// + const int NumberOfMaximumLevels = 10; + + /// + /// Approximate the integral by the double exponential transformation + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static double Integrate(Func f, double intervalBegin, double intervalEnd, double targetRelativeError) + { + return NewtonCotesTrapeziumRule.IntegrateAdaptiveTransformedOdd( + f, + intervalBegin, intervalEnd, + Enumerable.Range(0, NumberOfMaximumLevels).Select(EvaluateAbcissas), + Enumerable.Range(0, NumberOfMaximumLevels).Select(EvaluateWeights), + 1.0, + targetRelativeError); + } + + /// + /// Approximate the integral by the double exponential transformation + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrate(Func f, double intervalBegin, double intervalEnd, double targetRelativeError) + { + return NewtonCotesTrapeziumRule.ContourIntegrateAdaptiveTransformedOdd( + f, + intervalBegin, intervalEnd, + Enumerable.Range(0, NumberOfMaximumLevels).Select(EvaluateAbcissas), + Enumerable.Range(0, NumberOfMaximumLevels).Select(EvaluateWeights), + 1.0, + targetRelativeError); + } + + /// + /// Compute the abscissa vector for a single level. + /// + /// The level to evaluate the abscissa vector for. + /// Abscissa Vector. + static double[] EvaluateAbcissas(int level) + { + if (level < PrecomputedAbscissas.Length) + { + return PrecomputedAbscissas[level]; + } + + double step = level <= 1 ? 1.0 : (1.0 / (2 << (level - 2))); + double offset = level == 0 ? 0.0 : (1.0 / (2 << (level - 1))); + int length = level == 0 ? 4 : (3 << (level - 1)); + + double t = 0; + var abcissas = new double[length]; + for (int i = 0; i < abcissas.Length; i++) + { + double arg = offset + t; + t += step; + + abcissas[i] = Math.Tanh(Constants.PiOver2 * Math.Sinh(arg)); + } + + return abcissas; + } + + /// + /// Compute the weight vector for a single level. + /// + /// The level to evaluate the weight vector for. + /// Weight Vector. + static double[] EvaluateWeights(int level) + { + if (level < PrecomputedWeights.Length) + { + return PrecomputedWeights[level]; + } + + double step = level <= 1 ? 1.0 : (1.0 / (2 << (level - 2))); + double offset = level == 0 ? 0.0 : (1.0 / (2 << (level - 1))); + int length = level == 0 ? 4 : (3 << (level - 1)); + + double t = 0; + var weights = new double[length]; + for (int i = 0; i < weights.Length; i++) + { + double arg = offset + t; + t += step; + + // TODO: reuse abscissas as computed in EvaluateAbcissas + double abcissa = Math.Tanh(Constants.PiOver2 * Math.Sinh(arg)); + weights[i] = Constants.PiOver2 * (1 - (abcissa * abcissa)) * Math.Cosh(arg); + } + + return weights; + } + + #region Precomputed Abscissas and Weights + + /// + /// Precomputed abscissa vector per level. + /// + static readonly double[][] PrecomputedAbscissas = + { + new[] + { + 0.00000000000000000000, + 0.95136796407274694574, + 0.99997747719246159286, + 0.99999999999995705839 + }, + new[] + { + 0.67427149224843582608, + 0.99751485645722438683, + 0.99999998887566488198 + }, + new[] + { + 0.37720973816403417379, + 0.85956905868989663517, + 0.98704056050737689169, + 0.99968826402835320905, + 0.99999920473711471266, + 0.99999999995285644818 + }, + new[] + { + 0.19435700332493543161, + 0.53914670538796776905, + 0.78060743898320029925, + 0.91487926326457461091, + 0.97396686819567744856, + 0.99405550663140214329, + 0.99906519645578584642, + 0.99990938469514399984, + 0.99999531604122052843, + 0.99999989278161241838, + 0.99999999914270509218, + 0.99999999999823216531 + }, + new[] + { + 0.097923885287832333262, + 0.28787993274271591456, + 0.46125354393958570440, + 0.61027365750063894488, + 0.73101803479256151149, + 0.82331700550640237006, + 0.88989140278426019808, + 0.93516085752198468323, + 0.96411216422354729193, + 0.98145482667733517003, + 0.99112699244169880223, + 0.99610866543750854254, + 0.99845420876769773751, + 0.99945143443527460584, + 0.99982882207287494166, + 0.99995387100562796075, + 0.99998948201481850361, + 0.99999801714059543208, + 0.99999969889415261122, + 0.99999996423908091534, + 0.99999999678719909830, + 0.99999999978973286224, + 0.99999999999039393352, + 0.99999999999970809734 + }, + new[] + { + 0.049055967305077886315, + 0.14641798429058794053, + 0.24156631953888365838, + 0.33314226457763809244, + 0.41995211127844715849, + 0.50101338937930910152, + 0.57558449063515165995, + 0.64317675898520470128, + 0.70355000514714201566, + 0.75669390863372994941, + 0.80279874134324126576, + 0.84221924635075686382, + 0.87543539763040867837, + 0.90301328151357387064, + 0.92556863406861266645, + 0.94373478605275715685, + 0.95813602271021369012, + 0.96936673289691733517, + 0.97797623518666497298, + 0.98445883116743083087, + 0.98924843109013389601, + 0.99271699719682728538, + 0.99517602615532735426, + 0.99688031812819187372, + 0.99803333631543375402, + 0.99879353429880589929, + 0.99928111192179195541, + 0.99958475035151758732, + 0.99976797159956083506, + 0.99987486504878034648, + 0.99993501992508242369, + 0.99996759306794345976, + 0.99998451990227082442, + 0.99999293787666288565, + 0.99999693244919035751, + 0.99999873547186590954, + 0.99999950700571943689, + 0.99999981889371276701, + 0.99999993755407837378, + 0.99999997987450320175, + 0.99999999396413420165, + 0.99999999832336194826, + 0.99999999957078777261, + 0.99999999989927772326, + 0.99999999997845533741, + 0.99999999999582460688, + 0.99999999999927152627, + 0.99999999999988636130 + }, + new[] + { + 0.024539763574649160379, + 0.073525122985671294475, + 0.12222912220155764235, + 0.17046797238201051811, + 0.21806347346971200463, + 0.26484507658344795046, + 0.31065178055284596083, + 0.35533382516507453330, + 0.39875415046723775644, + 0.44078959903390086627, + 0.48133184611690504422, + 0.52028805069123015958, + 0.55758122826077823080, + 0.59315035359195315880, + 0.62695020805104287950, + 0.65895099174335012438, + 0.68913772506166767176, + 0.71750946748732412721, + 0.74407838354734739913, + 0.76886868676824658459, + 0.79191549237614211447, + 0.81326360850297385168, + 0.83296629391941087564, + 0.85108400798784873261, + 0.86768317577564598669, + 0.88283498824466895513, + 0.89661425428007602579, + 0.90909831816302043511, + 0.92036605303195280235, + 0.93049693799715340631, + 0.93957022393327475539, + 0.94766419061515309734, + 0.95485549580502268541, + 0.96121861515111640753, + 0.96682537031235585284, + 0.97174454156548730892, + 0.97604156025657673933, + 0.97977827580061576265, + 0.98301279148110110558, + 0.98579936302528343597, + 0.98818835380074264243, + 0.99022624046752774694, + 0.99195566300267761562, + 0.99341551316926403900, + 0.99464105571251119672, + 0.99566407681695316965, + 0.99651305464025377317, + 0.99721334704346870224, + 0.99778739195890653083, + 0.99825491617199629344, + 0.99863314864067747762, + 0.99893703483351217373, + 0.99917944893488591716, + 0.99937140114093768690, + 0.99952223765121720422, + 0.99963983134560036519, + 0.99973076151980848263, + 0.99980048143113838630, + 0.99985347277311141171, + 0.99989338654759256426, + 0.99992317012928932869, + 0.99994518061445869309, + 0.99996128480785666613, + 0.99997294642523223656, + 0.99998130127012072679, + 0.99998722128200062811, + 0.99999136844834487344, + 0.99999423962761663478, + 0.99999620334716617675, + 0.99999752962380516793, + 0.99999841381096473542, + 0.99999899541068996962, + 0.99999937270733536947, + 0.99999961398855024275, + 0.99999976602333243312, + 0.99999986037121459941, + 0.99999991800479471056, + 0.99999995264266446185, + 0.99999997311323594362, + 0.99999998500307631173, + 0.99999999178645609907, + 0.99999999558563361584, + 0.99999999767323673790, + 0.99999999879798350040, + 0.99999999939177687583, + 0.99999999969875436925, + 0.99999999985405611550, + 0.99999999993088839501, + 0.99999999996803321674, + 0.99999999998556879008, + 0.99999999999364632387, + 0.99999999999727404948, + 0.99999999999886126543, + 0.99999999999953722654, + 0.99999999999981720098, + 0.99999999999992987953 + } + }; + + /// + /// Precomputed weight vector per level. + /// + static readonly double[][] PrecomputedWeights = + { + new[] + { + 1.5707963267948966192, + 0.230022394514788685, + 0.00026620051375271690866, + 1.3581784274539090834e-12 + }, + new[] + { + 0.96597657941230114801, + 0.018343166989927842087, + 2.1431204556943039358e-7 + }, + new[] + { + 1.3896147592472563229, + 0.53107827542805397476, + 0.076385743570832304188, + 0.0029025177479013135936, + 0.000011983701363170720047, + 1.1631165814255782766e-9 + }, + new[] + { + 1.5232837186347052132, + 1.1934630258491569639, + 0.73743784836154784136, + 0.36046141846934367417, + 0.13742210773316772341, + 0.039175005493600779072, + 0.0077426010260642407123, + 0.00094994680428346871691, + 0.000062482559240744082891, + 1.8263320593710659699e-6, + 1.8687282268736410132e-8, + 4.9378538776631926964e-11 + }, + new[] + { + 1.5587733555333301451, + 1.466014426716965781, + 1.297475750424977998, + 1.0816349854900704074, + 0.85017285645662006895, + 0.63040513516474369106, + 0.44083323627385823707, + 0.290240679312454185, + 0.17932441211072829296, + 0.10343215422333290062, + 0.055289683742240583845, + 0.027133510013712003219, + 0.012083543599157953493, + 0.0048162981439284630173, + 0.0016908739981426396472, + 0.00051339382406790336017, + 0.00013205234125609974879, + 0.000028110164327940134749, + 4.8237182032615502124e-6, + 6.4777566035929719908e-7, + 6.5835185127183396672e-8, + 4.8760060974240625869e-9, + 2.5216347918530148572e-10, + 8.6759314149796046502e-12 + }, + new[] + { + 1.5677814313072218572, + 1.5438811161769592204, + 1.4972262225410362896, + 1.4300083548722996676, + 1.3452788847662516615, + 1.2467012074518577048, + 1.1382722433763053734, + 1.0240449331118114483, + 0.90787937915489531693, + 0.79324270082051671787, + 0.68306851634426375464, + 0.57967810308778764708, + 0.48475809121475539287, + 0.39938474152571713515, + 0.32408253961152890402, + 0.258904639514053516, + 0.20352399885860174519, + 0.15732620348436615027, + 0.11949741128869592428, + 0.089103139240941462841, + 0.065155533432536205042, + 0.046668208054846613644, + 0.032698732726609031113, + 0.022379471063648476483, + 0.014937835096050129696, + 0.0097072237393916892692, + 0.0061300376320830301252, + 0.0037542509774318343023, + 0.0022250827064786427022, + 0.0012733279447082382027, + 0.0007018595156842422708, + 0.00037166693621677760301, + 0.00018856442976700318572, + 0.000091390817490710122732, + 0.000042183183841757600604, + 0.000018481813599879217116, + 7.6595758525203162562e-6, + 2.9916615878138787094e-6, + 1.0968835125901264732e-6, + 3.7595411862360630091e-7, + 1.1992442782902770219e-7, + 3.5434777171421953043e-8, + 9.6498888961089633609e-9, + 2.4091773256475940779e-9, + 5.482835779709497755e-10, + 1.1306055347494680536e-10, + 2.0989335404511469109e-11, + 3.4841937670261059685e-12 + }, + new[] + { + 1.5700420292795931467, + 1.5640214037732320999, + 1.5520531698454121192, + 1.5342817381543034316, + 1.5109197230741697127, + 1.48224329788553807, + 1.4485862549613225916, + 1.4103329714462590129, + 1.3679105116808964881, + 1.3217801174437728579, + 1.2724283455378627082, + 1.2203581095793582207, + 1.1660798699324345766, + 1.1101031939653403796, + 1.0529288799552666556, + 0.99504180404613271514, + 0.93690461274566793366, + 0.87895234555278212039, + 0.82158803526696470334, + 0.7651792989089561367, + 0.71005590120546898385, + 0.65650824613162753076, + 0.60478673057840362158, + 0.55510187800363350959, + 0.5076251588319080997, + 0.4624903980553677613, + 0.41979566844501548066, + 0.37960556938665160999, + 0.3419537959230168323, + 0.30684590941791694932, + 0.27426222968906810637, + 0.24416077786983990868, + 0.21648020911729617038, + 0.19114268413342749532, + 0.16805663794826916233, + 0.14711941325785693248, + 0.12821973363120098675, + 0.11123999898874453035, + 0.096058391865189467849, + 0.082550788110701737654, + 0.070592469906866999352, + 0.060059642358636300319, + 0.05083075757257047107, + 0.042787652157725676034, + 0.035816505604196436523, + 0.029808628117310126969, + 0.024661087314753282511, + 0.020277183817500123926, + 0.016566786254247575375, + 0.013446536605285730674, + 0.010839937168255907211, + 0.0086773307495391815854, + 0.0068957859690660035329, + 0.0054388997976239984331, + 0.0042565295990178580165, + 0.0033044669940348302363, + 0.0025440657675291729678, + 0.0019418357759843675814, + 0.0014690143599429791058, + 0.0011011261134519383862, + 0.00081754101332469493115, + 0.00060103987991147422573, + 0.00043739495615911687786, + 0.00031497209186021200274, + 0.00022435965205008549104, + 0.00015802788400701191949, + 0.00011002112846666697224, + 0.000075683996586201477788, + 0.000051421497447658802092, + 0.0000344921247593431977, + 0.000022832118109036146591, + 0.000014908514031870608449, + 9.5981941283784710776e-6, + 6.0899100320949039256e-6, + 3.8061983264644899045e-6, + 2.3421667208528096843e-6, + 1.4183067155493917523e-6, + 8.4473756384859863469e-7, + 4.9458288702754198508e-7, + 2.8449923659159806339e-7, + 1.6069394579076224911e-7, + 8.9071395140242387124e-8, + 4.8420950198072369669e-8, + 2.579956822953589238e-8, + 1.3464645522302038796e-8, + 6.8784610955899001111e-9, + 3.4371856744650090511e-9, + 1.6788897682161906807e-9, + 8.0099784479729665356e-10, + 3.7299501843052790038e-10, + 1.6939457789411646876e-10, + 7.4967397573818224522e-11, + 3.230446433325236576e-11, + 1.3542512912336274432e-11, + 5.5182369468174885821e-12, + 2.1835922099233609052e-12 + } + }; + + #endregion +} diff --git a/MathNet.Numerics/Integration/GaussKronrodRule.cs b/MathNet.Numerics/Integration/GaussKronrodRule.cs new file mode 100644 index 0000000..56e912f --- /dev/null +++ b/MathNet.Numerics/Integration/GaussKronrodRule.cs @@ -0,0 +1,436 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2019 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// This file uses code from the Boost Project. +// Copyright John Maddock 2017. +// Copyright Nick Thompson 2017. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// https://github.com/boostorg/math/blob/develop/include/boost/math/quadrature/gauss_kronrod.hpp + +using MathNet.Numerics.Integration.GaussRule; + +using System; +using System.Numerics; + +namespace MathNet.Numerics.Integration; + +public class GaussKronrodRule +{ + private readonly GaussPointPair gaussKronrodPoint; + + /// + /// Getter for the order. + /// + public int Order + { + get + { + return gaussKronrodPoint.Order; + } + } + + /// + /// Getter that returns a clone of the array containing the Kronrod abscissas. + /// + public double[] KronrodAbscissas + { + get + { + return gaussKronrodPoint.Abscissas.Clone() as double[]; + } + } + + /// + /// Getter that returns a clone of the array containing the Kronrod weights. + /// + public double[] KronrodWeights + { + get + { + return gaussKronrodPoint.Weights.Clone() as double[]; + } + } + + /// + /// Getter that returns a clone of the array containing the Gauss weights. + /// + public double[] GaussWeights + { + get + { + return gaussKronrodPoint.SecondWeights.Clone() as double[]; + } + } + + public GaussKronrodRule(int order) + { + gaussKronrodPoint = GaussKronrodPointFactory.GetGaussPoint(order); + } + + /// + /// Performs adaptive Gauss-Kronrod quadrature on function f over the range (a,b) + /// + /// The analytic smooth function to integrate + /// Where the interval starts + /// Where the interval stops + /// The difference between the (N-1)/2 point Gauss approximation and the N-point Gauss-Kronrod approximation + /// The L1 norm of the result, if there is a significant difference between this and the returned value, then the result is likely to be ill-conditioned. + /// The maximum relative error in the result + /// The maximum number of interval splittings permitted before stopping + /// The number of Gauss-Kronrod points. Pre-computed for 15, 21, 31, 41, 51 and 61 points + public static double Integrate(Func f, double intervalBegin, double intervalEnd, out double error, out double L1Norm, double targetRelativeError = 1E-10, int maximumDepth = 15, int order = 15) + { + // Formula used for variable subsitution from + // 1. Shampine, L. F. (2008). Vectorized adaptive quadrature in MATLAB. Journal of Computational and Applied Mathematics, 211(2), 131-140. + // 2. quadgk.m, GNU Octave + + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (intervalBegin > intervalEnd) + { + return -Integrate(f, intervalEnd, intervalBegin, out error, out L1Norm, targetRelativeError, maximumDepth, order); + } + + GaussPointPair gaussKronrodPoint = GaussKronrodPointFactory.GetGaussPoint(order); + + // (-oo, oo) => [-1, 1] + // + // integral_(-oo)^(oo) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = t / (1 - t^2) + // g'(t) = (1 + t^2) / (1 - t^2)^2 + if ((intervalBegin < double.MinValue) && (intervalEnd > double.MaxValue)) + { + Func u = (t) => + { + return f(t / (1 - t * t)) * (1 + t * t) / ((1 - t * t) * (1 - t * t)); + }; + return recursive_adaptive_integrate(u, -1, 1, maximumDepth, targetRelativeError, 0, out error, out L1Norm, gaussKronrodPoint); + } + // [a, oo) => [0, 1] + // + // integral_(a)^(oo) f(x) dx = integral_(0)^(oo) f(a + t^2) 2 t dt + // = integral_(0)^(1) f(a + g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 - s) + // g'(s) = 1 / (1 - s)^2 + else if (intervalEnd > double.MaxValue) + { + Func u = (s) => + { + return 2 * s * f(intervalBegin + (s / (1 - s)) * (s / (1 - s))) / ((1 - s) * (1 - s) * (1 - s)); + }; + return recursive_adaptive_integrate(u, 0, 1, maximumDepth, targetRelativeError, 0, out error, out L1Norm, gaussKronrodPoint); + } + // (-oo, b] => [-1, 0] + // + // integral_(-oo)^(b) f(x) dx = -integral_(-oo)^(0) f(b - t^2) 2 t dt + // = -integral_(-1)^(0) f(b - g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 + s) + // g'(s) = 1 / (1 + s)^2 + else if (intervalBegin < double.MinValue) + { + Func u = (s) => + { + return -2 * s * f(intervalEnd - s / (1 + s) * (s / (1 + s))) / ((1 + s) * (1 + s) * (1 + s)); + }; + return recursive_adaptive_integrate(u, -1, 0, maximumDepth, targetRelativeError, 0, out error, out L1Norm, gaussKronrodPoint); + } + // [a, b] => [-1, 1] + // + // integral_(a)^(b) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = (b - a) * t * (3 - t^2) / 4 + (b + a) / 2 + // g'(t) = 3 / 4 * (b - a) * (1 - t^2) + else + { + Func u = (t) => + { + return f((intervalEnd - intervalBegin) / 4 * t * (3 - t * t) + (intervalEnd + intervalBegin) / 2) * 3 * (intervalEnd - intervalBegin) / 4 * (1 - t * t); + }; + return recursive_adaptive_integrate(u, -1, 1, maximumDepth, targetRelativeError, 0d, out error, out L1Norm, gaussKronrodPoint); + } + } + + /// + /// Performs adaptive Gauss-Kronrod quadrature on function f over the range (a,b) + /// + /// The analytic smooth complex function to integrate, defined on the real axis. + /// Where the interval starts + /// Where the interval stops + /// The difference between the (N-1)/2 point Gauss approximation and the N-point Gauss-Kronrod approximation + /// The L1 norm of the result, if there is a significant difference between this and the returned value, then the result is likely to be ill-conditioned. + /// The maximum relative error in the result + /// The maximum number of interval splittings permitted before stopping + /// The number of Gauss-Kronrod points. Pre-computed for 15, 21, 31, 41, 51 and 61 points + /// + public static Complex ContourIntegrate(Func f, double intervalBegin, double intervalEnd, out double error, out double L1Norm, double targetRelativeError = 1E-10, int maximumDepth = 15, int order = 15) + { + // Formula used for variable subsitution from + // 1. Shampine, L. F. (2008). Vectorized adaptive quadrature in MATLAB. Journal of Computational and Applied Mathematics, 211(2), 131-140. + // 2. quadgk.m, GNU Octave + + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (intervalBegin > intervalEnd) + { + return -ContourIntegrate(f, intervalEnd, intervalBegin, out error, out L1Norm, targetRelativeError, maximumDepth, order); + } + + GaussPointPair gaussKronrodPoint = GaussKronrodPointFactory.GetGaussPoint(order); + + // (-oo, oo) => [-1, 1] + // + // integral_(-oo)^(oo) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = t / (1 - t^2) + // g'(t) = (1 + t^2) / (1 - t^2)^2 + if ((intervalBegin < double.MinValue) && (intervalEnd > double.MaxValue)) + { + Func u = (t) => + { + return f(t / (1 - t * t)) * (1 + t * t) / ((1 - t * t) * (1 - t * t)); + }; + return contour_recursive_adaptive_integrate(u, -1, 1, maximumDepth, targetRelativeError, 0, out error, out L1Norm, gaussKronrodPoint); + } + // [a, oo) => [0, 1] + // + // integral_(a)^(oo) f(x) dx = integral_(0)^(oo) f(a + t^2) 2 t dt + // = integral_(0)^(1) f(a + g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 - s) + // g'(s) = 1 / (1 - s)^2 + else if (intervalEnd > double.MaxValue) + { + Func u = (s) => + { + return 2 * s * f(intervalBegin + (s / (1 - s)) * (s / (1 - s))) / ((1 - s) * (1 - s) * (1 - s)); + }; + return contour_recursive_adaptive_integrate(u, 0, 1, maximumDepth, targetRelativeError, 0, out error, out L1Norm, gaussKronrodPoint); + } + // (-oo, b] => [-1, 0] + // + // integral_(-oo)^(b) f(x) dx = -integral_(-oo)^(0) f(b - t^2) 2 t dt + // = -integral_(-1)^(0) f(b - g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 + s) + // g'(s) = 1 / (1 + s)^2 + else if (intervalBegin < double.MinValue) + { + Func u = (s) => + { + return -2 * s * f(intervalEnd - s / (1 + s) * (s / (1 + s))) / ((1 + s) * (1 + s) * (1 + s)); + }; + return contour_recursive_adaptive_integrate(u, -1, 0, maximumDepth, targetRelativeError, 0, out error, out L1Norm, gaussKronrodPoint); + } + // [a, b] => [-1, 1] + // + // integral_(a)^(b) f(x) dx = integral_(-1)^(1) f(g(t)) g'(t) dt + // g(t) = (b - a) * t * (3 - t^2) / 4 + (b + a) / 2 + // g'(t) = 3 / 4 * (b - a) * (1 - t^2) + else + { + Func u = (t) => + { + return f((intervalEnd - intervalBegin) / 4 * t * (3 - t * t) + (intervalEnd + intervalBegin) / 2) * 3 * (intervalEnd - intervalBegin) / 4 * (1 - t * t); + }; + return contour_recursive_adaptive_integrate(u, -1, 1, maximumDepth, targetRelativeError, 0d, out error, out L1Norm, gaussKronrodPoint); + } + } + + private static double integrate_non_adaptive_m1_1(Func f, out double error, out double pL1, GaussPointPair gaussKronrodPoint) + { + int gauss_start = 2; + int kronrod_start = 1; + int gauss_order = (gaussKronrodPoint.Order - 1) / 2; + + double kronrod_result = 0d; + double gauss_result = 0d; + double fp, fm; + + var KAbscissa = gaussKronrodPoint.Abscissas; + var KWeights = gaussKronrodPoint.Weights; + var GWeights = gaussKronrodPoint.SecondWeights; + + if ((gauss_order & 1) == 1) + { + fp = f(0); + kronrod_result = fp * KWeights[0]; + gauss_result += fp * GWeights[0]; + } + else + { + fp = f(0); + kronrod_result = fp * KWeights[0]; + gauss_start = 1; + kronrod_start = 2; + } + + double L1 = Math.Abs(kronrod_result); + + for (int i = gauss_start; i < KAbscissa.Length; i += 2) + { + fp = f(KAbscissa[i]); + fm = f(-KAbscissa[i]); + kronrod_result += (fp + fm) * KWeights[i]; + L1 += (Math.Abs(fp) + Math.Abs(fm)) * KWeights[i]; + gauss_result += (fp + fm) * GWeights[i / 2]; + } + + for (int i = kronrod_start; i < KAbscissa.Length; i += 2) + { + fp = f(KAbscissa[i]); + fm = f(-KAbscissa[i]); + kronrod_result += (fp + fm) * KWeights[i]; + L1 += (Math.Abs(fp) + Math.Abs(fm)) * KWeights[i]; + } + + pL1 = L1; + error = Math.Max(Math.Abs(kronrod_result - gauss_result), Math.Abs(kronrod_result * Precision.MachineEpsilon * 2d)); + return kronrod_result; + } + + private static Complex contour_integrate_non_adaptive_m1_1(Func f, out double error, out double pL1, GaussPointPair gaussKronrodPoint) + { + int gauss_start = 2; + int kronrod_start = 1; + int gauss_order = (gaussKronrodPoint.Order - 1) / 2; + + Complex kronrod_result = new Complex(); + Complex gauss_result = new Complex(); + Complex fp, fm; + + var KAbscissa = gaussKronrodPoint.Abscissas; + var KWeights = gaussKronrodPoint.Weights; + var GWeights = gaussKronrodPoint.SecondWeights; + + if (gauss_order.IsOdd()) + { + fp = f(0); + kronrod_result = fp * KWeights[0]; + gauss_result += fp * GWeights[0]; + } + else + { + fp = f(0); + kronrod_result = fp * KWeights[0]; + gauss_start = 1; + kronrod_start = 2; + } + + double L1 = Complex.Abs(kronrod_result); + + for (int i = gauss_start; i < KAbscissa.Length; i += 2) + { + fp = f(KAbscissa[i]); + fm = f(-KAbscissa[i]); + kronrod_result += (fp + fm) * KWeights[i]; + L1 += (Complex.Abs(fp) + Complex.Abs(fm)) * KWeights[i]; + gauss_result += (fp + fm) * GWeights[i / 2]; + } + + for (int i = kronrod_start; i < KAbscissa.Length; i += 2) + { + fp = f(KAbscissa[i]); + fm = f(-KAbscissa[i]); + kronrod_result += (fp + fm) * KWeights[i]; + L1 += (Complex.Abs(fp) + Complex.Abs(fm)) * KWeights[i]; + } + + pL1 = L1; + error = Math.Max(Complex.Abs(kronrod_result - gauss_result), Complex.Abs(kronrod_result * Precision.MachineEpsilon * 2d)); + return kronrod_result; + } + + private static double recursive_adaptive_integrate(Func f, double a, double b, int max_levels, double rel_tol, double abs_tol, out double error, out double L1, GaussPointPair gaussKronrodPoint) + { + double error_local; + double mean = (b + a) / 2; + double scale = (b - a) / 2; + + var r1 = integrate_non_adaptive_m1_1((x) => f(scale * x + mean), out error_local, out L1, gaussKronrodPoint); + var estimate = scale * r1; + + var tmp = estimate * rel_tol; + var abs_tol1 = Math.Abs(tmp); + if (abs_tol == 0) + { + abs_tol = abs_tol1; + } + + if (max_levels > 0 && (abs_tol1 < error_local) && (abs_tol < error_local)) + { + double mid = (a + b) / 2d; + double L1_local; + estimate = recursive_adaptive_integrate(f, a, mid, max_levels - 1, rel_tol, abs_tol / 2, out error, out L1, gaussKronrodPoint); + estimate += recursive_adaptive_integrate(f, mid, b, max_levels - 1, rel_tol, abs_tol / 2, out error_local, out L1_local, gaussKronrodPoint); + error += error_local; + L1 += L1_local; + return estimate; + } + + L1 *= scale; + error = error_local; + return estimate; + } + + private static Complex contour_recursive_adaptive_integrate(Func f, double a, double b, int max_levels, double rel_tol, double abs_tol, out double error, out double L1, GaussPointPair gaussKronrodPoint) + { + double error_local; + double mean = (b + a) / 2; + double scale = (b - a) / 2; + + var r1 = contour_integrate_non_adaptive_m1_1((x) => f(scale * x + mean), out error_local, out L1, gaussKronrodPoint); + var estimate = scale * r1; + + var tmp = estimate * rel_tol; + var abs_tol1 = Complex.Abs(tmp); + if (abs_tol == 0) + { + abs_tol = abs_tol1; + } + + if (max_levels > 0 && (abs_tol1 < error_local) && (abs_tol < error_local)) + { + double mid = (a + b) / 2d; + double L1_local; + estimate = contour_recursive_adaptive_integrate(f, a, mid, max_levels - 1, rel_tol, abs_tol / 2, out error, out L1, gaussKronrodPoint); + estimate += contour_recursive_adaptive_integrate(f, mid, b, max_levels - 1, rel_tol, abs_tol / 2, out error_local, out L1_local, gaussKronrodPoint); + error += error_local; + L1 += L1_local; + return estimate; + } + + L1 *= scale; + error = error_local; + return estimate; + } +} diff --git a/MathNet.Numerics/Integration/GaussLegendreRule.cs b/MathNet.Numerics/Integration/GaussLegendreRule.cs new file mode 100644 index 0000000..1c53286 --- /dev/null +++ b/MathNet.Numerics/Integration/GaussLegendreRule.cs @@ -0,0 +1,283 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Integration.GaussRule; + +using System; +using System.Numerics; + +namespace MathNet.Numerics.Integration; + +/// +/// Approximates a definite integral using an Nth order Gauss-Legendre rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. +/// +public class GaussLegendreRule +{ + private readonly GaussPoint _gaussLegendrePoint; + + /// + /// Initializes a new instance of the class. + /// + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + public GaussLegendreRule(double intervalBegin, double intervalEnd, int order) + { + _gaussLegendrePoint = GaussLegendrePointFactory.GetGaussPoint(intervalBegin, intervalEnd, order); + } + + /// + /// Gettter for the ith abscissa. + /// + /// Index of the ith abscissa. + /// The ith abscissa. + public double GetAbscissa(int index) + { + return _gaussLegendrePoint.Abscissas[index]; + } + + /// + /// Getter that returns a clone of the array containing the abscissas. + /// + public double[] Abscissas + { + get + { + return _gaussLegendrePoint.Abscissas.Clone() as double[]; + } + } + + /// + /// Getter for the ith weight. + /// + /// Index of the ith weight. + /// The ith weight. + public double GetWeight(int index) + { + return _gaussLegendrePoint.Weights[index]; + } + + /// + /// Getter that returns a clone of the array containing the weights. + /// + public double[] Weights + { + get + { + return _gaussLegendrePoint.Weights.Clone() as double[]; + } + } + + /// + /// Getter for the order. + /// + public int Order + { + get + { + return _gaussLegendrePoint.Order; + } + } + + /// + /// Getter for the InvervalBegin. + /// + public double IntervalBegin + { + get + { + return _gaussLegendrePoint.IntervalBegin; + } + } + + /// + /// Getter for the InvervalEnd. + /// + public double IntervalEnd + { + get + { + return _gaussLegendrePoint.IntervalEnd; + } + } + + /// + /// Approximates a definite integral using an Nth order Gauss-Legendre rule. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, exclusive and finite. + /// Where the interval ends, exclusive and finite. + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Approximation of the finite integral in the given interval. + public static double Integrate(Func f, double invervalBegin, double invervalEnd, int order) + { + GaussPoint gaussLegendrePoint = GaussLegendrePointFactory.GetGaussPoint(order); + + double sum, ax; + int i; + int m = (order + 1) >> 1; + + double a = 0.5 * (invervalEnd - invervalBegin); + double b = 0.5 * (invervalEnd + invervalBegin); + + if (order.IsOdd()) + { + sum = gaussLegendrePoint.Weights[0] * f(b); + for (i = 1; i < m; i++) + { + ax = a * gaussLegendrePoint.Abscissas[i]; + sum += gaussLegendrePoint.Weights[i] * (f(b + ax) + f(b - ax)); + } + } + else + { + sum = 0.0; + for (i = 0; i < m; i++) + { + ax = a * gaussLegendrePoint.Abscissas[i]; + sum += gaussLegendrePoint.Weights[i] * (f(b + ax) + f(b - ax)); + } + } + + return a * sum; + } + + /// + /// Approximates a definite integral using an Nth order Gauss-Legendre rule. + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts, exclusive and finite. + /// Where the interval ends, exclusive and finite. + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrate(Func f, double invervalBegin, double invervalEnd, int order) + { + GaussPoint gaussLegendrePoint = GaussLegendrePointFactory.GetGaussPoint(order); + + Complex sum; + double ax; + int i; + int m = (order + 1) >> 1; + + double a = 0.5 * (invervalEnd - invervalBegin); + double b = 0.5 * (invervalEnd + invervalBegin); + + if (order.IsOdd()) + { + sum = gaussLegendrePoint.Weights[0] * f(b); + for (i = 1; i < m; i++) + { + ax = a * gaussLegendrePoint.Abscissas[i]; + sum += gaussLegendrePoint.Weights[i] * (f(b + ax) + f(b - ax)); + } + } + else + { + sum = 0.0; + for (i = 0; i < m; i++) + { + ax = a * gaussLegendrePoint.Abscissas[i]; + sum += gaussLegendrePoint.Weights[i] * (f(b + ax) + f(b - ax)); + } + } + + return a * sum; + } + + /// + /// Approximates a 2-dimensional definite integral using an Nth order Gauss-Legendre rule over the rectangle [a,b] x [c,d]. + /// + /// The 2-dimensional analytic smooth function to integrate. + /// Where the interval starts for the first (inside) integral, exclusive and finite. + /// Where the interval ends for the first (inside) integral, exclusive and finite. + /// Where the interval starts for the second (outside) integral, exclusive and finite. + /// /// Where the interval ends for the second (outside) integral, exclusive and finite. + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Approximation of the finite integral in the given interval. + public static double Integrate(Func f, double invervalBeginA, double invervalEndA, double invervalBeginB, double invervalEndB, int order) + { + GaussPoint gaussLegendrePoint = GaussLegendrePointFactory.GetGaussPoint(order); + + double ax, cy, sum; + int i, j; + int m = (order + 1) >> 1; + + double a = 0.5 * (invervalEndA - invervalBeginA); + double b = 0.5 * (invervalEndA + invervalBeginA); + double c = 0.5 * (invervalEndB - invervalBeginB); + double d = 0.5 * (invervalEndB + invervalBeginB); + + if (order.IsOdd()) + { + sum = gaussLegendrePoint.Weights[0] * gaussLegendrePoint.Weights[0] * f(b, d); + + double t; + for (j = 1, t = 0.0; j < m; j++) + { + cy = c * gaussLegendrePoint.Abscissas[j]; + t += gaussLegendrePoint.Weights[j] * (f(b, d + cy) + f(b, d - cy)); + } + + sum += gaussLegendrePoint.Weights[0] * t; + + for (i = 1, t = 0.0; i < m; i++) + { + ax = a * gaussLegendrePoint.Abscissas[i]; + t += gaussLegendrePoint.Weights[i] * (f(b + ax, d) + f(b - ax, d)); + } + + sum += gaussLegendrePoint.Weights[0] * t; + + for (i = 1; i < m; i++) + { + ax = a * gaussLegendrePoint.Abscissas[i]; + for (j = 1; j < m; j++) + { + cy = c * gaussLegendrePoint.Abscissas[j]; + sum += gaussLegendrePoint.Weights[i] * gaussLegendrePoint.Weights[j] * (f(b + ax, d + cy) + f(ax + b, d - cy) + f(b - ax, d + cy) + f(b - ax, d - cy)); + } + } + } + else + { + sum = 0.0; + for (i = 0; i < m; i++) + { + ax = a * gaussLegendrePoint.Abscissas[i]; + for (j = 0; j < m; j++) + { + cy = c * gaussLegendrePoint.Abscissas[j]; + sum += gaussLegendrePoint.Weights[i] * gaussLegendrePoint.Weights[j] * (f(b + ax, d + cy) + f(ax + b, d - cy) + f(b - ax, d + cy) + f(b - ax, d - cy)); + } + } + } + + return c * a * sum; + } +} diff --git a/MathNet.Numerics/Integration/GaussRule/GaussKronrodPoint.cs b/MathNet.Numerics/Integration/GaussRule/GaussKronrodPoint.cs new file mode 100644 index 0000000..336314b --- /dev/null +++ b/MathNet.Numerics/Integration/GaussRule/GaussKronrodPoint.cs @@ -0,0 +1,618 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Integration.GaussRule; + +/// +/// Contains a method to compute the Gauss-Kronrod abscissas/weights and precomputed abscissas/weights for orders 15, 21, 31, 41, 51, 61. +/// +internal static partial class GaussKronrodPoint +{ + /// + /// Precomputed abscissas/weights for orders 15, 21, 31, 41, 51, 61. + /// + internal static readonly Dictionary PreComputed = new Dictionary + { + { 15, new GaussPointPair(15, + new[] // 15-point Gauss-Kronrod Abscissa + { + 0.00000000000000000e+00, + 2.07784955007898468e-01, + 4.05845151377397167e-01, + 5.86087235467691130e-01, + 7.41531185599394440e-01, + 8.64864423359769073e-01, + 9.49107912342758525e-01, + 9.91455371120812639e-01, + }, + new[] // 15-point Gauss-Kronrod Weights + { + 2.09482141084727828e-01, + 2.04432940075298892e-01, + 1.90350578064785410e-01, + 1.69004726639267903e-01, + 1.40653259715525919e-01, + 1.04790010322250184e-01, + 6.30920926299785533e-02, + 2.29353220105292250e-02, + }, 7, + new[] // 7-point Gauss Weights + { + 4.17959183673469388e-01, + 3.81830050505118945e-01, + 2.79705391489276668e-01, + 1.29484966168869693e-01, + }) + }, + { 21, new GaussPointPair(21, + new[] // 21-point Gauss-Kronrod Abscissa + { + 0.00000000000000000e+00, + 1.48874338981631211e-01, + 2.94392862701460198e-01, + 4.33395394129247191e-01, + 5.62757134668604683e-01, + 6.79409568299024406e-01, + 7.80817726586416897e-01, + 8.65063366688984511e-01, + 9.30157491355708226e-01, + 9.73906528517171720e-01, + 9.95657163025808081e-01, + }, + new[] // 21-point Gauss-Kronrod Weights + { + 1.49445554002916906e-01, + 1.47739104901338491e-01, + 1.42775938577060081e-01, + 1.34709217311473326e-01, + 1.23491976262065851e-01, + 1.09387158802297642e-01, + 9.31254545836976055e-02, + 7.50396748109199528e-02, + 5.47558965743519960e-02, + 3.25581623079647275e-02, + 1.16946388673718743e-02, + }, 10, + new[] // 10-point Gauss Weights + { + 2.95524224714752870e-01, + 2.69266719309996355e-01, + 2.19086362515982044e-01, + 1.49451349150580593e-01, + 6.66713443086881376e-02, + }) + }, + { 31, new GaussPointPair(31, + new[] // 31-point Gauss-Kronrod Abscissa + { + 0.00000000000000000e+00, + 1.01142066918717499e-01, + 2.01194093997434522e-01, + 2.99180007153168812e-01, + 3.94151347077563370e-01, + 4.85081863640239681e-01, + 5.70972172608538848e-01, + 6.50996741297416971e-01, + 7.24417731360170047e-01, + 7.90418501442465933e-01, + 8.48206583410427216e-01, + 8.97264532344081901e-01, + 9.37273392400705904e-01, + 9.67739075679139134e-01, + 9.87992518020485428e-01, + 9.98002298693397060e-01, + }, + new[] // 31-point Gauss-Kronrod Weights + { + 1.01330007014791549e-01, + 1.00769845523875595e-01, + 9.91735987217919593e-02, + 9.66427269836236785e-02, + 9.31265981708253212e-02, + 8.85644430562117706e-02, + 8.30805028231330210e-02, + 7.68496807577203789e-02, + 6.98541213187282587e-02, + 6.20095678006706403e-02, + 5.34815246909280873e-02, + 4.45897513247648766e-02, + 3.53463607913758462e-02, + 2.54608473267153202e-02, + 1.50079473293161225e-02, + 5.37747987292334899e-03, + }, 15, + new[] // 15-point Gauss Weights + { + 2.02578241925561273e-01, + 1.98431485327111576e-01, + 1.86161000015562211e-01, + 1.66269205816993934e-01, + 1.39570677926154314e-01, + 1.07159220467171935e-01, + 7.03660474881081247e-02, + 3.07532419961172684e-02, + }) + }, + { 41, new GaussPointPair(41, + new[] // 41-point Gauss-Kronrod Abscissa + { + 0.00000000000000000e+00, + 7.65265211334973338e-02, + 1.52605465240922676e-01, + 2.27785851141645078e-01, + 3.01627868114913004e-01, + 3.73706088715419561e-01, + 4.43593175238725103e-01, + 5.10867001950827098e-01, + 5.75140446819710315e-01, + 6.36053680726515025e-01, + 6.93237656334751385e-01, + 7.46331906460150793e-01, + 7.95041428837551198e-01, + 8.39116971822218823e-01, + 8.78276811252281976e-01, + 9.12234428251325906e-01, + 9.40822633831754754e-01, + 9.63971927277913791e-01, + 9.81507877450250259e-01, + 9.93128599185094925e-01, + 9.98859031588277664e-01, + }, + new[] // 41-point Gauss-Kronrod Weights + { + 7.66007119179996564e-02, + 7.63778676720807367e-02, + 7.57044976845566747e-02, + 7.45828754004991890e-02, + 7.30306903327866675e-02, + 7.10544235534440683e-02, + 6.86486729285216193e-02, + 6.58345971336184221e-02, + 6.26532375547811680e-02, + 5.91114008806395724e-02, + 5.51951053482859947e-02, + 5.09445739237286919e-02, + 4.64348218674976747e-02, + 4.16688733279736863e-02, + 3.66001697582007980e-02, + 3.12873067770327990e-02, + 2.58821336049511588e-02, + 2.03883734612665236e-02, + 1.46261692569712530e-02, + 8.60026985564294220e-03, + 3.07358371852053150e-03, + }, 20, + new[] // 20-point Gauss Weights + { + 1.52753387130725851e-01, + 1.49172986472603747e-01, + 1.42096109318382051e-01, + 1.31688638449176627e-01, + 1.18194531961518417e-01, + 1.01930119817240435e-01, + 8.32767415767047487e-02, + 6.26720483341090636e-02, + 4.06014298003869413e-02, + 1.76140071391521183e-02, + }) + }, + { 51, new GaussPointPair(51, + new[] // 51-point Gauss-Kronrod Abscissa + { + 0.00000000000000000e+00, + 6.15444830056850789e-02, + 1.22864692610710396e-01, + 1.83718939421048892e-01, + 2.43866883720988432e-01, + 3.03089538931107830e-01, + 3.61172305809387838e-01, + 4.17885382193037749e-01, + 4.73002731445714961e-01, + 5.26325284334719183e-01, + 5.77662930241222968e-01, + 6.26810099010317413e-01, + 6.73566368473468364e-01, + 7.17766406813084388e-01, + 7.59259263037357631e-01, + 7.97873797998500059e-01, + 8.33442628760834001e-01, + 8.65847065293275595e-01, + 8.94991997878275369e-01, + 9.20747115281701562e-01, + 9.42974571228974339e-01, + 9.61614986425842512e-01, + 9.76663921459517511e-01, + 9.88035794534077248e-01, + 9.95556969790498098e-01, + 9.99262104992609834e-01, + }, + new[] // 51-point Gauss-Kronrod Weights + { + 6.15808180678329351e-02, + 6.14711898714253167e-02, + 6.11285097170530483e-02, + 6.05394553760458629e-02, + 5.97203403241740600e-02, + 5.86896800223942080e-02, + 5.74371163615678329e-02, + 5.59508112204123173e-02, + 5.42511298885454901e-02, + 5.23628858064074759e-02, + 5.02776790807156720e-02, + 4.79825371388367139e-02, + 4.55029130499217889e-02, + 4.28728450201700495e-02, + 4.00838255040323821e-02, + 3.71162714834155436e-02, + 3.40021302743293378e-02, + 3.07923001673874889e-02, + 2.74753175878517378e-02, + 2.40099456069532162e-02, + 2.04353711458828355e-02, + 1.68478177091282982e-02, + 1.32362291955716748e-02, + 9.47397338617415161e-03, + 5.56193213535671376e-03, + 1.98738389233031593e-03, + }, 25, + new[] // 25-point Gauss Weights + { + 1.23176053726715451e-01, + 1.22242442990310042e-01, + 1.19455763535784772e-01, + 1.14858259145711648e-01, + 1.08519624474263653e-01, + 1.00535949067050644e-01, + 9.10282619829636498e-02, + 8.01407003350010180e-02, + 6.80383338123569172e-02, + 5.49046959758351919e-02, + 4.09391567013063127e-02, + 2.63549866150321373e-02, + 1.13937985010262879e-02, + }) + }, + { 61, new GaussPointPair(61, + new[] // 61-point Gauss-Kronrod Abscissa + { + 0.00000000000000000e+00, + 5.14718425553176958e-02, + 1.02806937966737030e-01, + 1.53869913608583547e-01, + 2.04525116682309891e-01, + 2.54636926167889846e-01, + 3.04073202273625077e-01, + 3.52704725530878113e-01, + 4.00401254830394393e-01, + 4.47033769538089177e-01, + 4.92480467861778575e-01, + 5.36624148142019899e-01, + 5.79345235826361692e-01, + 6.20526182989242861e-01, + 6.60061064126626961e-01, + 6.97850494793315797e-01, + 7.33790062453226805e-01, + 7.67777432104826195e-01, + 7.99727835821839083e-01, + 8.29565762382768397e-01, + 8.57205233546061099e-01, + 8.82560535792052682e-01, + 9.05573307699907799e-01, + 9.26200047429274326e-01, + 9.44374444748559979e-01, + 9.60021864968307512e-01, + 9.73116322501126268e-01, + 9.83668123279747210e-01, + 9.91630996870404595e-01, + 9.96893484074649540e-01, + 9.99484410050490638e-01, + }, + new[] // 61-point Gauss-Kronrod Weights + { + 5.14947294294515676e-02, + 5.14261285374590259e-02, + 5.12215478492587722e-02, + 5.08817958987496065e-02, + 5.04059214027823468e-02, + 4.97956834270742064e-02, + 4.90554345550297789e-02, + 4.81858617570871291e-02, + 4.71855465692991539e-02, + 4.60592382710069881e-02, + 4.48148001331626632e-02, + 4.34525397013560693e-02, + 4.19698102151642461e-02, + 4.03745389515359591e-02, + 3.86789456247275930e-02, + 3.68823646518212292e-02, + 3.49793380280600241e-02, + 3.29814470574837260e-02, + 3.09072575623877625e-02, + 2.87540487650412928e-02, + 2.65099548823331016e-02, + 2.41911620780806014e-02, + 2.18280358216091923e-02, + 1.94141411939423812e-02, + 1.69208891890532726e-02, + 1.43697295070458048e-02, + 1.18230152534963417e-02, + 9.27327965951776343e-03, + 6.63070391593129217e-03, + 3.89046112709988405e-03, + 1.38901369867700762e-03, + }, 30, + new[] // 30-point Gauss Weights + { + 1.02852652893558840e-01, + 1.01762389748405505e-01, + 9.95934205867952671e-02, + 9.63687371746442596e-02, + 9.21225222377861287e-02, + 8.68997872010829798e-02, + 8.07558952294202154e-02, + 7.37559747377052063e-02, + 6.59742298821804951e-02, + 5.74931562176190665e-02, + 4.84026728305940529e-02, + 3.87991925696270496e-02, + 2.87847078833233693e-02, + 1.84664683110909591e-02, + 7.96819249616660562e-03, + }) + }, + }; +} + +/// +/// Contains a method to compute the Gauss-Kronrod abscissas/weights. +/// +internal static partial class GaussKronrodPoint +{ + /// + /// Computes the Gauss-Kronrod abscissas/weights and Gauss weights. + /// + /// Defines an Nth order Gauss-Kronrod rule. The order also defines the number of abscissas and weights for the rule. + /// Required precision to compute the abscissas/weights. + /// Object containing the non-negative abscissas/weights, order. + internal static GaussPointPair Generate(int order, double eps) + { + int gaussOrder = (order - 1) / 2; + int gaussStart = gaussOrder.IsOdd() ? 0 : 1; + int kronrodStart = gaussOrder.IsOdd() ? 1 : 0; + + var gaussPoint = GaussLegendrePointFactory.GetGaussPoint(gaussOrder); + var gaussAbscissas = gaussPoint.Abscissas; + var gaussWeights = gaussPoint.Weights; + + // Calculate Kronrod polynomial in terms of Legendre polynomials + // K(x) = c0*P(0, x) + c1*P(1, x) + ... + + var c = StieltjesP(gaussOrder + 1); + + // Calculate Abscissas for Kronrod polynomial + + int r = gaussOrder.IsOdd() ? (gaussOrder - 1) / 2 + 1 : gaussOrder / 2 + 1; + var kronrodAbscissas = new double[r]; + + for (int k = 1; k <= gaussOrder + 1; k = k + 2) + { + var x0 = (1.0 - (1.0 - 1.0 / gaussOrder) / (8 * gaussOrder * gaussOrder)) * Math.Cos((k - 0.5) * Math.PI / (2.0 * gaussOrder + 1.0)); + var dx = 0d; + var j = 1; // iterations + + // Newton iterations + do + { + var E = LegendreSeries(c, x0); + dx = E.Item1 / E.Item2; + x0 = x0 - dx; + j++; + } + while (Math.Abs(dx) > eps && j < 100); + + if (Math.Abs(x0) < Precision.MachineEpsilon) + x0 = 0.0; + + kronrodAbscissas[(k - 1) / 2] = x0; + } + + // Concatenate two abscissas + + var abscissas = new double[gaussAbscissas.Length + kronrodAbscissas.Length]; + gaussAbscissas.CopyTo(abscissas, 0); + kronrodAbscissas.CopyTo(abscissas, gaussAbscissas.Length); + abscissas = abscissas.OrderBy(v => v).ToArray(); + + // Calculate weights for abscissas + + var weights = new double[gaussAbscissas.Length + kronrodAbscissas.Length]; + for (int i = gaussStart; i < abscissas.Length; i += 2) + { + var x = abscissas[i]; + + var E = LegendreSeries(c, x); + var L = LegendreP(gaussOrder, x); + + var p = L.Item2; + var w2 = 2.0 / ((1.0 - x * x) * p * p); // Gauss weight + weights[i] = w2 + 2.0 / ((gaussOrder + 1.0) * p * E.Item1); + } + + for (int i = kronrodStart; i < abscissas.Length; i += 2) + { + var x = abscissas[i]; + + var E = LegendreSeries(c, x); + var L = LegendreP(gaussOrder, x); + + weights[i] = 2.0 / ((gaussOrder + 1.0) * L.Item1 * E.Item2); + } + + return new GaussPointPair(order, abscissas, weights, gaussOrder, gaussWeights); + } + + /// + /// Returns coefficients of a Stieltjes polynomial in terms of Legendre polynomials. + /// + internal static double[] StieltjesP(int order) + { + // Reference: + // 1. Patterson, Thomas NL. "The optimum addition of points to quadrature formulae." Mathematics of Computation 22.104 (1968): 847-856. + // 2. Piessens, Robert, and Maria Branders. "A note on the optimal addition of abscissas to quadrature formulas of Gauss and Lobatto type." Mathematics of Computation (1974): 135-139. + // 3. Legendre-Stieltjes Polynomials, Boost.Math + // + // Here, we are using Patterson algorithm, expanding the Stieltjes polynomial in terms of Legendre polynomials. + // + // Kronrod Polynomial K[n + 1, x] is expanded in terms of Legendre Polynomial P[n, x]. + // + // K[n + 1, x] = sum_(n=1)^r a[i] P[2 * i - 1 - q, x] + // + // where P[n, x] is the Legendre polynomial of degree n, + // [x] denotes the integer part of x, + // q = n - 2[n/2] + // r = [(n + 3)/2] + // + // The added n + 1 Kronrod abscissae is the roots of the Kronrod polynomial. + + if (order == 1) // P(1, x) + return new double[] { 0, 1 }; + else if (order == 2) // -2/5 * P(0, x) + P(2, x) + return new double[] { -0.4, 0, 1 }; + else if (order == 3) // -9/14 * P(1, x) + P(3, x) + return new double[] { 0, -0.642857142857142857142857142857, 0, 1 }; + else if (order == 4) // 14/891 * P(0, x) - 20/27 * P(2, x) + P(4, x) + return new double[] { 0.0157126823793490460157126823793, 0, -0.740740740740740740740740740741, 0, 1 }; + else if (order == 5) // 135/12584 * P(1, x) - 35/44 * P(3, x) + P(5, x) + return new double[] { 0, 0.0107279084551811824539097266370, 0, -0.795454545454545454545454545455, 0, 1 }; + + int n = order - 1; + int q = n.IsOdd() ? 1 : 0; + int r = n.IsOdd() ? (n - 1) / 2 + 2 : n / 2 + 1; + + double[] a = new double[r + 1]; + + // Calculate a[i] for i = 1, ..., r + // + // a[r] = 1; + // a[r - 1] = -a[r] * S[r, 1] / S[r - 1, 1]; + // a[r - 2] = -a[r] * S[r, 2] / S[r - 2, 2] - a[r - 1] * S[r - 1, 2] / S[r - 2, 2]; + // ... + // a[1] = -a[r] * S[r, r - 1] / S[1, r - 1] - a[r - 1] * S[r - 1, r - 1] / S[1, r - 1] - ... - a[2] * S[2, r - 1] / S[1, r - 1]; + // + // S[i, k] / S[r - k, k] = S[i - 1, k] / S[r - k, k] + // * ((n - q + 2 * (i + k - 1)) * (n + q + 2 * (k - i + 1)) * (n - 1 - q + 2 * (i - k)) * (2 * (k + i - 1) - 1 - q - n)) + // / ((n - q + 2 * (i - k)) * (2 * (k + i - 1) - q - n) * (n + 1 + q + 2 * (k - i)) * (n - 1 - q + 2 * (i + k))); + + a[r] = 1.0; + for (int k = 1; k < r; k++) + { + double ratio = 1.0; + a[r - k] = 0.0; + for (int i = r + 1 - k; i <= r; i++) + { + double numerator = (n - q + 2 * (i + k - 1)) * (n + q + 2 * (k - i + 1)) * (n - 1 - q + 2 * (i - k)) * (2 * (k + i - 1) - 1 - q - n); + double denominator = (n - q + 2 * (i - k)) * (2 * (k + i - 1) - q - n) * (n + 1 + q + 2 * (k - i)) * (n - 1 - q + 2 * (i + k)); + ratio = ratio * numerator / denominator; + a[r - k] -= a[i] * ratio; + } + } + + // K = sum c[k] P[k, x] + + double[] c = new double[2 * r - q]; + for (int i = 1; i < a.Length; i++) + { + c[2 * i - 1 - q] = a[i]; + } + + return c; + } + + /// + /// Return value and derivative of a Legendre series at given points. + /// + internal static Tuple LegendreSeries(double[] a, double x) + { + // S = a[0]*P[0, x] + ... + a[k]*P[k, x] + ... + a[n]*P[n, x] + // where P[k, x] is the Legendre polynomial of order k + // + // According to the Clenshaw algorithm, S can be written by + // S = a[0] + x*b[1, x] - 1/2 * b[2,x] + // + // b[n + 1, x] = 0 + // b[n + 2, x] = 0 + // b[k, x] = a[k] + (2k + 1)/(k + 1)*x*b[k + 1, x] - (k + 1)/(k + 2)*b[k + 2, x] + // + // Derivative of S is given by + // S' = b[1, x] + x*b'[1, x] - 1/2 * b'[2,x] + // + // b'[k, x] = (2k + 1)/(k + 1)*b[k + 1, x] + (2k + 1)/(k + 1)*x*b'[k + 1, x] - (k + 1)/(k + 2)*b'[k + 2, x] + + if (a.Length == 1) + return new Tuple(a[0], 0); + if (a.Length == 2) + return new Tuple(a[0] + a[1] * x, a[1]); + + double b0 = 0.0, b1 = 0.0, b2 = 0.0; + double p0 = 0.0, p1 = 0.0, p2 = 0.0; + + for (int k = a.Length - 1; k >= 1; k--) + { + b0 = a[k] + (2.0 * k + 1.0) / (k + 1.0) * x * b1 - (k + 1.0) / (k + 2.0) * b2; + p0 = (2.0 * k + 1.0) / (k + 1.0) * (b1 + x * p1) - (k + 1.0) / (k + 2.0) * p2; + + b2 = b1; + b1 = b0; + p2 = p1; + p1 = p0; + } + + var value = a[0] + b1 * x - 0.5 * b2; + var derivative = b1 + p1 * x - 0.5 * p2; + + return new Tuple(value, derivative); + } + + /// + /// Return value and derivative of a Legendre polynomial of order at given points. + /// + internal static Tuple LegendreP(int order, double x) + { + // The Legendre polynomial, P[n, x], is defined by the recurrence relation: + // + // P[0, x] = 1 + // P[1, x] = x + // (n + 1) * P[n + 1, x] = (2 * n + 1) * x * P[n, x] - n * P[n - 1, x] + // + // The derivative of the Legendre polynomial, P'[n, x] is given by + // P'[0, x] = 0 + // P'[1, x] = 1 + // (n + 1) * P'[n + 1, x] = (2 * n + 1) * P[n, x] + (2 * n + 1) * x * P'[n, x] - n * P'[n - 1, x] + // = (2 * n + 1) * (P[n, x] + x * P'[n, x]) - n * P'[n - 1, x] + + if (order == 0) + return new Tuple(1.0, 0.0); + if (order == 1) + return new Tuple(x, 1.0); + + double b0 = 0.0, b1 = 1.0, b2 = 0.0; + double p0 = 0.0, p1 = 0.0, p2 = 0.0; + + for (int k = 1; k <= order; k++) + { + b0 = (2.0 * k - 1.0) / k * x * b1 - (k - 1.0) / k * b2; // L(k, x) + p0 = (2.0 * k - 1.0) / k * (b1 + x * p1) - (k - 1.0) / k * p2; // L'(k, x) + + b2 = b1; + b1 = b0; + p2 = p1; + p1 = p0; + } + + var value = b0; + var derivative = p0; + + return new Tuple(value, derivative); + } +} diff --git a/MathNet.Numerics/Integration/GaussRule/GaussKronrodPointFactory.cs b/MathNet.Numerics/Integration/GaussRule/GaussKronrodPointFactory.cs new file mode 100644 index 0000000..53619b2 --- /dev/null +++ b/MathNet.Numerics/Integration/GaussRule/GaussKronrodPointFactory.cs @@ -0,0 +1,33 @@ +using System; + +namespace MathNet.Numerics.Integration.GaussRule; + +/// +/// Creates a Gauss-Kronrod point. +/// +internal static class GaussKronrodPointFactory +{ + [ThreadStatic] + private static GaussPointPair gaussKronrodPoint; + + /// + /// Getter for the GaussKronrodPoint. + /// + /// Defines an Nth order Gauss-Kronrod rule. Precomputed Gauss-Kronrod abscissas/weights for orders 15, 21, 31, 41, 51, 61 are used, otherwise they're calculated on the fly. + /// Object containing the non-negative abscissas/weights, and order. + public static GaussPointPair GetGaussPoint(int order) + { + // Try to get the GaussKronrodPoint from the cached static field. + bool gaussKronrodPointIsCached = gaussKronrodPoint != null && gaussKronrodPoint.Order == order; + if (!gaussKronrodPointIsCached) + { + // Try to find the GaussKronrodPoint in the precomputed dictionary. + if (!GaussKronrodPoint.PreComputed.TryGetValue(order, out gaussKronrodPoint)) + { + gaussKronrodPoint = GaussKronrodPoint.Generate(order, 1E-10); + } + } + + return gaussKronrodPoint; + } +} diff --git a/MathNet.Numerics/Integration/GaussRule/GaussLegendrePoint.cs b/MathNet.Numerics/Integration/GaussRule/GaussLegendrePoint.cs new file mode 100644 index 0000000..d5e195b --- /dev/null +++ b/MathNet.Numerics/Integration/GaussRule/GaussLegendrePoint.cs @@ -0,0 +1,186 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Integration.GaussRule; + +/// +/// Contains a method to compute the Gauss-Legendre abscissas/weights and precomputed abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024. +/// +internal static partial class GaussLegendrePoint +{ + /// + /// Precomputed abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024. + /// + internal static readonly Dictionary PreComputed = new Dictionary + { + // Even + { 2, new GaussPoint(2, new[] { 0.5773502691896257645091488 }, new[] { 1.0000000000000000000000000 }) }, + { 4, new GaussPoint(4, new[] { 0.3399810435848562648026658, 0.8611363115940525752239465 }, new[] { 0.6521451548625461426269361, 0.3478548451374538573730639 }) }, + { 6, new GaussPoint(6, new[] { 0.2386191860831969086305017, 0.6612093864662645136613996, 0.9324695142031520278123016 }, new[] { 0.4679139345726910473898703, 0.3607615730481386075698335, 0.1713244923791703450402961 }) }, + { 8, new GaussPoint(8, new[] { 0.1834346424956498049394761, 0.5255324099163289858177390, 0.7966664774136267395915539, 0.9602898564975362316835609 }, new[] { 0.3626837833783619829651504, 0.3137066458778872873379622, 0.2223810344533744705443560, 0.1012285362903762591525314 }) }, + { 10, new GaussPoint(10, new[] { 0.1488743389816312108848260, 0.4333953941292471907992659, 0.6794095682990244062343274, 0.8650633666889845107320967, 0.9739065285171717200779640 }, new[] { 0.2955242247147528701738930, 0.2692667193099963550912269, 0.2190863625159820439955349, 0.1494513491505805931457763, 0.0666713443086881375935688 }) }, + { 12, new GaussPoint(12, new[] { 0.1252334085114689154724414, 0.3678314989981801937526915, 0.5873179542866174472967024, 0.7699026741943046870368938, 0.9041172563704748566784659, 0.9815606342467192506905491 }, new[] { 0.2491470458134027850005624, 0.2334925365383548087608499, 0.2031674267230659217490645, 0.1600783285433462263346525, 0.1069393259953184309602547, 0.0471753363865118271946160 }) }, + { 14, new GaussPoint(14, new[] { 0.1080549487073436620662447, 0.3191123689278897604356718, 0.5152486363581540919652907, 0.6872929048116854701480198, 0.8272013150697649931897947, 0.9284348836635735173363911, 0.9862838086968123388415973 }, new[] { 0.2152638534631577901958764, 0.2051984637212956039659241, 0.1855383974779378137417166, 0.1572031671581935345696019, 0.1215185706879031846894148, 0.0801580871597602098056333, 0.0351194603317518630318329 }) }, + { 16, new GaussPoint(16, new[] { 0.0950125098376374401853193, 0.2816035507792589132304605, 0.4580167776572273863424194, 0.6178762444026437484466718, 0.7554044083550030338951012, 0.8656312023878317438804679, 0.9445750230732325760779884, 0.9894009349916499325961542 }, new[] { 0.1894506104550684962853967, 0.1826034150449235888667637, 0.1691565193950025381893121, 0.1495959888165767320815017, 0.1246289712555338720524763, 0.0951585116824927848099251, 0.0622535239386478928628438, 0.0271524594117540948517806 }) }, + { 18, new GaussPoint(18, new[] { 0.0847750130417353012422619, 0.2518862256915055095889729, 0.4117511614628426460359318, 0.5597708310739475346078715, 0.6916870430603532078748911, 0.8037049589725231156824175, 0.8926024664975557392060606, 0.9558239495713977551811959, 0.9915651684209309467300160 }, new[] { 0.1691423829631435918406565, 0.1642764837458327229860538, 0.1546846751262652449254180, 0.1406429146706506512047313, 0.1225552067114784601845191, 0.1009420441062871655628140, 0.0764257302548890565291297, 0.0497145488949697964533349, 0.0216160135264833103133427 }) }, + { 20, new GaussPoint(20, new[] { 0.0765265211334973337546404, 0.2277858511416450780804962, 0.3737060887154195606725482, 0.5108670019508270980043641, 0.6360536807265150254528367, 0.7463319064601507926143051, 0.8391169718222188233945291, 0.9122344282513259058677524, 0.9639719272779137912676661, 0.9931285991850949247861224 }, new[] { 0.1527533871307258506980843, 0.1491729864726037467878287, 0.1420961093183820513292983, 0.1316886384491766268984945, 0.1181945319615184173123774, 0.1019301198172404350367501, 0.0832767415767047487247581, 0.0626720483341090635695065, 0.0406014298003869413310400, 0.0176140071391521183118620 }) }, + { 32, new GaussPoint(32, new[] { 0.0483076656877383162348126, 0.1444719615827964934851864, 0.2392873622521370745446032, 0.3318686022821276497799168, 0.4213512761306353453641194, 0.5068999089322293900237475, 0.5877157572407623290407455, 0.6630442669302152009751152, 0.7321821187402896803874267, 0.7944837959679424069630973, 0.8493676137325699701336930, 0.8963211557660521239653072, 0.9349060759377396891709191, 0.9647622555875064307738119, 0.9856115115452683354001750, 0.9972638618494815635449811 }, new[] { 0.0965400885147278005667648, 0.0956387200792748594190820, 0.0938443990808045656391802, 0.0911738786957638847128686, 0.0876520930044038111427715, 0.0833119242269467552221991, 0.0781938957870703064717409, 0.0723457941088485062253994, 0.0658222227763618468376501, 0.0586840934785355471452836, 0.0509980592623761761961632, 0.0428358980222266806568786, 0.0342738629130214331026877, 0.0253920653092620594557526, 0.0162743947309056706051706, 0.0070186100094700966004071 }) }, + { 64, new GaussPoint(64, new[] { 0.0243502926634244325089558, 0.0729931217877990394495429, 0.1214628192961205544703765, 0.1696444204239928180373136, 0.2174236437400070841496487, 0.2646871622087674163739642, 0.3113228719902109561575127, 0.3572201583376681159504426, 0.4022701579639916036957668, 0.4463660172534640879849477, 0.4894031457070529574785263, 0.5312794640198945456580139, 0.5718956462026340342838781, 0.6111553551723932502488530, 0.6489654712546573398577612, 0.6852363130542332425635584, 0.7198818501716108268489402, 0.7528199072605318966118638, 0.7839723589433414076102205, 0.8132653151227975597419233, 0.8406292962525803627516915, 0.8659993981540928197607834, 0.8893154459951141058534040, 0.9105221370785028057563807, 0.9295691721319395758214902, 0.9464113748584028160624815, 0.9610087996520537189186141, 0.9733268277899109637418535, 0.9833362538846259569312993, 0.9910133714767443207393824, 0.9963401167719552793469245, 0.9993050417357721394569056 }, new[] { 0.0486909570091397203833654, 0.0485754674415034269347991, 0.0483447622348029571697695, 0.0479993885964583077281262, 0.0475401657148303086622822, 0.0469681828162100173253263, 0.0462847965813144172959532, 0.0454916279274181444797710, 0.0445905581637565630601347, 0.0435837245293234533768279, 0.0424735151236535890073398, 0.0412625632426235286101563, 0.0399537411327203413866569, 0.0385501531786156291289625, 0.0370551285402400460404151, 0.0354722132568823838106931, 0.0338051618371416093915655, 0.0320579283548515535854675, 0.0302346570724024788679741, 0.0283396726142594832275113, 0.0263774697150546586716918, 0.0243527025687108733381776, 0.0222701738083832541592983, 0.0201348231535302093723403, 0.0179517157756973430850453, 0.0157260304760247193219660, 0.0134630478967186425980608, 0.0111681394601311288185905, 0.0088467598263639477230309, 0.0065044579689783628561174, 0.0041470332605624676352875, 0.0017832807216964329472961 }) }, + { 96, new GaussPoint(96, new[] { 0.0162767448496029695791346, 0.0488129851360497311119582, 0.0812974954644255589944713, 0.1136958501106659209112081, 0.1459737146548969419891073, 0.1780968823676186027594026, 0.2100313104605672036028472, 0.2417431561638400123279319, 0.2731988125910491414872722, 0.3043649443544963530239298, 0.3352085228926254226163256, 0.3656968614723136350308956, 0.3957976498289086032850002, 0.4254789884073005453648192, 0.4547094221677430086356761, 0.4834579739205963597684056, 0.5116941771546676735855097, 0.5393881083243574362268026, 0.5665104185613971684042502, 0.5930323647775720806835558, 0.6189258401254685703863693, 0.6441634037849671067984124, 0.6687183100439161539525572, 0.6925645366421715613442458, 0.7156768123489676262251441, 0.7380306437444001328511657, 0.7596023411766474987029704, 0.7803690438674332176036045, 0.8003087441391408172287961, 0.8194003107379316755389996, 0.8376235112281871214943028, 0.8549590334346014554627870, 0.8713885059092965028737748, 0.8868945174024204160568774, 0.9014606353158523413192327, 0.9150714231208980742058845, 0.9277124567223086909646905, 0.9393703397527552169318574, 0.9500327177844376357560989, 0.9596882914487425393000680, 0.9683268284632642121736594, 0.9759391745851364664526010, 0.9825172635630146774470458, 0.9880541263296237994807628, 0.9925439003237626245718923, 0.9959818429872092906503991, 0.9983643758631816777241494, 0.9996895038832307668276901 }, new[] { 0.0325506144923631662419614, 0.0325161187138688359872055, 0.0324471637140642693640128, 0.0323438225685759284287748, 0.0322062047940302506686671, 0.0320344562319926632181390, 0.0318287588944110065347537, 0.0315893307707271685580207, 0.0313164255968613558127843, 0.0310103325863138374232498, 0.0306713761236691490142288, 0.0302999154208275937940888, 0.0298963441363283859843881, 0.0294610899581679059704363, 0.0289946141505552365426788, 0.0284974110650853856455995, 0.0279700076168483344398186, 0.0274129627260292428234211, 0.0268268667255917621980567, 0.0262123407356724139134580, 0.0255700360053493614987972, 0.0249006332224836102883822, 0.0242048417923646912822673, 0.0234833990859262198422359, 0.0227370696583293740013478, 0.0219666444387443491947564, 0.0211729398921912989876739, 0.0203567971543333245952452, 0.0195190811401450224100852, 0.0186606796274114673851568, 0.0177825023160452608376142, 0.0168854798642451724504775, 0.0159705629025622913806165, 0.0150387210269949380058763, 0.0140909417723148609158616, 0.0131282295669615726370637, 0.0121516046710883196351814, 0.0111621020998384985912133, 0.0101607705350084157575876, 0.0091486712307833866325846, 0.0081268769256987592173824, 0.0070964707911538652691442, 0.0060585455042359616833167, 0.0050142027429275176924702, 0.0039645543384446866737334, 0.0029107318179349464084106, 0.0018539607889469217323359, 0.0007967920655520124294381 }) }, + { 100, new GaussPoint(100, new[] { 0.0156289844215430828722167, 0.0468716824215916316149239, 0.0780685828134366366948174, 0.1091892035800611150034260, 0.1402031372361139732075146, 0.1710800805386032748875324, 0.2017898640957359972360489, 0.2323024818449739696495100, 0.2625881203715034791689293, 0.2926171880384719647375559, 0.3223603439005291517224766, 0.3517885263724217209723438, 0.3808729816246299567633625, 0.4095852916783015425288684, 0.4378974021720315131089780, 0.4657816497733580422492166, 0.4932107892081909335693088, 0.5201580198817630566468157, 0.5465970120650941674679943, 0.5725019326213811913168704, 0.5978474702471787212648065, 0.6226088602037077716041908, 0.6467619085141292798326303, 0.6702830156031410158025870, 0.6931491993558019659486479, 0.7153381175730564464599671, 0.7368280898020207055124277, 0.7575981185197071760356680, 0.7776279096494954756275514, 0.7968978923903144763895729, 0.8153892383391762543939888, 0.8330838798884008235429158, 0.8499645278795912842933626, 0.8660146884971646234107400, 0.8812186793850184155733168, 0.8955616449707269866985210, 0.9090295709825296904671263, 0.9216092981453339526669513, 0.9332885350430795459243337, 0.9440558701362559779627747, 0.9539007829254917428493369, 0.9628136542558155272936593, 0.9707857757637063319308979, 0.9778093584869182885537811, 0.9838775407060570154961002, 0.9889843952429917480044187, 0.9931249370374434596520099, 0.9962951347331251491861317, 0.9984919506395958184001634, 0.9997137267734412336782285 }, new[] { 0.0312554234538633569476425, 0.0312248842548493577323765, 0.0311638356962099067838183, 0.0310723374275665165878102, 0.0309504788504909882340635, 0.0307983790311525904277139, 0.0306161865839804484964594, 0.0304040795264548200165079, 0.0301622651051691449190687, 0.0298909795933328309168368, 0.0295904880599126425117545, 0.0292610841106382766201190, 0.0289030896011252031348762, 0.0285168543223950979909368, 0.0281027556591011733176483, 0.0276611982207923882942042, 0.0271926134465768801364916, 0.0266974591835709626603847, 0.0261762192395456763423087, 0.0256294029102081160756420, 0.0250575444815795897037642, 0.0244612027079570527199750, 0.0238409602659682059625604, 0.0231974231852541216224889, 0.0225312202563362727017970, 0.0218430024162473863139537, 0.0211334421125276415426723, 0.0204032326462094327668389, 0.0196530874944353058653815, 0.0188837396133749045529412, 0.0180959407221281166643908, 0.0172904605683235824393442, 0.0164680861761452126431050, 0.0156296210775460027239369, 0.0147758845274413017688800, 0.0139077107037187726879541, 0.0130259478929715422855586, 0.0121314576629794974077448, 0.0112251140231859771172216, 0.0103078025748689695857821, 0.0093804196536944579514182, 0.0084438714696689714026208, 0.0074990732554647115788287, 0.0065469484508453227641521, 0.0055884280038655151572119, 0.0046244500634221193510958, 0.0036559612013263751823425, 0.0026839253715534824194396, 0.0017093926535181052395294, 0.0007346344905056717304063 }) }, + { 128, new GaussPoint(128, new[] { 0.0122236989606157641980521, 0.0366637909687334933302153, 0.0610819696041395681037870, 0.0854636405045154986364980, 0.1097942311276437466729747, 0.1340591994611877851175753, 0.1582440427142249339974755, 0.1823343059853371824103826, 0.2063155909020792171540580, 0.2301735642266599864109866, 0.2538939664226943208556180, 0.2774626201779044028062316, 0.3008654388776772026671541, 0.3240884350244133751832523, 0.3471177285976355084261628, 0.3699395553498590266165917, 0.3925402750332674427356482, 0.4149063795522750154922739, 0.4370245010371041629370429, 0.4588814198335521954490891, 0.4804640724041720258582757, 0.5017595591361444642896063, 0.5227551520511754784539479, 0.5434383024128103634441936, 0.5637966482266180839144308, 0.5838180216287630895500389, 0.6034904561585486242035732, 0.6228021939105849107615396, 0.6417416925623075571535249, 0.6602976322726460521059468, 0.6784589224477192593677557, 0.6962147083695143323850866, 0.7135543776835874133438599, 0.7304675667419088064717369, 0.7469441667970619811698824, 0.7629743300440947227797691, 0.7785484755064119668504941, 0.7936572947621932902433329, 0.8082917575079136601196422, 0.8224431169556438424645942, 0.8361029150609068471168753, 0.8492629875779689691636001, 0.8619154689395484605906323, 0.8740527969580317986954180, 0.8856677173453972174082924, 0.8967532880491581843864474, 0.9073028834017568139214859, 0.9173101980809605370364836, 0.9267692508789478433346245, 0.9356743882779163757831268, 0.9440202878302201821211114, 0.9518019613412643862177963, 0.9590147578536999280989185, 0.9656543664319652686458290, 0.9717168187471365809043384, 0.9771984914639073871653744, 0.9820961084357185360247656, 0.9864067427245862088712355, 0.9901278184917343833379303, 0.9932571129002129353034372, 0.9957927585349811868641612, 0.9977332486255140198821574, 0.9990774599773758950119878, 0.9998248879471319144736081 }, new[] { 0.0244461801962625182113259, 0.0244315690978500450548486, 0.0244023556338495820932980, 0.0243585572646906258532685, 0.0243002001679718653234426, 0.0242273192228152481200933, 0.0241399579890192849977167, 0.0240381686810240526375873, 0.0239220121367034556724504, 0.0237915577810034006387807, 0.0236468835844476151436514, 0.0234880760165359131530253, 0.0233152299940627601224157, 0.0231284488243870278792979, 0.0229278441436868469204110, 0.0227135358502364613097126, 0.0224856520327449668718246, 0.0222443288937997651046291, 0.0219897106684604914341221, 0.0217219495380520753752610, 0.0214412055392084601371119, 0.0211476464682213485370195, 0.0208414477807511491135839, 0.0205227924869600694322850, 0.0201918710421300411806732, 0.0198488812328308622199444, 0.0194940280587066028230219, 0.0191275236099509454865185, 0.0187495869405447086509195, 0.0183604439373313432212893, 0.0179603271850086859401969, 0.0175494758271177046487069, 0.0171281354231113768306810, 0.0166965578015892045890915, 0.0162550009097851870516575, 0.0158037286593993468589656, 0.0153430107688651440859909, 0.0148731226021473142523855, 0.0143943450041668461768239, 0.0139069641329519852442880, 0.0134112712886163323144890, 0.0129075627392673472204428, 0.0123961395439509229688217, 0.0118773073727402795758911, 0.0113513763240804166932817, 0.0108186607395030762476596, 0.0102794790158321571332153, 0.0097341534150068058635483, 0.0091830098716608743344787, 0.0086263777986167497049788, 0.0080645898904860579729286, 0.0074979819256347286876720, 0.0069268925668988135634267, 0.0063516631617071887872143, 0.0057726375428656985893346, 0.0051901618326763302050708, 0.0046045842567029551182905, 0.0040162549837386423131943, 0.0034255260409102157743378, 0.0028327514714579910952857, 0.0022382884309626187436221, 0.0016425030186690295387909, 0.0010458126793403487793129, 0.0004493809602920903763943 }) }, + { 256, new GaussPoint(256, new[] { 0.0061239123751895295011702, 0.0183708184788136651179263, 0.0306149687799790293662786, 0.0428545265363790983812423, 0.0550876556946339841045614, 0.0673125211657164002422903, 0.0795272891002329659032271, 0.0917301271635195520311456, 0.1039192048105094036391969, 0.1160926935603328049407349, 0.1282487672706070947420496, 0.1403856024113758859130249, 0.1525013783386563953746068, 0.1645942775675538498292845, 0.1766624860449019974037218, 0.1887041934213888264615036, 0.2007175933231266700680007, 0.2127008836226259579370402, 0.2246522667091319671478783, 0.2365699497582840184775084, 0.2484521450010566668332427, 0.2602970699919425419785609, 0.2721029478763366095052447, 0.2838680076570817417997658, 0.2955904844601356145637868, 0.3072686197993190762586103, 0.3189006618401062756316834, 0.3304848656624169762291870, 0.3420194935223716364807297, 0.3535028151129699895377902, 0.3649331078236540185334649, 0.3763086569987163902830557, 0.3876277561945155836379846, 0.3988887074354591277134632, 0.4100898214687165500064336, 0.4212294180176238249768124, 0.4323058260337413099534411, 0.4433173839475273572169258, 0.4542624399175899987744552, 0.4651393520784793136455705, 0.4759464887869833063907375, 0.4866822288668903501036214, 0.4973449618521814771195124, 0.5079330882286160362319249, 0.5184450196736744762216617, 0.5288791792948222619514764, 0.5392340018660591811279362, 0.5495079340627185570424269, 0.5596994346944811451369074, 0.5698069749365687590576675, 0.5798290385590829449218317, 0.5897641221544543007857861, 0.5996107353629683217303882, 0.6093674010963339395223108, 0.6190326557592612194309676, 0.6286050494690149754322099, 0.6380831462729113686686886, 0.6474655243637248626170162, 0.6567507762929732218875002, 0.6659375091820485599064084, 0.6750243449311627638559187, 0.6840099204260759531248771, 0.6928928877425769601053416, 0.7016719143486851594060835, 0.7103456833045433133945663, 0.7189128934599714483726399, 0.7273722596496521265868944, 0.7357225128859178346203729, 0.7439624005491115684556831, 0.7520906865754920595875297, 0.7601061516426554549419068, 0.7680075933524456359758906, 0.7757938264113257391320526, 0.7834636828081838207506702, 0.7910160119895459945467075, 0.7984496810321707587825429, 0.8057635748129986232573891, 0.8129565961764315431364104, 0.8200276660989170674034781, 0.8269757238508125142890929, 0.8337997271555048943484439, 0.8404986523457627138950680, 0.8470714945172962071870724, 0.8535172676795029650730355, 0.8598350049033763506961731, 0.8660237584665545192975154, 0.8720825999954882891300459, 0.8780106206047065439864349, 0.8838069310331582848598262, 0.8894706617776108888286766, 0.8950009632230845774412228, 0.9003970057703035447716200, 0.9056579799601446470826819, 0.9107830965950650118909072, 0.9157715868574903845266696, 0.9206227024251464955050471, 0.9253357155833162028727303, 0.9299099193340056411802456, 0.9343446275020030942924765, 0.9386391748378148049819261, 0.9427929171174624431830761, 0.9468052312391274813720517, 0.9506755153166282763638521, 0.9544031887697162417644479, 0.9579876924111781293657904, 0.9614284885307321440064075, 0.9647250609757064309326123, 0.9678769152284894549090038, 0.9708835784807430293209233, 0.9737445997043704052660786, 0.9764595497192341556210107, 0.9790280212576220388242380, 0.9814496290254644057693031, 0.9837240097603154961666861, 0.9858508222861259564792451, 0.9878297475648606089164877, 0.9896604887450652183192437, 0.9913427712075830869221885, 0.9928763426088221171435338, 0.9942609729224096649628775, 0.9954964544810963565926471, 0.9965826020233815404305044, 0.9975192527567208275634088, 0.9983062664730064440555005, 0.9989435258434088565550263, 0.9994309374662614082408542, 0.9997684374092631861048786, 0.9999560500189922307348012 }, new[] { 0.0122476716402897559040703, 0.0122458343697479201424639, 0.0122421601042728007697281, 0.0122366493950401581092426, 0.0122293030687102789041463, 0.0122201222273039691917087, 0.0122091082480372404075141, 0.0121962627831147135181810, 0.0121815877594817721740476, 0.0121650853785355020613073, 0.0121467581157944598155598, 0.0121266087205273210347185, 0.0121046402153404630977578, 0.0120808558957245446559752, 0.0120552593295601498143471, 0.0120278543565825711612675, 0.0119986450878058119345367, 0.0119676359049058937290073, 0.0119348314595635622558732, 0.0119002366727664897542872, 0.0118638567340710787319046, 0.0118256971008239777711607, 0.0117857634973434261816901, 0.0117440619140605503053767, 0.0117005986066207402881898, 0.0116553800949452421212989, 0.0116084131622531057220847, 0.0115597048540436357726687, 0.0115092624770394979585864, 0.0114570935980906391523344, 0.0114032060430391859648471, 0.0113476078955454919416257, 0.0112903074958755095083676, 0.0112313134396496685726568, 0.0111706345765534494627109, 0.0111082800090098436304608, 0.0110442590908139012635176, 0.0109785814257295706379882, 0.0109112568660490397007968, 0.0108422955111147959952935, 0.0107717077058046266366536, 0.0106995040389797856030482, 0.0106256953418965611339617, 0.0105502926865814815175336, 0.0104733073841704030035696, 0.0103947509832117289971017, 0.0103146352679340150682607, 0.0102329722564782196569549, 0.0101497741990948656546341, 0.0100650535763063833094610, 0.0099788230970349101247339, 0.0098910956966958286026307, 0.0098018845352573278254988, 0.0097112029952662799642497, 0.0096190646798407278571622, 0.0095254834106292848118297, 0.0094304732257377527473528, 0.0093340483776232697124660, 0.0092362233309563026873787, 0.0091370127604508064020005, 0.0090364315486628736802278, 0.0089344947837582075484084, 0.0088312177572487500253183, 0.0087266159616988071403366, 0.0086207050884010143053688, 0.0085135010250224906938384, 0.0084050198532215357561803, 0.0082952778462352254251714, 0.0081842914664382699356198, 0.0080720773628734995009470, 0.0079586523687543483536132, 0.0078440334989397118668103, 0.0077282379473815556311102, 0.0076112830845456594616187, 0.0074931864548058833585998, 0.0073739657738123464375724, 0.0072536389258339137838291, 0.0071322239610753900716724, 0.0070097390929698226212344, 0.0068862026954463203467133, 0.0067616333001737987809279, 0.0066360495937810650445900, 0.0065094704150536602678099, 0.0063819147521078805703752, 0.0062534017395424012720636, 0.0061239506555679325423891, 0.0059935809191153382211277, 0.0058623120869226530606616, 0.0057301638506014371773844, 0.0055971560336829100775514, 0.0054633085886443102775705, 0.0053286415939159303170811, 0.0051931752508692809303288, 0.0050569298807868423875578, 0.0049199259218138656695588, 0.0047821839258926913729317, 0.0046437245556800603139791, 0.0045045685814478970686418, 0.0043647368779680566815684, 0.0042242504213815362723565, 0.0040831302860526684085998, 0.0039413976414088336277290, 0.0037990737487662579981170, 0.0036561799581425021693892, 0.0035127377050563073309711, 0.0033687685073155510120191, 0.0032242939617941981570107, 0.0030793357411993375832054, 0.0029339155908297166460123, 0.0027880553253277068805748, 0.0026417768254274905641208, 0.0024951020347037068508395, 0.0023480529563273120170065, 0.0022006516498399104996849, 0.0020529202279661431745488, 0.0019048808534997184044191, 0.0017565557363307299936069, 0.0016079671307493272424499, 0.0014591373333107332010884, 0.0013100886819025044578317, 0.0011608435575677247239706, 0.0010114243932084404526058, 0.0008618537014200890378141, 0.0007121541634733206669090, 0.0005623489540314098028152, 0.0004124632544261763284322, 0.0002625349442964459062875, 0.0001127890178222721755125 }) }, + { 512, new GaussPoint(512, new[] { 0.0030649621851593961529232, 0.0091947713864329108047442, 0.0153242350848981855249677, 0.0214531229597748745137841, 0.0275812047119197840615246, 0.0337082500724805951232271, 0.0398340288115484476830396, 0.0459583107468090617788760, 0.0520808657521920701127271, 0.0582014637665182372392330, 0.0643198748021442404045319, 0.0704358689536046871990309, 0.0765492164062510452915674, 0.0826596874448871596284651, 0.0887670524624010326092165, 0.0948710819683925428909483, 0.1009715465977967786264323, 0.1070682171195026611052004, 0.1131608644449665349442888, 0.1192492596368204011642726, 0.1253331739174744696875513, 0.1314123786777137080093018, 0.1374866454852880630171099, 0.1435557460934960331730353, 0.1496194524497612685217272, 0.1556775367042018762501969, 0.1617297712181921097989489, 0.1677759285729161198103670, 0.1738157815779134454985394, 0.1798491032796159253350647, 0.1858756669698757062678115, 0.1918952461944840310240859, 0.1979076147616804833961808, 0.2039125467506523717658375, 0.2099098165200239314947094, 0.2158991987163350271904893, 0.2218804682825090362529109, 0.2278534004663095955103621, 0.2338177708287858931763260, 0.2397733552527061887852891, 0.2457199299509792442100997, 0.2516572714750633493170137, 0.2575851567233626262808095, 0.2635033629496102970603704, 0.2694116677712385990250046, 0.2753098491777350342234845, 0.2811976855389846383013106, 0.2870749556135979555970354, 0.2929414385572244074855835, 0.2987969139308507415853707, 0.3046411617090842500066247, 0.3104739622884204453906292, 0.3162950964954948840736281, 0.3221043455953188263048133, 0.3279014912994984240551598, 0.3336863157744371275728377, 0.3394586016495210024715049, 0.3452181320252866497799379, 0.3509646904815714220351686, 0.3566980610856456291665404, 0.3624180284003264285948478, 0.3681243774920730946589582, 0.3738168939390633631820054, 0.3794953638392505477003659, 0.3851595738184011246011504, 0.3908093110381124851478484, 0.3964443632038105531190080, 0.4020645185727269675414064, 0.4076695659618555307670286, 0.4132592947558876229222955, 0.4188334949151262845483445, 0.4243919569833786700527309, 0.4299344720958265754056529, 0.4354608319868747443376920, 0.4409708289979766581310498, 0.4464642560854375149423431, 0.4519409068281941054521446, 0.4574005754355712925046003, 0.4628430567550148032795831, 0.4682681462798000434299255, 0.4736756401567166435172692, 0.4790653351937284489919577, 0.4844370288676086658851277, 0.4897905193315498753147078, 0.4951256054227486308513615, 0.5004420866699643537454866, 0.5057397633010522419821678, 0.5110184362504699101074361, 0.5162779071667574777562819, 0.5215179784199908258105606, 0.5267384531092077401231844, 0.5319391350698066637637706, 0.5371198288809177797701793, 0.5422803398727461474300859, 0.5474204741338866161668468, 0.5525400385186102421644070, 0.5576388406541219339368088, 0.5627166889477890541289656, 0.5677733925943407059267120, 0.5728087615830374335557009, 0.5778226067048110674604360, 0.5828147395593744458765762, 0.5877849725623007456415722, 0.5927331189520721562306608, 0.5976589927970976321572046, 0.6025624090026994600382737, 0.6074431833180683777981926, 0.6123011323431869846644595, 0.6171360735357211818019505, 0.6219478252178793846326095, 0.6267362065832392490988318, 0.6315010377035416553494506, 0.6362421395354516935575740, 0.6409593339272863978194482, 0.6456524436257089753330001, 0.6503212922823892793136899, 0.6549657044606302753737317, 0.6595855056419602523685720, 0.6641805222326905300017078, 0.6687505815704384167754210, 0.6732955119306151731807642, 0.6778151425328787363350998, 0.6823093035475509635996236, 0.6867778261019991540425409, 0.6912205422869816079558685, 0.6956372851629569859851427, 0.7000278887663572307915895, 0.7043921881158238155354902, 0.7087300192184070848475163, 0.7130412190757284553416507, 0.7173256256901052441189100, 0.7215830780706378951153816, 0.7258134162392593745610389, 0.7300164812367465082373380, 0.7341921151286930346516885, 0.7383401610114441496854630, 0.7424604630179923197192207, 0.7465528663238341416942072, 0.7506172171527880300329109, 0.7546533627827725118134392, 0.7586611515515449130726824, 0.7626404328624002206015913, 0.7665910571898299050923647, 0.7705128760851404930018538, 0.7744057421820316760079998, 0.7782695092021337484565606, 0.7821040319605041647237048, 0.7859091663710830099561901, 0.7896847694521071791947507, 0.7934306993314830614379285, 0.7971468152521175267628422, 0.8008329775772070161862372, 0.8044890477954845355235412, 0.8081148885264243560855026, 0.8117103635254042266412553, 0.8152753376888249026732770, 0.8188096770591868005536242, 0.8223132488301235858819787, 0.8257859213513925068443721, 0.8292275641338212850768968, 0.8326380478542113781512150, 0.8360172443601974294381733, 0.8393650266750627227522641, 0.8426812690025104608329811, 0.8459658467313906883792422, 0.8492186364403826820199251, 0.8524395159026326312771384, 0.8556283640903464362590494, 0.8587850611793374495058711, 0.8619094885535289911058997, 0.8650015288094114678982387, 0.8680610657604539292849800, 0.8710879844414698938880857, 0.8740821711129372830049576, 0.8770435132652722985416439, 0.8799718996230570848337538, 0.8828672201492210155023745, 0.8857293660491754482355527, 0.8885582297749017921351663, 0.8913537050289927340242104, 0.8941156867686464718706125, 0.8968440712096138052506156, 0.8995387558300979345474886, 0.9021996393746068223597927, 0.9048266218577579723776075, 0.9074196045680354827749729, 0.9099784900714992329623006, 0.9125031822154460643436214, 0.9149935861320228175302595, 0.9174496082417910902748409, 0.9198711562572435822074657, 0.9222581391862718942794141, 0.9246104673355856526489486, 0.9269280523140828285786768, 0.9292108070361711277546193, 0.9314586457250403242837002, 0.9336714839158854164789745, 0.9358492384590804834007204, 0.9379918275233031229867813, 0.9400991705986093544775539, 0.9421711884994588697201555, 0.9442078033676905198230562, 0.9462089386754479255274304, 0.9481745192280551015654245, 0.9501044711668419871894147, 0.9519987219719197769813274, 0.9538572004649059479887372, 0.9556798368115988811866200, 0.9574665625246019772327448, 0.9592173104658971684737507, 0.9609320148493677311718534, 0.9626106112432703039637754, 0.9642530365726560206402068, 0.9658592291217406674540047, 0.9674291285362237773389233, 0.9689626758255565756615864, 0.9704598133651586944555050, 0.9719204848985835745206522, 0.9733446355396324773464471, 0.9747322117744170315712560, 0.9760831614633702416830300, 0.9773974338432058899681861, 0.9786749795288262664309572, 0.9799157505151781656726285, 0.9811197001790570947322311, 0.9822867832808596419166429, 0.9834169559662839640681455, 0.9845101757679783590716126, 0.9855664016071379024692622, 0.9865855937950491429603684, 0.9875677140345828729848910, 0.9885127254216350200148487, 0.9894205924465157453777048, 0.9902912809952868962106899, 0.9911247583510480415528399, 0.9919209931951714500244370, 0.9926799556084865573546763, 0.9934016170724147657859271, 0.9940859504700558793702825, 0.9947329300872282225027936, 0.9953425316134657151476031, 0.9959147321429772566997088, 0.9964495101755774022837600, 0.9969468456176038804367370, 0.9974067197828498321611183, 0.9978291153935628466036470, 0.9982140165816127953876923, 0.9985614088900397275573677, 0.9988712792754494246541769, 0.9991436161123782382453400, 0.9993784092025992514480161, 0.9995756497983108555936109, 0.9997353306710426625827368, 0.9998574463699794385446275, 0.9999419946068456536361287, 0.9999889909843818679872841 }, new[] { 0.0061299051754057857591564, 0.0061296748380364986664278, 0.0061292141719530834395471, 0.0061285231944655327693402, 0.0061276019315380226384508, 0.0061264504177879366912426, 0.0061250686964845654506976, 0.0061234568195474804311878, 0.0061216148475445832082156, 0.0061195428496898295184288, 0.0061172409038406284754329, 0.0061147090964949169991245, 0.0061119475227879095684795, 0.0061089562864885234199252, 0.0061057354999954793256260, 0.0061022852843330780981965, 0.0060986057691466529805468, 0.0060946970926976980917399, 0.0060905594018586731119147, 0.0060861928521074844014940, 0.0060815976075216427620556, 0.0060767738407720980583934, 0.0060717217331167509334394, 0.0060664414743936418598512, 0.0060609332630138177841916, 0.0060551973059538766317450, 0.0060492338187481899521175, 0.0060430430254808039978627, 0.0060366251587770195404584, 0.0060299804597946507400317, 0.0060231091782149633972884, 0.0060160115722332929281516, 0.0060086879085493424136484, 0.0060011384623571610896056, 0.0059933635173348036527221, 0.0059853633656336707715812, 0.0059771383078675312031423, 0.0059686886531012259272183, 0.0059600147188390547233923, 0.0059511168310128456267588, 0.0059419953239697077107922, 0.0059326505404594676575446, 0.0059230828316217905872556, 0.0059132925569729856313229, 0.0059032800843924967444267, 0.0058930457901090792634301, 0.0058825900586866627324847, 0.0058719132830099005255609, 0.0058610158642694068093892, 0.0058498982119466814015496, 0.0058385607437987230901727, 0.0058270038858423319934219, 0.0058152280723381015486124, 0.0058032337457741007324836, 0.0057910213568492471257818, 0.0057785913644563714469284, 0.0057659442356649741911390, 0.0057530804457036750229319, 0.0057400004779423555815070, 0.0057267048238739963699973, 0.0057131939830962084110906, 0.0056994684632924603629882, 0.0056855287802130018011102, 0.0056713754576554833823756, 0.0056570090274452746202723, 0.0056424300294154800102991, 0.0056276390113866542566918, 0.0056126365291462173626557, 0.0055974231464275703576030, 0.0055819994348889124461425, 0.0055663659740917603747899, 0.0055505233514791708235538, 0.0055344721623536666407146, 0.0055182130098548677502395, 0.0055017465049368275723757, 0.0054850732663450758090285, 0.0054681939205933684565648, 0.0054511091019401459196852, 0.0054338194523647001109732, 0.0054163256215430514316688, 0.0053986282668235365401123, 0.0053807280532021078251738, 0.0053626256532973455128155, 0.0053443217473251833447318, 0.0053258170230733487787774, 0.0053071121758755186716175, 0.0052882079085851914147269, 0.0052691049315492765055207, 0.0052498039625814025460136, 0.0052303057269349446719890, 0.0052106109572757724261988, 0.0051907203936547190996206, 0.0051706347834797735752665, 0.0051503548814879957194620, 0.0051298814497171563759039, 0.0051092152574771030281542, 0.0050883570813208522065339, 0.0050673077050154097256505, 0.0050460679195123198490183, 0.0050246385229179444874178, 0.0050030203204634735477834, 0.0049812141244746675595135, 0.0049592207543413337151533, 0.0049370410364865364724225, 0.0049146758043355438745290, 0.0048921258982845107556462, 0.0048693921656689000083132, 0.0048464754607316430993636, 0.0048233766445910410307843, 0.0048000965852084069516609, 0.0047766361573554516370718, 0.0047529962425814130594576, 0.0047291777291799312876071, 0.0047051815121556699579709, 0.0046810084931906855725376, 0.0046566595806105458869828, 0.0046321356893501986622283, 0.0046074377409195920619320, 0.0045825666633690479877601, 0.0045575233912543896535753, 0.0045323088656018247089130, 0.0045069240338725852313010, 0.0044813698499273259161146, 0.0044556472739902818017469, 0.0044297572726131868769073, 0.0044037008186389549258496, 0.0043774788911651239762643, 0.0043510924755070657234522, 0.0043245425631609613132305, 0.0042978301517665448748000, 0.0042709562450696162035304, 0.0042439218528843240022977, 0.0042167279910552210986262, 0.0041893756814190930634598, 0.0041618659517665616659011, 0.0041341998358034646067195, 0.0041063783731120129818357, 0.0040784026091117279353449, 0.0040502735950201579699371, 0.0040219923878133783908191, 0.0039935600501862743674273, 0.0039649776505126091053562, 0.0039362462628048786290012, 0.0039073669666739546834366, 0.0038783408472885172720108, 0.0038491689953342783540510, 0.0038198525069729982349166, 0.0037903924838012961884344, 0.0037607900328092568594835, 0.0037310462663388340021755, 0.0037011623020420531166926, 0.0036711392628390145554094, 0.0036409782768756986764252, 0.0036106804774815746300758, 0.0035802470031270143713799, 0.0035496789973805134987000, 0.0035189776088657205261605, 0.0034881439912182762045767, 0.0034571793030424645127888, 0.0034260847078676769483860, 0.0033948613741046917538288, 0.0033635104750017697209450, 0.0033320331886005682236783, 0.0033004306976918751358177, 0.0032687041897711642972145, 0.0032368548569939741987234, 0.0032048838961311115627642, 0.0031727925085236815030060, 0.0031405819000379459532169, 0.0031082532810200120618074, 0.0030758078662503522550163, 0.0030432468748981576780527, 0.0030105715304755267298129, 0.0029777830607914904130339, 0.0029448826979058762279357, 0.0029118716780830123435331, 0.0028787512417452737868732, 0.0028455226334264723964728, 0.0028121871017250922921949, 0.0027787458992573726197173, 0.0027452002826102393336092, 0.0027115515122940877888456, 0.0026778008526954179163600, 0.0026439495720293237639656, 0.0026099989422918391896635, 0.0025759502392121415000167, 0.0025418047422046148318992, 0.0025075637343207750815413, 0.0024732285022010581903898, 0.0024388003360264736029032, 0.0024042805294701247170072, 0.0023696703796485981535706, 0.0023349711870732236769383, 0.0023001842556012066042973, 0.0022653108923866345474810, 0.0022303524078313603367724, 0.0021953101155357629823745, 0.0021601853322493885355395, 0.0021249793778214727179358, 0.0020896935751513471947536, 0.0020543292501387313744068, 0.0020188877316339116255770, 0.0019833703513878098109153, 0.0019477784440019430461334, 0.0019121133468782766036998, 0.0018763764001689718921795, 0.0018405689467260314557679, 0.0018046923320508429542037, 0.0017687479042436241015783, 0.0017327370139527705642995, 0.0016966610143241088445575, 0.0016605212609500562072903, 0.0016243191118186897474239, 0.0015880559272627267421479, 0.0015517330699084184928942, 0.0015153519046243599371387, 0.0014789137984702174059640, 0.0014424201206453770259886, 0.0014058722424375164225552, 0.0013692715371711025869345, 0.0013326193801558190401403, 0.0012959171486349257824991, 0.0012591662217335559930561, 0.0012223679804069540808915, 0.0011855238073886605549070, 0.0011486350871386503607080, 0.0011117032057914329649653, 0.0010747295511041247428251, 0.0010377155124045074300544, 0.0010006624805390909706032, 0.0009635718478212056798501, 0.0009264450079791582697455, 0.0008892833561045005372012, 0.0008520882886004809402792, 0.0008148612031307819965602, 0.0007776034985686972438014, 0.0007403165749469818962867, 0.0007030018334087411433900, 0.0006656606761599343409382, 0.0006282945064244358390880, 0.0005909047284032230162400, 0.0005534927472403894647847, 0.0005160599690007674370993, 0.0004786078006679509066920, 0.0004411376501795405636493, 0.0004036509265333198797447, 0.0003661490400356268530141, 0.0003286334028523334162522, 0.0002911054302514885125319, 0.0002535665435705865135866, 0.0002160181779769908583388, 0.0001784618055459532946077, 0.0001408990173881984930124, 0.0001033319034969132362968, 0.0000657657316592401958310, 0.0000282526373739346920387 }) }, + { 1024, new GaussPoint(1024, new[] { 0.0015332313560626384065387, 0.0045996796509132604743248, 0.0076660846940754867627839, 0.0107324176515422803327458, 0.0137986496899844401539048, 0.0168647519770217265449962, 0.0199306956814939776907024, 0.0229964519737322146859283, 0.0260619920258297325581921, 0.0291272870119131747190088, 0.0321923081084135882953009, 0.0352570264943374577920498, 0.0383214133515377145376052, 0.0413854398649847193632977, 0.0444490772230372159692514, 0.0475122966177132524285687, 0.0505750692449610682823599, 0.0536373663049299446784129, 0.0566991590022410150066456, 0.0597604185462580334848567, 0.0628211161513580991486838, 0.0658812230372023327000985, 0.0689407104290065036692117, 0.0719995495578116053446277, 0.0750577116607543749280791, 0.0781151679813377563695878, 0.0811718897697013033399379, 0.0842278482828915197978074, 0.0872830147851321356094940, 0.0903373605480943146797811, 0.0933908568511667930531222, 0.0964434749817259444449839, 0.0994951862354057706638682, 0.1025459619163678143852404, 0.1055957733375709917393206, 0.1086445918210413421754502, 0.1116923886981416930665228, 0.1147391353098412365177689, 0.1177848030069850158450139, 0.1208293631505633191883714, 0.1238727871119809777282145, 0.1269150462733265659711591, 0.1299561120276415015747167, 0.1329959557791890421802183, 0.1360345489437231767245806, 0.1390718629487574087024745, 0.1421078692338334288514767, 0.1451425392507896747338214, 0.1481758444640297746894331, 0.1512077563507908736360111, 0.1542382464014118381930443, 0.1572672861196013386077717, 0.1602948470227058049622614, 0.1633209006419772551419632, 0.1663454185228409920472972, 0.1693683722251631675310675, 0.1723897333235182105457458, 0.1754094734074561169859457, 0.1784275640817695987127083, 0.1814439769667610892475458, 0.1844586836985096036255346, 0.1874716559291374498981239, 0.1904828653270767897777182, 0.1934922835773360459175133, 0.1964998823817661533215037, 0.1995056334593266523810493, 0.2025095085463516210358758, 0.2055114793968154435588961, 0.2085115177825984134657778, 0.2115095954937521680517391, 0.2145056843387649520596422, 0.2174997561448267079850562, 0.2204917827580939905255947, 0.2234817360439547026834844, 0.2264695878872926510320010, 0.2294553101927519176581055, 0.2324388748850010462953415, 0.2354202539089970401627982, 0.2383994192302491690277166, 0.2413763428350825830111093, 0.2443509967309017306575811, 0.2473233529464535787923793, 0.2502933835320906316905658, 0.2532610605600337470850902, 0.2562263561246347465424530, 0.2591892423426388177365829, 0.2621496913534467061535080, 0.2651076753193766937613805, 0.2680631664259263621824189, 0.2710161368820341379053566, 0.2739665589203406170790369, 0.2769144047974496674298651, 0.2798596467941893048479266, 0.2828022572158723421886958, 0.2857422083925568078394062, 0.2886794726793061316013119, 0.2916140224564490954412652, 0.2945458301298395466682397, 0.2974748681311158710926665, 0.3004011089179602237287060, 0.3033245249743575146018584, 0.3062450888108541472266190, 0.3091627729648165073212094, 0.3120775500006891993287636, 0.3149893925102530283167230, 0.3178982731128827248285835, 0.3208041644558044102645582, 0.3237070392143528003701590, 0.3266068700922281444141618, 0.3295036298217528976399056, 0.3323972911641281245763845, 0.3352878269096896307981228, 0.3381752098781638207253743, 0.3410594129189232790587667, 0.3439404089112420734451077, 0.3468181707645507759736923, 0.3496926714186912011050938, 0.3525638838441708576370887, 0.3554317810424171123150528, 0.3582963360460310626968790, 0.3611575219190411168852009, 0.3640153117571562777424605, 0.3668696786880191292071420, 0.3697205958714585223322883, 0.3725680364997419586702471, 0.3754119737978276686304337, 0.3782523810236163824397703, 0.3810892314682027913383487, 0.3839224984561266966457784, 0.3867521553456238443366159, 0.3895781755288764427662286, 0.3924005324322633611914264, 0.3952191995166100067331951, 0.3980341502774378774318886, 0.4008453582452137890482864, 0.4036527969855987732669841, 0.4064564400996966449616823, 0.4092562612243022361850445, 0.4120522340321492945489319, 0.4148443322321580436639788, 0.4176325295696824033106488, 0.4204167998267568670171117, 0.4231971168223430347225035, 0.4259734544125757982073747, 0.4287457864910091769763965, 0.4315140869888618022816824, 0.4342783298752620469783905, 0.4370384891574927989076034, 0.4397945388812358755048319, 0.4425464531308160773358662, 0.4452942060294448782650898, 0.4480377717394637499647905, 0.4507771244625871184774399, 0.4535122384401449505463744, 0.4562430879533249674337895, 0.4589696473234144839484647, 0.4616918909120418704091584, 0.4644097931214176352731591, 0.4671233283945751261630457, 0.4698324712156108470282980, 0.4725371961099243891820077, 0.4752374776444579739565725, 0.4779332904279356047259052, 0.4806246091111018260453658, 0.4833114083869600876643171, 0.4859936629910107111699206, 0.4886713477014884570245255, 0.4913444373395996897627612, 0.4940129067697591391182235, 0.4966767308998262548534419, 0.4993358846813411530706387, 0.5019903431097601517846292, 0.5046400812246908935430768, 0.5072850741101270528831987, 0.5099252968946826264179220, 0.5125607247518258033484145, 0.5151913329001124142038603, 0.5178170966034189556133159, 0.5204379911711751889184691, 0.5230539919585963104401304, 0.5256650743669146912153147, 0.5282712138436111840258187, 0.5308723858826459955432696, 0.5334685660246891214197081, 0.5360597298573503421568799, 0.5386458530154087775915395, 0.5412269111810419978382210, 0.5438028800840546885350993, 0.5463737355021068682427603, 0.5489394532609416558499039, 0.5515000092346125858442412, 0.5540553793457104693110943, 0.5566055395655897985264809, 0.5591504659145946930157566, 0.5616901344622843849532002, 0.5642245213276582417822586, 0.5667536026793803239405196, 0.5692773547360034755778519, 0.5717957537661929461605442, 0.5743087760889495408586850, 0.5768163980738322976184566, 0.5793185961411806888254667, 0.5818153467623363454697137, 0.5843066264598643017272666, 0.5867924118077737578782574, 0.5892726794317383594853053, 0.5917474060093159907610475, 0.5942165682701680800580147, 0.5966801429962784154186793, 0.5991381070221714681281111, 0.6015904372351302222163013, 0.6040371105754135078618616, 0.6064781040364728366534687, 0.6089133946651687366701116, 0.6113429595619865853458987, 0.6137667758812519380899084, 0.6161848208313453506363029, 0.6185970716749166931046915, 0.6210035057290989537555048, 0.6234041003657215304299416, 0.6257988330115230076688675, 0.6281876811483634175098794, 0.6305706223134359819666081, 0.6329476340994783351992008, 0.6353186941549832233898213, 0.6376837801844086803419153, 0.6400428699483876768269192, 0.6423959412639372417070377, 0.6447429720046670528676835, 0.6470839401009874959981582, 0.6494188235403171892641570, 0.6517476003672899719207013, 0.6540702486839613549191454, 0.6563867466500144315669620, 0.6586970724829652463040876, 0.6610012044583676196647058, 0.6632991209100174274984589, 0.6655908002301563325302097, 0.6678762208696749663426270, 0.6701553613383155598710345, 0.6724282002048740205051479, 0.6746947160974014538975312, 0.6769548877034051285838219, 0.6792086937700488815250166, 0.6814561131043529626873631, 0.6836971245733933167806834, 0.6859317071045003002812397, 0.6881598396854568318705713, 0.6903815013646959744270519, 0.6925966712514979467122689, 0.6948053285161865628996815, 0.6970074523903250980984011, 0.6992030221669115780303307, 0.7013920172005734910243170, 0.7035744169077619204963997, 0.7057502007669450960906928, 0.7079193483188013616608982, 0.7100818391664115582779368, 0.7122376529754508204546805, 0.7143867694743797837842896, 0.7165291684546352021941915, 0.7186648297708199730232898, 0.7207937333408925681355609, 0.7229158591463558692887801, 0.7250311872324454059827217, 0.7271396977083169940167956, 0.7292413707472337729927181, 0.7313361865867526410034676, 0.7334241255289100847554419, 0.7355051679404074033764222, 0.7375792942527953241676460, 0.7396464849626580085640129, 0.7417067206317964465721772, 0.7437599818874112379620360, 0.7458062494222847584928838, 0.7478455039949627094612890, 0.7498777264299350488635483, 0.7519028976178163024713854, 0.7539209985155252531253957, 0.7559320101464640065565832, 0.7579359136006964320521972, 0.7599326900351259762879594, 0.7619223206736728486546595, 0.7639047868074505764130149, 0.7658800697949419280166093, 0.7678481510621742029486694, 0.7698090121028938864243967, 0.7717626344787406673165402, 0.7737089998194208176678866, 0.7756480898228799321603470, 0.7775798862554750259163361, 0.7795043709521459890141759, 0.7814215258165863961053031, 0.7833313328214136695271245, 0.7852337740083385943114429, 0.7871288314883341834944720, 0.7890164874418038921405657, 0.7908967241187491784979139, 0.7927695238389364107105941, 0.7946348689920631175175217, 0.7964927420379235813750136, 0.7983431255065737724458586, 0.8001860019984956219039900, 0.8020213541847606330100649, 0.8038491648071928284194859, 0.8056694166785310321906380, 0.8074820926825904849673728, 0.8092871757744237908160400, 0.8110846489804811942036542, 0.8128744953987701856100790, 0.8146566981990144342734272, 0.8164312406228120465742028, 0.8181981059837931485700490, 0.8199572776677767911993239, 0.8217087391329271766780945, 0.8234524739099092046215225, 0.8251884656020433364270094, 0.8269166978854597764628854, 0.8286371545092519686128428, 0.8303498192956294067327593, 0.8320546761400697575830038, 0.8337517090114702948057846, 0.8354409019522986425235764, 0.8371222390787428271411563, 0.8387957045808606359402829, 0.8404612827227282810625704, 0.8421189578425883674826439, 0.8437687143529971635802028, 0.8454105367409711729261812, 0.8470444095681330059047621, 0.8486703174708565497995875, 0.8502882451604114359791023, 0.8518981774231068028225812, 0.8535000991204343530350070, 0.8550939951892107040056078, 0.8566798506417190298715048, 0.8582576505658499939545848, 0.8598273801252419702463831, 0.8613890245594205526224495, 0.8629425691839373504743648, 0.8644879993905080694542896, 0.8660253006471498760336444, 0.8675544584983180445842596, 0.8690754585650418856970762, 0.8705882865450599544602407, 0.8720929282129545374252050, 0.8735893694202854169962281, 0.8750775960957229119854680, 0.8765575942451801930826613, 0.8780293499519448719952049, 0.8794928493768098630212838, 0.8809480787582035158255322, 0.8823950244123190181935674, 0.8838336727332430675485994, 0.8852640101930838100201983, 0.8866860233420980458621863, 0.8880996988088177000235219, 0.8895050233001755566829532, 0.8909019836016302565651375, 0.8922905665772905558628607, 0.8936707591700388455969280, 0.8950425484016539302522575, 0.8964059213729330645356690, 0.8977608652638132471078410, 0.8991073673334917701488930, 0.9004454149205460236240486, 0.9017749954430525531228459, 0.9030960963987053701523781, 0.9044087053649335137720782, 0.9057128099990178624646022, 0.9070083980382071951444166, 0.9082954572998335002127549, 0.9095739756814265315746820, 0.9108439411608276105410847, 0.9121053417963026725455006, 0.9133581657266545576127977, 0.9146024011713345435238301, 0.9158380364305531206273175, 0.9170650598853900072573273, 0.9182834599979034047218800, 0.9194932253112384908353520, 0.9206943444497351509745089, 0.9218868061190349456451742, 0.9230705991061873135537215, 0.9242457122797550091847637, 0.9254121345899187738936182, 0.9265698550685812395293315, 0.9277188628294700636112689, 0.9288591470682402950895005, 0.9299906970625759697264543, 0.9311135021722909341445515, 0.9322275518394288975917975, 0.9333328355883627104845635, 0.9344293430258928687940732, 0.9355170638413452433503852, 0.9365959878066680331449597, 0.9376661047765279417201973, 0.9387274046884055757416456, 0.9397798775626900648558921, 0.9408235135027729019444869, 0.9418583026951410028915762, 0.9428842354094689849902736, 0.9439013019987106631201510, 0.9449094928991897628355911, 0.9459087986306898495121205, 0.9468992097965434727052183, 0.9478807170837205248834878, 0.9488533112629158137054760, 0.9498169831886358470168335, 0.9507717237992848297519245, 0.9517175241172498719314184, 0.9526543752489854069548347, 0.9535822683850968193944507, 0.9545011948004232815044368, 0.9554111458541197976665483, 0.9563121129897384560011695, 0.9572040877353088863799924, 0.9580870617034179240840996, 0.9589610265912884783587268, 0.9598259741808576051234879, 0.9606818963388537831043733, 0.9615287850168733926613630, 0.9623666322514563965930439, 0.9631954301641612222071790, 0.9640151709616388439537466, 0.9648258469357060659245549, 0.9656274504634180035311332, 0.9664199740071397636802195, 0.9672034101146173227737943, 0.9679777514190476018682591, 0.9687429906391477383350273, 0.9694991205792235533724866, 0.9702461341292372147270016, 0.9709840242648740939883669, 0.9717127840476088178328839, 0.9724324066247705125950353, 0.9731428852296072415565604, 0.9738442131813496343496072, 0.9745363838852737078785517, 0.9752193908327628781730396, 0.9758932276013691625928266, 0.9765578878548735718130775, 0.9772133653433456910269459, 0.9778596539032024498104955, 0.9784967474572660801033674, 0.9791246400148212617670490, 0.9797433256716714551911835, 0.9803527986101944204270933, 0.9809530530993969223366037, 0.9815440834949686212533729, 0.9821258842393351486632952, 0.9826984498617103674201996, 0.9832617749781478160230522, 0.9838158542915913364912672, 0.9843606825919248853856025, 0.9848962547560215275335618, 0.9854225657477916120303537, 0.9859396106182301300994116, 0.9864473845054632544104222, 0.9869458826347940594679517, 0.9874351003187474227003598, 0.9879150329571141058970610, 0.9883856760369940166627304, 0.9888470251328386495802522, 0.9892990759064927068006818, 0.9897418241072348978090276, 0.9901752655718179181502248, 0.9905993962245076069415402, 0.9910142120771212830473891, 0.9914197092290652598522332, 0.9918158838673715386394944, 0.9922027322667336806727008, 0.9925802507895418581838653, 0.9929484358859170846092543, 0.9933072840937446245820355, 0.9936567920387065844051246, 0.9939969564343136839997662, 0.9943277740819362116746914, 0.9946492418708341635125525, 0.9949613567781865697596566, 0.9952641158691200113800912, 0.9955575162967363309635588, 0.9958415553021395435525955, 0.9961162302144619548145649, 0.9963815384508894965215124, 0.9966374775166862927999356, 0.9968840450052184754903082, 0.9971212385979772738362093, 0.9973490560646014135491635, 0.9975674952628988745188845, 0.9977765541388680773265018, 0.9979762307267185998745420, 0.9981665231488915727109186, 0.9983474296160799746514418, 0.9985189484272491654281575, 0.9986810779696581776171579, 0.9988338167188825964389443, 0.9989771632388403756649803, 0.9991111161818228462260355, 0.9992356742885348165163858, 0.9993508363881507486653971, 0.9994566013984000492749057, 0.9995529683257070064969677, 0.9996399362654382464576482, 0.9997175044023747284307007, 0.9997856720116889628341744, 0.9998444384611711916084367, 0.9998938032169419878731474, 0.9999337658606177711221103, 0.9999643261538894550943330, 0.9999854843850284447675914, 0.9999972450545584403516182 }, new[] { 0.0030664603092439082115513, 0.0030664314747171934849726, 0.0030663738059349007324470, 0.0030662873034393008056861, 0.0030661719680437936084028, 0.0030660278008329004477528, 0.0030658548031622538363679, 0.0030656529766585847450783, 0.0030654223232197073064431, 0.0030651628450145009692318, 0.0030648745444828901040266, 0.0030645574243358210601357, 0.0030642114875552366740338, 0.0030638367373940482295700, 0.0030634331773761048702058, 0.0030630008112961604635720, 0.0030625396432198379186545, 0.0030620496774835909559465, 0.0030615309186946633309249, 0.0030609833717310455112352, 0.0030604070417414288079918, 0.0030598019341451569616257, 0.0030591680546321751827342, 0.0030585054091629766484119, 0.0030578140039685464545661, 0.0030570938455503030247440, 0.0030563449406800369760227, 0.0030555672963998474425352, 0.0030547609200220758572342, 0.0030539258191292371925135, 0.0030530620015739486603347, 0.0030521694754788558725307, 0.0030512482492365564619779, 0.0030502983315095211653578, 0.0030493197312300123682482, 0.0030483124576000001133114, 0.0030472765200910755723677, 0.0030462119284443619831693, 0.0030451186926704230517109, 0.0030439968230491688209395, 0.0030428463301297590067471, 0.0030416672247305038021562, 0.0030404595179387621506312, 0.0030392232211108374894710, 0.0030379583458718709642643, 0.0030366649041157321154111, 0.0030353429080049070377385, 0.0030339923699703840142628, 0.0030326133027115366251721, 0.0030312057191960043331307, 0.0030297696326595705460252, 0.0030283050566060381583022, 0.0030268120048071025720655, 0.0030252904913022221991274, 0.0030237405303984864452325, 0.0030221621366704811776946, 0.0030205553249601516777118, 0.0030189201103766630786495, 0.0030172565082962582916016, 0.0030155645343621134195681, 0.0030138442044841906616068, 0.0030120955348390887083441, 0.0030103185418698906302495, 0.0030085132422860092601062, 0.0030066796530630300711306, 0.0030048177914425515522176, 0.0030029276749320230818149, 0.0030010093213045803019478, 0.0029990627485988779939449, 0.0029970879751189204574353, 0.0029950850194338893942123, 0.0029930539003779692985814, 0.0029909946370501703558363, 0.0029889072488141488505262, 0.0029867917552980250862041, 0.0029846481763941988183689, 0.0029824765322591622023349, 0.0029802768433133102577897, 0.0029780491302407488518214, 0.0029757934139891002022209, 0.0029735097157693059028890, 0.0029711980570554274731990, 0.0029688584595844444331918, 0.0029664909453560499065010, 0.0029640955366324437529314, 0.0029616722559381232326340, 0.0029592211260596712038487, 0.0029567421700455418562030, 0.0029542354112058439815854, 0.0029517008731121217846274, 0.0029491385795971332348581, 0.0029465485547546259626151, 0.0029439308229391107008170, 0.0029412854087656322747309, 0.0029386123371095381418860, 0.0029359116331062444843108, 0.0029331833221509998552933, 0.0029304274298986463828860, 0.0029276439822633785324025, 0.0029248330054184994301727, 0.0029219945257961747508486, 0.0029191285700871841705750, 0.0029162351652406703883623, 0.0029133143384638857180205, 0.0029103661172219362530391, 0.0029073905292375236068160, 0.0029043876024906842306667, 0.0029013573652185263120627, 0.0028982998459149642555740, 0.0028952150733304507490135, 0.0028921030764717064173001, 0.0028889638846014470665859, 0.0028857975272381085212091, 0.0028826040341555690560623, 0.0028793834353828694269858, 0.0028761357612039305018167, 0.0028728610421572684947521, 0.0028695593090357078067012, 0.0028662305928860914743281, 0.0028628749250089892305081, 0.0028594923369584031789413, 0.0028560828605414710856927, 0.0028526465278181672904478, 0.0028491833711010012402964, 0.0028456934229547136488796, 0.0028421767161959702837564, 0.0028386332838930533848701, 0.0028350631593655507170153, 0.0028314663761840422592303, 0.0028278429681697845340603, 0.0028241929693943925796601, 0.0028205164141795195677262, 0.0028168133370965340702726, 0.0028130837729661949782821, 0.0028093277568583240752928, 0.0028055453240914762689974, 0.0028017365102326074839556, 0.0027979013510967402185435, 0.0027940398827466267692845, 0.0027901521414924101257281, 0.0027862381638912825390663, 0.0027822979867471417676962, 0.0027783316471102450029635, 0.0027743391822768604783394, 0.0027703206297889167653083, 0.0027662760274336497592617, 0.0027622054132432473587211, 0.0027581088254944918412282, 0.0027539863027083999392661, 0.0027498378836498606195970, 0.0027456636073272705694208, 0.0027414635129921673927833, 0.0027372376401388605206822, 0.0027329860285040598383428, 0.0027287087180665020331547, 0.0027244057490465746667821, 0.0027200771619059379749851, 0.0027157229973471443987056, 0.0027113432963132558499974, 0.0027069380999874587163979, 0.0027025074497926766073634, 0.0026980513873911808464073, 0.0026935699546841987126055, 0.0026890631938115194351518, 0.0026845311471510979446691, 0.0026799738573186563850015, 0.0026753913671672833892344, 0.0026707837197870311237119, 0.0026661509585045101038391, 0.0026614931268824817854798, 0.0026568102687194489357814, 0.0026521024280492437872770, 0.0026473696491406139791397, 0.0026426119764968062894804, 0.0026378294548551481626046, 0.0026330221291866270351630, 0.0026281900446954674651512, 0.0026233332468187060677353, 0.0026184517812257642618999, 0.0026135456938180188319369, 0.0026086150307283703078113, 0.0026036598383208091684657, 0.0025986801631899798721388, 0.0025936760521607427178014, 0.0025886475522877335418257, 0.0025835947108549212540321, 0.0025785175753751632172710, 0.0025734161935897584747222, 0.0025682906134679988291122, 0.0025631408832067177780710, 0.0025579670512298373098703, 0.0025527691661879125638030, 0.0025475472769576743594882, 0.0025423014326415695994010, 0.0025370316825672995489502, 0.0025317380762873559984451, 0.0025264206635785553113127, 0.0025210794944415703629476, 0.0025157146191004603745948, 0.0025103260880021986466869, 0.0025049139518161981960773, 0.0024994782614338353016280, 0.0024940190679679709626349, 0.0024885364227524702745874, 0.0024830303773417197267843, 0.0024775009835101424263432, 0.0024719482932517112531633, 0.0024663723587794599504176, 0.0024607732325249921551741, 0.0024551509671379883737605, 0.0024495056154857109065099, 0.0024438372306525067265426, 0.0024381458659393083172574, 0.0024324315748631324732279, 0.0024266944111565770692147, 0.0024209344287673158020275, 0.0024151516818575909099866, 0.0024093462248037038747545, 0.0024035181121955041103265, 0.0023976673988358756439882, 0.0023917941397402217940673, 0.0023858983901359478493246, 0.0023799802054619417548485, 0.0023740396413680528093376, 0.0023680767537145683786720, 0.0023620915985716886306938, 0.0023560842322189992961374, 0.0023500547111449424606655, 0.0023440030920462853929883, 0.0023379294318275874140606, 0.0023318337876006648123684, 0.0023257162166840538103394, 0.0023195767766024715869239, 0.0023134155250862753614165, 0.0023072325200709195436049, 0.0023010278196964109553481, 0.0022948014823067621287099, 0.0022885535664494426857857, 0.0022822841308748288053830, 0.0022759932345356507817318, 0.0022696809365864386804193, 0.0022633472963829660967620, 0.0022569923734816920218464, 0.0022506162276392008214839, 0.0022442189188116403333494, 0.0022378005071541580875846, 0.0022313610530203356561684, 0.0022249006169616211363732, 0.0022184192597267597736437, 0.0022119170422612227292520, 0.0022053940257066339981005, 0.0021988502714001954820607, 0.0021922858408741102242558, 0.0021857007958550038097087, 0.0021790951982633439377969, 0.0021724691102128581719720, 0.0021658225940099498722195, 0.0021591557121531123157498, 0.0021524685273323410114303, 0.0021457611024285442134846, 0.0021390335005129516400021, 0.0021322857848465214018174, 0.0021255180188793451473363, 0.0021187302662500514289029, 0.0021119225907852072963166, 0.0021050950564987181231273, 0.0020982477275912256713511, 0.0020913806684495044002679, 0.0020844939436458560249764, 0.0020775876179375023304007, 0.0020706617562659762464561, 0.0020637164237565111901030, 0.0020567516857174286800274, 0.0020497676076395242297101, 0.0020427642551954515246552, 0.0020357416942391048895728, 0.0020286999908050000513193, 0.0020216392111076532034194, 0.0020145594215409583780096, 0.0020074606886775631310555, 0.0020003430792682425467160, 0.0019932066602412715667394, 0.0019860514987017956507927, 0.0019788776619311997736447, 0.0019716852173864757651327, 0.0019644742326995879988655, 0.0019572447756768374356240, 0.0019499969142982240274419, 0.0019427307167168074883601, 0.0019354462512580664378677, 0.0019281435864192559230531, 0.0019208227908687633255086, 0.0019134839334454626590447, 0.0019061270831580672642844, 0.0018987523091844809062265, 0.0018913596808711472808775, 0.0018839492677323979370705, 0.0018765211394497986196010, 0.0018690753658714940398285, 0.0018616120170115510799024, 0.0018541311630493004367905, 0.0018466328743286767122991, 0.0018391172213575569552912, 0.0018315842748070976623218, 0.0018240341055110702429247, 0.0018164667844651949558009, 0.0018088823828264733221690, 0.0018012809719125190225581, 0.0017936626232008872833327, 0.0017860274083284027592567, 0.0017783753990904859184165, 0.0017707066674404779358362, 0.0017630212854889641021349, 0.0017553193255030957535871, 0.0017476008599059107299616, 0.0017398659612756523665312, 0.0017321147023450870266539, 0.0017243471560008201813452, 0.0017165633952826110422716, 0.0017087634933826857546100, 0.0017009475236450491562317, 0.0016931155595647951096823, 0.0016852676747874154134422, 0.0016774039431081072989678, 0.0016695244384710795200224, 0.0016616292349688570408253, 0.0016537184068415843295541, 0.0016457920284763272637533, 0.0016378501744063736542136, 0.0016298929193105323938983, 0.0016219203380124312385075, 0.0016139325054798132252838, 0.0016059294968238317366751, 0.0015979113872983442154825, 0.0015898782522992045381361, 0.0015818301673635540527516, 0.0015737672081691112886347, 0.0015656894505334603439125, 0.0015575969704133379579831, 0.0015494898439039192754876, 0.0015413681472381023085203, 0.0015332319567857911038062, 0.0015250813490531776215856, 0.0015169164006820223329593, 0.0015087371884489335424584, 0.0015005437892646454426166, 0.0014923362801732949073323, 0.0014841147383516970308228, 0.0014758792411086194189814, 0.0014676298658840552399621, 0.0014593666902484950408286, 0.0014510897919021973371136, 0.0014427992486744579821480, 0.0014344951385228783230315, 0.0014261775395326321501237, 0.0014178465299157314469528, 0.0014095021880102909474427, 0.0014011445922797915073771, 0.0013927738213123422970256, 0.0013843899538199418218713, 0.0013759930686377377783877, 0.0013675832447232857518263, 0.0013591605611558067629844, 0.0013507250971354436709363, 0.0013422769319825164387192, 0.0013338161451367762689788, 0.0013253428161566586165863, 0.0013168570247185350852537, 0.0013083588506159642151809, 0.0012998483737589411687807, 0.0012913256741731463215379, 0.0012827908319991927650686, 0.0012742439274918727294554, 0.0012656850410194029319476, 0.0012571142530626688591208, 0.0012485316442144679896043, 0.0012399372951787519644928, 0.0012313312867698677125706, 0.0012227136999117975374834, 0.0012140846156373981740056, 0.0012054441150876388205601, 0.0011967922795108381551550, 0.0011881291902619003419159, 0.0011794549288015500353964, 0.0011707695766955663898644, 0.0011620732156140160807669, 0.0011533659273304853455891, 0.0011446477937213110513287, 0.0011359188967648107958214, 0.0011271793185405120501566, 0.0011184291412283803494364, 0.0011096684471080465391373, 0.0011008973185580330843445, 0.0010921158380549794491381, 0.0010833240881728665534171, 0.0010745221515822403144596, 0.0010657101110494342805238, 0.0010568880494357913638046, 0.0010480560496968846800697, 0.0010392141948817375023057, 0.0010303625681320423357186, 0.0010215012526813791214350, 0.0010126303318544325762649, 0.0010037498890662086758941, 0.0009948600078212502888805, 0.0009859607717128519688418, 0.0009770522644222739122264, 0.0009681345697179550890732, 0.0009592077714547255541688, 0.0009502719535730179460261, 0.0009413272000980781811114, 0.0009323735951391753507612, 0.0009234112228888108282347, 0.0009144401676219265933610, 0.0009054605136951127822476, 0.0008964723455458144695262, 0.0008874757476915376906225, 0.0008784708047290547115472, 0.0008694576013336085537138, 0.0008604362222581167813022, 0.0008514067523323745586954, 0.0008423692764622569855308, 0.0008333238796289207169173, 0.0008242706468880048763834, 0.0008152096633688312691343, 0.0008061410142736039032099, 0.0007970647848766078261514, 0.0007879810605234072847989, 0.0007788899266300432158601, 0.0007697914686822300749096, 0.0007606857722345520114971, 0.0007515729229096583980656, 0.0007424530063974587204051, 0.0007333261084543168373926, 0.0007241923149022446178008, 0.0007150517116280949619884, 0.0007059043845827542163241, 0.0006967504197803339882351, 0.0006875899032973623698204, 0.0006784229212719745780188, 0.0006692495599031030193850, 0.0006600699054496667875923, 0.0006508840442297606018626, 0.0006416920626198431946113, 0.0006324940470539251567018, 0.0006232900840227562488244, 0.0006140802600730121876541, 0.0006048646618064809156059, 0.0005956433758792483631993, 0.0005864164890008837132649, 0.0005771840879336241764943, 0.0005679462594915592881427, 0.0005587030905398147360662, 0.0005494546679937357307118, 0.0005402010788180699282026, 0.0005309424100261499182844, 0.0005216787486790752896494, 0.0005124101818848942860548, 0.0005031367967977850677401, 0.0004938586806172365939677, 0.0004845759205872291441124, 0.0004752886039954144966810, 0.0004659968181722957880391, 0.0004567006504904070755681, 0.0004474001883634926336095, 0.0004380955192456860150653, 0.0004287867306306889171352, 0.0004194739100509498966958, 0.0004101571450768429896514, 0.0004008365233158462997325, 0.0003915121324117206363681, 0.0003821840600436882993131, 0.0003728523939256121308821, 0.0003635172218051749865499, 0.0003541786314630598135175, 0.0003448367107121305776064, 0.0003354915473966143456333, 0.0003261432293912849189248, 0.0003167918446006485317858, 0.0003074374809581322877037, 0.0002980802264252762217455, 0.0002887201689909301727620, 0.0002793573966704570567274, 0.0002699919975049447012834, 0.0002606240595604292032823, 0.0002512536709271339139118, 0.0002418809197187298044384, 0.0002325058940716253739001, 0.0002231286821442978268308, 0.0002137493721166826096154, 0.0002043680521896465790359, 0.0001949848105845827899210, 0.0001855997355431850062940, 0.0001762129153274925249194, 0.0001668244382203495280013, 0.0001574343925265138930609, 0.0001480428665748079976500, 0.0001386499487219861751244, 0.0001292557273595155266326, 0.0001198602909254695827354, 0.0001104637279257437565603, 0.0001010661269730276014588, 0.0000916675768613669107254, 0.0000822681667164572752810, 0.0000728679863190274661367, 0.0000634671268598044229933, 0.0000540656828939400071988, 0.0000446637581285753393838, 0.0000352614859871986975067, 0.0000258591246764618586716, 0.0000164577275798968681068, 0.0000070700764101825898713 }) }, + + // Odd + { 3, new GaussPoint(3, new[] { 0.0000000000000000000000000, 0.7745966692414833770358531 }, new[] { 0.8888888888888888888888889, 0.5555555555555555555555556 }) }, + { 5, new GaussPoint(5, new[] { 0.0000000000000000000000000, 0.5384693101056830910363144, 0.9061798459386639927976269 }, new[] { 0.5688888888888888888888889, 0.4786286704993664680412915, 0.2369268850561890875142640 }) }, + { 7, new GaussPoint(7, new[] { 0.0000000000000000000000000, 0.4058451513773971669066064, 0.7415311855993944398638648, 0.9491079123427585245261897 }, new[] { 0.4179591836734693877551020, 0.3818300505051189449503698, 0.2797053914892766679014678, 0.1294849661688696932706114 }) }, + { 9, new GaussPoint(9, new[] { 0.0000000000000000000000000, 0.3242534234038089290385380, 0.6133714327005903973087020, 0.8360311073266357942994298, 0.9681602395076260898355762 }, new[] { 0.3302393550012597631645251, 0.3123470770400028400686304, 0.2606106964029354623187429, 0.1806481606948574040584720, 0.0812743883615744119718922 }) }, + { 11, new GaussPoint(11, new[] { 0.0000000000000000000000000, 0.2695431559523449723315320, 0.5190961292068118159257257, 0.7301520055740493240934163, 0.8870625997680952990751578, 0.9782286581460569928039380 }, new[] { 0.2729250867779006307144835, 0.2628045445102466621806889, 0.2331937645919904799185237, 0.1862902109277342514260976, 0.1255803694649046246346943, 0.0556685671161736664827537 }) }, + { 13, new GaussPoint(13, new[] { 0.0000000000000000000000000, 0.2304583159551347940655281, 0.4484927510364468528779129, 0.6423493394403402206439846, 0.8015780907333099127942065, 0.9175983992229779652065478, 0.9841830547185881494728294 }, new[] { 0.2325515532308739101945895, 0.2262831802628972384120902, 0.2078160475368885023125232, 0.1781459807619457382800467, 0.1388735102197872384636018, 0.0921214998377284479144218, 0.0404840047653158795200216 }) }, + { 15, new GaussPoint(15, new[] { 0.0000000000000000000000000, 0.2011940939974345223006283, 0.3941513470775633698972074, 0.5709721726085388475372267, 0.7244177313601700474161861, 0.8482065834104272162006483, 0.9372733924007059043077589, 0.9879925180204854284895657 }, new[] { 0.2025782419255612728806202, 0.1984314853271115764561183, 0.1861610000155622110268006, 0.1662692058169939335532009, 0.1395706779261543144478048, 0.1071592204671719350118695, 0.0703660474881081247092674, 0.0307532419961172683546284 }) }, + { 17, new GaussPoint(17, new[] { 0.0000000000000000000000000, 0.1784841814958478558506775, 0.3512317634538763152971855, 0.5126905370864769678862466, 0.6576711592166907658503022, 0.7815140038968014069252301, 0.8802391537269859021229557, 0.9506755217687677612227170, 0.9905754753144173356754340 }, new[] { 0.1794464703562065254582656, 0.1765627053669926463252710, 0.1680041021564500445099707, 0.1540457610768102880814316, 0.1351363684685254732863200, 0.1118838471934039710947884, 0.0850361483171791808835354, 0.0554595293739872011294402, 0.0241483028685479319601100 }) }, + { 19, new GaussPoint(19, new[] { 0.0000000000000000000000000, 0.1603586456402253758680961, 0.3165640999636298319901173, 0.4645707413759609457172671, 0.6005453046616810234696382, 0.7209661773352293786170959, 0.8227146565371428249789225, 0.9031559036148179016426609, 0.9602081521348300308527788, 0.9924068438435844031890177 }, new[] { 0.1610544498487836959791636, 0.1589688433939543476499564, 0.1527660420658596667788554, 0.1426067021736066117757461, 0.1287539625393362276755158, 0.1115666455473339947160239, 0.0914900216224499994644621, 0.0690445427376412265807083, 0.0448142267656996003328382, 0.0194617882297264770363120 }) } + }; +} + +/// +/// Contains a method to compute the Gauss-Legendre abscissas/weights and precomputed abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024. +/// +internal static partial class GaussLegendrePoint +{ + /// + /// Computes the Gauss-Legendre abscissas/weights. + /// See Pavel Holoborodko for a description of the algorithm. + /// + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. + /// Required precision to compute the abscissas/weights. 1e-10 is usually fine. + /// Object containing the non-negative abscissas/weights, order, and intervalBegin/intervalEnd. The non-negative abscissas/weights are generated over the interval [-1,1] for the given order. + internal static GaussPoint Generate(int order, double eps) + { + // Look up table for fast calculation of Legendre polynomial for n < 1024 + double[] ltbl = { 0.00000000000000000000, 0.00000000000000000000, 0.50000000000000000000, 0.66666666666666674000, 0.75000000000000000000, 0.80000000000000004000, 0.83333333333333337000, 0.85714285714285721000, 0.87500000000000000000, 0.88888888888888884000, 0.90000000000000002000, 0.90909090909090906000, 0.91666666666666663000, 0.92307692307692313000, 0.92857142857142860000, 0.93333333333333335000, 0.93750000000000000000, 0.94117647058823528000, 0.94444444444444442000, 0.94736842105263164000, 0.94999999999999996000, 0.95238095238095233000, 0.95454545454545459000, 0.95652173913043481000, 0.95833333333333337000, 0.95999999999999996000, 0.96153846153846156000, 0.96296296296296302000, 0.96428571428571430000, 0.96551724137931039000, 0.96666666666666667000, 0.96774193548387100000, 0.96875000000000000000, 0.96969696969696972000, 0.97058823529411764000, 0.97142857142857142000, 0.97222222222222221000, 0.97297297297297303000, 0.97368421052631582000, 0.97435897435897434000, 0.97499999999999998000, 0.97560975609756095000, 0.97619047619047616000, 0.97674418604651159000, 0.97727272727272729000, 0.97777777777777775000, 0.97826086956521741000, 0.97872340425531912000, 0.97916666666666663000, 0.97959183673469385000, 0.97999999999999998000, 0.98039215686274506000, 0.98076923076923073000, 0.98113207547169812000, 0.98148148148148151000, 0.98181818181818181000, 0.98214285714285710000, 0.98245614035087714000, 0.98275862068965514000, 0.98305084745762716000, 0.98333333333333328000, 0.98360655737704916000, 0.98387096774193550000, 0.98412698412698418000, 0.98437500000000000000, 0.98461538461538467000, 0.98484848484848486000, 0.98507462686567160000, 0.98529411764705888000, 0.98550724637681164000, 0.98571428571428577000, 0.98591549295774650000, 0.98611111111111116000, 0.98630136986301364000, 0.98648648648648651000, 0.98666666666666669000, 0.98684210526315785000, 0.98701298701298701000, 0.98717948717948723000, 0.98734177215189878000, 0.98750000000000004000, 0.98765432098765427000, 0.98780487804878048000, 0.98795180722891562000, 0.98809523809523814000, 0.98823529411764710000, 0.98837209302325579000, 0.98850574712643680000, 0.98863636363636365000, 0.98876404494382020000, 0.98888888888888893000, 0.98901098901098905000, 0.98913043478260865000, 0.98924731182795700000, 0.98936170212765961000, 0.98947368421052628000, 0.98958333333333337000, 0.98969072164948457000, 0.98979591836734693000, 0.98989898989898994000, 0.98999999999999999000, 0.99009900990099009000, 0.99019607843137258000, 0.99029126213592233000, 0.99038461538461542000, 0.99047619047619051000, 0.99056603773584906000, 0.99065420560747663000, 0.99074074074074070000, 0.99082568807339455000, 0.99090909090909096000, 0.99099099099099097000, 0.99107142857142860000, 0.99115044247787609000, 0.99122807017543857000, 0.99130434782608701000, 0.99137931034482762000, 0.99145299145299148000, 0.99152542372881358000, 0.99159663865546221000, 0.99166666666666670000, 0.99173553719008267000, 0.99180327868852458000, 0.99186991869918695000, 0.99193548387096775000, 0.99199999999999999000, 0.99206349206349209000, 0.99212598425196852000, 0.99218750000000000000, 0.99224806201550386000, 0.99230769230769234000, 0.99236641221374045000, 0.99242424242424243000, 0.99248120300751874000, 0.99253731343283580000, 0.99259259259259258000, 0.99264705882352944000, 0.99270072992700731000, 0.99275362318840576000, 0.99280575539568350000, 0.99285714285714288000, 0.99290780141843971000, 0.99295774647887325000, 0.99300699300699302000, 0.99305555555555558000, 0.99310344827586206000, 0.99315068493150682000, 0.99319727891156462000, 0.99324324324324320000, 0.99328859060402686000, 0.99333333333333329000, 0.99337748344370858000, 0.99342105263157898000, 0.99346405228758172000, 0.99350649350649356000, 0.99354838709677418000, 0.99358974358974361000, 0.99363057324840764000, 0.99367088607594933000, 0.99371069182389937000, 0.99375000000000002000, 0.99378881987577639000, 0.99382716049382713000, 0.99386503067484666000, 0.99390243902439024000, 0.99393939393939390000, 0.99397590361445787000, 0.99401197604790414000, 0.99404761904761907000, 0.99408284023668636000, 0.99411764705882355000, 0.99415204678362579000, 0.99418604651162790000, 0.99421965317919070000, 0.99425287356321834000, 0.99428571428571433000, 0.99431818181818177000, 0.99435028248587576000, 0.99438202247191010000, 0.99441340782122900000, 0.99444444444444446000, 0.99447513812154698000, 0.99450549450549453000, 0.99453551912568305000, 0.99456521739130432000, 0.99459459459459465000, 0.99462365591397850000, 0.99465240641711228000, 0.99468085106382975000, 0.99470899470899465000, 0.99473684210526314000, 0.99476439790575921000, 0.99479166666666663000, 0.99481865284974091000, 0.99484536082474229000, 0.99487179487179489000, 0.99489795918367352000, 0.99492385786802029000, 0.99494949494949492000, 0.99497487437185927000, 0.99500000000000000000, 0.99502487562189057000, 0.99504950495049505000, 0.99507389162561577000, 0.99509803921568629000, 0.99512195121951219000, 0.99514563106796117000, 0.99516908212560384000, 0.99519230769230771000, 0.99521531100478466000, 0.99523809523809526000, 0.99526066350710896000, 0.99528301886792447000, 0.99530516431924887000, 0.99532710280373837000, 0.99534883720930234000, 0.99537037037037035000, 0.99539170506912444000, 0.99541284403669728000, 0.99543378995433796000, 0.99545454545454548000, 0.99547511312217196000, 0.99549549549549554000, 0.99551569506726456000, 0.99553571428571430000, 0.99555555555555553000, 0.99557522123893805000, 0.99559471365638763000, 0.99561403508771928000, 0.99563318777292575000, 0.99565217391304350000, 0.99567099567099571000, 0.99568965517241381000, 0.99570815450643779000, 0.99572649572649574000, 0.99574468085106382000, 0.99576271186440679000, 0.99578059071729963000, 0.99579831932773111000, 0.99581589958159000000, 0.99583333333333335000, 0.99585062240663902000, 0.99586776859504134000, 0.99588477366255146000, 0.99590163934426235000, 0.99591836734693873000, 0.99593495934959353000, 0.99595141700404854000, 0.99596774193548387000, 0.99598393574297184000, 0.99600000000000000000, 0.99601593625498008000, 0.99603174603174605000, 0.99604743083003955000, 0.99606299212598426000, 0.99607843137254903000, 0.99609375000000000000, 0.99610894941634243000, 0.99612403100775193000, 0.99613899613899615000, 0.99615384615384617000, 0.99616858237547889000, 0.99618320610687028000, 0.99619771863117867000, 0.99621212121212122000, 0.99622641509433962000, 0.99624060150375937000, 0.99625468164794007000, 0.99626865671641796000, 0.99628252788104088000, 0.99629629629629635000, 0.99630996309963105000, 0.99632352941176472000, 0.99633699633699635000, 0.99635036496350360000, 0.99636363636363634000, 0.99637681159420288000, 0.99638989169675085000, 0.99640287769784175000, 0.99641577060931896000, 0.99642857142857144000, 0.99644128113879005000, 0.99645390070921991000, 0.99646643109540634000, 0.99647887323943662000, 0.99649122807017543000, 0.99650349650349646000, 0.99651567944250874000, 0.99652777777777779000, 0.99653979238754320000, 0.99655172413793103000, 0.99656357388316152000, 0.99657534246575341000, 0.99658703071672350000, 0.99659863945578231000, 0.99661016949152548000, 0.99662162162162160000, 0.99663299663299665000, 0.99664429530201337000, 0.99665551839464883000, 0.99666666666666670000, 0.99667774086378735000, 0.99668874172185429000, 0.99669966996699666000, 0.99671052631578949000, 0.99672131147540988000, 0.99673202614379086000, 0.99674267100977199000, 0.99675324675324672000, 0.99676375404530748000, 0.99677419354838714000, 0.99678456591639875000, 0.99679487179487181000, 0.99680511182108622000, 0.99681528662420382000, 0.99682539682539684000, 0.99683544303797467000, 0.99684542586750791000, 0.99685534591194969000, 0.99686520376175547000, 0.99687499999999996000, 0.99688473520249221000, 0.99689440993788825000, 0.99690402476780182000, 0.99691358024691357000, 0.99692307692307691000, 0.99693251533742333000, 0.99694189602446481000, 0.99695121951219512000, 0.99696048632218848000, 0.99696969696969695000, 0.99697885196374625000, 0.99698795180722888000, 0.99699699699699695000, 0.99700598802395213000, 0.99701492537313430000, 0.99702380952380953000, 0.99703264094955490000, 0.99704142011834318000, 0.99705014749262533000, 0.99705882352941178000, 0.99706744868035191000, 0.99707602339181289000, 0.99708454810495628000, 0.99709302325581395000, 0.99710144927536237000, 0.99710982658959535000, 0.99711815561959649000, 0.99712643678160917000, 0.99713467048710602000, 0.99714285714285711000, 0.99715099715099720000, 0.99715909090909094000, 0.99716713881019825000, 0.99717514124293782000, 0.99718309859154930000, 0.99719101123595510000, 0.99719887955182074000, 0.99720670391061450000, 0.99721448467966578000, 0.99722222222222223000, 0.99722991689750695000, 0.99723756906077343000, 0.99724517906336085000, 0.99725274725274726000, 0.99726027397260275000, 0.99726775956284153000, 0.99727520435967298000, 0.99728260869565222000, 0.99728997289972898000, 0.99729729729729732000, 0.99730458221024254000, 0.99731182795698925000, 0.99731903485254692000, 0.99732620320855614000, 0.99733333333333329000, 0.99734042553191493000, 0.99734748010610075000, 0.99735449735449733000, 0.99736147757255933000, 0.99736842105263157000, 0.99737532808398954000, 0.99738219895287961000, 0.99738903394255873000, 0.99739583333333337000, 0.99740259740259740000, 0.99740932642487046000, 0.99741602067183466000, 0.99742268041237114000, 0.99742930591259638000, 0.99743589743589745000, 0.99744245524296671000, 0.99744897959183676000, 0.99745547073791352000, 0.99746192893401020000, 0.99746835443037973000, 0.99747474747474751000, 0.99748110831234260000, 0.99748743718592969000, 0.99749373433583965000, 0.99750000000000005000, 0.99750623441396513000, 0.99751243781094523000, 0.99751861042183620000, 0.99752475247524752000, 0.99753086419753090000, 0.99753694581280783000, 0.99754299754299758000, 0.99754901960784315000, 0.99755501222493892000, 0.99756097560975610000, 0.99756690997566910000, 0.99757281553398058000, 0.99757869249394671000, 0.99758454106280192000, 0.99759036144578317000, 0.99759615384615385000, 0.99760191846522783000, 0.99760765550239239000, 0.99761336515513122000, 0.99761904761904763000, 0.99762470308788598000, 0.99763033175355453000, 0.99763593380614657000, 0.99764150943396224000, 0.99764705882352944000, 0.99765258215962438000, 0.99765807962529274000, 0.99766355140186913000, 0.99766899766899764000, 0.99767441860465111000, 0.99767981438515085000, 0.99768518518518523000, 0.99769053117782913000, 0.99769585253456217000, 0.99770114942528731000, 0.99770642201834858000, 0.99771167048054921000, 0.99771689497716898000, 0.99772209567198178000, 0.99772727272727268000, 0.99773242630385484000, 0.99773755656108598000, 0.99774266365688491000, 0.99774774774774777000, 0.99775280898876406000, 0.99775784753363228000, 0.99776286353467558000, 0.99776785714285710000, 0.99777282850779514000, 0.99777777777777776000, 0.99778270509977829000, 0.99778761061946908000, 0.99779249448123619000, 0.99779735682819382000, 0.99780219780219781000, 0.99780701754385970000, 0.99781181619256021000, 0.99781659388646293000, 0.99782135076252720000, 0.99782608695652175000, 0.99783080260303691000, 0.99783549783549785000, 0.99784017278617709000, 0.99784482758620685000, 0.99784946236559136000, 0.99785407725321884000, 0.99785867237687365000, 0.99786324786324787000, 0.99786780383795304000, 0.99787234042553197000, 0.99787685774946921000, 0.99788135593220340000, 0.99788583509513740000, 0.99789029535864981000, 0.99789473684210528000, 0.99789915966386555000, 0.99790356394129975000, 0.99790794979079500000, 0.99791231732776620000, 0.99791666666666667000, 0.99792099792099798000, 0.99792531120331951000, 0.99792960662525876000, 0.99793388429752061000, 0.99793814432989691000, 0.99794238683127567000, 0.99794661190965095000, 0.99795081967213117000, 0.99795501022494892000, 0.99795918367346936000, 0.99796334012219956000, 0.99796747967479671000, 0.99797160243407712000, 0.99797570850202433000, 0.99797979797979797000, 0.99798387096774188000, 0.99798792756539234000, 0.99799196787148592000, 0.99799599198396793000, 0.99800000000000000000, 0.99800399201596801000, 0.99800796812749004000, 0.99801192842942343000, 0.99801587301587302000, 0.99801980198019802000, 0.99802371541501977000, 0.99802761341222879000, 0.99803149606299213000, 0.99803536345776034000, 0.99803921568627452000, 0.99804305283757344000, 0.99804687500000000000, 0.99805068226120852000, 0.99805447470817121000, 0.99805825242718449000, 0.99806201550387597000, 0.99806576402321079000, 0.99806949806949807000, 0.99807321772639690000, 0.99807692307692308000, 0.99808061420345484000, 0.99808429118773945000, 0.99808795411089868000, 0.99809160305343514000, 0.99809523809523815000, 0.99809885931558939000, 0.99810246679316883000, 0.99810606060606055000, 0.99810964083175802000, 0.99811320754716981000, 0.99811676082862522000, 0.99812030075187974000, 0.99812382739212002000, 0.99812734082397003000, 0.99813084112149530000, 0.99813432835820892000, 0.99813780260707630000, 0.99814126394052050000, 0.99814471243042668000, 0.99814814814814812000, 0.99815157116451014000, 0.99815498154981552000, 0.99815837937384899000, 0.99816176470588236000, 0.99816513761467895000, 0.99816849816849818000, 0.99817184643510060000, 0.99817518248175185000, 0.99817850637522765000, 0.99818181818181817000, 0.99818511796733211000, 0.99818840579710144000, 0.99819168173598549000, 0.99819494584837543000, 0.99819819819819822000, 0.99820143884892087000, 0.99820466786355477000, 0.99820788530465954000, 0.99821109123434704000, 0.99821428571428572000, 0.99821746880570406000, 0.99822064056939497000, 0.99822380106571940000, 0.99822695035460995000, 0.99823008849557526000, 0.99823321554770317000, 0.99823633156966496000, 0.99823943661971826000, 0.99824253075571179000, 0.99824561403508771000, 0.99824868651488619000, 0.99825174825174823000, 0.99825479930191974000, 0.99825783972125437000, 0.99826086956521742000, 0.99826388888888884000, 0.99826689774696709000, 0.99826989619377160000, 0.99827288428324701000, 0.99827586206896557000, 0.99827882960413084000, 0.99828178694158076000, 0.99828473413379071000, 0.99828767123287676000, 0.99829059829059830000, 0.99829351535836175000, 0.99829642248722317000, 0.99829931972789121000, 0.99830220713073003000, 0.99830508474576274000, 0.99830795262267347000, 0.99831081081081086000, 0.99831365935919059000, 0.99831649831649827000, 0.99831932773109244000, 0.99832214765100669000, 0.99832495812395305000, 0.99832775919732442000, 0.99833055091819700000, 0.99833333333333329000, 0.99833610648918469000, 0.99833887043189373000, 0.99834162520729686000, 0.99834437086092720000, 0.99834710743801658000, 0.99834983498349839000, 0.99835255354200991000, 0.99835526315789469000, 0.99835796387520526000, 0.99836065573770494000, 0.99836333878887074000, 0.99836601307189543000, 0.99836867862969003000, 0.99837133550488599000, 0.99837398373983743000, 0.99837662337662336000, 0.99837925445705022000, 0.99838187702265369000, 0.99838449111470118000, 0.99838709677419357000, 0.99838969404186795000, 0.99839228295819937000, 0.99839486356340290000, 0.99839743589743590000, 0.99839999999999995000, 0.99840255591054317000, 0.99840510366826152000, 0.99840764331210186000, 0.99841017488076311000, 0.99841269841269842000, 0.99841521394611732000, 0.99841772151898733000, 0.99842022116903628000, 0.99842271293375395000, 0.99842519685039366000, 0.99842767295597479000, 0.99843014128728413000, 0.99843260188087779000, 0.99843505477308292000, 0.99843749999999998000, 0.99843993759750393000, 0.99844236760124616000, 0.99844479004665632000, 0.99844720496894412000, 0.99844961240310082000, 0.99845201238390091000, 0.99845440494590421000, 0.99845679012345678000, 0.99845916795069334000, 0.99846153846153851000, 0.99846390168970811000, 0.99846625766871167000, 0.99846860643185298000, 0.99847094801223246000, 0.99847328244274813000, 0.99847560975609762000, 0.99847792998477924000, 0.99848024316109418000, 0.99848254931714719000, 0.99848484848484853000, 0.99848714069591527000, 0.99848942598187307000, 0.99849170437405732000, 0.99849397590361444000, 0.99849624060150377000, 0.99849849849849848000, 0.99850074962518742000, 0.99850299401197606000, 0.99850523168908822000, 0.99850746268656720000, 0.99850968703427723000, 0.99851190476190477000, 0.99851411589895989000, 0.99851632047477745000, 0.99851851851851847000, 0.99852071005917165000, 0.99852289512555392000, 0.99852507374631272000, 0.99852724594992637000, 0.99852941176470589000, 0.99853157121879588000, 0.99853372434017595000, 0.99853587115666176000, 0.99853801169590639000, 0.99854014598540142000, 0.99854227405247808000, 0.99854439592430855000, 0.99854651162790697000, 0.99854862119013066000, 0.99855072463768113000, 0.99855282199710560000, 0.99855491329479773000, 0.99855699855699853000, 0.99855907780979825000, 0.99856115107913668000, 0.99856321839080464000, 0.99856527977044474000, 0.99856733524355301000, 0.99856938483547930000, 0.99857142857142855000, 0.99857346647646217000, 0.99857549857549854000, 0.99857752489331442000, 0.99857954545454541000, 0.99858156028368794000, 0.99858356940509918000, 0.99858557284299854000, 0.99858757062146897000, 0.99858956276445698000, 0.99859154929577465000, 0.99859353023909991000, 0.99859550561797750000, 0.99859747545582045000, 0.99859943977591037000, 0.99860139860139863000, 0.99860335195530725000, 0.99860529986053004000, 0.99860724233983289000, 0.99860917941585536000, 0.99861111111111112000, 0.99861303744798890000, 0.99861495844875348000, 0.99861687413554634000, 0.99861878453038677000, 0.99862068965517237000, 0.99862258953168048000, 0.99862448418156813000, 0.99862637362637363000, 0.99862825788751719000, 0.99863013698630132000, 0.99863201094391241000, 0.99863387978142082000, 0.99863574351978168000, 0.99863760217983655000, 0.99863945578231295000, 0.99864130434782605000, 0.99864314789687925000, 0.99864498644986455000, 0.99864682002706362000, 0.99864864864864866000, 0.99865047233468285000, 0.99865229110512133000, 0.99865410497981155000, 0.99865591397849462000, 0.99865771812080539000, 0.99865951742627346000, 0.99866131191432395000, 0.99866310160427807000, 0.99866488651535379000, 0.99866666666666670000, 0.99866844207723038000, 0.99867021276595747000, 0.99867197875166003000, 0.99867374005305043000, 0.99867549668874167000, 0.99867724867724872000, 0.99867899603698806000, 0.99868073878627972000, 0.99868247694334655000, 0.99868421052631584000, 0.99868593955321949000, 0.99868766404199472000, 0.99868938401048490000, 0.99869109947643975000, 0.99869281045751634000, 0.99869451697127942000, 0.99869621903520212000, 0.99869791666666663000, 0.99869960988296491000, 0.99870129870129876000, 0.99870298313878081000, 0.99870466321243523000, 0.99870633893919791000, 0.99870801033591727000, 0.99870967741935479000, 0.99871134020618557000, 0.99871299871299868000, 0.99871465295629824000, 0.99871630295250324000, 0.99871794871794872000, 0.99871959026888601000, 0.99872122762148341000, 0.99872286079182626000, 0.99872448979591832000, 0.99872611464968153000, 0.99872773536895676000, 0.99872935196950441000, 0.99873096446700504000, 0.99873257287705952000, 0.99873417721518987000, 0.99873577749683939000, 0.99873737373737370000, 0.99873896595208067000, 0.99874055415617125000, 0.99874213836477987000, 0.99874371859296485000, 0.99874529485570895000, 0.99874686716791983000, 0.99874843554443049000, 0.99875000000000003000, 0.99875156054931336000, 0.99875311720698257000, 0.99875466998754669000, 0.99875621890547261000, 0.99875776397515525000, 0.99875930521091816000, 0.99876084262701359000, 0.99876237623762376000, 0.99876390605686027000, 0.99876543209876545000, 0.99876695437731200000, 0.99876847290640391000, 0.99876998769987702000, 0.99877149877149873000, 0.99877300613496933000, 0.99877450980392157000, 0.99877600979192172000, 0.99877750611246940000, 0.99877899877899878000, 0.99878048780487805000, 0.99878197320341044000, 0.99878345498783450000, 0.99878493317132444000, 0.99878640776699024000, 0.99878787878787878000, 0.99878934624697335000, 0.99879081015719473000, 0.99879227053140096000, 0.99879372738238847000, 0.99879518072289153000, 0.99879663056558365000, 0.99879807692307687000, 0.99879951980792314000, 0.99880095923261392000, 0.99880239520958081000, 0.99880382775119614000, 0.99880525686977295000, 0.99880668257756566000, 0.99880810488676997000, 0.99880952380952381000, 0.99881093935790721000, 0.99881235154394299000, 0.99881376037959668000, 0.99881516587677721000, 0.99881656804733732000, 0.99881796690307334000, 0.99881936245572611000, 0.99882075471698117000, 0.99882214369846878000, 0.99882352941176467000, 0.99882491186839018000, 0.99882629107981225000, 0.99882766705744430000, 0.99882903981264637000, 0.99883040935672518000, 0.99883177570093462000, 0.99883313885647607000, 0.99883449883449882000, 0.99883585564610011000, 0.99883720930232556000, 0.99883855981416958000, 0.99883990719257543000, 0.99884125144843572000, 0.99884259259259256000, 0.99884393063583810000, 0.99884526558891451000, 0.99884659746251436000, 0.99884792626728114000, 0.99884925201380903000, 0.99885057471264371000, 0.99885189437428246000, 0.99885321100917435000, 0.99885452462772051000, 0.99885583524027455000, 0.99885714285714289000, 0.99885844748858443000, 0.99885974914481190000, 0.99886104783599083000, 0.99886234357224113000, 0.99886363636363640000, 0.99886492622020429000, 0.99886621315192747000, 0.99886749716874290000, 0.99886877828054299000, 0.99887005649717520000, 0.99887133182844245000, 0.99887260428410374000, 0.99887387387387383000, 0.99887514060742411000, 0.99887640449438198000, 0.99887766554433222000, 0.99887892376681620000, 0.99888017917133254000, 0.99888143176733779000, 0.99888268156424576000, 0.99888392857142860000, 0.99888517279821631000, 0.99888641425389757000, 0.99888765294771964000, 0.99888888888888894000, 0.99889012208657046000, 0.99889135254988914000, 0.99889258028792915000, 0.99889380530973448000, 0.99889502762430937000, 0.99889624724061810000, 0.99889746416758540000, 0.99889867841409696000, 0.99889988998899892000, 0.99890109890109891000, 0.99890230515916578000, 0.99890350877192979000, 0.99890470974808321000, 0.99890590809628010000, 0.99890710382513659000, 0.99890829694323147000, 0.99890948745910579000, 0.99891067538126366000, 0.99891186071817195000, 0.99891304347826082000, 0.99891422366992400000, 0.99891540130151846000, 0.99891657638136511000, 0.99891774891774887000, 0.99891891891891893000, 0.99892008639308860000, 0.99892125134843579000, 0.99892241379310343000, 0.99892357373519913000, 0.99892473118279568000, 0.99892588614393130000, 0.99892703862660948000, 0.99892818863879962000, 0.99892933618843682000, 0.99893048128342243000, 0.99893162393162394000, 0.99893276414087517000, 0.99893390191897657000, 0.99893503727369537000, 0.99893617021276593000, 0.99893730074388953000, 0.99893842887473461000, 0.99893955461293749000, 0.99894067796610164000, 0.99894179894179891000, 0.99894291754756870000, 0.99894403379091867000, 0.99894514767932485000, 0.99894625922023184000, 0.99894736842105258000, 0.99894847528916930000, 0.99894957983193278000, 0.99895068205666315000, 0.99895178197064993000, 0.99895287958115186000, 0.99895397489539750000, 0.99895506792058519000, 0.99895615866388310000, 0.99895724713242962000, 0.99895833333333328000, 0.99895941727367321000, 0.99896049896049899000, 0.99896157840083077000, 0.99896265560165975000, 0.99896373056994814000, 0.99896480331262938000, 0.99896587383660806000, 0.99896694214876036000, 0.99896800825593390000, 0.99896907216494846000, 0.99897013388259526000, 0.99897119341563789000, 0.99897225077081198000, 0.99897330595482547000, 0.99897435897435893000, 0.99897540983606559000, 0.99897645854657113000, 0.99897750511247441000, 0.99897854954034726000, 0.99897959183673468000, 0.99898063200815490000, 0.99898167006109984000, 0.99898270600203454000, 0.99898373983739841000, 0.99898477157360410000, 0.99898580121703850000, 0.99898682877406286000, 0.99898785425101211000, 0.99898887765419619000, 0.99898989898989898000, 0.99899091826437947000, 0.99899193548387100000, 0.99899295065458205000, 0.99899396378269623000, 0.99899497487437183000, 0.99899598393574296000, 0.99899699097291872000, 0.99899799599198402000, 0.99899899899899902000, 0.99900000000000000000, 0.99900099900099903000, 0.99900199600798401000, 0.99900299102691925000, 0.99900398406374502000, 0.99900497512437814000, 0.99900596421471177000, 0.99900695134061568000, 0.99900793650793651000, 0.99900891972249750000, 0.99900990099009901000, 0.99901088031651830000, 0.99901185770750989000, 0.99901283316880551000, 0.99901380670611439000, 0.99901477832512320000, 0.99901574803149606000, 0.99901671583087515000, 0.99901768172888017000, 0.99901864573110888000, 0.99901960784313726000, 0.99902056807051909000, 0.99902152641878672000, 0.99902248289345064000 }; + const double pi = 3.1415926535897932384626433832795028841971693993751; + + double w0 = 0; // Weights + int m = (order + 1) >> 1; + double[] abscissas = new double[m]; + double[] weights = new double[m]; + double t0 = 1.0 - (1.0 - 1.0 / order) / (8.0 * order * order); + double t1 = 1.0 / (4.0 * order + 2.0); + + // Find ith root of Legendre polynomial + for (int i = 1; i <= m; i++) + { + int j = 0; + double x0 = Math.Cos(pi * ((i << 2) - 1) * t1) * t0; // Initial guess + double x1, dx; // Abscissas + double w1, dw; // Weights + + // Newton iterations, at least one + do + { + // Legendre polynomial values + double p1 = 1.0; + double p0 = x0; + double p2; + int k; + double t2; + + // Optimized version using lookup tables + if (order < 1024) + { + // Use fast algorithm for small orders + for (k = 2; k <= order; k++) + { + p2 = p1; + p1 = p0; + t2 = x0 * p1; + + p0 = t2 + ltbl[k] * (t2 - p2); + } + } + else + { + // Use general algorithm for other orders + for (k = 2; k < 1024; k++) + { + p2 = p1; + p1 = p0; + t2 = x0 * p1; + + p0 = t2 + ltbl[k] * (t2 - p2); + } + + for (k = 1024; k <= order; k++) + { + p2 = p1; + p1 = p0; + t2 = x0 * p1; + double t3 = (k - 1.0) / k; + + p0 = t2 + t3 * (t2 - p2); + } + } + + double dpdx = ((x0 * p0 - p1) * order) / (x0 * x0 - 1.0); // Compute Legendre polynomial derivative at x0 + + x1 = x0 - p0 / dpdx; // Newton step + + w1 = 2.0 / ((1.0 - x1 * x1) * dpdx * dpdx); // Weight computing + + // Compute weight w0 on first iteration, needed for dw + if (j == 0) + { + w0 = 2.0 / ((1.0 - x0 * x0) * dpdx * dpdx); + } + + dx = x0 - x1; + dw = w0 - w1; + + x0 = x1; + w0 = w1; + j++; + } + while ((Math.Abs(dx) > eps || Math.Abs(dw) > eps) && j < 100); + + int index = (m - 1) - (i - 1); + + abscissas[index] = x1; + weights[index] = w1; + } + + return new GaussPoint(order, abscissas, weights); + } +} diff --git a/MathNet.Numerics/Integration/GaussRule/GaussLegendrePointFactory.cs b/MathNet.Numerics/Integration/GaussRule/GaussLegendrePointFactory.cs new file mode 100644 index 0000000..cfd7303 --- /dev/null +++ b/MathNet.Numerics/Integration/GaussRule/GaussLegendrePointFactory.cs @@ -0,0 +1,107 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Integration.GaussRule; + +/// +/// Creates and maps a Gauss-Legendre point. +/// +internal static class GaussLegendrePointFactory +{ + [ThreadStatic] + private static GaussPoint _gaussLegendrePoint; + + /// + /// Getter for the GaussPoint. + /// + /// Defines an Nth order Gauss-Legendre rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Object containing the non-negative abscissas/weights, order, and intervalBegin/intervalEnd. The non-negative abscissas/weights are generated over the interval [-1,1] for the given order. + public static GaussPoint GetGaussPoint(int order) + { + // Try to get the GaussPoint from the cached static field. + bool gaussLegendrePointIsCached = _gaussLegendrePoint != null && _gaussLegendrePoint.Order == order; + if (!gaussLegendrePointIsCached) + { + // Try to find the GaussPoint in the precomputed dictionary. + if (!GaussLegendrePoint.PreComputed.TryGetValue(order, out _gaussLegendrePoint)) + { + _gaussLegendrePoint = GaussLegendrePoint.Generate(order, 1e-10); // Generate the GaussPoint on the fly. + } + } + + return _gaussLegendrePoint; + } + + /// + /// Getter for the GaussPoint. + /// + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Defines an Nth order Gauss-Legendre rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Object containing the abscissas/weights, order, and intervalBegin/intervalEnd. + public static GaussPoint GetGaussPoint(double intervalBegin, double intervalEnd, int order) + { + return Map(intervalBegin, intervalEnd, GetGaussPoint(order)); + } + + /// + /// Maps the non-negative abscissas/weights from the interval [-1, 1] to the interval [intervalBegin, intervalEnd]. + /// + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Object containing the non-negative abscissas/weights, order, and intervalBegin/intervalEnd. The non-negative abscissas/weights are generated over the interval [-1,1] for the given order. + /// Object containing the abscissas/weights, order, and intervalBegin/intervalEnd. + private static GaussPoint Map(double intervalBegin, double intervalEnd, GaussPoint gaussPoint) + { + double[] abscissas = new double[gaussPoint.Order]; + double[] weights = new double[gaussPoint.Order]; + + double a = 0.5 * (intervalEnd - intervalBegin); + double b = 0.5 * (intervalEnd + intervalBegin); + + int m = (gaussPoint.Order + 1) >> 1; + + for (int i = 1; i <= m; i++) + { + int index1 = gaussPoint.Order - i; + int index2 = i - 1; + int index3 = m - i; + + abscissas[index1] = gaussPoint.Abscissas[index3] * a + b; + abscissas[index2] = -gaussPoint.Abscissas[index3] * a + b; + + weights[index1] = gaussPoint.Weights[index3] * a; + weights[index2] = gaussPoint.Weights[index3] * a; + } + + return new GaussPoint(intervalBegin, intervalEnd, gaussPoint.Order, abscissas, weights); + } +} diff --git a/MathNet.Numerics/Integration/GaussRule/GaussPoint.cs b/MathNet.Numerics/Integration/GaussRule/GaussPoint.cs new file mode 100644 index 0000000..7fd3bd8 --- /dev/null +++ b/MathNet.Numerics/Integration/GaussRule/GaussPoint.cs @@ -0,0 +1,59 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Integration.GaussRule; + +/// +/// Contains the abscissas/weights, order, and intervalBegin/intervalEnd. +/// +internal class GaussPoint +{ + internal double[] Abscissas { get; private set; } + + internal double[] Weights { get; private set; } + + internal double IntervalBegin { get; private set; } + + internal double IntervalEnd { get; private set; } + + internal int Order { get; private set; } + + internal GaussPoint(double intervalBegin, double intervalEnd, int order, double[] abscissas, double[] weights) + { + Abscissas = abscissas; + Weights = weights; + IntervalBegin = intervalBegin; + IntervalEnd = intervalEnd; + Order = order; + } + + internal GaussPoint(int order, double[] abscissas, double[] weights) : this(-1, 1, order, abscissas, weights) + { + } +} \ No newline at end of file diff --git a/MathNet.Numerics/Integration/GaussRule/GaussPointPair.cs b/MathNet.Numerics/Integration/GaussRule/GaussPointPair.cs new file mode 100644 index 0000000..c40d933 --- /dev/null +++ b/MathNet.Numerics/Integration/GaussRule/GaussPointPair.cs @@ -0,0 +1,39 @@ +namespace MathNet.Numerics.Integration.GaussRule; + +/// +/// Contains two GaussPoint. +/// +internal class GaussPointPair +{ + internal int Order { get; private set; } + + internal double[] Abscissas { get; private set; } + + internal double[] Weights { get; private set; } + + internal int SecondOrder { get; private set; } + + internal double[] SecondAbscissas { get; private set; } + + internal double[] SecondWeights { get; private set; } + + internal double IntervalBegin { get; private set; } + + internal double IntervalEnd { get; private set; } + + internal GaussPointPair(double intervalBegin, double intervalEnd, int order, double[] abscissas, double[] weights, int secondOrder, double[] secondAbscissas, double[] secondWeights) + { + IntervalBegin = intervalBegin; + IntervalEnd = intervalEnd; + Order = order; + Abscissas = abscissas; + Weights = weights; + SecondOrder = secondOrder; + SecondAbscissas = secondAbscissas; + SecondWeights = secondWeights; + } + + internal GaussPointPair(int order, double[] abscissas, double[] weights, int secondOrder, double[] secondWeights) + : this(-1, 1, order, abscissas, weights, secondOrder, null, secondWeights) + { } +} diff --git a/MathNet.Numerics/Integration/NewtonCotesTrapeziumRule.cs b/MathNet.Numerics/Integration/NewtonCotesTrapeziumRule.cs new file mode 100644 index 0000000..bbdeae2 --- /dev/null +++ b/MathNet.Numerics/Integration/NewtonCotesTrapeziumRule.cs @@ -0,0 +1,426 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace MathNet.Numerics.Integration; + +/// +/// Approximation algorithm for definite integrals by the Trapezium rule of the Newton-Cotes family. +/// +/// +/// Wikipedia - Trapezium Rule +/// +public static class NewtonCotesTrapeziumRule +{ + /// + /// Direct 2-point approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Approximation of the finite integral in the given interval. + public static double IntegrateTwoPoint(Func f, double intervalBegin, double intervalEnd) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + return (intervalEnd - intervalBegin) / 2 * (f(intervalBegin) + f(intervalEnd)); + } + + /// + /// Direct 2-point approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth complex function to integrate, defined on real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrateTwoPoint(Func f, double intervalBegin, double intervalEnd) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + return (intervalEnd - intervalBegin) / 2 * (f(intervalBegin) + f(intervalEnd)); + } + + /// + /// Composite N-point approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Number of composite subdivision partitions. + /// Approximation of the finite integral in the given interval. + public static double IntegrateComposite(Func f, double intervalBegin, double intervalEnd, int numberOfPartitions) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (numberOfPartitions <= 0) + { + throw new ArgumentOutOfRangeException(nameof(numberOfPartitions), Resources.ArgumentPositive); + } + + double step = (intervalEnd - intervalBegin) / numberOfPartitions; + + double offset = step; + double sum = 0.5 * (f(intervalBegin) + f(intervalEnd)); + for (int i = 0; i < numberOfPartitions - 1; i++) + { + // NOTE (ruegg, 2009-01-07): Do not combine intervalBegin and offset (numerical stability!) + sum += f(intervalBegin + offset); + offset += step; + } + + return step * sum; + } + + /// + /// Composite N-point approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth complex function to integrate, defined on real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Number of composite subdivision partitions. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrateComposite(Func f, double intervalBegin, double intervalEnd, int numberOfPartitions) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (numberOfPartitions <= 0) + { + throw new ArgumentOutOfRangeException(nameof(numberOfPartitions), Resources.ArgumentPositive); + } + + double step = (intervalEnd - intervalBegin) / numberOfPartitions; + + double offset = step; + Complex sum = 0.5 * (f(intervalBegin) + f(intervalEnd)); + for (int i = 0; i < numberOfPartitions - 1; i++) + { + // NOTE (ruegg, 2009-01-07): Do not combine intervalBegin and offset (numerical stability!) + sum += f(intervalBegin + offset); + offset += step; + } + + return step * sum; + } + + /// + /// Adaptive approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// The expected accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static double IntegrateAdaptive(Func f, double intervalBegin, double intervalEnd, double targetError) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + int numberOfPartitions = 1; + double step = intervalEnd - intervalBegin; + double sum = 0.5 * step * (f(intervalBegin) + f(intervalEnd)); + for (int k = 0; k < 20; k++) + { + double midpointsum = 0; + for (int i = 0; i < numberOfPartitions; i++) + { + midpointsum += f(intervalBegin + ((i + 0.5) * step)); + } + + midpointsum *= step; + sum = 0.5 * (sum + midpointsum); + step *= 0.5; + numberOfPartitions *= 2; + + if (sum.AlmostEqualRelative(midpointsum, targetError)) + { + break; + } + } + + return sum; + } + + /// + /// Adaptive approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth complex function to integrate, define don real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// The expected accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrateAdaptive(Func f, double intervalBegin, double intervalEnd, double targetError) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + int numberOfPartitions = 1; + double step = intervalEnd - intervalBegin; + Complex sum = 0.5 * step * (f(intervalBegin) + f(intervalEnd)); + for (int k = 0; k < 20; k++) + { + Complex midpointsum = 0; + for (int i = 0; i < numberOfPartitions; i++) + { + midpointsum += f(intervalBegin + ((i + 0.5) * step)); + } + + midpointsum *= step; + sum = 0.5 * (sum + midpointsum); + step *= 0.5; + numberOfPartitions *= 2; + + if (sum.AlmostEqualRelative(midpointsum, targetError)) + { + break; + } + } + + return sum; + } + + /// + /// Adaptive approximation of the definite integral by the trapezium rule. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Abscissa vector per level provider. + /// Weight vector per level provider. + /// First Level Step + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static double IntegrateAdaptiveTransformedOdd( + Func f, + double intervalBegin, double intervalEnd, + IEnumerable levelAbscissas, IEnumerable levelWeights, + double levelOneStep, double targetRelativeError) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (levelAbscissas == null) + { + throw new ArgumentNullException(nameof(levelAbscissas)); + } + + if (levelWeights == null) + { + throw new ArgumentNullException(nameof(levelWeights)); + } + + double linearSlope = 0.5 * (intervalEnd - intervalBegin); + double linearOffset = 0.5 * (intervalEnd + intervalBegin); + targetRelativeError /= 5 * linearSlope; + + using (var abcissasIterator = levelAbscissas.GetEnumerator()) + using (var weightsIterator = levelWeights.GetEnumerator()) + { + double step = levelOneStep; + + // First Level + abcissasIterator.MoveNext(); + weightsIterator.MoveNext(); + double[] abcissasL1 = abcissasIterator.Current; + double[] weightsL1 = weightsIterator.Current; + + double sum = f(linearOffset) * weightsL1[0]; + for (int i = 1; i < abcissasL1.Length; i++) + { + sum += weightsL1[i] * (f((linearSlope * abcissasL1[i]) + linearOffset) + f(-(linearSlope * abcissasL1[i]) + linearOffset)); + } + + sum *= step; + + // Additional Levels + double previousDelta = double.MaxValue; + for (int level = 1; abcissasIterator.MoveNext() && weightsIterator.MoveNext(); level++) + { + double[] abcissas = abcissasIterator.Current; + double[] weights = weightsIterator.Current; + + double midpointsum = 0; + for (int i = 0; i < abcissas.Length; i++) + { + midpointsum += weights[i] * (f((linearSlope * abcissas[i]) + linearOffset) + f(-(linearSlope * abcissas[i]) + linearOffset)); + } + + midpointsum *= step; + sum = 0.5 * (sum + midpointsum); + step *= 0.5; + + double delta = Math.Abs(sum - midpointsum); + + if (level == 1) + { + previousDelta = delta; + continue; + } + + double r = Math.Log(delta) / Math.Log(previousDelta); + previousDelta = delta; + + if (r > 1.9 && r < 2.1) + { + // convergence region + delta = Math.Sqrt(delta); + } + + if (sum.AlmostEqualNormRelative(midpointsum, delta, targetRelativeError)) + { + break; + } + } + + return sum * linearSlope; + } + } + + /// + /// Adaptive approximation of the definite integral by the trapezium rule. + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Abscissa vector per level provider. + /// Weight vector per level provider. + /// First Level Step + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrateAdaptiveTransformedOdd( + Func f, + double intervalBegin, double intervalEnd, + IEnumerable levelAbscissas, IEnumerable levelWeights, + double levelOneStep, double targetRelativeError) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (levelAbscissas == null) + { + throw new ArgumentNullException(nameof(levelAbscissas)); + } + + if (levelWeights == null) + { + throw new ArgumentNullException(nameof(levelWeights)); + } + + double linearSlope = 0.5 * (intervalEnd - intervalBegin); + double linearOffset = 0.5 * (intervalEnd + intervalBegin); + targetRelativeError /= 5 * linearSlope; + + using (var abcissasIterator = levelAbscissas.GetEnumerator()) + using (var weightsIterator = levelWeights.GetEnumerator()) + { + double step = levelOneStep; + + // First Level + abcissasIterator.MoveNext(); + weightsIterator.MoveNext(); + double[] abcissasL1 = abcissasIterator.Current; + double[] weightsL1 = weightsIterator.Current; + + Complex sum = f(linearOffset) * weightsL1[0]; + for (int i = 1; i < abcissasL1.Length; i++) + { + sum += weightsL1[i] * (f((linearSlope * abcissasL1[i]) + linearOffset) + f(-(linearSlope * abcissasL1[i]) + linearOffset)); + } + + sum *= step; + + // Additional Levels + double previousDelta = double.MaxValue; + for (int level = 1; abcissasIterator.MoveNext() && weightsIterator.MoveNext(); level++) + { + double[] abcissas = abcissasIterator.Current; + double[] weights = weightsIterator.Current; + + Complex midpointsum = 0; + for (int i = 0; i < abcissas.Length; i++) + { + midpointsum += weights[i] * (f((linearSlope * abcissas[i]) + linearOffset) + f(-(linearSlope * abcissas[i]) + linearOffset)); + } + + midpointsum *= step; + sum = 0.5 * (sum + midpointsum); + step *= 0.5; + + double delta = Complex.Abs(sum - midpointsum); + + if (level == 1) + { + previousDelta = delta; + continue; + } + + double r = Math.Log(delta) / Math.Log(previousDelta); + previousDelta = delta; + + if (r > 1.9 && r < 2.1) + { + // convergence region + delta = Math.Sqrt(delta); + } + + if (sum.Real.AlmostEqualNormRelative(midpointsum.Real, delta, targetRelativeError) + && sum.Imaginary.AlmostEqualNormRelative(midpointsum.Imaginary, delta, targetRelativeError)) + { + break; + } + } + + return sum * linearSlope; + } + } +} diff --git a/MathNet.Numerics/Integration/SimpsonRule.cs b/MathNet.Numerics/Integration/SimpsonRule.cs new file mode 100644 index 0000000..7469290 --- /dev/null +++ b/MathNet.Numerics/Integration/SimpsonRule.cs @@ -0,0 +1,100 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.Integration; + +/// +/// Approximation algorithm for definite integrals by Simpson's rule. +/// +public static class SimpsonRule +{ + /// + /// Direct 3-point approximation of the definite integral in the provided interval by Simpson's rule. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Approximation of the finite integral in the given interval. + public static double IntegrateThreePoint(Func f, double intervalBegin, double intervalEnd) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + double midpoint = (intervalEnd + intervalBegin) / 2; + return (intervalEnd - intervalBegin) / 6 * (f(intervalBegin) + f(intervalEnd) + (4 * f(midpoint))); + } + + /// + /// Composite N-point approximation of the definite integral in the provided interval by Simpson's rule. + /// + /// The analytic smooth function to integrate. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Even number of composite subdivision partitions. + /// Approximation of the finite integral in the given interval. + public static double IntegrateComposite(Func f, double intervalBegin, double intervalEnd, int numberOfPartitions) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (numberOfPartitions <= 0) + { + throw new ArgumentOutOfRangeException(nameof(numberOfPartitions), Resources.ArgumentPositive); + } + + if (numberOfPartitions.IsOdd()) + { + throw new ArgumentException(Resources.ArgumentEven, nameof(numberOfPartitions)); + } + + double step = (intervalEnd - intervalBegin) / numberOfPartitions; + double factor = step / 3; + + double offset = step; + int m = 4; + double sum = f(intervalBegin) + f(intervalEnd); + for (int i = 0; i < numberOfPartitions - 1; i++) + { + // NOTE (cdrnet, 2009-01-07): Do not combine intervalBegin and offset (numerical stability) + sum += m * f(intervalBegin + offset); + m = 6 - m; + offset += step; + } + + return factor * sum; + } +} diff --git a/MathNet.Numerics/Interpolate.cs b/MathNet.Numerics/Interpolate.cs new file mode 100644 index 0000000..ba0858c --- /dev/null +++ b/MathNet.Numerics/Interpolate.cs @@ -0,0 +1,266 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Interpolation; + +using System.Collections.Generic; + +namespace MathNet.Numerics; + +/// +/// Interpolation Factory. +/// +public static class Interpolate +{ + /// + /// Creates an interpolation based on arbitrary points. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.Barycentric.InterpolateRationalFloaterHormannSorted + /// instead, which is more efficient. + /// + public static IInterpolation Common(IEnumerable points, IEnumerable values) + { + return Barycentric.InterpolateRationalFloaterHormann(points, values); + } + + /// + /// Create a Floater-Hormann rational pole-free interpolation based on arbitrary points. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.Barycentric.InterpolateRationalFloaterHormannSorted + /// instead, which is more efficient. + /// + public static IInterpolation RationalWithoutPoles(IEnumerable points, IEnumerable values) + { + return Barycentric.InterpolateRationalFloaterHormann(points, values); + } + + /// + /// Create a Bulirsch Stoer rational interpolation based on arbitrary points. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.BulirschStoerRationalInterpolation.InterpolateSorted + /// instead, which is more efficient. + /// + public static IInterpolation RationalWithPoles(IEnumerable points, IEnumerable values) + { + return BulirschStoerRationalInterpolation.Interpolate(points, values); + } + + /// + /// Create a barycentric polynomial interpolation where the given sample points are equidistant. + /// + /// The sample points t, must be equidistant. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.Barycentric.InterpolatePolynomialEquidistantSorted + /// instead, which is more efficient. + /// + public static IInterpolation PolynomialEquidistant(IEnumerable points, IEnumerable values) + { + return Barycentric.InterpolatePolynomialEquidistant(points, values); + } + + /// + /// Create a Neville polynomial interpolation based on arbitrary points. + /// If the points happen to be equidistant, consider to use the much more robust PolynomialEquidistant instead. + /// Otherwise, consider whether RationalWithoutPoles would not be a more robust alternative. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.NevillePolynomialInterpolation.InterpolateSorted + /// instead, which is more efficient. + /// + public static IInterpolation Polynomial(IEnumerable points, IEnumerable values) + { + return NevillePolynomialInterpolation.Interpolate(points, values); + } + + /// + /// Create a piecewise linear interpolation based on arbitrary points. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.LinearSpline.InterpolateSorted + /// instead, which is more efficient. + /// + public static IInterpolation Linear(IEnumerable points, IEnumerable values) + { + return Interpolation.LinearSpline.Interpolate(points, values); + } + + /// + /// Create piecewise log-linear interpolation based on arbitrary points. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.LogLinear.InterpolateSorted + /// instead, which is more efficient. + /// + public static IInterpolation LogLinear(IEnumerable points, IEnumerable values) + { + return Interpolation.LogLinear.Interpolate(points, values); + } + + /// + /// Create an piecewise natural cubic spline interpolation based on arbitrary points, + /// with zero secondary derivatives at the boundaries. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.CubicSpline.InterpolateNaturalSorted + /// instead, which is more efficient. + /// + public static IInterpolation CubicSpline(IEnumerable points, IEnumerable values) + { + return Interpolation.CubicSpline.InterpolateNatural(points, values); + } + + /// + /// Create an piecewise cubic Akima spline interpolation based on arbitrary points. + /// Akima splines are robust to outliers. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.CubicSpline.InterpolateAkimaSorted + /// instead, which is more efficient. + /// + public static IInterpolation CubicSplineRobust(IEnumerable points, IEnumerable values) + { + return Interpolation.CubicSpline.InterpolateAkima(points, values); + } + + /// + /// Create a piecewise cubic Hermite spline interpolation based on arbitrary points + /// and their slopes/first derivative. + /// + /// The sample points t. + /// The sample point values x(t). + /// The slope at the sample points. Optimized for arrays. + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.CubicSpline.InterpolateHermiteSorted + /// instead, which is more efficient. + /// + public static IInterpolation CubicSplineWithDerivatives(IEnumerable points, IEnumerable values, IEnumerable firstDerivatives) + { + return Interpolation.CubicSpline.InterpolateHermite(points, values, firstDerivatives); + } + + /// + /// Create a step-interpolation based on arbitrary points. + /// + /// The sample points t. + /// The sample point values x(t). + /// + /// An interpolation scheme optimized for the given sample points and values, + /// which can then be used to compute interpolations and extrapolations + /// on arbitrary points. + /// + /// + /// if your data is already sorted in arrays, consider to use + /// MathNet.Numerics.Interpolation.StepInterpolation.InterpolateSorted + /// instead, which is more efficient. + /// + public static IInterpolation Step(IEnumerable points, IEnumerable values) + { + return StepInterpolation.Interpolate(points, values); + } +} diff --git a/MathNet.Numerics/Interpolation/Barycentric.cs b/MathNet.Numerics/Interpolation/Barycentric.cs new file mode 100644 index 0000000..db0fb8a --- /dev/null +++ b/MathNet.Numerics/Interpolation/Barycentric.cs @@ -0,0 +1,369 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Interpolation; + +/// +/// Barycentric Interpolation Algorithm. +/// +/// Supports neither differentiation nor integration. +public class Barycentric : IInterpolation +{ + readonly double[] _x; + readonly double[] _y; + readonly double[] _w; + + /// Sample points (N), sorted ascendingly. + /// Sample values (N), sorted ascendingly by x. + /// Barycentric weights (N), sorted ascendingly by x. + public Barycentric(double[] x, double[] y, double[] w) + { + if (x.Length != y.Length || x.Length != w.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 1) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 1), nameof(x)); + } + + _x = x; + _y = y; + _w = w; + } + + /// + /// Create a barycentric polynomial interpolation from a set of (x,y) value pairs with equidistant x, sorted ascendingly by x. + /// + public static Barycentric InterpolatePolynomialEquidistantSorted(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 1) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 1), nameof(x)); + } + + var weights = new double[x.Length]; + weights[0] = 1.0; + for (int i = 1; i < weights.Length; i++) + { + weights[i] = -(weights[i - 1] * (weights.Length - i)) / i; + } + + return new Barycentric(x, y, weights); + } + + /// + /// Create a barycentric polynomial interpolation from an unordered set of (x,y) value pairs with equidistant x. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static Barycentric InterpolatePolynomialEquidistantInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolatePolynomialEquidistantSorted(x, y); + } + + /// + /// Create a barycentric polynomial interpolation from an unsorted set of (x,y) value pairs with equidistant x. + /// + public static Barycentric InterpolatePolynomialEquidistant(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolatePolynomialEquidistantInplace(x.ToArray(), y.ToArray()); + } + + /// + /// Create a barycentric polynomial interpolation from a set of values related to linearly/equidistant spaced points within an interval. + /// + public static Barycentric InterpolatePolynomialEquidistant(double leftBound, double rightBound, IEnumerable y) + { + var yy = (y as double[]) ?? y.ToArray(); + var xx = Generate.LinearSpaced(yy.Length, leftBound, rightBound); + return InterpolatePolynomialEquidistantSorted(xx, yy); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// The values are assumed to be sorted ascendingly by x. + /// + /// Sample points (N), sorted ascendingly. + /// Sample values (N), sorted ascendingly by x. + /// + /// Order of the interpolation scheme, 0 <= order <= N. + /// In most cases a value between 3 and 8 gives good results. + /// + public static Barycentric InterpolateRationalFloaterHormannSorted(double[] x, double[] y, int order) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 1) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 1), nameof(x)); + } + + if (0 > order || x.Length <= order) + { + throw new ArgumentOutOfRangeException(nameof(order)); + } + + var weights = new double[x.Length]; + + // order: odd -> negative, even -> positive + double sign = ((order & 0x1) == 0x1) ? -1.0 : 1.0; + + // compute barycentric weights + for (int k = 0; k < x.Length; k++) + { + double s = 0; + for (int i = Math.Max(k - order, 0); i <= Math.Min(k, weights.Length - 1 - order); i++) + { + double v = 1; + for (int j = i; j <= i + order; j++) + { + if (j != k) + { + v = v / Math.Abs(x[k] - x[j]); + } + } + + s = s + v; + } + + weights[k] = sign * s; + sign = -sign; + } + + return new Barycentric(x, y, weights); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + /// Sample points (N), no sorting assumed. + /// Sample values (N). + /// + /// Order of the interpolation scheme, 0 <= order <= N. + /// In most cases a value between 3 and 8 gives good results. + /// + public static Barycentric InterpolateRationalFloaterHormannInplace(double[] x, double[] y, int order) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateRationalFloaterHormannSorted(x, y, order); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// + /// Sample points (N), no sorting assumed. + /// Sample values (N). + /// + /// Order of the interpolation scheme, 0 <= order <= N. + /// In most cases a value between 3 and 8 gives good results. + /// + public static Barycentric InterpolateRationalFloaterHormann(IEnumerable x, IEnumerable y, int order) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateRationalFloaterHormannInplace(x.ToArray(), y.ToArray(), order); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// The values are assumed to be sorted ascendingly by x. + /// + /// Sample points (N), sorted ascendingly. + /// Sample values (N), sorted ascendingly by x. + public static Barycentric InterpolateRationalFloaterHormannSorted(double[] x, double[] y) + { + return InterpolateRationalFloaterHormannSorted(x, y, Math.Min(3, x.Length - 1)); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + /// Sample points (N), no sorting assumed. + /// Sample values (N). + public static Barycentric InterpolateRationalFloaterHormannInplace(double[] x, double[] y) + { + return InterpolateRationalFloaterHormannInplace(x, y, Math.Min(3, x.Length - 1)); + } + + /// + /// Create a barycentric rational interpolation without poles, using Mike Floater and Kai Hormann's Algorithm. + /// + /// Sample points (N), no sorting assumed. + /// Sample values (N). + public static Barycentric InterpolateRationalFloaterHormann(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + var xx = x.ToArray(); + var order = Math.Min(3, xx.Length - 1); + return InterpolateRationalFloaterHormannInplace(xx, y.ToArray(), order); + } + + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool IInterpolation.SupportsDifferentiation + { + get { return false; } + } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool IInterpolation.SupportsIntegration + { + get { return false; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + // trivial case: only one sample? + if (_x.Length == 1) + { + return _y[0]; + } + + // evaluate closest point and offset from that point (no sorting assumed) + int closestPoint = 0; + double offset = t - _x[0]; + for (int i = 1; i < _x.Length; i++) + { + if (Math.Abs(t - _x[i]) < Math.Abs(offset)) + { + offset = t - _x[i]; + closestPoint = i; + } + } + + // trivial case: on a known sample point? + if (offset == 0.0) + { + // NOTE (cdrnet, 2009-08) not offset.AlmostZero() by design + return _y[closestPoint]; + } + + if (Math.Abs(offset) > 1e-150) + { + // no need to guard against overflow, so use fast formula + closestPoint = -1; + offset = 1.0; + } + + double s1 = 0.0; + double s2 = 0.0; + for (int i = 0; i < _x.Length; i++) + { + if (i != closestPoint) + { + double v = offset * _w[i] / (t - _x[i]); + s1 = s1 + (v * _y[i]); + s2 = s2 + v; + } + else + { + double v = _w[i]; + s1 = s1 + (v * _y[i]); + s2 = s2 + v; + } + } + + return s1 / s2; + } + + /// + /// Differentiate at point t. NOT SUPPORTED. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + double IInterpolation.Differentiate(double t) + { + throw new NotSupportedException(); + } + + /// + /// Differentiate twice at point t. NOT SUPPORTED. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + double IInterpolation.Differentiate2(double t) + { + throw new NotSupportedException(); + } + + /// + /// Indefinite integral at point t. NOT SUPPORTED. + /// + /// Point t to integrate at. + double IInterpolation.Integrate(double t) + { + throw new NotSupportedException(); + } + + /// + /// Definite integral between points a and b. NOT SUPPORTED. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + double IInterpolation.Integrate(double a, double b) + { + throw new NotSupportedException(); + } +} diff --git a/MathNet.Numerics/Interpolation/BulirschStoerRationalInterpolation.cs b/MathNet.Numerics/Interpolation/BulirschStoerRationalInterpolation.cs new file mode 100644 index 0000000..4e1fcb5 --- /dev/null +++ b/MathNet.Numerics/Interpolation/BulirschStoerRationalInterpolation.cs @@ -0,0 +1,217 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Interpolation; + +/// +/// Rational Interpolation (with poles) using Roland Bulirsch and Josef Stoer's Algorithm. +/// +/// +/// +/// This algorithm supports neither differentiation nor integration. +/// +/// +public class BulirschStoerRationalInterpolation : IInterpolation +{ + readonly double[] _x; + readonly double[] _y; + + /// Sample Points t, sorted ascendingly. + /// Sample Values x(t), sorted ascendingly by x. + public BulirschStoerRationalInterpolation(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 1) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 1), nameof(x)); + } + + _x = x; + _y = y; + } + + /// + /// Create a Bulirsch-Stoer rational interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// + public static BulirschStoerRationalInterpolation InterpolateSorted(double[] x, double[] y) + { + return new BulirschStoerRationalInterpolation(x, y); + } + + /// + /// Create a Bulirsch-Stoer rational interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static BulirschStoerRationalInterpolation InterpolateInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateSorted(x, y); + } + + /// + /// Create a Bulirsch-Stoer rational interpolation from an unsorted set of (x,y) value pairs. + /// + public static BulirschStoerRationalInterpolation Interpolate(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(x.ToArray(), y.ToArray()); + } + + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool IInterpolation.SupportsDifferentiation + { + get { return false; } + } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool IInterpolation.SupportsIntegration + { + get { return false; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + const double tiny = 1.0e-25; + int n = _x.Length; + + var c = new double[n]; + var d = new double[n]; + + int nearestIndex = 0; + double nearestDistance = Math.Abs(t - _x[0]); + + for (int i = 0; i < n; i++) + { + double distance = Math.Abs(t - _x[i]); + if (distance.AlmostEqual(0.0)) + { + return _y[i]; + } + + if (distance < nearestDistance) + { + nearestIndex = i; + nearestDistance = distance; + } + + c[i] = _y[i]; + d[i] = _y[i] + tiny; + } + + double x = _y[nearestIndex]; + + for (int level = 1; level < n; level++) + { + for (int i = 0; i < n - level; i++) + { + double hp = _x[i + level] - t; + double ho = (_x[i] - t) * d[i] / hp; + + double den = ho - c[i + 1]; + if (den.AlmostEqual(0.0)) + { + return double.NaN; // zero-div, singularity + } + + den = (c[i + 1] - d[i]) / den; + d[i] = c[i + 1] * den; + c[i] = ho * den; + } + + x += (2 * nearestIndex) < (n - level) + ? c[nearestIndex] + : d[--nearestIndex]; + } + + return x; + } + + /// + /// Differentiate at point t. NOT SUPPORTED. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + double IInterpolation.Differentiate(double t) + { + throw new NotSupportedException(); + } + + /// + /// Differentiate twice at point t. NOT SUPPORTED. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + double IInterpolation.Differentiate2(double t) + { + throw new NotSupportedException(); + } + + /// + /// Indefinite integral at point t. NOT SUPPORTED. + /// + /// Point t to integrate at. + double IInterpolation.Integrate(double t) + { + throw new NotSupportedException(); + } + + /// + /// Definite integral between points a and b. NOT SUPPORTED. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + double IInterpolation.Integrate(double a, double b) + { + throw new NotSupportedException(); + } +} diff --git a/MathNet.Numerics/Interpolation/CubicSpline.cs b/MathNet.Numerics/Interpolation/CubicSpline.cs new file mode 100644 index 0000000..71e4842 --- /dev/null +++ b/MathNet.Numerics/Interpolation/CubicSpline.cs @@ -0,0 +1,534 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Interpolation; + +/// +/// Cubic Spline Interpolation. +/// +/// Supports both differentiation and integration. +public class CubicSpline : IInterpolation +{ + readonly double[] _x; + readonly double[] _c0; + readonly double[] _c1; + readonly double[] _c2; + readonly double[] _c3; + readonly Lazy _indefiniteIntegral; + + /// sample points (N+1), sorted ascending + /// Zero order spline coefficients (N) + /// First order spline coefficients (N) + /// second order spline coefficients (N) + /// third order spline coefficients (N) + public CubicSpline(double[] x, double[] c0, double[] c1, double[] c2, double[] c3) + { + if (x.Length != c0.Length + 1 || x.Length != c1.Length + 1 || x.Length != c2.Length + 1 || x.Length != c3.Length + 1) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 2) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 2), nameof(x)); + } + + _x = x; + _c0 = c0; + _c1 = c1; + _c2 = c2; + _c3 = c3; + _indefiniteIntegral = new Lazy(ComputeIndefiniteIntegral); + } + + /// + /// Create a Hermite cubic spline interpolation from a set of (x,y) value pairs and their slope (first derivative), sorted ascendingly by x. + /// + public static CubicSpline InterpolateHermiteSorted(double[] x, double[] y, double[] firstDerivatives) + { + if (x.Length != y.Length || x.Length != firstDerivatives.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 2) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 2), nameof(x)); + } + + var c0 = new double[x.Length - 1]; + var c1 = new double[x.Length - 1]; + var c2 = new double[x.Length - 1]; + var c3 = new double[x.Length - 1]; + for (int i = 0; i < c1.Length; i++) + { + double w = x[i + 1] - x[i]; + double w2 = w * w; + c0[i] = y[i]; + c1[i] = firstDerivatives[i]; + c2[i] = (3 * (y[i + 1] - y[i]) / w - 2 * firstDerivatives[i] - firstDerivatives[i + 1]) / w; + c3[i] = (2 * (y[i] - y[i + 1]) / w + firstDerivatives[i] + firstDerivatives[i + 1]) / w2; + } + + return new CubicSpline(x, c0, c1, c2, c3); + } + + /// + /// Create a Hermite cubic spline interpolation from an unsorted set of (x,y) value pairs and their slope (first derivative). + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static CubicSpline InterpolateHermiteInplace(double[] x, double[] y, double[] firstDerivatives) + { + if (x.Length != y.Length || x.Length != firstDerivatives.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 2) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 2), nameof(x)); + } + + Sorting.Sort(x, y, firstDerivatives); + return InterpolateHermiteSorted(x, y, firstDerivatives); + } + + /// + /// Create a Hermite cubic spline interpolation from an unsorted set of (x,y) value pairs and their slope (first derivative). + /// + public static CubicSpline InterpolateHermite(IEnumerable x, IEnumerable y, IEnumerable firstDerivatives) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateHermiteInplace(x.ToArray(), y.ToArray(), firstDerivatives.ToArray()); + } + + /// + /// Create an Akima cubic spline interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// Akima splines are robust to outliers. + /// + public static CubicSpline InterpolateAkimaSorted(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 5) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 5), nameof(x)); + } + + /* Prepare divided differences (diff) and weights (w) */ + + var diff = new double[x.Length - 1]; + var weights = new double[x.Length - 1]; + + for (int i = 0; i < diff.Length; i++) + { + diff[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]); + } + + for (int i = 1; i < weights.Length; i++) + { + weights[i] = Math.Abs(diff[i] - diff[i - 1]); + } + + /* Prepare Hermite interpolation scheme */ + + var dd = new double[x.Length]; + + for (int i = 2; i < dd.Length - 2; i++) + { + dd[i] = weights[i - 1].AlmostEqual(0.0) && weights[i + 1].AlmostEqual(0.0) + ? (((x[i + 1] - x[i]) * diff[i - 1]) + ((x[i] - x[i - 1]) * diff[i])) / (x[i + 1] - x[i - 1]) + : ((weights[i + 1] * diff[i - 1]) + (weights[i - 1] * diff[i])) / (weights[i + 1] + weights[i - 1]); + } + + dd[0] = DifferentiateThreePoint(x, y, 0, 0, 1, 2); + dd[1] = DifferentiateThreePoint(x, y, 1, 0, 1, 2); + dd[x.Length - 2] = DifferentiateThreePoint(x, y, x.Length - 2, x.Length - 3, x.Length - 2, x.Length - 1); + dd[x.Length - 1] = DifferentiateThreePoint(x, y, x.Length - 1, x.Length - 3, x.Length - 2, x.Length - 1); + + /* Build Akima spline using Hermite interpolation scheme */ + + return InterpolateHermiteSorted(x, y, dd); + } + + /// + /// Create an Akima cubic spline interpolation from an unsorted set of (x,y) value pairs. + /// Akima splines are robust to outliers. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static CubicSpline InterpolateAkimaInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateAkimaSorted(x, y); + } + + /// + /// Create an Akima cubic spline interpolation from an unsorted set of (x,y) value pairs. + /// Akima splines are robust to outliers. + /// + public static CubicSpline InterpolateAkima(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateAkimaInplace(x.ToArray(), y.ToArray()); + } + + /// + /// Create a cubic spline interpolation from a set of (x,y) value pairs, sorted ascendingly by x, + /// and custom boundary/termination conditions. + /// + public static CubicSpline InterpolateBoundariesSorted(double[] x, double[] y, + SplineBoundaryCondition leftBoundaryCondition, double leftBoundary, + SplineBoundaryCondition rightBoundaryCondition, double rightBoundary) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 2) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 2), nameof(x)); + } + + int n = x.Length; + + // normalize special cases + if ((n == 2) + && (leftBoundaryCondition == SplineBoundaryCondition.ParabolicallyTerminated) + && (rightBoundaryCondition == SplineBoundaryCondition.ParabolicallyTerminated)) + { + leftBoundaryCondition = SplineBoundaryCondition.SecondDerivative; + leftBoundary = 0d; + rightBoundaryCondition = SplineBoundaryCondition.SecondDerivative; + rightBoundary = 0d; + } + + if (leftBoundaryCondition == SplineBoundaryCondition.Natural) + { + leftBoundaryCondition = SplineBoundaryCondition.SecondDerivative; + leftBoundary = 0d; + } + + if (rightBoundaryCondition == SplineBoundaryCondition.Natural) + { + rightBoundaryCondition = SplineBoundaryCondition.SecondDerivative; + rightBoundary = 0d; + } + + var a1 = new double[n]; + var a2 = new double[n]; + var a3 = new double[n]; + var b = new double[n]; + + // Left Boundary + switch (leftBoundaryCondition) + { + case SplineBoundaryCondition.ParabolicallyTerminated: + a1[0] = 0; + a2[0] = 1; + a3[0] = 1; + b[0] = 2 * (y[1] - y[0]) / (x[1] - x[0]); + break; + case SplineBoundaryCondition.FirstDerivative: + a1[0] = 0; + a2[0] = 1; + a3[0] = 0; + b[0] = leftBoundary; + break; + case SplineBoundaryCondition.SecondDerivative: + a1[0] = 0; + a2[0] = 2; + a3[0] = 1; + b[0] = (3 * ((y[1] - y[0]) / (x[1] - x[0]))) - (0.5 * leftBoundary * (x[1] - x[0])); + break; + default: + throw new NotSupportedException(Resources.InvalidLeftBoundaryCondition); + } + + // Central Conditions + for (int i = 1; i < x.Length - 1; 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 + switch (rightBoundaryCondition) + { + case SplineBoundaryCondition.ParabolicallyTerminated: + 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]); + break; + case SplineBoundaryCondition.FirstDerivative: + a1[n - 1] = 0; + a2[n - 1] = 1; + a3[n - 1] = 0; + b[n - 1] = rightBoundary; + break; + case SplineBoundaryCondition.SecondDerivative: + 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 * rightBoundary * (x[n - 1] - x[n - 2])); + break; + default: + throw new NotSupportedException(Resources.InvalidRightBoundaryCondition); + } + + // Build Spline + double[] dd = SolveTridiagonal(a1, a2, a3, b); + return InterpolateHermiteSorted(x, y, dd); + } + + /// + /// Create a cubic spline interpolation from an unsorted set of (x,y) value pairs and custom boundary/termination conditions. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static CubicSpline InterpolateBoundariesInplace(double[] x, double[] y, + SplineBoundaryCondition leftBoundaryCondition, double leftBoundary, + SplineBoundaryCondition rightBoundaryCondition, double rightBoundary) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateBoundariesSorted(x, y, leftBoundaryCondition, leftBoundary, rightBoundaryCondition, rightBoundary); + } + + /// + /// Create a cubic spline interpolation from an unsorted set of (x,y) value pairs and custom boundary/termination conditions. + /// + public static CubicSpline InterpolateBoundaries(IEnumerable x, IEnumerable y, + SplineBoundaryCondition leftBoundaryCondition, double leftBoundary, + SplineBoundaryCondition rightBoundaryCondition, double rightBoundary) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateBoundariesInplace(x.ToArray(), y.ToArray(), leftBoundaryCondition, leftBoundary, rightBoundaryCondition, rightBoundary); + } + + /// + /// Create a natural cubic spline interpolation from a set of (x,y) value pairs + /// and zero second derivatives at the two boundaries, sorted ascendingly by x. + /// + public static CubicSpline InterpolateNaturalSorted(double[] x, double[] y) + { + return InterpolateBoundariesSorted(x, y, SplineBoundaryCondition.SecondDerivative, 0.0, SplineBoundaryCondition.SecondDerivative, 0.0); + } + + /// + /// Create a natural cubic spline interpolation from an unsorted set of (x,y) value pairs + /// and zero second derivatives at the two boundaries. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static CubicSpline InterpolateNaturalInplace(double[] x, double[] y) + { + return InterpolateBoundariesInplace(x, y, SplineBoundaryCondition.SecondDerivative, 0.0, SplineBoundaryCondition.SecondDerivative, 0.0); + } + + /// + /// Create a natural cubic spline interpolation from an unsorted set of (x,y) value pairs + /// and zero second derivatives at the two boundaries. + /// + public static CubicSpline InterpolateNatural(IEnumerable x, IEnumerable y) + { + return InterpolateBoundaries(x, y, SplineBoundaryCondition.SecondDerivative, 0.0, SplineBoundaryCondition.SecondDerivative, 0.0); + } + + /// + /// Three-Point Differentiation Helper. + /// + /// Sample Points t. + /// Sample Values x(t). + /// Index of the point of the differentiation. + /// Index of the first sample. + /// Index of the second sample. + /// Index of the third sample. + /// The derivative approximation. + static double DifferentiateThreePoint(double[] xx, double[] yy, int indexT, int index0, int index1, int index2) + { + double x0 = yy[index0]; + double x1 = yy[index1]; + double x2 = yy[index2]; + + double t = xx[indexT] - xx[index0]; + double t1 = xx[index1] - xx[index0]; + double t2 = xx[index2] - xx[index0]; + + double a = (x2 - x0 - (t2 / t1 * (x1 - x0))) / (t2 * t2 - t1 * t2); + double b = (x1 - x0 - a * t1 * t1) / t1; + return (2 * a * t) + b; + } + + /// + /// Tridiagonal Solve Helper. + /// + /// The a-vector[n]. + /// The b-vector[n], will be modified by this function. + /// The c-vector[n]. + /// The d-vector[n], will be modified by this function. + /// The x-vector[n] + static double[] SolveTridiagonal(double[] a, double[] b, double[] c, double[] d) + { + for (int k = 1; k < a.Length; k++) + { + double t = a[k] / b[k - 1]; + b[k] = b[k] - (t * c[k - 1]); + d[k] = d[k] - (t * d[k - 1]); + } + + var x = new double[a.Length]; + x[x.Length - 1] = d[d.Length - 1] / b[b.Length - 1]; + for (int k = x.Length - 2; k >= 0; k--) + { + x[k] = (d[k] - (c[k] * x[k + 1])) / b[k]; + } + + return x; + } + + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool IInterpolation.SupportsDifferentiation + { + get { return true; } + } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool IInterpolation.SupportsIntegration + { + get { return true; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + int k = LeftSegmentIndex(t); + var x = t - _x[k]; + return _c0[k] + x * (_c1[k] + x * (_c2[k] + x * _c3[k])); + } + + /// + /// Differentiate at point t. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + public double Differentiate(double t) + { + int k = LeftSegmentIndex(t); + var x = t - _x[k]; + return _c1[k] + x * (2 * _c2[k] + x * 3 * _c3[k]); + } + + /// + /// Differentiate twice at point t. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + public double Differentiate2(double t) + { + int k = LeftSegmentIndex(t); + var x = t - _x[k]; + return 2 * _c2[k] + x * 6 * _c3[k]; + } + + /// + /// Indefinite integral at point t. + /// + /// Point t to integrate at. + public double Integrate(double t) + { + int k = LeftSegmentIndex(t); + var x = t - _x[k]; + return _indefiniteIntegral.Value[k] + x * (_c0[k] + x * (_c1[k] / 2 + x * (_c2[k] / 3 + x * _c3[k] / 4))); + } + + /// + /// Definite integral between points a and b. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + public double Integrate(double a, double b) + { + return Integrate(b) - Integrate(a); + } + + double[] ComputeIndefiniteIntegral() + { + var integral = new double[_c1.Length]; + for (int i = 0; i < integral.Length - 1; i++) + { + double w = _x[i + 1] - _x[i]; + integral[i + 1] = integral[i] + w * (_c0[i] + w * (_c1[i] / 2 + w * (_c2[i] / 3 + w * _c3[i] / 4))); + } + + return integral; + } + + /// + /// Find the index of the greatest sample point smaller than t, + /// or the left index of the closest segment for extrapolation. + /// + int LeftSegmentIndex(double t) + { + int index = Array.BinarySearch(_x, t); + if (index < 0) + { + index = ~index - 1; + } + + return Math.Min(Math.Max(index, 0), _x.Length - 2); + } +} diff --git a/MathNet.Numerics/Interpolation/IInterpolation.cs b/MathNet.Numerics/Interpolation/IInterpolation.cs new file mode 100644 index 0000000..18940c1 --- /dev/null +++ b/MathNet.Numerics/Interpolation/IInterpolation.cs @@ -0,0 +1,80 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Interpolation; + +/// +/// Interpolation within the range of a discrete set of known data points. +/// +public interface IInterpolation +{ + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool SupportsDifferentiation { get; } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool SupportsIntegration { get; } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + double Interpolate(double t); + + /// + /// Differentiate at point t. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + double Differentiate(double t); + + /// + /// Differentiate twice at point t. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + double Differentiate2(double t); + + /// + /// Indefinite integral at point t. + /// + /// Point t to integrate at. + double Integrate(double t); + + /// + /// Definite integral between points a and b. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + double Integrate(double a, double b); +} diff --git a/MathNet.Numerics/Interpolation/LinearSpline.cs b/MathNet.Numerics/Interpolation/LinearSpline.cs new file mode 100644 index 0000000..ee02421 --- /dev/null +++ b/MathNet.Numerics/Interpolation/LinearSpline.cs @@ -0,0 +1,213 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Interpolation; + +/// +/// Piece-wise Linear Interpolation. +/// +/// Supports both differentiation and integration. +public class LinearSpline : IInterpolation +{ + readonly double[] _x; + readonly double[] _c0; + readonly double[] _c1; + readonly Lazy _indefiniteIntegral; + + /// Sample points (N+1), sorted ascending + /// Sample values (N or N+1) at the corresponding points; intercept, zero order coefficients + /// Slopes (N) at the sample points (first order coefficients): N + public LinearSpline(double[] x, double[] c0, double[] c1) + { + if ((x.Length != c0.Length + 1 && x.Length != c0.Length) || x.Length != c1.Length + 1) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 2) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 2), nameof(x)); + } + + _x = x; + _c0 = c0; + _c1 = c1; + _indefiniteIntegral = new Lazy(ComputeIndefiniteIntegral); + } + + /// + /// Create a linear spline interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// + public static LinearSpline InterpolateSorted(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 2) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 2), nameof(x)); + } + + var c1 = new double[x.Length - 1]; + for (int i = 0; i < c1.Length; i++) + { + c1[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]); + } + + return new LinearSpline(x, y, c1); + } + + /// + /// Create a linear spline interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static LinearSpline InterpolateInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateSorted(x, y); + } + + /// + /// Create a linear spline interpolation from an unsorted set of (x,y) value pairs. + /// + public static LinearSpline Interpolate(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(x.ToArray(), y.ToArray()); + } + + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool IInterpolation.SupportsDifferentiation + { + get { return true; } + } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool IInterpolation.SupportsIntegration + { + get { return true; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + int k = LeftSegmentIndex(t); + return _c0[k] + (t - _x[k]) * _c1[k]; + } + + /// + /// Differentiate at point t. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + public double Differentiate(double t) + { + int k = LeftSegmentIndex(t); + return _c1[k]; + } + + /// + /// Differentiate twice at point t. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + public double Differentiate2(double t) + { + return 0d; + } + + /// + /// Indefinite integral at point t. + /// + /// Point t to integrate at. + public double Integrate(double t) + { + int k = LeftSegmentIndex(t); + var x = t - _x[k]; + return _indefiniteIntegral.Value[k] + x * (_c0[k] + x * _c1[k] / 2); + } + + /// + /// Definite integral between points a and b. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + public double Integrate(double a, double b) + { + return Integrate(b) - Integrate(a); + } + + double[] ComputeIndefiniteIntegral() + { + var integral = new double[_c1.Length]; + for (int i = 0; i < integral.Length - 1; i++) + { + double w = _x[i + 1] - _x[i]; + integral[i + 1] = integral[i] + w * (_c0[i] + w * _c1[i] / 2); + } + + return integral; + } + + /// + /// Find the index of the greatest sample point smaller than t, + /// or the left index of the closest segment for extrapolation. + /// + int LeftSegmentIndex(double t) + { + int index = Array.BinarySearch(_x, t); + if (index < 0) + { + index = ~index - 1; + } + + return Math.Min(Math.Max(index, 0), _x.Length - 2); + } +} diff --git a/MathNet.Numerics/Interpolation/LogLinear.cs b/MathNet.Numerics/Interpolation/LogLinear.cs new file mode 100644 index 0000000..e86dc46 --- /dev/null +++ b/MathNet.Numerics/Interpolation/LogLinear.cs @@ -0,0 +1,181 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Interpolation; + +/// +/// Piece-wise Log-Linear Interpolation +/// +/// This algorithm supports differentiation, not integration. +public class LogLinear : IInterpolation +{ + /// + /// Internal Spline Interpolation + /// + readonly LinearSpline _spline; + + /// Sample points (N), sorted ascending + /// Natural logarithm of the sample values (N) at the corresponding points + public LogLinear(double[] x, double[] logy) + { + _spline = LinearSpline.InterpolateSorted(x, logy); + } + + /// + /// Create a piecewise log-linear interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// + public static LogLinear InterpolateSorted(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var logy = new double[y.Length]; + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + logy[i] = Math.Log(y[i]); + } + }); + + return new LogLinear(x, logy); + } + + /// + /// Create a piecewise log-linear interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered and modified. + /// + public static LogLinear InterpolateInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + y[i] = Math.Log(y[i]); + } + }); + + return new LogLinear(x, y); + } + + /// + /// Create a piecewise log-linear interpolation from an unsorted set of (x,y) value pairs. + /// + public static LogLinear Interpolate(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(x.ToArray(), y.ToArray()); + } + + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool IInterpolation.SupportsDifferentiation + { + get { return true; } + } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool IInterpolation.SupportsIntegration + { + get { return false; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + return Math.Exp(_spline.Interpolate(t)); + } + + /// + /// Differentiate at point t. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + public double Differentiate(double t) + { + return Interpolate(t) * _spline.Differentiate(t); + } + + /// + /// Differentiate twice at point t. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + public double Differentiate2(double t) + { + var linearFirstDerivative = _spline.Differentiate(t); + var linearSecondDerivative = _spline.Differentiate2(t); + + var secondDerivative = Differentiate(t) * linearFirstDerivative + + Interpolate(t) * linearSecondDerivative; + + return secondDerivative; + } + + /// + /// Indefinite integral at point t. + /// + /// Point t to integrate at. + double IInterpolation.Integrate(double t) + { + throw new NotSupportedException(); + } + + /// + /// Definite integral between points a and b. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + double IInterpolation.Integrate(double a, double b) + { + throw new NotSupportedException(); + } +} diff --git a/MathNet.Numerics/Interpolation/NevillePolynomialInterpolation.cs b/MathNet.Numerics/Interpolation/NevillePolynomialInterpolation.cs new file mode 100644 index 0000000..ed98648 --- /dev/null +++ b/MathNet.Numerics/Interpolation/NevillePolynomialInterpolation.cs @@ -0,0 +1,226 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Interpolation; + +/// +/// Lagrange Polynomial Interpolation using Neville's Algorithm. +/// +/// +/// +/// This algorithm supports differentiation, but doesn't support integration. +/// +/// +/// When working with equidistant or Chebyshev sample points it is +/// recommended to use the barycentric algorithms specialized for +/// these cases instead of this arbitrary Neville algorithm. +/// +/// +public class NevillePolynomialInterpolation : IInterpolation +{ + readonly double[] _x; + readonly double[] _y; + + /// Sample Points t, sorted ascendingly. + /// Sample Values x(t), sorted ascendingly by x. + public NevillePolynomialInterpolation(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 1) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 1), nameof(x)); + } + + for (var i = 1; i < x.Length; ++i) + { + if (x[i] == x[i - 1]) + { + throw new ArgumentException(Resources.Interpolation_Initialize_SamplePointsNotUnique, nameof(x)); + } + } + + _x = x; + _y = y; + } + + /// + /// Create a Neville polynomial interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// + public static NevillePolynomialInterpolation InterpolateSorted(double[] x, double[] y) + { + return new NevillePolynomialInterpolation(x, y); + } + + /// + /// Create a Neville polynomial interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static NevillePolynomialInterpolation InterpolateInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateSorted(x, y); + } + + /// + /// Create a Neville polynomial interpolation from an unsorted set of (x,y) value pairs. + /// + public static NevillePolynomialInterpolation Interpolate(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(x.ToArray(), y.ToArray()); + } + + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool IInterpolation.SupportsDifferentiation + { + get { return true; } + } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool IInterpolation.SupportsIntegration + { + get { return false; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + var x = new double[_y.Length]; + _y.CopyTo(x, 0); + + for (int level = 1; level < x.Length; level++) + { + for (int i = 0; i < x.Length - level; i++) + { + double hp = t - _x[i + level]; + double ho = _x[i] - t; + double den = _x[i] - _x[i + level]; + x[i] = ((hp * x[i]) + (ho * x[i + 1])) / den; + } + } + + return x[0]; + } + + /// + /// Differentiate at point t. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + public double Differentiate(double t) + { + var x = new double[_y.Length]; + var dx = new double[_y.Length]; + _y.CopyTo(x, 0); + + for (int level = 1; level < x.Length; level++) + { + for (int i = 0; i < x.Length - level; i++) + { + double hp = t - _x[i + level]; + double ho = _x[i] - t; + double den = _x[i] - _x[i + level]; + dx[i] = ((hp * dx[i]) + x[i] + (ho * dx[i + 1]) - x[i + 1]) / den; + x[i] = ((hp * x[i]) + (ho * x[i + 1])) / den; + } + } + + return dx[0]; + } + + /// + /// Differentiate twice at point t. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + public double Differentiate2(double t) + { + var x = new double[_y.Length]; + var dx = new double[_y.Length]; + var ddx = new double[_y.Length]; + _y.CopyTo(x, 0); + + for (int level = 1; level < x.Length; level++) + { + for (int i = 0; i < x.Length - level; i++) + { + double hp = t - _x[i + level]; + double ho = _x[i] - t; + double den = _x[i] - _x[i + level]; + ddx[i] = ((hp * ddx[i]) + (ho * ddx[i + 1]) + (2 * dx[i]) - (2 * dx[i + 1])) / den; + dx[i] = ((hp * dx[i]) + x[i] + (ho * dx[i + 1]) - x[i + 1]) / den; + x[i] = ((hp * x[i]) + (ho * x[i + 1])) / den; + } + } + + return ddx[0]; + } + + /// + /// Indefinite integral at point t. NOT SUPPORTED. + /// + /// Point t to integrate at. + double IInterpolation.Integrate(double t) + { + throw new NotSupportedException(); + } + + /// + /// Definite integral between points a and b. NOT SUPPORTED. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + double IInterpolation.Integrate(double a, double b) + { + throw new NotSupportedException(); + } +} diff --git a/MathNet.Numerics/Interpolation/QuadraticSpline.cs b/MathNet.Numerics/Interpolation/QuadraticSpline.cs new file mode 100644 index 0000000..1a197e8 --- /dev/null +++ b/MathNet.Numerics/Interpolation/QuadraticSpline.cs @@ -0,0 +1,168 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.Interpolation; + +/// +/// Quadratic Spline Interpolation. +/// +/// Supports both differentiation and integration. +public class QuadraticSpline : IInterpolation +{ + readonly double[] _x; + readonly double[] _c0; + readonly double[] _c1; + readonly double[] _c2; + readonly Lazy _indefiniteIntegral; + + /// sample points (N+1), sorted ascending + /// Zero order spline coefficients (N) + /// First order spline coefficients (N) + /// second order spline coefficients (N) + public QuadraticSpline(double[] x, double[] c0, double[] c1, double[] c2) + { + if (x.Length != c0.Length + 1 || x.Length != c1.Length + 1 || x.Length != c2.Length + 1) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 2) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 2), nameof(x)); + } + + _x = x; + _c0 = c0; + _c1 = c1; + _c2 = c2; + _indefiniteIntegral = new Lazy(ComputeIndefiniteIntegral); + } + + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool IInterpolation.SupportsDifferentiation + { + get { return true; } + } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool IInterpolation.SupportsIntegration + { + get { return true; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + int k = LeftSegmentIndex(t); + var x = t - _x[k]; + return _c0[k] + x * (_c1[k] + x * _c2[k]); + } + + /// + /// Differentiate at point t. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + public double Differentiate(double t) + { + int k = LeftSegmentIndex(t); + return _c1[k] + (t - _x[k]) * 2 * _c2[k]; + } + + /// + /// Differentiate twice at point t. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + public double Differentiate2(double t) + { + int k = LeftSegmentIndex(t); + return 2 * _c2[k]; + } + + /// + /// Indefinite integral at point t. + /// + /// Point t to integrate at. + public double Integrate(double t) + { + int k = LeftSegmentIndex(t); + var x = t - _x[k]; + return _indefiniteIntegral.Value[k] + x * (_c0[k] + x * (_c1[k] / 2 + x * _c2[k] / 3)); + } + + /// + /// Definite integral between points a and b. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + public double Integrate(double a, double b) + { + return Integrate(b) - Integrate(a); + } + + double[] ComputeIndefiniteIntegral() + { + var integral = new double[_c1.Length]; + for (int i = 0; i < integral.Length - 1; i++) + { + double w = _x[i + 1] - _x[i]; + integral[i + 1] = integral[i] + w * (_c0[i] + w * (_c1[i] / 2 + w * _c2[i] / 3)); + } + + return integral; + } + + /// + /// Find the index of the greatest sample point smaller than t, + /// or the left index of the closest segment for extrapolation. + /// + int LeftSegmentIndex(double t) + { + int index = Array.BinarySearch(_x, t); + if (index < 0) + { + index = ~index - 1; + } + + return Math.Min(Math.Max(index, 0), _x.Length - 2); + } +} diff --git a/MathNet.Numerics/Interpolation/SplineBoundaryCondition.cs b/MathNet.Numerics/Interpolation/SplineBoundaryCondition.cs new file mode 100644 index 0000000..8ff2564 --- /dev/null +++ b/MathNet.Numerics/Interpolation/SplineBoundaryCondition.cs @@ -0,0 +1,56 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Interpolation; + +/// +/// Left and right boundary conditions. +/// +public enum SplineBoundaryCondition +{ + /// + /// Natural Boundary (Zero second derivative). + /// + Natural = 0, + + /// + /// Parabolically Terminated boundary. + /// + ParabolicallyTerminated, + + /// + /// Fixed first derivative at the boundary. + /// + FirstDerivative, + + /// + /// Fixed second derivative at the boundary. + /// + SecondDerivative +} diff --git a/MathNet.Numerics/Interpolation/StepInterpolation.cs b/MathNet.Numerics/Interpolation/StepInterpolation.cs new file mode 100644 index 0000000..d713964 --- /dev/null +++ b/MathNet.Numerics/Interpolation/StepInterpolation.cs @@ -0,0 +1,198 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Interpolation; + +/// +/// A step function where the start of each segment is included, and the last segment is open-ended. +/// Segment i is [x_i, x_i+1) for i < N, or [x_i, infinity] for i = N. +/// The domain of the function is all real numbers, such that y = 0 where x <. +/// +/// Supports both differentiation and integration. +public class StepInterpolation : IInterpolation +{ + readonly double[] _x; + readonly double[] _y; + readonly Lazy _indefiniteIntegral; + + /// Sample points (N), sorted ascending + /// Samples values (N) of each segment starting at the corresponding sample point. + public StepInterpolation(double[] x, double[] sy) + { + if (x.Length != sy.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (x.Length < 1) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, 1), nameof(x)); + } + + _x = x; + _y = sy; + _indefiniteIntegral = new Lazy(ComputeIndefiniteIntegral); + } + + /// + /// Create a linear spline interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// + public static StepInterpolation InterpolateSorted(double[] x, double[] y) + { + return new StepInterpolation(x, y); + } + + /// + /// Create a linear spline interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered. + /// + public static StepInterpolation InterpolateInplace(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + return InterpolateSorted(x, y); + } + + /// + /// Create a linear spline interpolation from an unsorted set of (x,y) value pairs. + /// + public static StepInterpolation Interpolate(IEnumerable x, IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(x.ToArray(), y.ToArray()); + } + + bool IInterpolation.SupportsDifferentiation + { + get { return true; } + } + + bool IInterpolation.SupportsIntegration + { + get { return true; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + if (t < _x[0]) + { + return 0.0; + } + + int k = LeftBracketIndex(t); + return _y[k]; + } + + /// + /// Differentiate at point t. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + public double Differentiate(double t) + { + int index = Array.BinarySearch(_x, t); + if (index >= 0) + { + return double.NaN; + } + + return 0d; + } + + /// + /// Differentiate twice at point t. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + public double Differentiate2(double t) + { + return Differentiate(t); + } + + /// + /// Indefinite integral at point t. + /// + /// Point t to integrate at. + public double Integrate(double t) + { + if (t <= _x[0]) + { + return 0.0; + } + + int k = LeftBracketIndex(t); + var x = t - _x[k]; + return _indefiniteIntegral.Value[k] + x * _y[k]; + } + + /// + /// Definite integral between points a and b. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + public double Integrate(double a, double b) + { + return Integrate(b) - Integrate(a); + } + + double[] ComputeIndefiniteIntegral() + { + var integral = new double[_x.Length]; + for (int i = 0; i < integral.Length - 1; i++) + { + integral[i + 1] = integral[i] + (_x[i + 1] - _x[i]) * _y[i]; + } + + return integral; + } + + /// + /// Find the index of the greatest sample point smaller than t. + /// + int LeftBracketIndex(double t) + { + int index = Array.BinarySearch(_x, t); + return index >= 0 ? index : ~index - 1; + } +} diff --git a/MathNet.Numerics/Interpolation/TransformedInterpolation.cs b/MathNet.Numerics/Interpolation/TransformedInterpolation.cs new file mode 100644 index 0000000..6ed3c70 --- /dev/null +++ b/MathNet.Numerics/Interpolation/TransformedInterpolation.cs @@ -0,0 +1,184 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Interpolation; + +/// +/// Wraps an interpolation with a transformation of the interpolated values. +/// +/// Neither differentiation nor integration is supported. +public class TransformedInterpolation : IInterpolation +{ + readonly IInterpolation _interpolation; + readonly Func _transform; + + public TransformedInterpolation(IInterpolation interpolation, Func transform) + { + _interpolation = interpolation; + _transform = transform; + } + + /// + /// Create a linear spline interpolation from a set of (x,y) value pairs, sorted ascendingly by x. + /// + public static TransformedInterpolation InterpolateSorted( + Func transform, + Func transformInverse, + double[] x, + double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var yhat = new double[y.Length]; + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + yhat[i] = transformInverse(y[i]); + } + }); + + return new TransformedInterpolation(LinearSpline.InterpolateSorted(x, yhat), transform); + } + + /// + /// Create a linear spline interpolation from an unsorted set of (x,y) value pairs. + /// WARNING: Works in-place and can thus causes the data array to be reordered and modified. + /// + public static TransformedInterpolation InterpolateInplace( + Func transform, + Func transformInverse, + double[] x, + double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Sorting.Sort(x, y); + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + y[i] = transformInverse(y[i]); + } + }); + + return new TransformedInterpolation(LinearSpline.InterpolateSorted(x, y), transform); + } + + /// + /// Create a linear spline interpolation from an unsorted set of (x,y) value pairs. + /// + public static TransformedInterpolation Interpolate( + Func transform, + Func transformInverse, + IEnumerable x, + IEnumerable y) + { + // note: we must make a copy, even if the input was arrays already + return InterpolateInplace(transform, transformInverse, x.ToArray(), y.ToArray()); + } + + /// + /// Gets a value indicating whether the algorithm supports differentiation (interpolated derivative). + /// + bool IInterpolation.SupportsDifferentiation + { + get { return false; } + } + + /// + /// Gets a value indicating whether the algorithm supports integration (interpolated quadrature). + /// + bool IInterpolation.SupportsIntegration + { + get { return false; } + } + + /// + /// Interpolate at point t. + /// + /// Point t to interpolate at. + /// Interpolated value x(t). + public double Interpolate(double t) + { + return _transform(_interpolation.Interpolate(t)); + } + + /// + /// Differentiate at point t. NOT SUPPORTED. + /// + /// Point t to interpolate at. + /// Interpolated first derivative at point t. + double IInterpolation.Differentiate(double t) + { + throw new NotSupportedException(); + } + + /// + /// Differentiate twice at point t. NOT SUPPORTED. + /// + /// Point t to interpolate at. + /// Interpolated second derivative at point t. + double IInterpolation.Differentiate2(double t) + { + throw new NotSupportedException(); + } + + /// + /// Indefinite integral at point t. NOT SUPPORTED. + /// + /// Point t to integrate at. + double IInterpolation.Integrate(double t) + { + throw new NotSupportedException(); + } + + /// + /// Definite integral between points a and b. NOT SUPPORTED. + /// + /// Left bound of the integration interval [a,b]. + /// Right bound of the integration interval [a,b]. + double IInterpolation.Integrate(double a, double b) + { + throw new NotSupportedException(); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Builder.cs b/MathNet.Numerics/LinearAlgebra/Builder.cs new file mode 100644 index 0000000..79472d8 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Builder.cs @@ -0,0 +1,1656 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Random; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double +{ + internal class MatrixBuilder : MatrixBuilder + { + public override double Zero + { + get { return 0d; } + } + + public override double One + { + get { return 1d; } + } + + public override Matrix Dense(DenseColumnMajorMatrixStorage storage) + { + return new DenseMatrix(storage); + } + + public override Matrix Sparse(SparseCompressedRowMatrixStorage storage) + { + return new SparseMatrix(storage); + } + + public override Matrix Diagonal(DiagonalMatrixStorage storage) + { + return new DiagonalMatrix(storage); + } + + public override Matrix Random(int rows, int columns, IContinuousDistribution distribution) + { + return Dense(rows, columns, Generate.Random(rows * columns, distribution)); + } + + public override IIterationStopCriterion[] IterativeSolverStopCriteria(int maxIterations = 1000) + { + return new IIterationStopCriterion[] + { + new FailureStopCriterion(), + new DivergenceStopCriterion(), + new IterationCountStopCriterion(maxIterations), + new ResidualStopCriterion(1e-12) + }; + } + } + + internal class VectorBuilder : VectorBuilder + { + public override double Zero + { + get { return 0d; } + } + + public override double One + { + get { return 1d; } + } + + public override Vector Dense(DenseVectorStorage storage) + { + return new DenseVector(storage); + } + + public override Vector Sparse(SparseVectorStorage storage) + { + return new SparseVector(storage); + } + + public override Vector Random(int length, IContinuousDistribution distribution) + { + return Dense(Generate.Random(length, distribution)); + } + } +} + +namespace MathNet.Numerics.LinearAlgebra.Single +{ + internal class MatrixBuilder : MatrixBuilder + { + public override float Zero + { + get { return 0f; } + } + + public override float One + { + get { return 1f; } + } + + public override Matrix Dense(DenseColumnMajorMatrixStorage storage) + { + return new DenseMatrix(storage); + } + + public override Matrix Sparse(SparseCompressedRowMatrixStorage storage) + { + return new SparseMatrix(storage); + } + + public override Matrix Diagonal(DiagonalMatrixStorage storage) + { + return new DiagonalMatrix(storage); + } + + public override Matrix Random(int rows, int columns, IContinuousDistribution distribution) + { + return Dense(rows, columns, Generate.RandomSingle(rows * columns, distribution)); + } + + public override IIterationStopCriterion[] IterativeSolverStopCriteria(int maxIterations = 1000) + { + return new IIterationStopCriterion[] + { + new FailureStopCriterion(), + new DivergenceStopCriterion(), + new IterationCountStopCriterion(maxIterations), + new ResidualStopCriterion(1e-6) + }; + } + } + + internal class VectorBuilder : VectorBuilder + { + public override float Zero + { + get { return 0f; } + } + + public override float One + { + get { return 1f; } + } + + public override Vector Dense(DenseVectorStorage storage) + { + return new DenseVector(storage); + } + + public override Vector Sparse(SparseVectorStorage storage) + { + return new SparseVector(storage); + } + + public override Vector Random(int length, IContinuousDistribution distribution) + { + return Dense(Generate.RandomSingle(length, distribution)); + } + } +} + +namespace MathNet.Numerics.LinearAlgebra.Complex +{ + using Complex = System.Numerics.Complex; + + internal class MatrixBuilder : MatrixBuilder + { + public override Complex Zero + { + get { return Complex.Zero; } + } + + public override Complex One + { + get { return Complex.One; } + } + + public override Matrix Dense(DenseColumnMajorMatrixStorage storage) + { + return new DenseMatrix(storage); + } + + public override Matrix Sparse(SparseCompressedRowMatrixStorage storage) + { + return new SparseMatrix(storage); + } + + public override Matrix Diagonal(DiagonalMatrixStorage storage) + { + return new DiagonalMatrix(storage); + } + + public override Matrix Random(int rows, int columns, IContinuousDistribution distribution) + { + return Dense(rows, columns, Generate.RandomComplex(rows * columns, distribution)); + } + + public override IIterationStopCriterion[] IterativeSolverStopCriteria(int maxIterations = 1000) + { + return new IIterationStopCriterion[] + { + new FailureStopCriterion(), + new DivergenceStopCriterion(), + new IterationCountStopCriterion(maxIterations), + new ResidualStopCriterion(1e-12) + }; + } + } + + internal class VectorBuilder : VectorBuilder + { + public override Complex Zero + { + get { return Complex.Zero; } + } + + public override Complex One + { + get { return Complex.One; } + } + + public override Vector Dense(DenseVectorStorage storage) + { + return new DenseVector(storage); + } + + public override Vector Sparse(SparseVectorStorage storage) + { + return new SparseVector(storage); + } + + public override Vector Random(int length, IContinuousDistribution distribution) + { + return Dense(Generate.RandomComplex(length, distribution)); + } + } +} + +namespace MathNet.Numerics.LinearAlgebra.Complex32 +{ + internal class MatrixBuilder : MatrixBuilder + { + public override Numerics.Complex32 Zero + { + get { return Numerics.Complex32.Zero; } + } + + public override Numerics.Complex32 One + { + get { return Numerics.Complex32.One; } + } + + public override Matrix Dense(DenseColumnMajorMatrixStorage storage) + { + return new DenseMatrix(storage); + } + + public override Matrix Sparse(SparseCompressedRowMatrixStorage storage) + { + return new SparseMatrix(storage); + } + + public override Matrix Diagonal(DiagonalMatrixStorage storage) + { + return new DiagonalMatrix(storage); + } + + public override Matrix Random(int rows, int columns, IContinuousDistribution distribution) + { + return Dense(rows, columns, Generate.RandomComplex32(rows * columns, distribution)); + } + + public override IIterationStopCriterion[] IterativeSolverStopCriteria(int maxIterations = 1000) + { + return new IIterationStopCriterion[] + { + new FailureStopCriterion(), + new DivergenceStopCriterion(), + new IterationCountStopCriterion(maxIterations), + new ResidualStopCriterion(1e-6) + }; + } + } + + internal class VectorBuilder : VectorBuilder + { + public override Numerics.Complex32 Zero + { + get { return Numerics.Complex32.Zero; } + } + + public override Numerics.Complex32 One + { + get { return Numerics.Complex32.One; } + } + + public override Vector Dense(DenseVectorStorage storage) + { + return new DenseVector(storage); + } + + public override Vector Sparse(SparseVectorStorage storage) + { + return new SparseVector(storage); + } + + public override Vector Random(int length, IContinuousDistribution distribution) + { + return Dense(Generate.RandomComplex32(length, distribution)); + } + } +} + +namespace MathNet.Numerics.LinearAlgebra +{ + /// + /// Generic linear algebra type builder, for situations where a matrix or vector + /// must be created in a generic way. Usage of generic builders should not be + /// required in normal user code. + /// + public abstract class MatrixBuilder where T : struct, IEquatable, IFormattable + { + /// + /// Gets the value of 0.0 for type T. + /// + public abstract T Zero { get; } + + /// + /// Gets the value of 1.0 for type T. + /// + public abstract T One { get; } + + /// + /// Create a new matrix straight from an initialized matrix storage instance. + /// If you have an instance of a discrete storage type instead, use their direct methods instead. + /// + public Matrix OfStorage(MatrixStorage storage) + { + if (storage == null) + throw new ArgumentNullException(nameof(storage)); + + var dense = storage as DenseColumnMajorMatrixStorage; + if (dense != null) + return Dense(dense); + + var sparse = storage as SparseCompressedRowMatrixStorage; + if (sparse != null) + return Sparse(sparse); + + var diagonal = storage as DiagonalMatrixStorage; + if (diagonal != null) + return Diagonal(diagonal); + + throw new NotSupportedException(string.Format("Matrix storage type '{0}' is not supported. Only DenseColumnMajorMatrixStorage, SparseCompressedRowMatrixStorage and DiagonalMatrixStorage are supported as this point.", storage.GetType().Name)); + } + + /// + /// Create a new matrix with the same kind of the provided example. + /// + public Matrix SameAs(Matrix example, int rows, int columns, bool fullyMutable = false) + where TU : struct, IEquatable, IFormattable + { + var storage = example.Storage; + if (storage is DenseColumnMajorMatrixStorage) + return Dense(rows, columns); + if (storage is DiagonalMatrixStorage) + return fullyMutable ? Sparse(rows, columns) : Diagonal(rows, columns); + if (storage is SparseCompressedRowMatrixStorage) + return Sparse(rows, columns); + return Dense(rows, columns); + } + + /// + /// Create a new matrix with the same kind and dimensions of the provided example. + /// + public Matrix SameAs(Matrix example) + where TU : struct, IEquatable, IFormattable + { + return SameAs(example, example.RowCount, example.ColumnCount); + } + + /// + /// Create a new matrix with the same kind of the provided example. + /// + public Matrix SameAs(Vector example, int rows, int columns) + { + return example.Storage.IsDense ? Dense(rows, columns) : Sparse(rows, columns); + } + + /// + /// Create a new matrix with a type that can represent and is closest to both provided samples. + /// + public Matrix SameAs(Matrix example, Matrix otherExample, int rows, int columns, bool fullyMutable = false) + { + var storage1 = example.Storage; + var storage2 = otherExample.Storage; + if (storage1 is DenseColumnMajorMatrixStorage || storage2 is DenseColumnMajorMatrixStorage) + return Dense(rows, columns); + if (storage1 is DiagonalMatrixStorage && storage2 is DiagonalMatrixStorage) + return fullyMutable ? Sparse(rows, columns) : Diagonal(rows, columns); + if (storage1 is SparseCompressedRowMatrixStorage || storage2 is SparseCompressedRowMatrixStorage) + return Sparse(rows, columns); + return Dense(rows, columns); + } + + /// + /// Create a new matrix with a type that can represent and is closest to both provided samples and the dimensions of example. + /// + public Matrix SameAs(Matrix example, Matrix otherExample) + { + return SameAs(example, otherExample, example.RowCount, example.ColumnCount); + } + + /// + /// Create a new dense matrix with values sampled from the provided random distribution. + /// + public abstract Matrix Random(int rows, int columns, IContinuousDistribution distribution); + + /// + /// Create a new dense matrix with values sampled from the standard distribution with a system random source. + /// + public Matrix Random(int rows, int columns) + { + return Random(rows, columns, new Normal(SystemRandomSource.Default)); + } + + /// + /// Create a new dense matrix with values sampled from the standard distribution with a system random source. + /// + public Matrix Random(int rows, int columns, int seed) + { + return Random(rows, columns, new Normal(new SystemRandomSource(seed, true))); + } + + /// + /// Create a new positive definite dense matrix where each value is the product + /// of two samples from the provided random distribution. + /// + public Matrix RandomPositiveDefinite(int order, IContinuousDistribution distribution) + { + var a = Random(order, order, distribution); + return a.ConjugateTransposeThisAndMultiply(a); + } + + /// + /// Create a new positive definite dense matrix where each value is the product + /// of two samples from the standard distribution. + /// + public Matrix RandomPositiveDefinite(int order) + { + var a = Random(order, order, new Normal(SystemRandomSource.Default)); + return a.ConjugateTransposeThisAndMultiply(a); + } + + /// + /// Create a new positive definite dense matrix where each value is the product + /// of two samples from the provided random distribution. + /// + public Matrix RandomPositiveDefinite(int order, int seed) + { + var a = Random(order, order, new Normal(new SystemRandomSource(seed, true))); + return a.ConjugateTransposeThisAndMultiply(a); + } + + /// + /// Create a new dense matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public abstract Matrix Dense(DenseColumnMajorMatrixStorage storage); + + /// + /// Create a new dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + public Matrix Dense(int rows, int columns) + { + return Dense(new DenseColumnMajorMatrixStorage(rows, columns)); + } + + /// + /// Create a new dense matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to be in column-major order (column by column) and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + /// + public Matrix Dense(int rows, int columns, T[] storage) + { + return Dense(new DenseColumnMajorMatrixStorage(rows, columns, storage)); + } + + /// + /// Create a new dense matrix and initialize each value to the same provided value. + /// + public Matrix Dense(int rows, int columns, T value) + { + if (Zero.Equals(value)) + return Dense(rows, columns); + return Dense(DenseColumnMajorMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new dense matrix and initialize each value using the provided init function. + /// + public Matrix Dense(int rows, int columns, Func init) + { + return Dense(DenseColumnMajorMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value to the same provided value. + /// + public Matrix DenseDiagonal(int rows, int columns, T value) + { + if (Zero.Equals(value)) + return Dense(rows, columns); + return Dense(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value to the same provided value. + /// + public Matrix DenseDiagonal(int order, T value) + { + if (Zero.Equals(value)) + return Dense(order, order); + return Dense(DenseColumnMajorMatrixStorage.OfDiagonalInit(order, order, i => value)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value using the provided init function. + /// + public Matrix DenseDiagonal(int rows, int columns, Func init) + { + return Dense(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new diagonal dense identity matrix with a one-diagonal. + /// + public Matrix DenseIdentity(int rows, int columns) + { + return Dense(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, i => One)); + } + + /// + /// Create a new diagonal dense identity matrix with a one-diagonal. + /// + public Matrix DenseIdentity(int order) + { + return Dense(DenseColumnMajorMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Create a new dense matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfMatrix(Matrix matrix) + { + return Dense(DenseColumnMajorMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new dense matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfArray(T[,] array) + { + return Dense(DenseColumnMajorMatrixStorage.OfArray(array)); + } + + /// + /// Create a new dense matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return Dense(DenseColumnMajorMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfColumnMajor(int rows, int columns, IEnumerable columnMajor) + { + return Dense(DenseColumnMajorMatrixStorage.OfColumnMajorEnumerable(rows, columns, columnMajor)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfColumns(IEnumerable> data) + { + return Dense(DenseColumnMajorMatrixStorage.OfColumnArrays(data.Select(v => (v as T[]) ?? v.ToArray()).ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfColumns(int rows, int columns, IEnumerable> data) + { + return Dense(DenseColumnMajorMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix of T as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfColumnArrays(params T[][] columns) + { + return Dense(DenseColumnMajorMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new dense matrix of T as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfColumnArrays(IEnumerable columns) + { + return Dense(DenseColumnMajorMatrixStorage.OfColumnArrays((columns as T[][]) ?? columns.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return Dense(DenseColumnMajorMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfColumnVectors(IEnumerable> columns) + { + return Dense(DenseColumnMajorMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in row-major order (row by row). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfRowMajor(int rows, int columns, IEnumerable columnMajor) + { + return Dense(DenseColumnMajorMatrixStorage.OfRowMajorEnumerable(rows, columns, columnMajor)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfRows(IEnumerable> data) + { + return Dense(DenseColumnMajorMatrixStorage.OfRowArrays(data.Select(v => (v as T[]) ?? v.ToArray()).ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfRows(int rows, int columns, IEnumerable> data) + { + return Dense(DenseColumnMajorMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix of T as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfRowArrays(params T[][] rows) + { + return Dense(DenseColumnMajorMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new dense matrix of T as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfRowArrays(IEnumerable rows) + { + return Dense(DenseColumnMajorMatrixStorage.OfRowArrays((rows as T[][]) ?? rows.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return Dense(DenseColumnMajorMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfRowVectors(IEnumerable> rows) + { + return Dense(DenseColumnMajorMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfDiagonalVector(Vector diagonal) + { + var m = Dense(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = Dense(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfDiagonalArray(T[] diagonal) + { + var m = Dense(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DenseOfDiagonalArray(int rows, int columns, T[] diagonal) + { + var m = Dense(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix from a 2D array of existing matrices. + /// The matrices in the array are not required to be dense already. + /// If the matrices do not align properly, they are placed on the top left + /// corner of their cell with the remaining fields left zero. + /// + public Matrix DenseOfMatrixArray(Matrix[,] matrices) + { + var rowspans = new int[matrices.GetLength(0)]; + var colspans = new int[matrices.GetLength(1)]; + for (int i = 0; i < rowspans.Length; i++) + { + for (int j = 0; j < colspans.Length; j++) + { + rowspans[i] = Math.Max(rowspans[i], matrices[i, j].RowCount); + colspans[j] = Math.Max(colspans[j], matrices[i, j].ColumnCount); + } + } + + var m = Dense(rowspans.Sum(), colspans.Sum()); + int rowoffset = 0; + for (int i = 0; i < rowspans.Length; i++) + { + int coloffset = 0; + for (int j = 0; j < colspans.Length; j++) + { + m.SetSubMatrix(rowoffset, coloffset, matrices[i, j]); + coloffset += colspans[j]; + } + + rowoffset += rowspans[i]; + } + + return m; + } + + /// + /// Create a new sparse matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public abstract Matrix Sparse(SparseCompressedRowMatrixStorage storage); + + /// + /// Create a sparse matrix of T with the given number of rows and columns. + /// + /// The number of rows. + /// The number of columns. + public Matrix Sparse(int rows, int columns) + { + return Sparse(new SparseCompressedRowMatrixStorage(rows, columns)); + } + + /// + /// Create a new sparse matrix and initialize each value to the same provided value. + /// + public Matrix Sparse(int rows, int columns, T value) + { + if (Zero.Equals(value)) + return Sparse(rows, columns); + return Sparse(SparseCompressedRowMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new sparse matrix and initialize each value using the provided init function. + /// + public Matrix Sparse(int rows, int columns, Func init) + { + return Sparse(SparseCompressedRowMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value to the same provided value. + /// + public Matrix SparseDiagonal(int rows, int columns, T value) + { + if (Zero.Equals(value)) + return Sparse(rows, columns); + return Sparse(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value to the same provided value. + /// + public Matrix SparseDiagonal(int order, T value) + { + if (Zero.Equals(value)) + return Sparse(order, order); + return Sparse(SparseCompressedRowMatrixStorage.OfDiagonalInit(order, order, i => value)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value using the provided init function. + /// + public Matrix SparseDiagonal(int rows, int columns, Func init) + { + return Sparse(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new diagonal dense identity matrix with a one-diagonal. + /// + public Matrix SparseIdentity(int rows, int columns) + { + return Sparse(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, i => One)); + } + + /// + /// Create a new diagonal dense identity matrix with a one-diagonal. + /// + public Matrix SparseIdentity(int order) + { + return Sparse(SparseCompressedRowMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Create a new sparse matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfMatrix(Matrix matrix) + { + return Sparse(SparseCompressedRowMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfArray(T[,] array) + { + return Sparse(SparseCompressedRowMatrixStorage.OfArray(array)); + } + + /// + /// Create a new sparse matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return Sparse(SparseCompressedRowMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in row-major order (row by row). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + /// + public Matrix SparseOfRowMajor(int rows, int columns, IEnumerable rowMajor) + { + return Sparse(SparseCompressedRowMatrixStorage.OfRowMajorEnumerable(rows, columns, rowMajor)); + } + + /// + /// Create a new sparse matrix with the given number of rows and columns as a copy of the given array. + /// The array is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + /// + public Matrix SparseOfColumnMajor(int rows, int columns, IList columnMajor) + { + return Sparse(SparseCompressedRowMatrixStorage.OfColumnMajorList(rows, columns, columnMajor)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfColumns(IEnumerable> data) + { + return Sparse(SparseCompressedRowMatrixStorage.OfColumnArrays(data.Select(v => (v as T[]) ?? v.ToArray()).ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfColumns(int rows, int columns, IEnumerable> data) + { + return Sparse(SparseCompressedRowMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfColumnArrays(params T[][] columns) + { + return Sparse(SparseCompressedRowMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfColumnArrays(IEnumerable columns) + { + return Sparse(SparseCompressedRowMatrixStorage.OfColumnArrays((columns as T[][]) ?? columns.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return Sparse(SparseCompressedRowMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfColumnVectors(IEnumerable> columns) + { + return Sparse(SparseCompressedRowMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfRows(IEnumerable> data) + { + return Sparse(SparseCompressedRowMatrixStorage.OfRowArrays(data.Select(v => (v as T[]) ?? v.ToArray()).ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfRows(int rows, int columns, IEnumerable> data) + { + return Sparse(SparseCompressedRowMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfRowArrays(params T[][] rows) + { + return Sparse(SparseCompressedRowMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfRowArrays(IEnumerable rows) + { + return Sparse(SparseCompressedRowMatrixStorage.OfRowArrays((rows as T[][]) ?? rows.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return Sparse(SparseCompressedRowMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfRowVectors(IEnumerable> rows) + { + return Sparse(SparseCompressedRowMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfDiagonalVector(Vector diagonal) + { + var m = Sparse(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = Sparse(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfDiagonalArray(T[] diagonal) + { + var m = Sparse(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix SparseOfDiagonalArray(int rows, int columns, T[] diagonal) + { + var m = Sparse(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix from a 2D array of existing matrices. + /// The matrices in the array are not required to be sparse already. + /// If the matrices do not align properly, they are placed on the top left + /// corner of their cell with the remaining fields left zero. + /// + public Matrix SparseOfMatrixArray(Matrix[,] matrices) + { + var rowspans = new int[matrices.GetLength(0)]; + var colspans = new int[matrices.GetLength(1)]; + for (int i = 0; i < rowspans.Length; i++) + { + for (int j = 0; j < colspans.Length; j++) + { + rowspans[i] = Math.Max(rowspans[i], matrices[i, j].RowCount); + colspans[j] = Math.Max(colspans[j], matrices[i, j].ColumnCount); + } + } + + var m = Sparse(rowspans.Sum(), colspans.Sum()); + int rowoffset = 0; + for (int i = 0; i < rowspans.Length; i++) + { + int coloffset = 0; + for (int j = 0; j < colspans.Length; j++) + { + m.SetSubMatrix(rowoffset, coloffset, matrices[i, j]); + coloffset += colspans[j]; + } + + rowoffset += rowspans[i]; + } + + return m; + } + + /// + /// Create a new diagonal matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public abstract Matrix Diagonal(DiagonalMatrixStorage storage); + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + public Matrix Diagonal(int rows, int columns) + { + return Diagonal(new DiagonalMatrixStorage(rows, columns)); + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to represent the diagonal values and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + public Matrix Diagonal(int rows, int columns, T[] storage) + { + return Diagonal(new DiagonalMatrixStorage(rows, columns, storage)); + } + + /// + /// Create a new square diagonal matrix directly binding to a raw array. + /// The array is assumed to represent the diagonal values and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + public Matrix Diagonal(T[] storage) + { + return Diagonal(new DiagonalMatrixStorage(storage.Length, storage.Length, storage)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value to the same provided value. + /// + public Matrix Diagonal(int rows, int columns, T value) + { + if (Zero.Equals(value)) + return Diagonal(rows, columns); + return Diagonal(DiagonalMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value using the provided init function. + /// + public Matrix Diagonal(int rows, int columns, Func init) + { + return Diagonal(DiagonalMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal identity matrix with a one-diagonal. + /// + public Matrix DiagonalIdentity(int rows, int columns) + { + return Diagonal(DiagonalMatrixStorage.OfValue(rows, columns, One)); + } + + /// + /// Create a new diagonal identity matrix with a one-diagonal. + /// + public Matrix DiagonalIdentity(int order) + { + return Diagonal(DiagonalMatrixStorage.OfValue(order, order, One)); + } + + /// + /// Create a new diagonal matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DiagonalOfDiagonalVector(Vector diagonal) + { + var m = Diagonal(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new diagonal matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DiagonalOfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = Diagonal(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new diagonal matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DiagonalOfDiagonalArray(T[] diagonal) + { + var m = Diagonal(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new diagonal matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public Matrix DiagonalOfDiagonalArray(int rows, int columns, T[] diagonal) + { + var m = Diagonal(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + public abstract IIterationStopCriterion[] IterativeSolverStopCriteria(int maxIterations = 1000); + } + + /// + /// Generic linear algebra type builder, for situations where a matrix or vector + /// must be created in a generic way. Usage of generic builders should not be + /// required in normal user code. + /// + public abstract class VectorBuilder where T : struct, IEquatable, IFormattable + { + /// + /// Gets the value of 0.0 for type T. + /// + public abstract T Zero { get; } + + /// + /// Gets the value of 1.0 for type T. + /// + public abstract T One { get; } + + /// + /// Create a new vector straight from an initialized matrix storage instance. + /// If you have an instance of a discrete storage type instead, use their direct methods instead. + /// + public Vector OfStorage(VectorStorage storage) + { + if (storage == null) + throw new ArgumentNullException(nameof(storage)); + + var dense = storage as DenseVectorStorage; + if (dense != null) + return Dense(dense); + + var sparse = storage as SparseVectorStorage; + if (sparse != null) + return Sparse(sparse); + + throw new NotSupportedException(string.Format("Vector storage type '{0}' is not supported. Only DenseVectorStorage and SparseVectorStorage are supported as this point.", storage.GetType().Name)); + } + + /// + /// Create a new vector with the same kind of the provided example. + /// + public Vector SameAs(Vector example, int length) + where TU : struct, IEquatable, IFormattable + { + return example.Storage.IsDense ? Dense(length) : Sparse(length); + } + + /// + /// Create a new vector with the same kind and dimension of the provided example. + /// + public Vector SameAs(Vector example) + where TU : struct, IEquatable, IFormattable + { + return example.Storage.IsDense ? Dense(example.Count) : Sparse(example.Count); + } + + /// + /// Create a new vector with the same kind of the provided example. + /// + public Vector SameAs(Matrix example, int length) + where TU : struct, IEquatable, IFormattable + { + return example.Storage.IsDense ? Dense(length) : Sparse(length); + } + + /// + /// Create a new vector with a type that can represent and is closest to both provided samples. + /// + public Vector SameAs(Vector example, Vector otherExample, int length) + { + return example.Storage.IsDense || otherExample.Storage.IsDense ? Dense(length) : Sparse(length); + } + + /// + /// Create a new vector with a type that can represent and is closest to both provided samples and the dimensions of example. + /// + public Vector SameAs(Vector example, Vector otherExample) + { + return example.Storage.IsDense || otherExample.Storage.IsDense ? Dense(example.Count) : Sparse(example.Count); + } + + /// + /// Create a new vector with a type that can represent and is closest to both provided samples. + /// + public Vector SameAs(Matrix matrix, Vector vector, int length) + { + return matrix.Storage.IsDense || vector.Storage.IsDense ? Dense(length) : Sparse(length); + } + + /// + /// Create a new dense vector with values sampled from the provided random distribution. + /// + public abstract Vector Random(int length, IContinuousDistribution distribution); + + /// + /// Create a new dense vector with values sampled from the standard distribution with a system random source. + /// + public Vector Random(int length) + { + return Random(length, new Normal(SystemRandomSource.Default)); + } + + /// + /// Create a new dense vector with values sampled from the standard distribution with a system random source. + /// + public Vector Random(int length, int seed) + { + return Random(length, new Normal(new SystemRandomSource(seed, true))); + } + + /// + /// Create a new dense vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public abstract Vector Dense(DenseVectorStorage storage); + + /// + /// Create a dense vector of T with the given size. + /// + /// The size of the vector. + public Vector Dense(int size) + { + return Dense(new DenseVectorStorage(size)); + } + + /// + /// Create a dense vector of T that is directly bound to the specified array. + /// + public Vector Dense(T[] array) + { + return Dense(new DenseVectorStorage(array.Length, array)); + } + + /// + /// Create a new dense vector and initialize each value using the provided value. + /// + public Vector Dense(int length, T value) + { + if (Zero.Equals(value)) + return Dense(length); + return Dense(DenseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new dense vector and initialize each value using the provided init function. + /// + public Vector Dense(int length, Func init) + { + return Dense(DenseVectorStorage.OfInit(length, init)); + } + + /// + /// Create a new dense vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public Vector DenseOfVector(Vector vector) + { + return Dense(DenseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new dense vector as a copy of the given array. + /// This new vector will be independent from the array. + /// A new memory block will be allocated for storing the vector. + /// + public Vector DenseOfArray(T[] array) + { + return Dense(DenseVectorStorage.OfVector(new DenseVectorStorage(array.Length, array))); + } + + /// + /// Create a new dense vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public Vector DenseOfEnumerable(IEnumerable enumerable) + { + return Dense(DenseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new dense vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public Vector DenseOfIndexed(int length, IEnumerable> enumerable) + { + return Dense(DenseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new sparse vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public abstract Vector Sparse(SparseVectorStorage storage); + + /// + /// Create a sparse vector of T with the given size. + /// + /// The size of the vector. + public Vector Sparse(int size) + { + return Sparse(new SparseVectorStorage(size)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided value. + /// + public Vector Sparse(int length, T value) + { + if (Zero.Equals(value)) + return Sparse(length); + return Sparse(SparseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided init function. + /// + public Vector Sparse(int length, Func init) + { + return Sparse(SparseVectorStorage.OfInit(length, init)); + } + + /// + /// Create a new sparse vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public Vector SparseOfVector(Vector vector) + { + return Sparse(SparseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new sparse vector as a copy of the given array. + /// This new vector will be independent from the array. + /// A new memory block will be allocated for storing the vector. + /// + public Vector SparseOfArray(T[] array) + { + return Sparse(SparseVectorStorage.OfEnumerable(array)); + } + + /// + /// Create a new sparse vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public Vector SparseOfEnumerable(IEnumerable enumerable) + { + return Sparse(SparseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new sparse vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public Vector SparseOfIndexed(int length, IEnumerable> enumerable) + { + return Sparse(SparseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + } + + internal static class BuilderInstance where T : struct, IEquatable, IFormattable + { + static Lazy, VectorBuilder>> _singleton = new Lazy, VectorBuilder>>(Create); + + static Tuple, VectorBuilder> Create() + { + if (typeof(T) == typeof(System.Numerics.Complex)) + { + return new Tuple, VectorBuilder>( + (MatrixBuilder)(object)new LinearAlgebra.Complex.MatrixBuilder(), + (VectorBuilder)(object)new LinearAlgebra.Complex.VectorBuilder()); + } + + if (typeof(T) == typeof(Numerics.Complex32)) + { + return new Tuple, VectorBuilder>( + (MatrixBuilder)(object)new Complex32.MatrixBuilder(), + (VectorBuilder)(object)new Complex32.VectorBuilder()); + } + + if (typeof(T) == typeof(double)) + { + return new Tuple, VectorBuilder>( + (MatrixBuilder)(object)new Double.MatrixBuilder(), + (VectorBuilder)(object)new Double.VectorBuilder()); + } + + if (typeof(T) == typeof(float)) + { + return new Tuple, VectorBuilder>( + (MatrixBuilder)(object)new Single.MatrixBuilder(), + (VectorBuilder)(object)new Single.VectorBuilder()); + } + + throw new NotSupportedException(string.Format("Matrices and vectors of type '{0}' are not supported. Only Double, Single, Complex or Complex32 are supported at this point.", typeof(T).Name)); + } + + public static void Register(MatrixBuilder matrixBuilder, VectorBuilder vectorBuilder) + { + _singleton = new Lazy, VectorBuilder>>(() => new Tuple, VectorBuilder>(matrixBuilder, vectorBuilder)); + } + + public static MatrixBuilder Matrix + { + get { return _singleton.Value.Item1; } + } + + public static VectorBuilder Vector + { + get { return _singleton.Value.Item2; } + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/DenseMatrix.cs b/MathNet.Numerics/LinearAlgebra/Complex/DenseMatrix.cs new file mode 100644 index 0000000..14ee5fc --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/DenseMatrix.cs @@ -0,0 +1,1343 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Complex.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex; + +using Complex = System.Numerics.Complex; + +/// +/// A Matrix class with dense storage. The underlying storage is a one dimensional array in column-major order (column by column). +/// +[Serializable] +[DebuggerDisplay("DenseMatrix {RowCount}x{ColumnCount}-Complex")] +public class DenseMatrix : Matrix +{ + /// + /// Number of rows. + /// + /// Using this instead of the RowCount property to speed up calculating + /// a matrix index in the data array. + readonly int _rowCount; + + /// + /// Number of columns. + /// + /// Using this instead of the ColumnCount property to speed up calculating + /// a matrix index in the data array. + readonly int _columnCount; + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + readonly Complex[] _values; + + /// + /// Create a new dense matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DenseMatrix(DenseColumnMajorMatrixStorage storage) + : base(storage) + { + _rowCount = storage.RowCount; + _columnCount = storage.ColumnCount; + _values = storage.Data; + } + + /// + /// Create a new square dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public DenseMatrix(int order) + : this(new DenseColumnMajorMatrixStorage(order, order)) + { + } + + /// + /// Create a new dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DenseMatrix(int rows, int columns) + : this(new DenseColumnMajorMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new dense matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to be in column-major order (column by column) and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + /// + public DenseMatrix(int rows, int columns, Complex[] storage) + : this(new DenseColumnMajorMatrixStorage(rows, columns, storage)) + { + } + + /// + /// Create a new dense matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfMatrix(Matrix matrix) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new dense matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfArray(Complex[,] array) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfArray(array)); + } + + /// + /// Create a new dense matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnMajor(int rows, int columns, IEnumerable columnMajor) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnMajorEnumerable(rows, columns, columnMajor)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumns(IEnumerable> data) + { + return OfColumnArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumns(int rows, int columns, IEnumerable> data) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnArrays(params Complex[][] columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new dense matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnArrays(IEnumerable columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnArrays((columns as Complex[][]) ?? columns.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnVectors(IEnumerable> columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRows(IEnumerable> data) + { + return OfRowArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRows(int rows, int columns, IEnumerable> data) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowArrays(params Complex[][] rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new dense matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowArrays(IEnumerable rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowArrays((rows as Complex[][]) ?? rows.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowVectors(IEnumerable> rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalVector(Vector diagonal) + { + var m = new DenseMatrix(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = new DenseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalArray(Complex[] diagonal) + { + var m = new DenseMatrix(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalArray(int rows, int columns, Complex[] diagonal) + { + var m = new DenseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix and initialize each value to the same provided value. + /// + public static DenseMatrix Create(int rows, int columns, Complex value) + { + if (value == Complex.Zero) + return new DenseMatrix(rows, columns); + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new dense matrix and initialize each value using the provided init function. + /// + public static DenseMatrix Create(int rows, int columns, Func init) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value to the same provided value. + /// + public static DenseMatrix CreateDiagonal(int rows, int columns, Complex value) + { + if (value == Complex.Zero) + return new DenseMatrix(rows, columns); + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value using the provided init function. + /// + public static DenseMatrix CreateDiagonal(int rows, int columns, Func init) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static DenseMatrix CreateIdentity(int order) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Create a new dense matrix with values sampled from the provided random distribution. + /// + public static DenseMatrix CreateRandom(int rows, int columns, IContinuousDistribution distribution) + { + return new DenseMatrix(new DenseColumnMajorMatrixStorage(rows, columns, Generate.RandomComplex(rows * columns, distribution))); + } + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + public Complex[] Values + { + get { return _values; } + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.OneNorm, _rowCount, _columnCount, _values); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.InfinityNorm, _rowCount, _columnCount, _values); + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.FrobeniusNorm, _rowCount, _columnCount, _values); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(-1, _values, denseResult._values); + return; + } + + base.DoNegate(result); + } + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected override void DoConjugate(Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult != null) + { + LinearAlgebraControl.Provider.ConjugateArray(_values, denseResult._values); + return; + } + + base.DoConjugate(result); + } + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected override void DoAdd(Complex scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoAdd(scalar, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = _values[i] + scalar; + } + }); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of add + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + // dense + dense = dense + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + var denseResult = result.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.AddArrays(_values, denseOther.Data, denseResult.Data); + return; + } + + // dense + diagonal = any + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + Storage.CopyToUnchecked(result.Storage, ExistingData.Clear); + var diagonal = diagonalOther.Data; + for (int i = 0; i < diagonal.Length; i++) + { + result.At(i, i, result.At(i, i) + diagonal[i]); + } + + return; + } + + base.DoAdd(other, result); + } + + /// + /// Subtracts a scalar from each element of the matrix and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(Complex scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoSubtract(scalar, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = _values[i] - scalar; + } + }); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(Matrix other, Matrix result) + { + // dense + dense = dense + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + var denseResult = result.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.SubtractArrays(_values, denseOther.Data, denseResult.Data); + return; + } + + // dense + diagonal = matrix + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + CopyTo(result); + var diagonal = diagonalOther.Data; + for (int i = 0; i < diagonal.Length; i++) + { + result.At(i, i, result.At(i, i) - diagonal[i]); + } + + return; + } + + base.DoSubtract(other, result); + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(Complex scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoMultiply(scalar, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(scalar, _values, denseResult._values); + } + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + + if (denseRight == null || denseResult == null) + { + base.DoMultiply(rightSide, result); + } + else + { + LinearAlgebraControl.Provider.MatrixMultiply( + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + denseResult.Values); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiply( + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(ColumnCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.ColumnCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoMultiply(other, result); + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.DontTranspose, + Providers.LinearAlgebra.Transpose.Transpose, + 1.0, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(ColumnCount, other.RowCount); + if (d < other.RowCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.RowCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.DontTranspose, + Providers.LinearAlgebra.Transpose.ConjugateTranspose, + 1.0, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var conjugateDiagonal = new Complex[diagonal.Length]; + for (int i = 0; i < diagonal.Length; i++) + { + conjugateDiagonal[i] = diagonal[i].Conjugate(); + } + + var d = Math.Min(ColumnCount, other.RowCount); + if (d < other.RowCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.RowCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * conjugateDiagonal[j]); + index++; + } + } + + return; + } + + base.DoConjugateTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + if (denseRight != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.Transpose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0, + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + 0.0, + denseResult.Values); + return; + } + + base.DoTransposeThisAndMultiply(rightSide, result); + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + if (denseRight != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.ConjugateTranspose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0, + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + 0.0, + denseResult.Values); + return; + } + + base.DoConjugateTransposeThisAndMultiply(rightSide, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.Transpose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(RowCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, ColumnCount, RowCount, other.ColumnCount - RowCount); + } + + int index = 0; + for (int i = 0; i < ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + + index += (RowCount - d); + } + + return; + } + + base.DoTransposeThisAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.ConjugateTranspose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(RowCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, ColumnCount, RowCount, other.ColumnCount - RowCount); + } + + int index = 0; + for (int i = 0; i < ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(i, j, _values[index].Conjugate() * diagonal[j]); + index++; + } + + index += (RowCount - d); + } + + return; + } + + base.DoConjugateTransposeThisAndMultiply(other, result); + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(Complex divisor, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(1.0 / divisor, _values, denseResult._values); + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseMultiply(other, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + var denseDivisor = divisor as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseDivisor == null || denseResult == null) + { + base.DoPointwiseDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseDivideArrays(_values, denseDivisor._values, denseResult._values); + } + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Matrix exponent, Matrix result) + { + var denseExponent = exponent as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseExponent == null || denseResult == null) + { + base.DoPointwisePower(exponent, result); + } + else + { + LinearAlgebraControl.Provider.PointWisePowerArrays(_values, denseExponent._values, denseResult._values); + } + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public override Complex Trace() + { + if (_rowCount != _columnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var sum = Complex.Zero; + for (var i = 0; i < _rowCount; i++) + { + sum += _values[(i * _rowCount) + i]; + } + + return sum; + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static DenseMatrix operator +(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide._rowCount != rightSide._rowCount || leftSide._columnCount != rightSide._columnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Add(rightSide); + } + + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static DenseMatrix operator +(DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Clone(); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static DenseMatrix operator -(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide._rowCount != rightSide._rowCount || leftSide._columnCount != rightSide._columnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Subtract(rightSide); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static DenseMatrix operator -(DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Negate(); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator *(DenseMatrix leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator *(Complex leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static DenseMatrix operator *(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide._columnCount != rightSide._rowCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static DenseVector operator *(DenseMatrix leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static DenseVector operator *(DenseVector leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.LeftMultiply(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator %(DenseMatrix leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseMatrix)leftSide.Remainder(rightSide); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public override bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = j + 1; i < RowCount; i++) + { + if (_values[(i * ColumnCount) + j] != _values[index + i]) + { + return false; + } + } + } + + return true; + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public override bool IsHermitian() + { + if (RowCount != ColumnCount) + { + return false; + } + + int stride = RowCount + 1; + for (var k = 0; k < _values.Length; k += stride) + { + if (!_values[k].IsReal()) + { + return false; + } + } + + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = j + 1; i < RowCount; i++) + { + if (_values[(i * ColumnCount) + j] != _values[index + i].Conjugate()) + { + return false; + } + } + } + + return true; + } + + public override Cholesky Cholesky() + { + return DenseCholesky.Create(this); + } + + public override LU LU() + { + return DenseLU.Create(this); + } + + public override QR QR(QRMethod method = QRMethod.Thin) + { + return DenseQR.Create(this, method); + } + + public override GramSchmidt GramSchmidt() + { + return DenseGramSchmidt.Create(this); + } + + public override Svd Svd(bool computeVectors = true) + { + return DenseSvd.Create(this, computeVectors); + } + + public override Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown) + { + return DenseEvd.Create(this, symmetricity); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/DenseVector.cs b/MathNet.Numerics/LinearAlgebra/Complex/DenseVector.cs new file mode 100644 index 0000000..96dea58 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/DenseVector.cs @@ -0,0 +1,825 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex; + +using Complex = System.Numerics.Complex; + +/// +/// A vector using dense storage. +/// +[Serializable] +[DebuggerDisplay("DenseVector {" + nameof(Count) + "}-Complex")] +public class DenseVector : Vector +{ + /// + /// Number of elements + /// + readonly int _length; + + /// + /// Gets the vector's data. + /// + readonly Complex[] _values; + + /// + /// Create a new dense vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DenseVector(DenseVectorStorage storage) + : base(storage) + { + _length = storage.Length; + _values = storage.Data; + } + + /// + /// Create a new dense vector with the given length. + /// All cells of the vector will be initialized to zero. + /// Zero-length vectors are not supported. + /// + /// If length is less than one. + public DenseVector(int length) + : this(new DenseVectorStorage(length)) + { + } + + /// + /// Create a new dense vector directly binding to a raw array. + /// The array is used directly without copying. + /// Very efficient, but changes to the array and the vector will affect each other. + /// + public DenseVector(Complex[] storage) + : this(new DenseVectorStorage(storage.Length, storage)) + { + } + + /// + /// Create a new dense vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfVector(Vector vector) + { + return new DenseVector(DenseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new dense vector as a copy of the given array. + /// This new vector will be independent from the array. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfArray(Complex[] array) + { + return new DenseVector(DenseVectorStorage.OfVector(new DenseVectorStorage(array.Length, array))); + } + + /// + /// Create a new dense vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfEnumerable(IEnumerable enumerable) + { + return new DenseVector(DenseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new dense vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfIndexedEnumerable(int length, IEnumerable> enumerable) + { + return new DenseVector(DenseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new dense vector and initialize each value using the provided value. + /// + public static DenseVector Create(int length, Complex value) + { + if (value == Complex.Zero) + return new DenseVector(length); + return new DenseVector(DenseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new dense vector and initialize each value using the provided init function. + /// + public static DenseVector Create(int length, Func init) + { + return new DenseVector(DenseVectorStorage.OfInit(length, init)); + } + + /// + /// Create a new dense vector with values sampled from the provided random distribution. + /// + public static DenseVector CreateRandom(int length, IContinuousDistribution distribution) + { + var samples = Generate.RandomComplex(length, distribution); + return new DenseVector(new DenseVectorStorage(length, samples)); + } + + /// + /// Gets the vector's data. + /// + /// The vector's data. + public Complex[] Values + { + get { return _values; } + } + + /// + /// Returns a reference to the internal data structure. + /// + /// The DenseVector whose internal data we are + /// returning. + /// + /// A reference to the internal date of the given vector. + /// + public static explicit operator Complex[](DenseVector vector) + { + if (vector == null) + { + throw new ArgumentNullException(nameof(vector)); + } + + return vector.Values; + } + + /// + /// Returns a vector bound directly to a reference of the provided array. + /// + /// The array to bind to the DenseVector object. + /// + /// A DenseVector whose values are bound to the given array. + /// + public static implicit operator DenseVector(Complex[] array) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + return new DenseVector(array); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to add. + /// The vector to store the result of the addition. + protected override void DoAdd(Complex scalar, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoAdd(scalar, result); + } + else + { + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] + scalar; + } + }); + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + protected override void DoAdd(Vector other, Vector result) + { + var otherDense = other as DenseVector; + var resultDense = result as DenseVector; + + if (otherDense == null || resultDense == null) + { + base.DoAdd(other, result); + } + else + { + LinearAlgebraControl.Provider.AddArrays(_values, otherDense._values, resultDense._values); + } + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static Vector operator +(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.Add(rightSide); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The vector to store the result of the subtraction. + protected override void DoSubtract(Complex scalar, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoSubtract(scalar, result); + } + else + { + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] - scalar; + } + }); + } + } + + /// + /// Subtracts another vector from this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + protected override void DoSubtract(Vector other, Vector result) + { + var otherDense = other as DenseVector; + var resultDense = result as DenseVector; + + if (otherDense == null || resultDense == null) + { + base.DoSubtract(other, result); + } + else + { + LinearAlgebraControl.Provider.SubtractArrays(_values, otherDense._values, resultDense._values); + } + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static Vector operator -(DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static Vector operator -(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.Subtract(rightSide); + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + var denseResult = result as DenseVector; + if (denseResult == null) + { + base.DoNegate(result); + return; + } + + LinearAlgebraControl.Provider.ScaleArray(-Complex.One, _values, denseResult.Values); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + protected override void DoConjugate(Vector result) + { + var resultDense = result as DenseVector; + if (resultDense == null) + { + base.DoConjugate(result); + return; + } + + LinearAlgebraControl.Provider.ConjugateArray(_values, resultDense._values); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to multiply. + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(Complex scalar, Vector result) + { + var denseResult = result as DenseVector; + if (denseResult == null) + { + base.DoMultiply(scalar, result); + return; + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, _values, denseResult.Values); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override Complex DoDotProduct(Vector other) + { + var denseVector = other as DenseVector; + return denseVector == null + ? base.DoDotProduct(other) + : LinearAlgebraControl.Provider.DotProduct(_values, denseVector.Values); + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected override Complex DoConjugateDotProduct(Vector other) + { + var denseVector = other as DenseVector; + if (denseVector == null) + return base.DoConjugateDotProduct(other); + + // TODO: provide native zdotc routine + var dot = Complex.Zero; + for (var i = 0; i < _values.Length; i++) + { + dot += _values[i].Conjugate() * denseVector._values[i]; + } + + return dot; + } + + /// + /// Multiplies a vector with a complex. + /// + /// The vector to scale. + /// The Complex value. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(DenseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The Complex value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(Complex leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static Complex operator *(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a complex. + /// + /// The vector to divide. + /// The Complex value. + /// The result of the division. + /// If is . + public static DenseVector operator /(DenseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Divide(rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = _values[index].Magnitude; + for (var i = 1; i < _length; i++) + { + var test = _values[i].Magnitude; + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = _values[index].Magnitude; + for (var i = 1; i < _length; i++) + { + var test = _values[i].Magnitude; + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override Complex Sum() + { + var sum = Complex.Zero; + for (var i = 0; i < _length; i++) + { + sum += _values[i]; + } + + return sum; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double sum = 0d; + for (var i = 0; i < _length; i++) + { + sum += _values[i].Magnitude; + } + + return sum; + } + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public override double L2Norm() + { + // TODO: native provider + return _values.Aggregate(Complex.Zero, SpecialFunctions.Hypotenuse).Magnitude; + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(_values, (i, v) => v.Magnitude, Math.Max, 0d); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = ( ∑|this[i]|^p )^(1/p) + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var i = 0; i < _length; i++) + { + sum += Math.Pow(_values[i].Magnitude, p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + var denseOther = other as DenseVector; + var denseResult = result as DenseVector; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseMultiply(other, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + /// + protected override void DoPointwiseDivide(Vector divisor, Vector result) + { + var denseDivisor = divisor as DenseVector; + var denseResult = result as DenseVector; + + if (denseDivisor == null || denseResult == null) + { + base.DoPointwiseDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseDivideArrays(_values, denseDivisor._values, denseResult._values); + } + } + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Vector exponent, Vector result) + { + var denseExponent = exponent as DenseVector; + var denseResult = result as DenseVector; + + if (denseExponent == null || denseResult == null) + { + base.DoPointwisePower(exponent, result); + } + else + { + LinearAlgebraControl.Provider.PointWisePowerArrays(_values, denseExponent._values, denseResult._values); + } + } + + #region Parse Functions + + /// + /// Creates a Complex dense vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n;n;..', '(n;n;..)', '[n;n;...]', where n is a double. + /// + /// + /// A Complex dense vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static DenseVector Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // parsing + var strongTokens = value.Split(new[] { formatProvider.GetTextInfo().ListSeparator }, StringSplitOptions.RemoveEmptyEntries); + var data = new List(); + foreach (string strongToken in strongTokens) + { + var weakTokens = strongToken.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); + string current = string.Empty; + for (int i = 0; i < weakTokens.Length; i++) + { + current += weakTokens[i]; + if (current.EndsWith("+") || current.EndsWith("-") || current.StartsWith("(") && !current.EndsWith(")")) + { + continue; + } + + var ahead = i < weakTokens.Length - 1 ? weakTokens[i + 1] : string.Empty; + if (ahead.StartsWith("+") || ahead.StartsWith("-")) + { + continue; + } + + data.Add(current.ToComplex(formatProvider)); + current = string.Empty; + } + + if (current != string.Empty) + { + throw new FormatException(); + } + } + + if (data.Count == 0) + { + throw new FormatException(); + } + + return new DenseVector(data.ToArray()); + } + + /// + /// Converts the string representation of a complex dense vector to double-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out DenseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a complex dense vector to double-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out DenseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + + #endregion +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/DiagonalMatrix.cs b/MathNet.Numerics/LinearAlgebra/Complex/DiagonalMatrix.cs new file mode 100644 index 0000000..495d74d --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/DiagonalMatrix.cs @@ -0,0 +1,1039 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex; + +using Complex = System.Numerics.Complex; + +/// +/// A matrix type for diagonal matrices. +/// +/// +/// Diagonal matrices can be non-square matrices but the diagonal always starts +/// at element 0,0. A diagonal matrix will throw an exception if non diagonal +/// entries are set. The exception to this is when the off diagonal elements are +/// 0.0 or NaN; these settings will cause no change to the diagonal matrix. +/// +[Serializable] +[DebuggerDisplay("DiagonalMatrix {RowCount}x{ColumnCount}-Complex")] +public class DiagonalMatrix : Matrix +{ + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + readonly Complex[] _data; + + /// + /// Create a new diagonal matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DiagonalMatrix(DiagonalMatrixStorage storage) + : base(storage) + { + _data = storage.Data; + } + + /// + /// Create a new square diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public DiagonalMatrix(int order) + : this(new DiagonalMatrixStorage(order, order)) + { + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DiagonalMatrix(int rows, int columns) + : this(new DiagonalMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All diagonal cells of the matrix will be initialized to the provided value, all non-diagonal ones to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DiagonalMatrix(int rows, int columns, Complex diagonalValue) + : this(rows, columns) + { + for (var i = 0; i < _data.Length; i++) + { + _data[i] = diagonalValue; + } + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to contain the diagonal elements only and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + public DiagonalMatrix(int rows, int columns, Complex[] diagonalStorage) + : this(new DiagonalMatrixStorage(rows, columns, diagonalStorage)) + { + } + + /// + /// Create a new diagonal matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// The matrix to copy from must be diagonal as well. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfMatrix(Matrix matrix) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new diagonal matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// The array to copy from must be diagonal as well. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfArray(Complex[,] array) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfArray(array)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value from the provided indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfIndexedDiagonal(int rows, int columns, IEnumerable> diagonal) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfIndexedEnumerable(rows, columns, diagonal)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value from the provided enumerable. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfDiagonal(int rows, int columns, IEnumerable diagonal) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfEnumerable(rows, columns, diagonal)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value using the provided init function. + /// + public static DiagonalMatrix Create(int rows, int columns, Func init) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static DiagonalMatrix CreateIdentity(int order) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfValue(order, order, One)); + } + + /// + /// Create a new diagonal matrix with diagonal values sampled from the provided random distribution. + /// + public static DiagonalMatrix CreateRandom(int rows, int columns, IContinuousDistribution distribution) + { + return new DiagonalMatrix(new DiagonalMatrixStorage(rows, columns, Generate.RandomComplex(Math.Min(rows, columns), distribution))); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(-1, _data, diagResult._data); + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, -_data[i]); + } + } + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected override void DoConjugate(Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ConjugateArray(_data, diagResult._data); + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i].Conjugate()); + } + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + // diagonal + diagonal = diagonal + var diagOther = other as DiagonalMatrix; + var diagResult = result as DiagonalMatrix; + if (diagOther != null && diagResult != null) + { + LinearAlgebraControl.Provider.AddArrays(_data, diagOther._data, diagResult._data); + return; + } + + other.CopyTo(result); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, result.At(i, i) + _data[i]); + } + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + // diagonal - diagonal = diagonal + var diagOther = other as DiagonalMatrix; + var diagResult = result as DiagonalMatrix; + if (diagOther != null && diagResult != null) + { + LinearAlgebraControl.Provider.SubtractArrays(_data, diagOther._data, diagResult._data); + return; + } + + other.Negate(result); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, result.At(i, i) + _data[i]); + } + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + /// If the result matrix's dimensions are not the same as this matrix. + protected override void DoMultiply(Complex scalar, Matrix result) + { + if (scalar == 0.0) + { + result.Clear(); + return; + } + + if (scalar == 1.0) + { + CopyTo(result); + return; + } + + var diagResult = result as DiagonalMatrix; + if (diagResult == null) + { + base.DoMultiply(scalar, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(scalar, _data, diagResult._data); + } + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubVector(ColumnCount, RowCount - ColumnCount); + } + + if (d == ColumnCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i] * rightSide.At(i)); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex[diagonalResult._data.Length]; + var otherDataCopy = new Complex[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.RowCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.RowCount, RowCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + if (ColumnCount == RowCount) + { + other.Storage.MapIndexedTo(result.Storage, (i, j, x) => x * _data[i], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Clear(); + other.Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * _data[i], 0, 0, other.RowCount, 0, 0, other.ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex[diagonalResult._data.Length]; + var otherDataCopy = new Complex[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.ColumnCount, RowCount - denseOther.ColumnCount, 0, denseOther.RowCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < denseOther.RowCount; i++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex[diagonalResult._data.Length]; + var otherDataCopy = new Complex[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + // TODO: merge/MulByConj + LinearAlgebraControl.Provider.ConjugateArray(otherDataCopy, otherDataCopy); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.ColumnCount, RowCount - denseOther.ColumnCount, 0, denseOther.RowCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < denseOther.RowCount; i++) + { + result.At(j, i, dense[index].Conjugate() * diagonal[j]); + index++; + } + } + + return; + } + + base.DoConjugateTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex[diagonalResult._data.Length]; + var otherDataCopy = new Complex[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.RowCount, ColumnCount); + if (d < ColumnCount) + { + result.ClearSubMatrix(denseOther.RowCount, ColumnCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + if (ColumnCount == RowCount) + { + other.Storage.MapIndexedTo(result.Storage, (i, j, x) => x * _data[i], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Clear(); + other.Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * _data[i], 0, 0, other.RowCount, 0, 0, other.ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex[diagonalResult._data.Length]; + var otherDataCopy = new Complex[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + // TODO: merge/MulByConj + LinearAlgebraControl.Provider.ConjugateArray(thisDataCopy, thisDataCopy); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var conjugateDiagonal = new Complex[_data.Length]; + for (int i = 0; i < _data.Length; i++) + { + conjugateDiagonal[i] = _data[i].Conjugate(); + } + + var d = Math.Min(denseOther.RowCount, ColumnCount); + if (d < ColumnCount) + { + result.ClearSubMatrix(denseOther.RowCount, ColumnCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * conjugateDiagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + base.DoConjugateTransposeThisAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < ColumnCount) + { + result.ClearSubVector(RowCount, ColumnCount - RowCount); + } + + if (d == RowCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i] * rightSide.At(i)); + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < ColumnCount) + { + result.ClearSubVector(RowCount, ColumnCount - RowCount); + } + + if (d == RowCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + // TODO: merge/MulByConj + LinearAlgebraControl.Provider.ConjugateArray(_data, denseResult.Data); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(denseResult.Data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i].Conjugate() * rightSide.At(i)); + } + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(Complex divisor, Matrix result) + { + if (divisor == Complex.One) + { + CopyTo(result); + return; + } + + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(1.0 / divisor, _data, diagResult._data); + return; + } + + result.Clear(); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i] / divisor); + } + } + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to add. + /// The matrix to store the result of the division. + protected override void DoDivideByThis(Complex dividend, Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + var resultData = diagResult._data; + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + resultData[i] = dividend / _data[i]; + } + }); + return; + } + + result.Clear(); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, dividend / _data[i]); + } + } + + /// + /// Computes the determinant of this matrix. + /// + /// The determinant of this matrix. + public override Complex Determinant() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + return _data.Aggregate(Complex.One, (current, t) => current * t); + } + + /// + /// Returns the elements of the diagonal in a . + /// + /// The elements of the diagonal. + /// For non-square matrices, the method returns Min(Rows, Columns) elements where + /// i == j (i is the row index, and j is the column index). + public override Vector Diagonal() + { + return new DenseVector(_data).Clone(); + } + + /// + /// Copies the values of the given array to the diagonal. + /// + /// The array to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(Complex[] source) + { + if (source.Length != _data.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(source)); + } + + Array.Copy(source, 0, _data, 0, source.Length); + } + + /// + /// Copies the values of the given to the diagonal. + /// + /// The vector to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(Vector source) + { + var denseSource = source as DenseVector; + if (denseSource == null) + { + base.SetDiagonal(source); + return; + } + + if (_data.Length != denseSource.Values.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(source)); + } + + Array.Copy(denseSource.Values, 0, _data, 0, denseSource.Values.Length); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + return _data.Aggregate(0d, (current, t) => Math.Max(current, t.Magnitude)); + } + + /// Calculates the induced L2 norm of the matrix. + /// The largest singular value of the matrix. + public override double L2Norm() + { + return _data.Aggregate(0d, (current, t) => Math.Max(current, t.Magnitude)); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + return L1Norm(); + } + + /// Calculates the Frobenius norm of this matrix. + /// The Frobenius norm of this matrix. + public override double FrobeniusNorm() + { + return Math.Sqrt(_data.Sum(t => t.Magnitude * t.Magnitude)); + } + + /// Calculates the condition number of this matrix. + /// The condition number of the matrix. + public override Complex ConditionNumber() + { + var maxSv = double.NegativeInfinity; + var minSv = double.PositiveInfinity; + foreach (var t in _data) + { + maxSv = Math.Max(maxSv, t.Magnitude); + minSv = Math.Min(minSv, t.Magnitude); + } + + return maxSv / minSv; + } + + /// Computes the inverse of this matrix. + /// If is not a square matrix. + /// If is singular. + /// The inverse of this matrix. + public override Matrix Inverse() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var inverse = (DiagonalMatrix)Clone(); + for (var i = 0; i < _data.Length; i++) + { + if (_data[i] != 0.0) + { + inverse._data[i] = 1.0 / _data[i]; + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixNotSingular); + } + } + + return inverse; + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + return Clone(); + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i]); + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + return Clone(); + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i]); + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + } + + /// + /// Creates a matrix that contains the values from the requested sub-matrix. + /// + /// The row to start copying from. + /// The number of rows to copy. Must be positive. + /// The column to start copying from. + /// The number of columns to copy. Must be positive. + /// The requested sub-matrix. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + /// If or + /// is not positive. + public override Matrix SubMatrix(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + var target = rowIndex == columnIndex + ? (Matrix)new DiagonalMatrix(rowCount, columnCount) + : new SparseMatrix(rowCount, columnCount); + + Storage.CopySubMatrixTo(target.Storage, rowIndex, 0, rowCount, columnIndex, 0, columnCount, ExistingData.AssumeZeros); + return target; + } + + /// + /// Permute the columns of a matrix according to a permutation. + /// + /// The column permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteColumns(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Permute the rows of a matrix according to a permutation. + /// + /// The row permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteRows(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public sealed override bool IsSymmetric() + { + return true; + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public sealed override bool IsHermitian() + { + for (var k = 0; k < _data.Length; k++) + { + if (!_data[k].IsReal()) + { + return false; + } + } + + return true; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Cholesky.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Cholesky.cs new file mode 100644 index 0000000..8dfb9bb --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Cholesky.cs @@ -0,0 +1,86 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal abstract class Cholesky : Cholesky +{ + protected Cholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Gets the determinant of the matrix for which the Cholesky matrix was computed. + /// + public override Complex Determinant + { + get + { + var det = Complex.One; + for (var j = 0; j < Factor.RowCount; j++) + { + var d = Factor.At(j, j); + det *= d * d; + } + + return det; + } + } + + /// + /// Gets the log determinant of the matrix for which the Cholesky matrix was computed. + /// + public override Complex DeterminantLn + { + get + { + var det = Complex.Zero; + for (var j = 0; j < Factor.RowCount; j++) + { + det += 2.0 * Factor.At(j, j).Ln(); + } + + return det; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseCholesky.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseCholesky.cs new file mode 100644 index 0000000..0bc7ff1 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseCholesky.cs @@ -0,0 +1,189 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization for dense matrices. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal sealed class DenseCholesky : Cholesky +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public static DenseCholesky Create(DenseMatrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var factor = (DenseMatrix)matrix.Clone(); + LinearAlgebraControl.Provider.CholeskyFactor(factor.Values, factor.RowCount); + return new DenseCholesky(factor); + } + + DenseCholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + Array.Copy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)Factor; + LinearAlgebraControl.Provider.CholeskySolveFactored(dfactor.Values, dfactor.RowCount, dresult.Values, dresult.ColumnCount); + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + Array.Copy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)Factor; + LinearAlgebraControl.Provider.CholeskySolveFactored(dfactor.Values, dfactor.RowCount, dresult.Values, 1); + } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + /// If does not have the same dimensions as the existing factor. + public override void Factorize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + if (matrix.RowCount != Factor.RowCount || matrix.ColumnCount != Factor.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix, Factor); + } + + var dmatrix = matrix as DenseMatrix; + if (dmatrix == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dfactor = (DenseMatrix)Factor; + + // Overwrite the existing Factor matrix with the input. + Array.Copy(dmatrix.Values, 0, dfactor.Values, 0, dmatrix.Values.Length); + + // Perform factorization (while overwriting). + LinearAlgebraControl.Provider.CholeskyFactor(dfactor.Values, dfactor.RowCount); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseEvd.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseEvd.cs new file mode 100644 index 0000000..a2b4c62 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseEvd.cs @@ -0,0 +1,945 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a complex matrix. +/// +/// +/// If A is Hermitian, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is Hermitian. +/// I.e. A = V*D*V' and V*VH=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal sealed class DenseEvd : Evd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If it is known whether the matrix is symmetric or not the routine can skip checking it itself. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public static DenseEvd Create(DenseMatrix matrix, Symmetricity symmetricity) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matrices for eigenvalues and eigenvectors + var eigenVectors = DenseMatrix.CreateIdentity(order); + var blockDiagonal = new DenseMatrix(order); + var eigenValues = new DenseVector(order); + + bool isSymmetric; + switch (symmetricity) + { + case Symmetricity.Hermitian: + isSymmetric = true; + break; + case Symmetricity.Asymmetric: + isSymmetric = false; + break; + default: + isSymmetric = matrix.IsHermitian(); + break; + } + + LinearAlgebraControl.Provider.EigenDecomp(isSymmetric, order, matrix.Values, eigenVectors.Values, eigenValues.Values, blockDiagonal.Values); + + return new DenseEvd(eigenVectors, eigenValues, blockDiagonal, isSymmetric); + } + + DenseEvd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Reduces a complex Hermitian matrix to a real symmetric tridiagonal matrix using unitary similarity transformations. + /// + /// Source matrix to reduce + /// Output: Arrays for internal storage of real parts of eigenvalues + /// Output: Arrays for internal storage of imaginary parts of eigenvalues + /// Output: Arrays that contains further information about the transformations. + /// Order of initial matrix + /// This is derived from the Algol procedures HTRIDI by + /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + internal static void SymmetricTridiagonalize(Complex[] matrixA, double[] d, double[] e, Complex[] tau, int order) + { + double hh; + tau[order - 1] = Complex.One; + + for (var i = 0; i < order; i++) + { + d[i] = matrixA[i * order + i].Real; + } + + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0; + var h = 0.0; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(matrixA[k * order + i].Real) + Math.Abs(matrixA[k * order + i].Imaginary); + } + + if (scale == 0.0) + { + tau[i - 1] = Complex.One; + e[i] = 0.0; + } + else + { + for (var k = 0; k < i; k++) + { + matrixA[k * order + i] /= scale; + h += matrixA[k * order + i].MagnitudeSquared(); + } + + Complex g = Math.Sqrt(h); + e[i] = scale * g.Real; + + Complex temp; + var im1Oi = (i - 1) * order + i; + var f = matrixA[im1Oi]; + if (f.Magnitude != 0) + { + temp = -(matrixA[im1Oi].Conjugate() * tau[i].Conjugate()) / f.Magnitude; + h += f.Magnitude * g.Real; + g = 1.0 + (g / f.Magnitude); + matrixA[im1Oi] *= g; + } + else + { + temp = -tau[i].Conjugate(); + matrixA[im1Oi] = g; + } + + if ((f.Magnitude == 0) || (i != 1)) + { + f = Complex.Zero; + for (var j = 0; j < i; j++) + { + var tmp = Complex.Zero; + var jO = j * order; + // Form element of A*U. + for (var k = 0; k <= j; k++) + { + tmp += matrixA[k * order + j] * matrixA[k * order + i].Conjugate(); + } + + for (var k = j + 1; k <= i - 1; k++) + { + tmp += matrixA[jO + k].Conjugate() * matrixA[k * order + i].Conjugate(); + } + + // Form element of P + tau[j] = tmp / h; + f += (tmp / h) * matrixA[jO + i]; + } + + hh = f.Real / (h + h); + + // Form the reduced A. + for (var j = 0; j < i; j++) + { + f = matrixA[j * order + i].Conjugate(); + g = tau[j] - (hh * f); + tau[j] = g.Conjugate(); + + for (var k = 0; k <= j; k++) + { + matrixA[k * order + j] -= (f * tau[k]) + (g * matrixA[k * order + i]); + } + } + } + + for (var k = 0; k < i; k++) + { + matrixA[k * order + i] *= scale; + } + + tau[i - 1] = temp.Conjugate(); + } + + hh = d[i]; + d[i] = matrixA[i * order + i].Real; + matrixA[i * order + i] = new Complex(hh, scale * Math.Sqrt(h)); + } + + hh = d[0]; + d[0] = matrixA[0].Real; + matrixA[0] = hh; + e[0] = 0.0; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + internal static void SymmetricDiagonalize(Complex[] dataEv, double[] d, double[] e, int order) + { + const int maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0; + + var f = 0.0; + var tst1 = 0.0; + var eps = Precision.DoublePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0 * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0; + var s2 = 0.0; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = dataEv[((i + 1) * order) + k].Real; + dataEv[((i + 1) * order) + k] = (s * dataEv[(i * order) + k].Real) + (c * h); + dataEv[(i * order) + k] = (c * dataEv[(i * order) + k].Real) - (s * h); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + } while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = dataEv[(i * order) + j].Real; + dataEv[(i * order) + j] = dataEv[(k * order) + j]; + dataEv[(k * order) + j] = p; + } + } + } + } + + /// + /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation + /// + /// Data array of matrix V (eigenvectors) + /// Previously tridiagonalized matrix by . + /// Contains further information about the transformations + /// Input matrix order + /// This is derived from the Algol procedures HTRIBK, by + /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + internal static void SymmetricUntridiagonalize(Complex[] dataEv, Complex[] matrixA, Complex[] tau, int order) + { + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + dataEv[(j * order) + i] = dataEv[(j * order) + i].Real * tau[i].Conjugate(); + } + } + + // Recover and apply the Householder matrices. + for (var i = 1; i < order; i++) + { + var h = matrixA[i * order + i].Imaginary; + if (h != 0) + { + for (var j = 0; j < order; j++) + { + var s = Complex.Zero; + for (var k = 0; k < i; k++) + { + s += dataEv[(j * order) + k] * matrixA[k * order + i]; + } + + s = (s / h) / h; + + for (var k = 0; k < i; k++) + { + dataEv[(j * order) + k] -= s * matrixA[k * order + i].Conjugate(); + } + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + internal static void NonsymmetricReduceToHessenberg(Complex[] dataEv, Complex[] matrixH, int order) + { + var ort = new Complex[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0; + var mm1O = (m - 1) * order; + for (var i = m; i < order; i++) + { + scale += Math.Abs(matrixH[mm1O + i].Real) + Math.Abs(matrixH[mm1O + i].Imaginary); + } + + if (scale != 0.0) + { + // Compute Householder transformation. + var h = 0.0; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[mm1O + i] / scale; + h += ort[i].MagnitudeSquared(); + } + + var g = Math.Sqrt(h); + if (ort[m].Magnitude != 0) + { + h = h + (ort[m].Magnitude * g); + g /= ort[m].Magnitude; + ort[m] = (1.0 + g) * ort[m]; + } + else + { + ort[m] = g; + matrixH[mm1O + m] = scale; + } + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = Complex.Zero; + var jO = j * order; + for (var i = order - 1; i >= m; i--) + { + f += ort[i].Conjugate() * matrixH[jO + i]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[jO + i] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = Complex.Zero; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[j * order + i]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[j * order + i] -= f * ort[j].Conjugate(); + } + } + + ort[m] = scale * ort[m]; + matrixH[mm1O + m] *= -g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + dataEv[(j * order) + i] = i == j ? Complex.One : Complex.Zero; + } + } + + for (var m = order - 2; m >= 1; m--) + { + var mm1O = (m - 1) * order; + var mm1Om = mm1O + m; + if (matrixH[mm1Om] != Complex.Zero && ort[m] != Complex.Zero) + { + var norm = (matrixH[mm1Om].Real * ort[m].Real) + (matrixH[mm1Om].Imaginary * ort[m].Imaginary); + + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[mm1O + i]; + } + + for (var j = m; j < order; j++) + { + var g = Complex.Zero; + for (var i = m; i < order; i++) + { + g += ort[i].Conjugate() * dataEv[(j * order) + i]; + } + + // Double division avoids possible underflow + g /= norm; + for (var i = m; i < order; i++) + { + dataEv[(j * order) + i] += g * ort[i]; + } + } + } + } + + // Create real subdiagonal elements. + for (var i = 1; i < order; i++) + { + var im1 = i - 1; + var im1O = im1 * order; + var im1Oi = im1O + i; + var iO = i * order; + if (matrixH[im1Oi].Imaginary != 0.0) + { + var y = matrixH[im1Oi] / matrixH[im1Oi].Magnitude; + matrixH[im1Oi] = matrixH[im1Oi].Magnitude; + for (var j = i; j < order; j++) + { + matrixH[j * order + i] *= y.Conjugate(); + } + + for (var j = 0; j <= Math.Min(i + 1, order - 1); j++) + { + matrixH[iO + j] *= y; + } + + for (var j = 0; j < order; j++) + { + dataEv[(i * order) + j] *= y; + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// Data array of the eigenvectors + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + internal static void NonsymmetricReduceHessenberToRealSchur(Complex[] vectorV, Complex[] dataEv, Complex[] matrixH, int order) + { + // Initialize + var n = order - 1; + var eps = Precision.DoublePrecision; + + double norm; + Complex x, y, z, exshift = Complex.Zero; + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + var lm1 = l - 1; + var lm1O = lm1 * order; + var lO = l * order; + var tst1 = Math.Abs(matrixH[lm1O + lm1].Real) + Math.Abs(matrixH[lm1O + lm1].Imaginary) + Math.Abs(matrixH[lO + l].Real) + Math.Abs(matrixH[lO + l].Imaginary); + if (Math.Abs(matrixH[lm1O + l].Real) < eps * tst1) + { + break; + } + + l--; + } + + var nm1 = n - 1; + var nm1O = nm1 * order; + var nO = n * order; + var nOn = nO + n; + // Check for convergence + // One root found + if (l == n) + { + matrixH[nOn] += exshift; + vectorV[n] = matrixH[nOn]; + n--; + iter = 0; + } + else + { + // Form shift + Complex s; + if (iter != 10 && iter != 20) + { + s = matrixH[nOn]; + x = matrixH[nO + nm1] * matrixH[nm1O + n].Real; + + if (x.Real != 0.0 || x.Imaginary != 0.0) + { + y = (matrixH[nm1O + nm1] - s) / 2.0; + z = ((y * y) + x).SquareRoot(); + if ((y.Real * z.Real) + (y.Imaginary * z.Imaginary) < 0.0) + { + z *= -1.0; + } + + x /= y + z; + s = s - x; + } + } + else + { + // Form exceptional shift + s = Math.Abs(matrixH[nm1O + n].Real) + Math.Abs(matrixH[(n - 2) * order + nm1].Real); + } + + for (var i = 0; i <= n; i++) + { + matrixH[i * order + i] -= s; + } + + exshift += s; + iter++; + + // Reduce to triangle (rows) + for (var i = l + 1; i <= n; i++) + { + var im1 = i - 1; + var im1O = im1 * order; + var im1Oim1 = im1O + im1; + s = matrixH[im1O + i].Real; + norm = SpecialFunctions.Hypotenuse(matrixH[im1Oim1].Magnitude, s.Real); + x = matrixH[im1Oim1] / norm; + vectorV[i - 1] = x; + matrixH[im1Oim1] = norm; + matrixH[im1O + i] = new Complex(0.0, s.Real / norm); + + for (var j = i; j < order; j++) + { + var jO = j * order; + y = matrixH[jO + im1]; + z = matrixH[jO + i]; + matrixH[jO + im1] = (x.Conjugate() * y) + (matrixH[im1O + i].Imaginary * z); + matrixH[jO + i] = (x * z) - (matrixH[im1O + i].Imaginary * y); + } + } + + s = matrixH[nOn]; + if (s.Imaginary != 0.0) + { + s /= matrixH[nOn].Magnitude; + matrixH[nOn] = matrixH[nOn].Magnitude; + + for (var j = n + 1; j < order; j++) + { + matrixH[j * order + n] *= s.Conjugate(); + } + } + + // Inverse operation (columns). + for (var j = l + 1; j <= n; j++) + { + x = vectorV[j - 1]; + var jO = j * order; + var jm1 = j - 1; + var jm1O = jm1 * order; + var jm1Oj = jm1O + j; + for (var i = 0; i <= j; i++) + { + var jm1Oi = jm1O + i; + z = matrixH[jO + i]; + if (i != j) + { + y = matrixH[jm1Oi]; + matrixH[jm1Oi] = (x * y) + (matrixH[jm1O + j].Imaginary * z); + } + else + { + y = matrixH[jm1Oi].Real; + matrixH[jm1Oi] = new Complex((x.Real * y.Real) - (x.Imaginary * y.Imaginary) + (matrixH[jm1O + j].Imaginary * z.Real), matrixH[jm1Oi].Imaginary); + } + + matrixH[jO + i] = (x.Conjugate() * z) - (matrixH[jm1O + j].Imaginary * y); + } + + for (var i = 0; i < order; i++) + { + y = dataEv[((j - 1) * order) + i]; + z = dataEv[(j * order) + i]; + dataEv[jm1O + i] = (x * y) + (matrixH[jm1Oj].Imaginary * z); + dataEv[jO + i] = (x.Conjugate() * z) - (matrixH[jm1Oj].Imaginary * y); + } + } + + if (s.Imaginary != 0.0) + { + for (var i = 0; i <= n; i++) + { + matrixH[nO + i] *= s; + } + + for (var i = 0; i < order; i++) + { + dataEv[nO + i] *= s; + } + } + } + } + + // All roots found. + // Backsubstitute to find vectors of upper triangular form + norm = 0.0; + for (var i = 0; i < order; i++) + { + for (var j = i; j < order; j++) + { + norm = Math.Max(norm, Math.Abs(matrixH[j * order + i].Real) + Math.Abs(matrixH[j * order + i].Imaginary)); + } + } + + if (order == 1) + { + return; + } + + if (norm == 0.0) + { + return; + } + + for (n = order - 1; n > 0; n--) + { + var nO = n * order; + var nOn = nO + n; + x = vectorV[n]; + matrixH[nOn] = 1.0; + + for (var i = n - 1; i >= 0; i--) + { + z = 0.0; + for (var j = i + 1; j <= n; j++) + { + z += matrixH[j * order + i] * matrixH[nO + j]; + } + + y = x - vectorV[i]; + if (y.Real == 0.0 && y.Imaginary == 0.0) + { + y = eps * norm; + } + + matrixH[nO + i] = z / y; + + // Overflow control + var tr = Math.Abs(matrixH[nO + i].Real) + Math.Abs(matrixH[nO + i].Imaginary); + if ((eps * tr) * tr > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[nO + j] = matrixH[nO + j] / tr; + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j > 0; j--) + { + var jO = j * order; + for (var i = 0; i < order; i++) + { + z = Complex.Zero; + for (var k = 0; k <= j; k++) + { + z += dataEv[(k * order) + i] * matrixH[jO + k]; + } + + dataEv[jO + i] = z; + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (EigenValues.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (EigenValues.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = EigenValues.Count; + var tmp = new Complex[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + Complex value = 0.0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(j * order) + i].Conjugate() * input.At(i, k); + } + + value /= EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + Complex value = 0.0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(i * order) + j] * tmp[i]; + } + + result.At(j, k, value); + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (EigenValues.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (EigenValues.Count != result.Count) + { + throw Matrix.DimensionsDontMatch(EigenValues, result); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VH * b; + var order = EigenValues.Count; + var tmp = new Complex[order]; + Complex value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(j * order) + i].Conjugate() * input[i]; + } + + value /= EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(i * order) + j] * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseGramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseGramSchmidt.cs new file mode 100644 index 0000000..4aa0525 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseGramSchmidt.cs @@ -0,0 +1,200 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any complex square matrix A may be decomposed as A = QR where Q is an unitary mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal sealed class DenseGramSchmidt : GramSchmidt +{ + /// + /// Initializes a new instance of the class. This object creates an unitary matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public static DenseGramSchmidt Create(Matrix matrix) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var q = (DenseMatrix)matrix.Clone(); + var r = new DenseMatrix(matrix.ColumnCount, matrix.ColumnCount); + Factorize(q.Values, q.RowCount, q.ColumnCount, r.Values); + + return new DenseGramSchmidt(q, r); + } + + DenseGramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Factorize matrix using the modified Gram-Schmidt method. + /// + /// Initial matrix. On exit is replaced by Q. + /// Number of rows in Q. + /// Number of columns in Q. + /// On exit is filled by R. + private static void Factorize(Complex[] q, int rowsQ, int columnsQ, Complex[] r) + { + for (var k = 0; k < columnsQ; k++) + { + var norm = 0.0; + for (var i = 0; i < rowsQ; i++) + { + norm += q[(k * rowsQ) + i].Magnitude * q[(k * rowsQ) + i].Magnitude; + } + + norm = Math.Sqrt(norm); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r[(k * columnsQ) + k] = norm; + for (var i = 0; i < rowsQ; i++) + { + q[(k * rowsQ) + i] /= norm; + } + + for (var j = k + 1; j < columnsQ; j++) + { + var k1 = k; + var j1 = j; + + var dot = Complex.Zero; + for (var index = 0; index < rowsQ; index++) + { + dot += q[(k1 * rowsQ) + index].Conjugate() * q[(j1 * rowsQ) + index]; + } + + r[(j * columnsQ) + k] = dot; + for (var i = 0; i < rowsQ; i++) + { + var value = q[(j * rowsQ) + i] - (q[(k * rowsQ) + i] * dot); + q[(j * rowsQ) + i] = value; + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (Q.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, null, dinput.Values, input.ColumnCount, dresult.Values, QRMethod.Thin); + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (Q.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(Q, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, null, dinput.Values, 1, dresult.Values, QRMethod.Thin); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseLU.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseLU.cs new file mode 100644 index 0000000..41484b7 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseLU.cs @@ -0,0 +1,196 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal sealed class DenseLU : LU +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public static DenseLU Create(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var pivots = new int[matrix.RowCount]; + + // Create a new matrix for the LU factors, then perform factorization (while overwriting). + var factors = (DenseMatrix)matrix.Clone(); + LinearAlgebraControl.Provider.LUFactor(factors.Values, factors.RowCount, pivots); + + return new DenseLU(factors, pivots); + } + + DenseLU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do LU factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do LU factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + Array.Copy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + LinearAlgebraControl.Provider.LUSolveFactored(input.ColumnCount, dfactors.Values, dfactors.RowCount, Pivots, dresult.Values); + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do LU factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do LU factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + Array.Copy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + LinearAlgebraControl.Provider.LUSolveFactored(1, dfactors.Values, dfactors.RowCount, Pivots, dresult.Values); + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var result = (DenseMatrix)Factors.Clone(); + LinearAlgebraControl.Provider.LUInverseFactored(result.Values, result.RowCount, Pivots); + return result; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseQR.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseQR.cs new file mode 100644 index 0000000..0790bc4 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseQR.cs @@ -0,0 +1,171 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// +internal sealed class DenseQR : QR +{ + /// + /// Gets or sets Tau vector. Contains additional information on Q - used for native solver. + /// + Complex[] Tau { get; set; } + + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// The type of QR factorization to perform. + /// If is null. + /// If row count is less then column count + public static DenseQR Create(DenseMatrix matrix, QRMethod method = QRMethod.Full) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var tau = new Complex[Math.Min(matrix.RowCount, matrix.ColumnCount)]; + Matrix q; + Matrix r; + + if (method == QRMethod.Full) + { + r = matrix.Clone(); + q = new DenseMatrix(matrix.RowCount); + LinearAlgebraControl.Provider.QRFactor(((DenseMatrix)r).Values, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)q).Values, tau); + } + else + { + q = matrix.Clone(); + r = new DenseMatrix(matrix.ColumnCount); + LinearAlgebraControl.Provider.ThinQRFactor(((DenseMatrix)q).Values, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)r).Values, tau); + } + + return new DenseQR(q, r, method, tau); + } + + DenseQR(Matrix q, Matrix rFull, QRMethod method, Complex[] tau) + : base(q, rFull, method) + { + Tau = tau; + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (FullR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do QR factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do QR factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, Tau, dinput.Values, input.ColumnCount, dresult.Values, Method); + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (FullR.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(FullR, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do QR factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do QR factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, Tau, dinput.Values, 1, dresult.Values, Method); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseSvd.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseSvd.cs new file mode 100644 index 0000000..3a9627a --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/DenseSvd.cs @@ -0,0 +1,163 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD) for . +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal sealed class DenseSvd : Svd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// If SVD algorithm failed to converge with matrix . + public static DenseSvd Create(DenseMatrix matrix, bool computeVectors) + { + var nm = Math.Min(matrix.RowCount, matrix.ColumnCount); + var s = new DenseVector(nm); + var u = new DenseMatrix(matrix.RowCount); + var vt = new DenseMatrix(matrix.ColumnCount); + LinearAlgebraControl.Provider.SingularValueDecomposition(computeVectors, ((DenseMatrix)matrix.Clone()).Values, matrix.RowCount, matrix.ColumnCount, s.Values, u.Values, vt.Values); + + return new DenseSvd(s, u, vt, computeVectors); + } + + DenseSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (U.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.SvdSolveFactored(U.RowCount, VT.ColumnCount, ((DenseVector)S).Values, ((DenseMatrix)U).Values, ((DenseMatrix)VT).Values, dinput.Values, input.ColumnCount, dresult.Values); + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (U.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VT.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(VT, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.SvdSolveFactored(U.RowCount, VT.ColumnCount, ((DenseVector)S).Values, ((DenseMatrix)U).Values, ((DenseMatrix)VT).Values, dinput.Values, 1, dresult.Values); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Evd.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Evd.cs new file mode 100644 index 0000000..5a0fdf3 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Evd.cs @@ -0,0 +1,122 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal abstract class Evd : Evd +{ + protected Evd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Gets the absolute value of determinant of the square matrix for which the EVD was computed. + /// + public override Complex Determinant + { + get + { + var det = Complex.One; + for (var i = 0; i < EigenValues.Count; i++) + { + det *= EigenValues[i]; + + if (EigenValues[i].AlmostEqual(Complex.Zero)) + { + return 0; + } + } + + return det.Magnitude; + } + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public override int Rank + { + get + { + var rank = 0; + for (var i = 0; i < EigenValues.Count; i++) + { + if (EigenValues[i].AlmostEqual(Complex.Zero)) + { + continue; + } + + rank++; + } + + return rank; + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < EigenValues.Count; i++) + { + if (EigenValues[i].AlmostEqual(Complex.Zero)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs new file mode 100644 index 0000000..1c8ec40 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs @@ -0,0 +1,98 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal abstract class GramSchmidt : GramSchmidt +{ + protected GramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public override Complex Determinant + { + get + { + if (FullR.RowCount != FullR.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = Complex.One; + for (var i = 0; i < FullR.ColumnCount; i++) + { + det *= FullR.At(i, i); + if (FullR.At(i, i).Magnitude.AlmostEqual(0.0)) + { + return 0; + } + } + + return det.Magnitude; + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < FullR.ColumnCount; i++) + { + if (FullR.At(i, i).Magnitude.AlmostEqual(0.0)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/LU.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/LU.cs new file mode 100644 index 0000000..7911ed5 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/LU.cs @@ -0,0 +1,76 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// In the Math.Net implementation we also store a set of pivot elements for increased +/// numerical stability. The pivot elements encode a permutation matrix P such that P*A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal abstract class LU : LU +{ + protected LU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Gets the determinant of the matrix for which the LU factorization was computed. + /// + public override Complex Determinant + { + get + { + var det = Complex.One; + for (var j = 0; j < Factors.RowCount; j++) + { + if (Pivots[j] != j) + { + det *= -Factors.At(j, j); + } + else + { + det *= Factors.At(j, j); + } + } + + return det; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/QR.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/QR.cs new file mode 100644 index 0000000..6db5275 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/QR.cs @@ -0,0 +1,103 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A (m x n) may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// If a factorization is performed, the resulting Q matrix is an m x m matrix +/// and the R matrix is an m x n matrix. If a factorization is performed, the +/// resulting Q matrix is an m x n matrix and the R matrix is an n x n matrix. +/// +internal abstract class QR : QR +{ + protected QR(Matrix q, Matrix rFull, QRMethod method) + : base(q, rFull, method) + { + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public override Complex Determinant + { + get + { + if (FullR.RowCount != FullR.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = Complex.One; + for (var i = 0; i < FullR.ColumnCount; i++) + { + det *= FullR.At(i, i); + if (FullR.At(i, i).Magnitude.AlmostEqual(0.0)) + { + return 0; + } + } + + return det.Magnitude; + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < FullR.ColumnCount; i++) + { + if (FullR.At(i, i).Magnitude.AlmostEqual(0.0)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Svd.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Svd.cs new file mode 100644 index 0000000..e355e09 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/Svd.cs @@ -0,0 +1,124 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD). +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal abstract class Svd : Svd +{ + protected Svd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public override int Rank + { + get + { + double tolerance = Precision.EpsilonOf(S.AbsoluteMaximum().Magnitude) * Math.Max(U.RowCount, VT.RowCount); + return S.Count(t => t.Magnitude > tolerance); + } + } + + /// + /// Gets the two norm of the . + /// + /// The 2-norm of the . + public override double L2Norm + { + get + { + return S[0].Magnitude; + } + } + + /// + /// Gets the condition number max(S) / min(S) + /// + /// The condition number. + public override Complex ConditionNumber + { + get + { + var tmp = Math.Min(U.RowCount, VT.ColumnCount) - 1; + return S[0].Magnitude / S[tmp].Magnitude; + } + } + + /// + /// Gets the determinant of the square matrix for which the SVD was computed. + /// + public override Complex Determinant + { + get + { + if (U.RowCount != VT.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = Complex.One; + foreach (var value in S) + { + det *= value; + if (value.Magnitude.AlmostEqual(0.0)) + { + return 0; + } + } + + return det.Magnitude; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserCholesky.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserCholesky.cs new file mode 100644 index 0000000..490d604 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserCholesky.cs @@ -0,0 +1,276 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization for user matrices. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal sealed class UserCholesky : Cholesky +{ + /// + /// Computes the Cholesky factorization in-place. + /// + /// On entry, the matrix to factor. On exit, the Cholesky factor matrix + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + static void DoCholesky(Matrix factor) + { + if (factor.RowCount != factor.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var tmpColumn = new Complex[factor.RowCount]; + + // Main loop - along the diagonal + for (var ij = 0; ij < factor.RowCount; ij++) + { + // "Pivot" element + var tmpVal = factor.At(ij, ij); + + if (tmpVal.Real > 0.0) + { + tmpVal = tmpVal.SquareRoot(); + factor.At(ij, ij, tmpVal); + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < factor.RowCount; i++) + { + factor.At(i, ij, factor.At(i, ij) / tmpVal); + tmpColumn[i] = factor.At(i, ij); + } + + // Remaining columns, below the diagonal + DoCholeskyStep(factor, factor.RowCount, ij + 1, factor.RowCount, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (var i = ij + 1; i < factor.RowCount; i++) + { + factor.At(ij, i, Complex.Zero); + } + } + } + + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public static UserCholesky Create(Matrix matrix) + { + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var factor = matrix.Clone(); + DoCholesky(factor); + return new UserCholesky(factor); + } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + /// If does not have the same dimensions as the existing factor. + public override void Factorize(Matrix matrix) + { + if (matrix.RowCount != Factor.RowCount || matrix.ColumnCount != Factor.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix, Factor); + } + + matrix.CopyTo(Factor); + DoCholesky(Factor); + } + + UserCholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(Matrix data, int rowDim, int firstCol, int colLimit, Complex[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data.At(i, j, data.At(i, j) - (multipliers[i] * tmpVal.Conjugate())); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + input.CopyTo(result); + var order = Factor.RowCount; + + for (var c = 0; c < result.ColumnCount; c++) + { + // Solve L*Y = B; + Complex sum; + for (var i = 0; i < order; i++) + { + sum = result.At(i, c); + for (var k = i - 1; k >= 0; k--) + { + sum -= Factor.At(i, k) * result.At(k, c); + } + + result.At(i, c, sum / Factor.At(i, i)); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result.At(i, c); + for (var k = i + 1; k < order; k++) + { + sum -= Factor.At(k, i).Conjugate() * result.At(k, c); + } + + result.At(i, c, sum / Factor.At(i, i)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + input.CopyTo(result); + var order = Factor.RowCount; + + // Solve L*Y = B; + Complex sum; + for (var i = 0; i < order; i++) + { + sum = result[i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= Factor.At(i, k) * result[k]; + } + + result[i] = sum / Factor.At(i, i); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result[i]; + for (var k = i + 1; k < order; k++) + { + sum -= Factor.At(k, i).Conjugate() * result[k]; + } + + result[i] = sum / Factor.At(i, i); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs new file mode 100644 index 0000000..aac16cd --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs @@ -0,0 +1,939 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a complex matrix. +/// +/// +/// If A is Hermitian, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is Hermitian. +/// I.e. A = V*D*V' and V*VH=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal sealed class UserEvd : Evd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If it is known whether the matrix is symmetric or not the routine can skip checking it itself. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public static UserEvd Create(Matrix matrix, Symmetricity symmetricity) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matrices for eigenvalues and eigenvectors + var eigenVectors = DenseMatrix.CreateIdentity(order); + var blockDiagonal = Matrix.Build.SameAs(matrix, order, order); + var eigenValues = new DenseVector(order); + + bool isSymmetric; + switch (symmetricity) + { + case Symmetricity.Hermitian: + isSymmetric = true; + break; + case Symmetricity.Asymmetric: + isSymmetric = false; + break; + default: + isSymmetric = matrix.IsHermitian(); + break; + } + + if (isSymmetric) + { + var matrixCopy = matrix.ToArray(); + var tau = new Complex[order]; + var d = new double[order]; + var e = new double[order]; + + SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + SymmetricDiagonalize(eigenVectors, d, e, order); + SymmetricUntridiagonalize(eigenVectors, matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + eigenValues[i] = new Complex(d[i], e[i]); + } + } + else + { + var matrixH = matrix.ToArray(); + NonsymmetricReduceToHessenberg(eigenVectors, matrixH, order); + NonsymmetricReduceHessenberToRealSchur(eigenVectors, eigenValues, matrixH, order); + } + + blockDiagonal.SetDiagonal(eigenValues); + + return new UserEvd(eigenVectors, eigenValues, blockDiagonal, isSymmetric); + } + + UserEvd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Reduces a complex Hermitian matrix to a real symmetric tridiagonal matrix using unitary similarity transformations. + /// + /// Source matrix to reduce + /// Output: Arrays for internal storage of real parts of eigenvalues + /// Output: Arrays for internal storage of imaginary parts of eigenvalues + /// Output: Arrays that contains further information about the transformations. + /// Order of initial matrix + /// This is derived from the Algol procedures HTRIDI by + /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void SymmetricTridiagonalize(Complex[,] matrixA, double[] d, double[] e, Complex[] tau, int order) + { + double hh; + tau[order - 1] = Complex.One; + + for (var i = 0; i < order; i++) + { + d[i] = matrixA[i, i].Real; + } + + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0; + var h = 0.0; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(matrixA[i, k].Real) + Math.Abs(matrixA[i, k].Imaginary); + } + + if (scale == 0.0) + { + tau[i - 1] = Complex.One; + e[i] = 0.0; + } + else + { + for (var k = 0; k < i; k++) + { + matrixA[i, k] /= scale; + h += matrixA[i, k].MagnitudeSquared(); + } + + Complex g = Math.Sqrt(h); + e[i] = scale * g.Real; + + Complex temp; + var f = matrixA[i, i - 1]; + if (f.Magnitude != 0) + { + temp = -(matrixA[i, i - 1].Conjugate() * tau[i].Conjugate()) / f.Magnitude; + h += f.Magnitude * g.Real; + g = 1.0 + (g / f.Magnitude); + matrixA[i, i - 1] *= g; + } + else + { + temp = -tau[i].Conjugate(); + matrixA[i, i - 1] = g; + } + + if ((f.Magnitude == 0) || (i != 1)) + { + f = Complex.Zero; + for (var j = 0; j < i; j++) + { + var tmp = Complex.Zero; + + // Form element of A*U. + for (var k = 0; k <= j; k++) + { + tmp += matrixA[j, k] * matrixA[i, k].Conjugate(); + } + + for (var k = j + 1; k <= i - 1; k++) + { + tmp += matrixA[k, j].Conjugate() * matrixA[i, k].Conjugate(); + } + + // Form element of P + tau[j] = tmp / h; + f += (tmp / h) * matrixA[i, j]; + } + + hh = f.Real / (h + h); + + // Form the reduced A. + for (var j = 0; j < i; j++) + { + f = matrixA[i, j].Conjugate(); + g = tau[j] - (hh * f); + tau[j] = g.Conjugate(); + + for (var k = 0; k <= j; k++) + { + matrixA[j, k] -= (f * tau[k]) + (g * matrixA[i, k]); + } + } + } + + for (var k = 0; k < i; k++) + { + matrixA[i, k] *= scale; + } + + tau[i - 1] = temp.Conjugate(); + } + + hh = d[i]; + d[i] = matrixA[i, i].Real; + matrixA[i, i] = new Complex(hh, scale * Math.Sqrt(h)); + } + + hh = d[0]; + d[0] = matrixA[0, 0].Real; + matrixA[0, 0] = hh; + e[0] = 0.0; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// The eigen vectors to work on. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + static void SymmetricDiagonalize(Matrix eigenVectors, double[] d, double[] e, int order) + { + const int maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0; + + var f = 0.0; + var tst1 = 0.0; + var eps = Precision.DoublePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0 * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0; + var s2 = 0.0; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = eigenVectors.At(k, i + 1).Real; + eigenVectors.At(k, i + 1, (s * eigenVectors.At(k, i).Real) + (c * h)); + eigenVectors.At(k, i, (c * eigenVectors.At(k, i).Real) - (s * h)); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + } while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = eigenVectors.At(j, i).Real; + eigenVectors.At(j, i, eigenVectors.At(j, k)); + eigenVectors.At(j, k, p); + } + } + } + } + + /// + /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation + /// + /// The eigen vectors to work on. + /// Previously tridiagonalized matrix by . + /// Contains further information about the transformations + /// Input matrix order + /// This is derived from the Algol procedures HTRIBK, by + /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void SymmetricUntridiagonalize(Matrix eigenVectors, Complex[,] matrixA, Complex[] tau, int order) + { + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + eigenVectors.At(i, j, eigenVectors.At(i, j).Real * tau[i].Conjugate()); + } + } + + // Recover and apply the Householder matrices. + for (var i = 1; i < order; i++) + { + var h = matrixA[i, i].Imaginary; + if (h != 0) + { + for (var j = 0; j < order; j++) + { + var s = Complex.Zero; + for (var k = 0; k < i; k++) + { + s += eigenVectors.At(k, j) * matrixA[i, k]; + } + + s = (s / h) / h; + + for (var k = 0; k < i; k++) + { + eigenVectors.At(k, j, eigenVectors.At(k, j) - s * matrixA[i, k].Conjugate()); + } + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// The eigen vectors to work on. + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + static void NonsymmetricReduceToHessenberg(Matrix eigenVectors, Complex[,] matrixH, int order) + { + var ort = new Complex[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0; + for (var i = m; i < order; i++) + { + scale += Math.Abs(matrixH[i, m - 1].Real) + Math.Abs(matrixH[i, m - 1].Imaginary); + } + + if (scale != 0.0) + { + // Compute Householder transformation. + var h = 0.0; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i].MagnitudeSquared(); + } + + var g = Math.Sqrt(h); + if (ort[m].Magnitude != 0) + { + h = h + (ort[m].Magnitude * g); + g /= ort[m].Magnitude; + ort[m] = (1.0 + g) * ort[m]; + } + else + { + ort[m] = g; + matrixH[m, m - 1] = scale; + } + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = Complex.Zero; + for (var i = order - 1; i >= m; i--) + { + f += ort[i].Conjugate() * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = Complex.Zero; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[i, j] -= f * ort[j].Conjugate(); + } + } + + ort[m] = scale * ort[m]; + matrixH[m, m - 1] *= -g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + eigenVectors.At(i, j, i == j ? Complex.One : Complex.Zero); + } + } + + for (var m = order - 2; m >= 1; m--) + { + if (matrixH[m, m - 1] != Complex.Zero && ort[m] != Complex.Zero) + { + var norm = (matrixH[m, m - 1].Real * ort[m].Real) + (matrixH[m, m - 1].Imaginary * ort[m].Imaginary); + + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j < order; j++) + { + var g = Complex.Zero; + for (var i = m; i < order; i++) + { + g += ort[i].Conjugate() * eigenVectors.At(i, j); + } + + // Double division avoids possible underflow + g /= norm; + for (var i = m; i < order; i++) + { + eigenVectors.At(i, j, eigenVectors.At(i, j) + g * ort[i]); + } + } + } + } + + // Create real subdiagonal elements. + for (var i = 1; i < order; i++) + { + if (matrixH[i, i - 1].Imaginary != 0.0) + { + var y = matrixH[i, i - 1] / matrixH[i, i - 1].Magnitude; + matrixH[i, i - 1] = matrixH[i, i - 1].Magnitude; + for (var j = i; j < order; j++) + { + matrixH[i, j] *= y.Conjugate(); + } + + for (var j = 0; j <= Math.Min(i + 1, order - 1); j++) + { + matrixH[j, i] *= y; + } + + for (var j = 0; j < order; j++) + { + eigenVectors.At(j, i, eigenVectors.At(j, i) * y); + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// The eigen vectors to work on. + /// The eigen values to work on. + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void NonsymmetricReduceHessenberToRealSchur(Matrix eigenVectors, Vector eigenValues, Complex[,] matrixH, int order) + { + // Initialize + var n = order - 1; + var eps = Precision.DoublePrecision; + + double norm; + Complex x, y, z, exshift = Complex.Zero; + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + var tst1 = Math.Abs(matrixH[l - 1, l - 1].Real) + Math.Abs(matrixH[l - 1, l - 1].Imaginary) + Math.Abs(matrixH[l, l].Real) + Math.Abs(matrixH[l, l].Imaginary); + if (Math.Abs(matrixH[l, l - 1].Real) < eps * tst1) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] += exshift; + eigenValues[n] = matrixH[n, n]; + n--; + iter = 0; + } + else + { + // Form shift + Complex s; + if (iter != 10 && iter != 20) + { + s = matrixH[n, n]; + x = matrixH[n - 1, n] * matrixH[n, n - 1].Real; + + if (x.Real != 0.0 || x.Imaginary != 0.0) + { + y = (matrixH[n - 1, n - 1] - s) / 2.0; + z = ((y * y) + x).SquareRoot(); + if ((y.Real * z.Real) + (y.Imaginary * z.Imaginary) < 0.0) + { + z *= -1.0; + } + + x /= y + z; + s = s - x; + } + } + else + { + // Form exceptional shift + s = Math.Abs(matrixH[n, n - 1].Real) + Math.Abs(matrixH[n - 1, n - 2].Real); + } + + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + iter++; + + // Reduce to triangle (rows) + for (var i = l + 1; i <= n; i++) + { + s = matrixH[i, i - 1].Real; + norm = SpecialFunctions.Hypotenuse(matrixH[i - 1, i - 1].Magnitude, s.Real); + x = matrixH[i - 1, i - 1] / norm; + eigenValues[i - 1] = x; + matrixH[i - 1, i - 1] = norm; + matrixH[i, i - 1] = new Complex(0.0, s.Real / norm); + + for (var j = i; j < order; j++) + { + y = matrixH[i - 1, j]; + z = matrixH[i, j]; + matrixH[i - 1, j] = (x.Conjugate() * y) + (matrixH[i, i - 1].Imaginary * z); + matrixH[i, j] = (x * z) - (matrixH[i, i - 1].Imaginary * y); + } + } + + s = matrixH[n, n]; + if (s.Imaginary != 0.0) + { + s /= matrixH[n, n].Magnitude; + matrixH[n, n] = matrixH[n, n].Magnitude; + + for (var j = n + 1; j < order; j++) + { + matrixH[n, j] *= s.Conjugate(); + } + } + + // Inverse operation (columns). + for (var j = l + 1; j <= n; j++) + { + x = eigenValues[j - 1]; + for (var i = 0; i <= j; i++) + { + z = matrixH[i, j]; + if (i != j) + { + y = matrixH[i, j - 1]; + matrixH[i, j - 1] = (x * y) + (matrixH[j, j - 1].Imaginary * z); + } + else + { + y = matrixH[i, j - 1].Real; + matrixH[i, j - 1] = new Complex((x.Real * y.Real) - (x.Imaginary * y.Imaginary) + (matrixH[j, j - 1].Imaginary * z.Real), matrixH[i, j - 1].Imaginary); + } + + matrixH[i, j] = (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y); + } + + for (var i = 0; i < order; i++) + { + y = eigenVectors.At(i, j - 1); + z = eigenVectors.At(i, j); + eigenVectors.At(i, j - 1, (x * y) + (matrixH[j, j - 1].Imaginary * z)); + eigenVectors.At(i, j, (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y)); + } + } + + if (s.Imaginary != 0.0) + { + for (var i = 0; i <= n; i++) + { + matrixH[i, n] *= s; + } + + for (var i = 0; i < order; i++) + { + eigenVectors.At(i, n, eigenVectors.At(i, n) * s); + } + } + } + } + + // All roots found. + // Backsubstitute to find vectors of upper triangular form + norm = 0.0; + for (var i = 0; i < order; i++) + { + for (var j = i; j < order; j++) + { + norm = Math.Max(norm, Math.Abs(matrixH[i, j].Real) + Math.Abs(matrixH[i, j].Imaginary)); + } + } + + if (order == 1) + { + return; + } + + if (norm == 0.0) + { + return; + } + + for (n = order - 1; n > 0; n--) + { + x = eigenValues[n]; + matrixH[n, n] = 1.0; + + for (var i = n - 1; i >= 0; i--) + { + z = 0.0; + for (var j = i + 1; j <= n; j++) + { + z += matrixH[i, j] * matrixH[j, n]; + } + + y = x - eigenValues[i]; + if (y.Real == 0.0 && y.Imaginary == 0.0) + { + y = eps * norm; + } + + matrixH[i, n] = z / y; + + // Overflow control + var tr = Math.Abs(matrixH[i, n].Real) + Math.Abs(matrixH[i, n].Imaginary); + if ((eps * tr) * tr > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / tr; + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j > 0; j--) + { + for (var i = 0; i < order; i++) + { + z = Complex.Zero; + for (var k = 0; k <= j; k++) + { + z += eigenVectors.At(i, k) * matrixH[k, j]; + } + + eigenVectors.At(i, j, z); + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (EigenValues.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (EigenValues.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = EigenValues.Count; + var tmp = new Complex[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + Complex value = 0.0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(i, j).Conjugate() * input.At(i, k); + } + + value /= EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + Complex value = 0.0; + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(j, i) * tmp[i]; + } + + result.At(j, k, value); + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (EigenValues.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (EigenValues.Count != result.Count) + { + throw Matrix.DimensionsDontMatch(EigenValues, result); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VH * b; + var order = EigenValues.Count; + var tmp = new Complex[order]; + Complex value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(i, j).Conjugate() * input[i]; + } + + value /= EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (int i = 0; i < order; i++) + { + value += EigenVectors.At(j, i) * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserGramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserGramSchmidt.cs new file mode 100644 index 0000000..88c49f4 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserGramSchmidt.cs @@ -0,0 +1,233 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any complex square matrix A may be decomposed as A = QR where Q is an unitary mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal sealed class UserGramSchmidt : GramSchmidt +{ + /// + /// Initializes a new instance of the class. This object creates an unitary matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public static UserGramSchmidt Create(Matrix matrix) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var q = matrix.Clone(); + var r = Matrix.Build.SameAs(matrix, matrix.ColumnCount, matrix.ColumnCount, fullyMutable: true); + + for (var k = 0; k < q.ColumnCount; k++) + { + var norm = q.Column(k).L2Norm(); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r.At(k, k, norm); + for (var i = 0; i < q.RowCount; i++) + { + q.At(i, k, q.At(i, k) / norm); + } + + for (var j = k + 1; j < q.ColumnCount; j++) + { + var dot = Complex.Zero; + for (int i = 0; i < q.RowCount; i++) + { + dot += q.Column(k)[i].Conjugate() * q.Column(j)[i]; + } + + r.At(k, j, dot); + for (var i = 0; i < q.RowCount; i++) + { + var value = q.At(i, j) - (q.At(i, k) * dot); + q.At(i, j, value); + } + } + } + + return new UserGramSchmidt(q, r); + } + + UserGramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (Q.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new Complex[Q.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < Q.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < Q.ColumnCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < Q.RowCount; k++) + { + s += Q.At(k, i).Conjugate() * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = Q.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / FullR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * FullR.At(i, k))); + } + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (Q.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(Q, result); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new Complex[Q.RowCount]; + for (var k = 0; k < Q.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < Q.ColumnCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < Q.RowCount; k++) + { + s += Q.At(k, i).Conjugate() * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = Q.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= FullR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * FullR.At(i, k); + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserLU.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserLU.cs new file mode 100644 index 0000000..16cc355 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserLU.cs @@ -0,0 +1,308 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal sealed class UserLU : LU +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public static UserLU Create(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var order = matrix.RowCount; + var factors = matrix.Clone(); + var pivots = new int[order]; + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + pivots[i] = i; + } + + var vectorLUcolj = new Complex[order]; + for (var j = 0; j < order; j++) + { + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vectorLUcolj[i] = factors.At(i, j); + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + var kmax = Math.Min(i, j); + var s = Complex.Zero; + for (var k = 0; k < kmax; k++) + { + s += factors.At(i, k) * vectorLUcolj[k]; + } + + vectorLUcolj[i] -= s; + factors.At(i, j, vectorLUcolj[i]); + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (vectorLUcolj[i].Magnitude > vectorLUcolj[p].Magnitude) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var temp = factors.At(p, k); + factors.At(p, k, factors.At(j, k)); + factors.At(j, k, temp); + } + + pivots[j] = p; + } + + // Compute multipliers. + if (j < order & factors.At(j, j) != 0.0) + { + for (var i = j + 1; i < order; i++) + { + factors.At(i, j, (factors.At(i, j) / factors.At(j, j))); + } + } + } + + return new UserLU(factors, pivots); + } + + UserLU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(p, j); + result.At(p, j, result.At(i, j)); + result.At(i, j, temp); + } + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + for (var j = 0; j < result.ColumnCount; j++) + { + result.At(k, j, (result.At(k, j) / Factors.At(k, k))); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + var temp = result[p]; + result[p] = result[i]; + result[i] = temp; + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + result[k] /= Factors.At(k, k); + for (var i = 0; i < k; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var order = Factors.RowCount; + var inverse = Matrix.Build.SameAs(Factors, order, order); + for (var i = 0; i < order; i++) + { + inverse.At(i, i, 1.0); + } + + return Solve(inverse); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserQR.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserQR.cs new file mode 100644 index 0000000..7c7814f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserQR.cs @@ -0,0 +1,351 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// +internal sealed class UserQR : QR +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// The QR factorization method to use. + /// If is null. + public static UserQR Create(Matrix matrix, QRMethod method = QRMethod.Full) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + Matrix q; + Matrix r; + + var minmn = Math.Min(matrix.RowCount, matrix.ColumnCount); + var u = new Complex[minmn][]; + + if (method == QRMethod.Full) + { + r = matrix.Clone(); + q = Matrix.Build.SameAs(matrix, matrix.RowCount, matrix.RowCount, fullyMutable: true); + + for (var i = 0; i < matrix.RowCount; i++) + { + q.At(i, i, 1.0f); + } + + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(r, i, i); + ComputeQR(u[i], r, i, matrix.RowCount, i + 1, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], q, i, matrix.RowCount, i, matrix.RowCount, Control.MaxDegreeOfParallelism); + } + } + else + { + q = matrix.Clone(); + + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(q, i, i); + ComputeQR(u[i], q, i, matrix.RowCount, i + 1, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + + r = q.SubMatrix(0, matrix.ColumnCount, 0, matrix.ColumnCount); + q.Clear(); + + for (var i = 0; i < matrix.ColumnCount; i++) + { + q.At(i, i, 1.0f); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], q, i, matrix.RowCount, i, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + } + + return new UserQR(q, r, method); + } + + UserQR(Matrix q, Matrix rFull, QRMethod method) + : base(q, rFull, method) + { + } + + /// + /// Generate column from initial matrix to work array + /// + /// Initial matrix + /// The first row + /// Column index + /// Generated vector + static Complex[] GenerateColumn(Matrix a, int row, int column) + { + var ru = a.RowCount - row; + var u = new Complex[ru]; + + for (var i = row; i < a.RowCount; i++) + { + u[i - row] = a.At(i, column); + a.At(i, column, 0.0); + } + + var norm = u.Aggregate(Complex.Zero, (current, t) => current + (t.Magnitude * t.Magnitude)); + norm = norm.SquareRoot(); + + if (row == a.RowCount - 1 || norm.Magnitude == 0) + { + a.At(row, column, -u[0]); + u[0] = Constants.Sqrt2; + return u; + } + + if (u[0].Magnitude != 0.0) + { + norm = norm.Magnitude * (u[0] / u[0].Magnitude); + } + + a.At(row, column, -norm); + + for (var i = 0; i < ru; i++) + { + u[i] /= norm; + } + + u[0] += 1.0; + + var s = (1.0 / u[0]).SquareRoot(); + for (var i = 0; i < ru; i++) + { + u[i] = u[i].Conjugate() * s; + } + + return u; + } + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Q or R matrices + /// The first row + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(Complex[] u, Matrix a, int rowStart, int rowDim, int columnStart, int columnDim, int availableCores) + { + if (rowDim < rowStart || columnDim < columnStart) + { + return; + } + + var tmpColCount = columnDim - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(u, a, rowStart, rowDim, columnStart, tmpSplit, tmpCores), + () => ComputeQR(u, a, rowStart, rowDim, tmpSplit, columnDim, tmpCores)); + } + else + { + for (var j = columnStart; j < columnDim; j++) + { + var scale = Complex.Zero; + for (var i = rowStart; i < rowDim; i++) + { + scale += u[i - rowStart] * a.At(i, j); + } + + for (var i = rowStart; i < rowDim; i++) + { + a.At(i, j, a.At(i, j) - (u[i - rowStart].Conjugate() * scale)); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (FullR.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (FullR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new Complex[FullR.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < FullR.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < FullR.RowCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < FullR.RowCount; k++) + { + s += Q.At(k, i).Conjugate() * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = FullR.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / FullR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * FullR.At(i, k))); + } + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + for (var j = 0; j < inputCopy.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (FullR.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (FullR.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(FullR, result); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new Complex[FullR.RowCount]; + for (var k = 0; k < FullR.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < FullR.RowCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < FullR.RowCount; k++) + { + s += Q.At(k, i).Conjugate() * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = FullR.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= FullR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * FullR.At(i, k); + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserSvd.cs b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserSvd.cs new file mode 100644 index 0000000..099f799 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Factorization/UserSvd.cs @@ -0,0 +1,919 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD) for . +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal sealed class UserSvd : Svd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// + public static UserSvd Create(Matrix matrix, bool computeVectors) + { + var nm = Math.Min(matrix.RowCount + 1, matrix.ColumnCount); + var matrixCopy = matrix.Clone(); + + var s = Vector.Build.SameAs(matrixCopy, nm); + var u = Matrix.Build.SameAs(matrixCopy, matrixCopy.RowCount, matrixCopy.RowCount, fullyMutable: true); + var vt = Matrix.Build.SameAs(matrixCopy, matrixCopy.ColumnCount, matrixCopy.ColumnCount, fullyMutable: true); + + const int maxiter = 1000; + var e = new Complex[matrixCopy.ColumnCount]; + var work = new Complex[matrixCopy.RowCount]; + + int i, j; + int l, lp1; + Complex t; + + var ncu = matrixCopy.RowCount; + + // Reduce matrixCopy to bidiagonal form, storing the diagonal elements + // In s and the super-diagonal elements in e. + var nct = Math.Min(matrixCopy.RowCount - 1, matrixCopy.ColumnCount); + var nrt = Math.Max(0, Math.Min(matrixCopy.ColumnCount - 2, matrixCopy.RowCount)); + var lu = Math.Max(nct, nrt); + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and place the l-th diagonal in VectorS[l]. + s[l] = Cnrm2Column(matrixCopy, matrixCopy.RowCount, l, l); + if (s[l].Magnitude != 0.0) + { + if (matrixCopy.At(l, l).Magnitude != 0.0) + { + s[l] = Csign(s[l], matrixCopy.At(l, l)); + } + + CscalColumn(matrixCopy, matrixCopy.RowCount, l, l, 1.0 / s[l]); + matrixCopy.At(l, l, (Complex.One + matrixCopy.At(l, l))); + } + + s[l] = -s[l]; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (l < nct) + { + if (s[l].Magnitude != 0.0) + { + // Apply the transformation. + t = -Cdotc(matrixCopy, matrixCopy.RowCount, l, j, l) / matrixCopy.At(l, l); + if (t != Complex.Zero) + { + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (t * matrixCopy.At(ii, l))); + } + } + } + } + + // Place the l-th row of matrixCopy into e for the + // Subsequent calculation of the row transformation. + e[j] = matrixCopy.At(l, j).Conjugate(); + } + + if (computeVectors && l < nct) + { + // Place the transformation in u for subsequent back multiplication. + for (i = l; i < matrixCopy.RowCount; i++) + { + u.At(i, l, matrixCopy.At(i, l)); + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = Cnrm2Vector(e, lp1); + e[l] = enorm; + if (e[l].Magnitude != 0.0) + { + if (e[lp1].Magnitude != 0.0) + { + e[l] = Csign(e[l], e[lp1]); + } + + CscalVector(e, lp1, 1.0 / e[l]); + e[lp1] = Complex.One + e[lp1]; + } + + e[l] = -e[l].Conjugate(); + if (lp1 < matrixCopy.RowCount && e[l].Magnitude != 0.0) + { + // Apply the transformation. + for (i = lp1; i < matrixCopy.RowCount; i++) + { + work[i] = Complex.Zero; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (e[j] != Complex.Zero) + { + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + work[ii] += e[j] * matrixCopy.At(ii, j); + } + } + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + var ww = (-e[j] / e[lp1]).Conjugate(); + if (ww != Complex.Zero) + { + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (ww * work[ii])); + } + } + } + } + + if (computeVectors) + { + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < matrixCopy.ColumnCount; i++) + { + vt.At(i, l, e[i]); + } + } + } + + // Set up the final bidiagonal matrixCopy or order m. + var m = Math.Min(matrixCopy.ColumnCount, matrixCopy.RowCount + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < matrixCopy.ColumnCount) + { + s[nctp1 - 1] = matrixCopy.At((nctp1 - 1), (nctp1 - 1)); + } + + if (matrixCopy.RowCount < m) + { + s[m - 1] = Complex.Zero; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = matrixCopy.At((nrtp1 - 1), (m - 1)); + } + + e[m - 1] = Complex.Zero; + + // If required, generate u. + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + u.At(i, j, Complex.Zero); + } + + u.At(j, j, Complex.One); + } + + for (l = nct - 1; l >= 0; l--) + { + if (s[l].Magnitude != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = -Cdotc(u, matrixCopy.RowCount, l, j, l) / u.At(l, l); + if (t != Complex.Zero) + { + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + u.At(ii, j, u.At(ii, j) + (t * u.At(ii, l))); + } + } + } + + CscalColumn(u, matrixCopy.RowCount, l, l, -1.0); + u.At(l, l, Complex.One + u.At(l, l)); + for (i = 0; i < l; i++) + { + u.At(i, l, Complex.Zero); + } + } + else + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + u.At(i, l, Complex.Zero); + } + + u.At(l, l, Complex.One); + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = matrixCopy.ColumnCount - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l].Magnitude != 0.0) + { + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + t = -Cdotc(vt, matrixCopy.ColumnCount, l, j, lp1) / vt.At(lp1, l); + if (t != Complex.Zero) + { + for (var ii = l; ii < matrixCopy.ColumnCount; ii++) + { + vt.At(ii, j, vt.At(ii, j) + (t * vt.At(ii, l))); + } + } + } + } + } + + for (i = 0; i < matrixCopy.ColumnCount; i++) + { + vt.At(i, l, Complex.Zero); + } + + vt.At(l, l, Complex.One); + } + } + + // Transform s and e so that they are real . + for (i = 0; i < m; i++) + { + Complex r; + if (s[i].Magnitude != 0.0) + { + t = s[i].Magnitude; + r = s[i] / t; + s[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + CscalColumn(u, matrixCopy.RowCount, i, 0, r); + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i].Magnitude != 0.0) + { + t = e[i].Magnitude; + r = t / e[i]; + e[i] = t; + s[i + 1] = s[i + 1] * r; + if (computeVectors) + { + CscalColumn(vt, matrixCopy.ColumnCount, i + 1, 0, r); + } + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays. On + // completion the variables case and l are set as follows. + // Case = 1 if VectorS[m] and e[l-1] are negligible and l < m + // Case = 2 if VectorS[l] is negligible and l < m + // Case = 3 if e[l-1] is negligible, l < m, and VectorS[l, ..., VectorS[m] are not negligible (qr step). + // Case = 4 if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = s[l].Magnitude + s[l + 1].Magnitude; + ztest = test + e[l].Magnitude; + if (ztest.AlmostEqualRelative(test, 15)) + { + e[l] = Complex.Zero; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + e[ls].Magnitude; + } + + if (ls != l + 1) + { + test = test + e[ls - 1].Magnitude; + } + + ztest = test + s[ls].Magnitude; + if (ztest.AlmostEqualRelative(test, 15)) + { + s[ls] = Complex.Zero; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + double f; + double cs; + double sn; + switch (kase) + { + // Deflate negligible VectorS[m]. + case 1: + f = e[m - 2].Real; + e[m - 2] = Complex.Zero; + double t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = s[k].Real; + Srotg(ref t1, ref f, out cs, out sn); + s[k] = t1; + if (k != l) + { + f = -sn * e[k - 1].Real; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + Csrot(vt, matrixCopy.ColumnCount, k, m - 1, cs, sn); + } + } + + break; + + // Split at negligible VectorS[l]. + case 2: + f = e[l - 1].Real; + e[l - 1] = Complex.Zero; + for (k = l; k < m; k++) + { + t1 = s[k].Real; + Srotg(ref t1, ref f, out cs, out sn); + s[k] = t1; + f = -sn * e[k].Real; + e[k] = cs * e[k]; + if (computeVectors) + { + Csrot(u, matrixCopy.RowCount, k, l - 1, cs, sn); + } + } + + break; + + // Perform one qr step. + case 3: + // Calculate the shift. + var scale = 0.0; + scale = Math.Max(scale, s[m - 1].Magnitude); + scale = Math.Max(scale, s[m - 2].Magnitude); + scale = Math.Max(scale, e[m - 2].Magnitude); + scale = Math.Max(scale, s[l].Magnitude); + scale = Math.Max(scale, e[l].Magnitude); + var sm = s[m - 1].Real / scale; + var smm1 = s[m - 2].Real / scale; + var emm1 = e[m - 2].Real / scale; + var sl = s[l].Real / scale; + var el = e[l].Real / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0; + + if (b != 0.0 || c != 0.0) + { + shift = Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros. + for (k = l; k < m - 1; k++) + { + Srotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * s[k].Real) + (sn * e[k].Real); + e[k] = (cs * e[k]) - (sn * s[k]); + g = sn * s[k + 1].Real; + s[k + 1] = cs * s[k + 1]; + if (computeVectors) + { + Csrot(vt, matrixCopy.ColumnCount, k, k + 1, cs, sn); + } + + Srotg(ref f, ref g, out cs, out sn); + s[k] = f; + f = (cs * e[k].Real) + (sn * s[k + 1].Real); + s[k + 1] = (-sn * e[k]) + (cs * s[k + 1]); + g = sn * e[k + 1].Real; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < matrixCopy.RowCount) + { + Csrot(u, matrixCopy.RowCount, k, k + 1, cs, sn); + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence. + case 4: + // Make the singular value positive + if (s[l].Real < 0.0) + { + s[l] = -s[l]; + if (computeVectors) + { + CscalColumn(vt, matrixCopy.ColumnCount, l, 0, -1.0); + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (s[l].Real >= s[l + 1].Real) + { + break; + } + + t = s[l]; + s[l] = s[l + 1]; + s[l + 1] = t; + if (computeVectors && l < matrixCopy.ColumnCount) + { + Swap(vt, matrixCopy.ColumnCount, l, l + 1); + } + + if (computeVectors && l < matrixCopy.RowCount) + { + Swap(u, matrixCopy.RowCount, l, l + 1); + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + vt = vt.ConjugateTranspose(); + } + + // Adjust the size of s if rows < columns. We are using ported copy of linpack's svd code and it uses + // a singular vector of length mRows+1 when mRows < mColumns. The last element is not used and needs to be removed. + // we should port lapack's svd routine to remove this problem. + if (matrixCopy.RowCount < matrixCopy.ColumnCount) + { + nm--; + var tmp = Vector.Build.SameAs(matrixCopy, nm); + for (i = 0; i < nm; i++) + { + tmp[i] = s[i]; + } + + s = tmp; + } + + return new UserSvd(s, u, vt, computeVectors); + } + + UserSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Calculates absolute value of multiplied on signum function of + /// + /// Complex value z1 + /// Complex value z2 + /// Result multiplication of signum function and absolute value + static Complex Csign(Complex z1, Complex z2) + { + return z1.Magnitude * (z2 / z2.Magnitude); + } + + /// + /// Interchanges two vectors and + /// + /// Source matrix + /// The number of rows in + /// Column A index to swap + /// Column B index to swap + static void Swap(Matrix a, int rowCount, int columnA, int columnB) + { + for (var i = 0; i < rowCount; i++) + { + var z = a.At(i, columnA); + a.At(i, columnA, a.At(i, columnB)); + a.At(i, columnB, z); + } + } + + /// + /// Scale column by starting from row + /// + /// Source matrix + /// The number of rows in + /// Column to scale + /// Row to scale from + /// Scale value + static void CscalColumn(Matrix a, int rowCount, int column, int rowStart, Complex z) + { + for (var i = rowStart; i < rowCount; i++) + { + a.At(i, column, a.At(i, column) * z); + } + } + + /// + /// Scale vector by starting from index + /// + /// Source vector + /// Row to scale from + /// Scale value + static void CscalVector(Complex[] a, int start, Complex z) + { + for (var i = start; i < a.Length; i++) + { + a[i] = a[i] * z; + } + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + static void Srotg(ref double da, ref double db, out double c, out double s) + { + double r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0) + { + c = 1.0; + s = 0.0; + r = 0.0; + z = 0.0; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0) + { + z = 1.0 / c; + } + } + + da = r; + db = z; + } + + /// + /// Calculate Norm 2 of the column in matrix starting from row + /// + /// Source matrix + /// The number of rows in + /// Column index + /// Start row index + /// Norm2 (Euclidean norm) of the column + static double Cnrm2Column(Matrix a, int rowCount, int column, int rowStart) + { + var s = 0.0; + for (var i = rowStart; i < rowCount; i++) + { + s += a.At(i, column).Magnitude * a.At(i, column).Magnitude; + } + + return Math.Sqrt(s); + } + + /// + /// Calculate Norm 2 of the vector starting from index + /// + /// Source vector + /// Start index + /// Norm2 (Euclidean norm) of the vector + static double Cnrm2Vector(Complex[] a, int rowStart) + { + var s = 0.0; + for (var i = rowStart; i < a.Length; i++) + { + s += a[i].Magnitude * a[i].Magnitude; + } + + return Math.Sqrt(s); + } + + /// + /// Calculate dot product of and conjugating the first vector. + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Starting row index + /// Dot product value + static Complex Cdotc(Matrix a, int rowCount, int columnA, int columnB, int rowStart) + { + var z = Complex.Zero; + for (var i = rowStart; i < rowCount; i++) + { + z += a.At(i, columnA).Conjugate() * a.At(i, columnB); + } + + return z; + } + + /// + /// Performs rotation of points in the plane. Given two vectors x and y , + /// each vector element of these vectors is replaced as follows: x(i) = c*x(i) + s*y(i); y(i) = c*y(i) - s*x(i) + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// scalar cos value + /// scalar sin value + static void Csrot(Matrix a, int rowCount, int columnA, int columnB, double c, double s) + { + for (var i = 0; i < rowCount; i++) + { + var z = (c * a.At(i, columnA)) + (s * a.At(i, columnB)); + var tmp = (c * a.At(i, columnB)) - (s * a.At(i, columnA)); + a.At(i, columnB, tmp); + a.At(i, columnA, z); + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (U.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var mn = Math.Min(U.RowCount, VT.ColumnCount); + var bn = input.ColumnCount; + + var tmp = new Complex[VT.ColumnCount]; + + for (var k = 0; k < bn; k++) + { + for (var j = 0; j < VT.ColumnCount; j++) + { + var value = Complex.Zero; + if (j < mn) + { + for (var i = 0; i < U.RowCount; i++) + { + value += U.At(i, j).Conjugate() * input.At(i, k); + } + + value /= S[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < VT.ColumnCount; j++) + { + var value = Complex.Zero; + for (var i = 0; i < VT.ColumnCount; i++) + { + value += VT.At(i, j).Conjugate() * tmp[i]; + } + + result.At(j, k, value); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (U.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VT.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(VT, result); + } + + var mn = Math.Min(U.RowCount, VT.ColumnCount); + var tmp = new Complex[VT.ColumnCount]; + for (var j = 0; j < VT.ColumnCount; j++) + { + var value = Complex.Zero; + if (j < mn) + { + for (var i = 0; i < U.RowCount; i++) + { + value += U.At(i, j).Conjugate() * input[i]; + } + + value /= S[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < VT.ColumnCount; j++) + { + var value = Complex.Zero; + for (var i = 0; i < VT.ColumnCount; i++) + { + value += VT.At(i, j).Conjugate() * tmp[i]; + } + + result[j] = value; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Matrix.cs b/MathNet.Numerics/LinearAlgebra/Complex/Matrix.cs new file mode 100644 index 0000000..b6c9324 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Matrix.cs @@ -0,0 +1,871 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Complex.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex; + +using Complex = System.Numerics.Complex; + +/// +/// Complex version of the class. +/// +[Serializable] +public abstract class Matrix : Matrix +{ + /// + /// Initializes a new instance of the Matrix class. + /// + protected Matrix(MatrixStorage storage) + : base(storage) + { + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero. + /// + public override void CoerceZero(double threshold) + { + MapInplace(x => x.Magnitude < threshold ? Complex.Zero : x, Zeros.AllowSkip); + } + + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public sealed override Matrix ConjugateTranspose() + { + var ret = Transpose(); + ret.MapInplace(c => c.Conjugate(), Zeros.AllowSkip); + return ret; + } + + /// + /// Puts the conjugate transpose of this matrix into the result matrix. + /// + public sealed override void ConjugateTranspose(Matrix result) + { + Transpose(result); + result.MapInplace(c => c.Conjugate(), Zeros.AllowSkip); + } + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected override void DoConjugate(Matrix result) + { + Map(Complex.Conjugate, result, Zeros.AllowSkip); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + Map(Complex.Negate, result, Zeros.AllowSkip); + } + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected override void DoAdd(Complex scalar, Matrix result) + { + Map(x => x + scalar, result, Zeros.Include); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + Map2(Complex.Add, other, result, Zeros.AllowSkip); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(Complex scalar, Matrix result) + { + Map(x => x - scalar, result, Zeros.Include); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract to this matrix. + /// The matrix to store the result of subtraction. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + Map2(Complex.Subtract, other, result, Zeros.AllowSkip); + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(Complex scalar, Matrix result) + { + Map(x => x * scalar, result, Zeros.AllowSkip); + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + for (var i = 0; i < RowCount; i++) + { + var s = Complex.Zero; + for (var j = 0; j < ColumnCount; j++) + { + s += At(i, j) * rightSide[j]; + } + + result[i] = s; + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + for (var i = 0; i < RowCount; i++) + { + for (var j = 0; j != other.ColumnCount; j++) + { + var s = Complex.Zero; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(Complex divisor, Matrix result) + { + Map(x => x / divisor, result, divisor.IsZero() ? Zeros.Include : Zeros.AllowSkip); + } + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to divide by each element of the matrix. + /// The matrix to store the result of the division. + protected override void DoDivideByThis(Complex dividend, Matrix result) + { + Map(x => dividend / x, result, Zeros.Include); + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.RowCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(j, k); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.RowCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(j, k).Conjugate(); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.ColumnCount; j++) + { + for (var i = 0; i < ColumnCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < RowCount; k++) + { + s += At(k, i) * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.ColumnCount; j++) + { + for (var i = 0; i < ColumnCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < RowCount; k++) + { + s += At(k, i).Conjugate() * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + for (var j = 0; j < ColumnCount; j++) + { + var s = Complex.Zero; + for (var i = 0; i < RowCount; i++) + { + s += At(i, j) * rightSide[i]; + } + + result[j] = s; + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + for (var j = 0; j < ColumnCount; j++) + { + var s = Complex.Zero; + for (var i = 0; i < RowCount; i++) + { + s += At(i, j).Conjugate() * rightSide[i]; + } + + result[j] = s; + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + Map2(Complex.Multiply, other, result, Zeros.AllowSkip); + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + Map2(Complex.Divide, divisor, result, Zeros.Include); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The matrix to store the result of the pointwise power. + protected override void DoPointwisePower(Complex exponent, Matrix result) + { + Map(x => x.Power(exponent), result, Zeros.Include); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Matrix exponent, Matrix result) + { + Map2(Complex.Pow, result, Zeros.Include); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected sealed override void DoPointwiseModulus(Matrix divisor, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected sealed override void DoPointwiseRemainder(Matrix divisor, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected sealed override void DoModulus(Complex divisor, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected sealed override void DoModulusByThis(Complex dividend, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected sealed override void DoRemainder(Complex divisor, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected sealed override void DoRemainderByThis(Complex dividend, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected override void DoPointwiseExp(Matrix result) + { + Map(Complex.Exp, result, Zeros.Include); + } + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected override void DoPointwiseLog(Matrix result) + { + Map(Complex.Log, result, Zeros.Include); + } + + protected override void DoPointwiseAbs(Matrix result) + { + Map(x => (Complex)Complex.Abs(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAcos(Matrix result) + { + Map(Complex.Acos, result, Zeros.Include); + } + protected override void DoPointwiseAsin(Matrix result) + { + Map(Complex.Asin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan(Matrix result) + { + Map(Complex.Atan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan2(Matrix other, Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseCeiling(Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseCos(Matrix result) + { + Map(Complex.Cos, result, Zeros.Include); + } + protected override void DoPointwiseCosh(Matrix result) + { + Map(Complex.Cosh, result, Zeros.Include); + } + protected override void DoPointwiseFloor(Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseLog10(Matrix result) + { + Map(Complex.Log10, result, Zeros.Include); + } + protected override void DoPointwiseRound(Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseSign(Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseSin(Matrix result) + { + Map(Complex.Sin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSinh(Matrix result) + { + Map(Complex.Sinh, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSqrt(Matrix result) + { + Map(Complex.Sqrt, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTan(Matrix result) + { + Map(Complex.Tan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTanh(Matrix result) + { + Map(Complex.Tanh, result, Zeros.AllowSkip); + } + + /// + /// Computes the Moore-Penrose Pseudo-Inverse of this matrix. + /// + public override Matrix PseudoInverse() + { + var svd = Svd(true); + var w = svd.W; + var s = svd.S; + double tolerance = Math.Max(RowCount, ColumnCount) * svd.L2Norm * Precision.DoublePrecision; + + for (int i = 0; i < s.Count; i++) + { + s[i] = s[i].Magnitude < tolerance ? 0 : 1 / s[i]; + } + + w.SetDiagonal(s); + return (svd.U * w * svd.VT).Transpose(); + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public override Complex Trace() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var sum = Complex.Zero; + for (var i = 0; i < RowCount; i++) + { + sum += At(i, i); + } + + return sum; + } + + protected override void DoPointwiseMinimum(Complex scalar, Matrix result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMaximum(Complex scalar, Matrix result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseAbsoluteMinimum(Complex scalar, Matrix result) + { + double absolute = scalar.Magnitude; + Map(x => Math.Min(absolute, x.Magnitude), result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Complex scalar, Matrix result) + { + double absolute = scalar.Magnitude; + Map(x => Math.Max(absolute, x.Magnitude), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(Matrix other, Matrix result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMaximum(Matrix other, Matrix result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseAbsoluteMinimum(Matrix other, Matrix result) + { + Map2((x, y) => Math.Min(x.Magnitude, y.Magnitude), other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Matrix other, Matrix result) + { + Map2((x, y) => Math.Max(x.Magnitude, y.Magnitude), other, result, Zeros.AllowSkip); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + var norm = 0d; + for (var j = 0; j < ColumnCount; j++) + { + var s = 0d; + for (var i = 0; i < RowCount; i++) + { + s += At(i, j).Magnitude; + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + var s = 0d; + for (var j = 0; j < ColumnCount; j++) + { + s += At(i, j).Magnitude; + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + var transpose = ConjugateTranspose(); + var aat = this * transpose; + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + norm += aat.At(i, i).Magnitude; + } + + return Math.Sqrt(norm); + } + + /// + /// Calculates the p-norms of all row vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public override Vector RowNorms(double norm) + { + if (norm <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(norm), Resources.ArgumentMustBePositive); + } + + var ret = new double[RowCount]; + if (norm == 2.0) + { + Storage.FoldByRowUnchecked(ret, (s, x) => s + x.MagnitudeSquared(), (x, c) => Math.Sqrt(x), ret, Zeros.AllowSkip); + } + else if (norm == 1.0) + { + Storage.FoldByRowUnchecked(ret, (s, x) => s + x.Magnitude, (x, c) => x, ret, Zeros.AllowSkip); + } + else if (double.IsPositiveInfinity(norm)) + { + Storage.FoldByRowUnchecked(ret, (s, x) => Math.Max(s, x.Magnitude), (x, c) => x, ret, Zeros.AllowSkip); + } + else + { + double invnorm = 1.0 / norm; + Storage.FoldByRowUnchecked(ret, (s, x) => s + Math.Pow(x.Magnitude, norm), (x, c) => Math.Pow(x, invnorm), ret, Zeros.AllowSkip); + } + + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the p-norms of all column vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public override Vector ColumnNorms(double norm) + { + if (norm <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(norm), Resources.ArgumentMustBePositive); + } + + var ret = new double[ColumnCount]; + if (norm == 2.0) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x.MagnitudeSquared(), (x, c) => Math.Sqrt(x), ret, Zeros.AllowSkip); + } + else if (norm == 1.0) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x.Magnitude, (x, c) => x, ret, Zeros.AllowSkip); + } + else if (double.IsPositiveInfinity(norm)) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => Math.Max(s, x.Magnitude), (x, c) => x, ret, Zeros.AllowSkip); + } + else + { + double invnorm = 1.0 / norm; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + Math.Pow(x.Magnitude, norm), (x, c) => Math.Pow(x, invnorm), ret, Zeros.AllowSkip); + } + + return Vector.Build.Dense(ret); + } + + /// + /// Normalizes all row vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public sealed override Matrix NormalizeRows(double norm) + { + var norminv = ((DenseVectorStorage)RowNorms(norm).Storage).Data; + for (int i = 0; i < norminv.Length; i++) + { + norminv[i] = norminv[i] == 0d ? 1d : 1d / norminv[i]; + } + + var result = Build.SameAs(this, RowCount, ColumnCount); + Storage.MapIndexedTo(result.Storage, (i, j, x) => norminv[i] * x, Zeros.AllowSkip, ExistingData.AssumeZeros); + return result; + } + + /// + /// Normalizes all column vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public sealed override Matrix NormalizeColumns(double norm) + { + var norminv = ((DenseVectorStorage)ColumnNorms(norm).Storage).Data; + for (int i = 0; i < norminv.Length; i++) + { + norminv[i] = norminv[i] == 0d ? 1d : 1d / norminv[i]; + } + + var result = Build.SameAs(this, RowCount, ColumnCount); + Storage.MapIndexedTo(result.Storage, (i, j, x) => norminv[j] * x, Zeros.AllowSkip, ExistingData.AssumeZeros); + return result; + } + + /// + /// Calculates the value sum of each row vector. + /// + public override Vector RowSums() + { + var ret = new Complex[RowCount]; + Storage.FoldByRowUnchecked(ret, (s, x) => s + x, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the absolute value sum of each row vector. + /// + public override Vector RowAbsoluteSums() + { + var ret = new Complex[RowCount]; + Storage.FoldByRowUnchecked(ret, (s, x) => s + x.Magnitude, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the value sum of each column vector. + /// + public override Vector ColumnSums() + { + var ret = new Complex[ColumnCount]; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the absolute value sum of each column vector. + /// + public override Vector ColumnAbsoluteSums() + { + var ret = new Complex[ColumnCount]; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x.Magnitude, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public override bool IsHermitian() + { + if (RowCount != ColumnCount) + { + return false; + } + + for (var k = 0; k < RowCount; k++) + { + if (!At(k, k).IsReal()) + { + return false; + } + } + + for (var row = 0; row < RowCount; row++) + { + for (var column = row + 1; column < ColumnCount; column++) + { + if (!At(row, column).Equals(At(column, row).Conjugate())) + { + return false; + } + } + } + + return true; + } + + public override Cholesky Cholesky() + { + return UserCholesky.Create(this); + } + + public override LU LU() + { + return UserLU.Create(this); + } + + public override QR QR(QRMethod method = QRMethod.Thin) + { + return UserQR.Create(this, method); + } + + public override GramSchmidt GramSchmidt() + { + return UserGramSchmidt.Create(this); + } + + public override Svd Svd(bool computeVectors = true) + { + return UserSvd.Create(this, computeVectors); + } + + public override Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown) + { + return UserEvd.Create(this, symmetricity); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/BiCgStab.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/BiCgStab.cs new file mode 100644 index 0000000..6bbc450 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/BiCgStab.cs @@ -0,0 +1,275 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// A Bi-Conjugate Gradient stabilized iterative matrix solver. +/// +/// +/// +/// The Bi-Conjugate Gradient Stabilized (BiCGStab) solver is an 'improvement' +/// of the standard Conjugate Gradient (CG) solver. Unlike the CG solver the +/// BiCGStab can be used on non-symmetric matrices.
+/// Note that much of the success of the solver depends on the selection of the +/// proper preconditioner. +///
+/// +/// The Bi-CGSTAB algorithm was taken from:
+/// Templates for the solution of linear systems: Building blocks +/// for iterative methods +///
+/// Richard Barrett, Michael Berry, Tony F. Chan, James Demmel, +/// June M. Donato, Jack Dongarra, Victor Eijkhout, Roldan Pozo, +/// Charles Romine and Henk van der Vorst +///
+/// Url: http://www.netlib.org/templates/Templates.html +///
+/// Algorithm is described in Chapter 2, section 2.3.8, page 27 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class BiCgStab : IIterativeSolver +{ + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + + // Do not use residual = residual.Negate() because it creates another object + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient , A. + /// The solution , b. + /// The result , x. + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, result); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // Compute r_0 = b - Ax_0 for some initial guess x_0 + // In this case we take x_0 = vector + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, result, input); + + // Choose r~ (for example, r~ = r_0) + var tempResiduals = residuals.Clone(); + + // create seven temporary vectors needed to hold temporary + // coefficients. All vectors are mangled in each iteration. + // These are defined here to prevent stressing the garbage collector + var vecP = new DenseVector(residuals.Count); + var vecPdash = new DenseVector(residuals.Count); + var nu = new DenseVector(residuals.Count); + var vecS = new DenseVector(residuals.Count); + var vecSdash = new DenseVector(residuals.Count); + var temp = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + + // create some temporary double variables that are needed + // to hold values in between iterations + Complex currentRho = 0; + Complex alpha = 0; + Complex omega = 0; + + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, result, input, residuals) == IterationStatus.Continue) + { + // rho_(i-1) = r~^T r_(i-1) // dotproduct r~ and r_(i-1) + var oldRho = currentRho; + currentRho = tempResiduals.ConjugateDotProduct(residuals); + + // if (rho_(i-1) == 0) // METHOD FAILS + // If rho is only 1 ULP from zero then we fail. + if (currentRho.Real.AlmostEqualNumbersBetween(0, 1) && currentRho.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + // Rho-type breakdown + throw new NumericalBreakdownException(); + } + + if (iterationNumber != 0) + { + // beta_(i-1) = (rho_(i-1)/rho_(i-2))(alpha_(i-1)/omega(i-1)) + var beta = (currentRho / oldRho) * (alpha / omega); + + // p_i = r_(i-1) + beta_(i-1)(p_(i-1) - omega_(i-1) * nu_(i-1)) + nu.Multiply(-omega, temp); + vecP.Add(temp, temp2); + temp2.CopyTo(vecP); + + vecP.Multiply(beta, vecP); + vecP.Add(residuals, temp2); + temp2.CopyTo(vecP); + } + else + { + // p_i = r_(i-1) + residuals.CopyTo(vecP); + } + + // SOLVE Mp~ = p_i // M = preconditioner + preconditioner.Approximate(vecP, vecPdash); + + // nu_i = Ap~ + matrix.Multiply(vecPdash, nu); + + // alpha_i = rho_(i-1)/ (r~^T nu_i) = rho / dotproduct(r~ and nu_i) + alpha = currentRho * 1 / tempResiduals.ConjugateDotProduct(nu); + + // s = r_(i-1) - alpha_i nu_i + nu.Multiply(-alpha, temp); + residuals.Add(temp, vecS); + + // Check if we're converged. If so then stop. Otherwise continue; + // Calculate the temporary result. + // Be careful not to change any of the temp vectors, except for + // temp. Others will be used in the calculation later on. + // x_i = x_(i-1) + alpha_i * p^_i + s^_i + vecPdash.Multiply(alpha, temp); + temp.Add(vecSdash, temp2); + temp2.CopyTo(temp); + temp.Add(result, temp2); + temp2.CopyTo(temp); + + // Check convergence and stop if we are converged. + if (iterator.DetermineStatus(iterationNumber, temp, input, vecS) != IterationStatus.Continue) + { + temp.CopyTo(result); + + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, result, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // We're all good now. + return; + } + + // Continue the calculation + iterationNumber++; + continue; + } + + // SOLVE Ms~ = s + preconditioner.Approximate(vecS, vecSdash); + + // temp = As~ + matrix.Multiply(vecSdash, temp); + + // omega_i = temp^T s / temp^T temp + omega = temp.ConjugateDotProduct(vecS) / temp.ConjugateDotProduct(temp); + + // x_i = x_(i-1) + alpha_i p^ + omega_i s^ + temp.Multiply(-omega, residuals); + residuals.Add(vecS, temp2); + temp2.CopyTo(residuals); + + vecSdash.Multiply(omega, temp); + result.Add(temp, temp2); + temp2.CopyTo(result); + + vecPdash.Multiply(alpha, temp); + result.Add(temp, temp2); + temp2.CopyTo(result); + + // for continuation it is necessary that omega_i != 0.0 + // If omega is only 1 ULP from zero then we fail. + if (omega.Real.AlmostEqualNumbersBetween(0, 1) && omega.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + // Omega-type breakdown + throw new NumericalBreakdownException(); + } + + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + // The residual calculation based on omega_i * s can be off by a factor 10. So here + // we calculate the real residual (which can be expensive) but we only do it if we're + // sufficiently close to the finish. + CalculateTrueResidual(matrix, residuals, result, input); + } + + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/CompositeSolver.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/CompositeSolver.cs new file mode 100644 index 0000000..13f9273 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/CompositeSolver.cs @@ -0,0 +1,154 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// A composite matrix solver. The actual solver is made by a sequence of +/// matrix solvers. +/// +/// +/// +/// Solver based on:
+/// Faster PDE-based simulations using robust composite linear solvers
+/// S. Bhowmicka, P. Raghavan a,*, L. McInnes b, B. Norris
+/// Future Generation Computer Systems, Vol 20, 2004, pp 373�387
+///
+/// +/// Note that if an iterator is passed to this solver it will be used for all the sub-solvers. +/// +///
+public sealed class CompositeSolver : IIterativeSolver +{ + /// + /// The collection of solvers that will be used + /// + readonly List, IPreconditioner>> _solvers; + + public CompositeSolver(IEnumerable> solvers) + { + _solvers = solvers.Select(setup => new Tuple, IPreconditioner>(setup.CreateSolver(), setup.CreatePreconditioner() ?? new UnitPreconditioner())).ToList(); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + // Create a copy of the solution and result vectors so we can use them + // later on + var internalInput = input.Clone(); + var internalResult = result.Clone(); + + foreach (var solver in _solvers) + { + // Store a reference to the solver so we can stop it. + + IterationStatus status; + try + { + // Reset the iterator and pass it to the solver + iterator.Reset(); + + // Start the solver + solver.Item1.Solve(matrix, internalInput, internalResult, iterator, solver.Item2 ?? preconditioner); + status = iterator.Status; + } + catch (Exception) + { + // The solver broke down. + // Log a message about this + // Switch to the next preconditioner. + // Reset the solution vector to the previous solution + input.CopyTo(internalInput); + continue; + } + + // There was no fatal breakdown so check the status + if (status == IterationStatus.Converged) + { + // We're done + internalResult.CopyTo(result); + break; + } + + // We're not done + // Either: + // - calculation finished without convergence + if (status == IterationStatus.StoppedWithoutConvergence) + { + // Copy the internal result to the result vector and + // continue with the calculation. + internalResult.CopyTo(result); + } + else + { + // - calculation failed --> restart with the original vector + // - calculation diverged --> restart with the original vector + // - Some unknown status occurred --> To be safe restart. + input.CopyTo(internalInput); + } + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/DiagonalPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/DiagonalPreconditioner.cs new file mode 100644 index 0000000..8a3a80f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/DiagonalPreconditioner.cs @@ -0,0 +1,108 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// A diagonal preconditioner. The preconditioner uses the inverse +/// of the matrix diagonal as preconditioning values. +/// +public sealed class DiagonalPreconditioner : IPreconditioner +{ + /// + /// The inverse of the matrix diagonal. + /// + Complex[] _inverseDiagonals; + + /// + /// Returns the decomposed matrix diagonal. + /// + /// The matrix diagonal. + internal DiagonalMatrix DiagonalEntries() + { + var result = new DiagonalMatrix(_inverseDiagonals.Length); + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + result.At(i, i, 1 / _inverseDiagonals[i]); + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _inverseDiagonals = new Complex[matrix.RowCount]; + for (var i = 0; i < matrix.RowCount; i++) + { + _inverseDiagonals[i] = 1 / matrix.At(i, i); + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_inverseDiagonals == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _inverseDiagonals.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rhs)); + } + + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + lhs[i] = rhs[i] * _inverseDiagonals[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/GpBiCg.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/GpBiCg.cs new file mode 100644 index 0000000..25560af --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/GpBiCg.cs @@ -0,0 +1,374 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// A Generalized Product Bi-Conjugate Gradient iterative matrix solver. +/// +/// +/// +/// The Generalized Product Bi-Conjugate Gradient (GPBiCG) solver is an +/// alternative version of the Bi-Conjugate Gradient stabilized (CG) solver. +/// Unlike the CG solver the GPBiCG solver can be used on +/// non-symmetric matrices.
+/// Note that much of the success of the solver depends on the selection of the +/// proper preconditioner. +///
+/// +/// The GPBiCG algorithm was taken from:
+/// GPBiCG(m,l): A hybrid of BiCGSTAB and GPBiCG methods with +/// efficiency and robustness +///
+/// S. Fujino +///
+/// Applied Numerical Mathematics, Volume 41, 2002, pp 107 - 117 +///
+///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class GpBiCg : IIterativeSolver +{ + /// + /// Indicates the number of BiCGStab steps should be taken + /// before switching. + /// + int _numberOfBiCgStabSteps = 1; + + /// + /// Indicates the number of GPBiCG steps should be taken + /// before switching. + /// + int _numberOfGpbiCgSteps = 4; + + /// + /// Gets or sets the number of steps taken with the BiCgStab algorithm + /// before switching over to the GPBiCG algorithm. + /// + public int NumberOfBiCgStabSteps + { + get { return _numberOfBiCgStabSteps; } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfBiCgStabSteps = value; + } + } + + /// + /// Gets or sets the number of steps taken with the GPBiCG algorithm + /// before switching over to the BiCgStab algorithm. + /// + public int NumberOfGpBiCgSteps + { + get { return _numberOfGpbiCgSteps; } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfGpbiCgSteps = value; + } + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Decide if to do steps with BiCgStab + /// + /// Number of iteration + /// true if yes, otherwise false + bool ShouldRunBiCgStabSteps(int iterationNumber) + { + // Run the first steps as BiCGStab + // The number of steps past a whole iteration set + var difference = iterationNumber % (_numberOfBiCgStabSteps + _numberOfGpbiCgSteps); + + // Do steps with BiCGStab if: + // - The difference is zero or more (i.e. we have done zero or more complete cycles) + // - The difference is less than the number of BiCGStab steps that should be taken + return (difference >= 0) && (difference < _numberOfBiCgStabSteps); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (input.Count != matrix.RowCount || result.Count != input.Count) + { + throw Matrix.DimensionsDontMatch(matrix, input, result); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // x_0 is initial guess + // Take x_0 = 0 + var xtemp = new DenseVector(input.Count); + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary scalars + Complex beta = 0; + + // Define the temporary vectors + // rDash_0 = r_0 + var rdash = DenseVector.OfVector(residuals); + + // t_-1 = 0 + var t = new DenseVector(residuals.Count); + var t0 = new DenseVector(residuals.Count); + + // w_-1 = 0 + var w = new DenseVector(residuals.Count); + + // Define the remaining temporary vectors + var c = new DenseVector(residuals.Count); + var p = new DenseVector(residuals.Count); + var s = new DenseVector(residuals.Count); + var u = new DenseVector(residuals.Count); + var y = new DenseVector(residuals.Count); + var z = new DenseVector(residuals.Count); + + var temp = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + var temp3 = new DenseVector(residuals.Count); + + // for (k = 0, 1, .... ) + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue) + { + // p_k = r_k + beta_(k-1) * (p_(k-1) - u_(k-1)) + p.Subtract(u, temp); + + temp.Multiply(beta, temp2); + residuals.Add(temp2, p); + + // Solve M b_k = p_k + preconditioner.Approximate(p, temp); + + // s_k = A b_k + matrix.Multiply(temp, s); + + // alpha_k = (r*_0 * r_k) / (r*_0 * s_k) + var alpha = rdash.ConjugateDotProduct(residuals) / rdash.ConjugateDotProduct(s); + + // y_k = t_(k-1) - r_k - alpha_k * w_(k-1) + alpha_k s_k + s.Subtract(w, temp); + t.Subtract(residuals, y); + + temp.Multiply(alpha, temp2); + y.Add(temp2, temp3); + temp3.CopyTo(y); + + // Store the old value of t in t0 + t.CopyTo(t0); + + // t_k = r_k - alpha_k s_k + s.Multiply(-alpha, temp2); + residuals.Add(temp2, t); + + // Solve M d_k = t_k + preconditioner.Approximate(t, temp); + + // c_k = A d_k + matrix.Multiply(temp, c); + var cdot = c.ConjugateDotProduct(c); + + // cDot can only be zero if c is a zero vector + // We'll set cDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // c.DotProduct(t) will be zero and so will c.DotProduct(y) + if (cdot.Real.AlmostEqualNumbersBetween(0, 1) && cdot.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + cdot = 1.0; + } + + // Even if we don't want to do any BiCGStab steps we'll still have + // to do at least one at the start to initialize the + // system, but we'll only have to take special measures + // if we don't do any so ... + var ctdot = c.ConjugateDotProduct(t); + Complex eta; + Complex sigma; + if (((_numberOfBiCgStabSteps == 0) && (iterationNumber == 0)) || ShouldRunBiCgStabSteps(iterationNumber)) + { + // sigma_k = (c_k * t_k) / (c_k * c_k) + sigma = ctdot / cdot; + + // eta_k = 0 + eta = 0; + } + else + { + var ydot = y.ConjugateDotProduct(y); + + // yDot can only be zero if y is a zero vector + // We'll set yDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // y.DotProduct(t) will be zero and so will c.DotProduct(y) + if (ydot.Real.AlmostEqualNumbersBetween(0, 1) && ydot.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + ydot = 1.0; + } + + var ytdot = y.ConjugateDotProduct(t); + var cydot = c.ConjugateDotProduct(y); + + var denom = (cdot * ydot) - (cydot * cydot); + + // sigma_k = ((y_k * y_k)(c_k * t_k) - (y_k * t_k)(c_k * y_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + sigma = ((ydot * ctdot) - (ytdot * cydot)) / denom; + + // eta_k = ((c_k * c_k)(y_k * t_k) - (y_k * c_k)(c_k * t_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + eta = ((cdot * ytdot) - (cydot * ctdot)) / denom; + } + + // u_k = sigma_k s_k + eta_k (t_(k-1) - r_k + beta_(k-1) u_(k-1)) + u.Multiply(beta, temp2); + t0.Add(temp2, temp); + + temp.Subtract(residuals, temp3); + temp3.CopyTo(temp); + temp.Multiply(eta, temp); + + s.Multiply(sigma, temp2); + temp.Add(temp2, u); + + // z_k = sigma_k r_k +_ eta_k z_(k-1) - alpha_k u_k + z.Multiply(eta, z); + u.Multiply(-alpha, temp2); + z.Add(temp2, temp3); + temp3.CopyTo(z); + + residuals.Multiply(sigma, temp2); + z.Add(temp2, temp3); + temp3.CopyTo(z); + + // x_(k+1) = x_k + alpha_k p_k + z_k + p.Multiply(alpha, temp2); + xtemp.Add(temp2, temp3); + temp3.CopyTo(xtemp); + + xtemp.Add(z, temp3); + temp3.CopyTo(xtemp); + + // r_(k+1) = t_k - eta_k y_k - sigma_k c_k + // Copy the old residuals to a temp vector because we'll + // need those in the next step + residuals.CopyTo(t0); + + y.Multiply(-eta, temp2); + t.Add(temp2, residuals); + + c.Multiply(-sigma, temp2); + residuals.Add(temp2, temp3); + temp3.CopyTo(residuals); + + // beta_k = alpha_k / sigma_k * (r*_0 * r_(k+1)) / (r*_0 * r_k) + // But first we check if there is a possible NaN. If so just reset beta to zero. + beta = (!sigma.Real.AlmostEqualNumbersBetween(0, 1) || !sigma.Imaginary.AlmostEqualNumbersBetween(0, 1)) ? alpha / sigma * rdash.ConjugateDotProduct(residuals) / rdash.ConjugateDotProduct(t0) : 0; + + // w_k = c_k + beta_k s_k + s.Multiply(beta, temp2); + c.Add(temp2, w); + + // Get the real value + preconditioner.Approximate(xtemp, result); + + // Now check for convergence + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, result, input); + } + + // Next iteration. + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/ILU0Preconditioner.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/ILU0Preconditioner.cs new file mode 100644 index 0000000..20df5e6 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/ILU0Preconditioner.cs @@ -0,0 +1,223 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// An incomplete, level 0, LU factorization preconditioner. +/// +/// +/// The ILU(0) algorithm was taken from:
+/// Iterative methods for sparse linear systems
+/// Yousef Saad
+/// Algorithm is described in Chapter 10, section 10.3.2, page 275
+///
+public sealed class ILU0Preconditioner : IPreconditioner +{ + /// + /// The matrix holding the lower (L) and upper (U) matrices. The + /// decomposition matrices are combined to reduce storage. + /// + SparseMatrix _decompositionLU; + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = i; j < _decompositionLU.ColumnCount; j++) + { + result[i, j] = _decompositionLU[i, j]; + } + } + + return result; + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = 0; j <= i; j++) + { + if (i == j) + { + result[i, j] = 1.0; + } + else + { + result[i, j] = _decompositionLU[i, j]; + } + } + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _decompositionLU = SparseMatrix.OfMatrix(matrix); + + // M == A + // for i = 2, ... , n do + // for k = 1, .... , i - 1 do + // if (i,k) == NZ(Z) then + // compute z(i,k) = z(i,k) / z(k,k); + // for j = k + 1, ...., n do + // if (i,j) == NZ(Z) then + // compute z(i,j) = z(i,j) - z(i,k) * z(k,j) + // end + // end + // end + // end + // end + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var k = 0; k < i; k++) + { + if (_decompositionLU[i, k] != 0.0) + { + var t = _decompositionLU[i, k] / _decompositionLU[k, k]; + _decompositionLU[i, k] = t; + if (_decompositionLU[k, i] != 0.0) + { + _decompositionLU[i, i] = _decompositionLU[i, i] - (t * _decompositionLU[k, i]); + } + + for (var j = k + 1; j < _decompositionLU.RowCount; j++) + { + if (j == i) + { + continue; + } + + if (_decompositionLU[i, j] != 0.0) + { + _decompositionLU[i, j] = _decompositionLU[i, j] - (t * _decompositionLU[k, j]); + } + } + } + } + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_decompositionLU == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _decompositionLU.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Solve: + // Lz = y + // Which gives + // for (int i = 1; i < matrix.RowLength; i++) + // { + // z_i = l_ii^-1 * (y_i - SUM_(j -1; i--) + // { + // x_i = u_ii^-1 * (z_i - SUM_(j > i) u_ij * x_j) + // } + for (var i = _decompositionLU.RowCount - 1; i > -1; i--) + { + _decompositionLU.Row(i, rowValues); + + var sum = Complex.Zero; + for (var j = _decompositionLU.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/ILUTPPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/ILUTPPreconditioner.cs new file mode 100644 index 0000000..3b8e8bb --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/ILUTPPreconditioner.cs @@ -0,0 +1,865 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// This class performs an Incomplete LU factorization with drop tolerance +/// and partial pivoting. The drop tolerance indicates which additional entries +/// will be dropped from the factorized LU matrices. +/// +/// +/// The ILUTP-Mem algorithm was taken from:
+/// ILUTP_Mem: a Space-Efficient Incomplete LU Preconditioner +///
+/// Tzu-Yi Chen, Department of Mathematics and Computer Science,
+/// Pomona College, Claremont CA 91711, USA
+/// Published in:
+/// Lecture Notes in Computer Science
+/// Volume 3046 / 2004
+/// pp. 20 - 28
+/// Algorithm is described in Section 2, page 22 +///
+public sealed class ILUTPPreconditioner : IPreconditioner +{ + /// + /// The default fill level. + /// + public const double DefaultFillLevel = 200.0; + + /// + /// The default drop tolerance. + /// + public const double DefaultDropTolerance = 0.0001; + + /// + /// The decomposed upper triangular matrix. + /// + SparseMatrix _upper; + + /// + /// The decomposed lower triangular matrix. + /// + SparseMatrix _lower; + + /// + /// The array containing the pivot values. + /// + int[] _pivots; + + /// + /// The fill level. + /// + double _fillLevel = DefaultFillLevel; + + /// + /// The drop tolerance. + /// + double _dropTolerance = DefaultDropTolerance; + + /// + /// The pivot tolerance. + /// + double _pivotTolerance; + + /// + /// Initializes a new instance of the class with the default settings. + /// + public ILUTPPreconditioner() + { + } + + /// + /// Initializes a new instance of the class with the specified settings. + /// + /// + /// The amount of fill that is allowed in the matrix. The value is a fraction of + /// the number of non-zero entries in the original matrix. Values should be positive. + /// + /// + /// The absolute drop tolerance which indicates below what absolute value an entry + /// will be dropped from the matrix. A drop tolerance of 0.0 means that no values + /// will be dropped. Values should always be positive. + /// + /// + /// The pivot tolerance which indicates at what level pivoting will take place. A + /// value of 0.0 means that no pivoting will take place. + /// + public ILUTPPreconditioner(double fillLevel, double dropTolerance, double pivotTolerance) + { + if (fillLevel < 0) + { + throw new ArgumentOutOfRangeException(nameof(fillLevel)); + } + + if (dropTolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(dropTolerance)); + } + + if (pivotTolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(pivotTolerance)); + } + + _fillLevel = fillLevel; + _dropTolerance = dropTolerance; + _pivotTolerance = pivotTolerance; + } + + /// + /// Gets or sets the amount of fill that is allowed in the matrix. The + /// value is a fraction of the number of non-zero entries in the original + /// matrix. The standard value is 200. + /// + /// + /// + /// Values should always be positive and can be higher than 1.0. A value lower + /// than 1.0 means that the eventual preconditioner matrix will have fewer + /// non-zero entries as the original matrix. A value higher than 1.0 means that + /// the eventual preconditioner can have more non-zero values than the original + /// matrix. + /// + /// + /// Note that any changes to the FillLevel after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double FillLevel + { + get { return _fillLevel; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _fillLevel = value; + } + } + + /// + /// Gets or sets the absolute drop tolerance which indicates below what absolute value + /// an entry will be dropped from the matrix. The standard value is 0.0001. + /// + /// + /// + /// The values should always be positive and can be larger than 1.0. A low value will + /// keep more small numbers in the preconditioner matrix. A high value will remove + /// more small numbers from the preconditioner matrix. + /// + /// + /// Note that any changes to the DropTolerance after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double DropTolerance + { + get { return _dropTolerance; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _dropTolerance = value; + } + } + + /// + /// Gets or sets the pivot tolerance which indicates at what level pivoting will + /// take place. The standard value is 0.0 which means pivoting will never take place. + /// + /// + /// + /// The pivot tolerance is used to calculate if pivoting is necessary. Pivoting + /// will take place if any of the values in a row is bigger than the + /// diagonal value of that row divided by the pivot tolerance, i.e. pivoting + /// will take place if row(i,j) > row(i,i) / PivotTolerance for + /// any j that is not equal to i. + /// + /// + /// Note that any changes to the PivotTolerance after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double PivotTolerance + { + get { return _pivotTolerance; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _pivotTolerance = value; + } + } + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + return _upper.Clone(); + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + return _lower.Clone(); + } + + /// + /// Returns the pivot array. This array is not needed for normal use because + /// the preconditioner will return the solution vector values in the proper order. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// The pivot array. + internal int[] Pivots() + { + var result = new int[_pivots.Length]; + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = _pivots[i]; + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. Note that the + /// method takes a general matrix type. However internally the data is stored + /// as a sparse matrix. Therefore it is not recommended to pass a dense matrix. + /// + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + var sparseMatrix = matrix as SparseMatrix ?? SparseMatrix.OfMatrix(matrix); + + // The creation of the preconditioner follows the following algorithm. + // spaceLeft = lfilNnz * nnz(A) + // for i = 1, .. , n + // { + // w = a(i,*) + // for j = 1, .. , i - 1 + // { + // if (w(j) != 0) + // { + // w(j) = w(j) / a(j,j) + // if (w(j) < dropTol) + // { + // w(j) = 0; + // } + // if (w(j) != 0) + // { + // w = w - w(j) * U(j,*) + // } + // } + // } + // + // for j = i, .. ,n + // { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + // } + // + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + // lfil = spaceRow / 2 // space for this row of L + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + // + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + // u(i,j) = w(j) for j = i, .. , n // only the largest lfil - 1 elements + // w = 0 + // + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + // } + // Create the lower triangular matrix + _lower = new SparseMatrix(sparseMatrix.RowCount); + + // Create the upper triangular matrix and copy the values + _upper = new SparseMatrix(sparseMatrix.RowCount); + + // Create the pivot array + _pivots = new int[sparseMatrix.RowCount]; + for (var i = 0; i < _pivots.Length; i++) + { + _pivots[i] = i; + } + + var workVector = new DenseVector(sparseMatrix.RowCount); + var rowVector = new DenseVector(sparseMatrix.ColumnCount); + var indexSorting = new int[sparseMatrix.RowCount]; + + // spaceLeft = lfilNnz * nnz(A) + var spaceLeft = (int)_fillLevel * sparseMatrix.NonZerosCount; + + // for i = 1, .. , n + for (var i = 0; i < sparseMatrix.RowCount; i++) + { + // w = a(i,*) + sparseMatrix.Row(i, workVector); + + // pivot the row + PivotRow(workVector); + var vectorNorm = workVector.InfinityNorm(); + + // for j = 1, .. , i - 1) + for (var j = 0; j < i; j++) + { + // if (w(j) != 0) + // { + // w(j) = w(j) / a(j,j) + // if (w(j) < dropTol) + // { + // w(j) = 0; + // } + // if (w(j) != 0) + // { + // w = w - w(j) * U(j,*) + // } + if (workVector[j] != 0.0) + { + // Calculate the multiplication factors that go into the L matrix + workVector[j] = workVector[j] / _upper[j, j]; + if (workVector[j].Magnitude < _dropTolerance) + { + workVector[j] = 0.0; + } + + // Calculate the addition factor + if (workVector[j] != 0.0) + { + // vector update all in one go + _upper.Row(j, rowVector); + + // zero out columnVector[k] because we don't need that + // one anymore for k = 0 to k = j + for (var k = 0; k <= j; k++) + { + rowVector[k] = 0.0; + } + + rowVector.Multiply(workVector[j], rowVector); + workVector.Subtract(rowVector, workVector); + } + } + } + + // for j = i, .. ,n + for (var j = i; j < sparseMatrix.RowCount; j++) + { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + if (workVector[j].Magnitude <= _dropTolerance * vectorNorm) + { + workVector[j] = 0.0; + } + } + + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + var spaceRow = spaceLeft / (sparseMatrix.RowCount - i + 1); + + // lfil = spaceRow / 2 // space for this row of L + var fillLevel = spaceRow / 2; + FindLargestItems(0, i - 1, indexSorting, workVector); + + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + var lowerNonZeroCount = 0; + var count = 0; + for (var j = 0; j < i; j++) + { + if ((count > fillLevel) || (indexSorting[j] == -1)) + { + break; + } + + _lower[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + lowerNonZeroCount += 1; + } + + FindLargestItems(i + 1, sparseMatrix.RowCount - 1, indexSorting, workVector); + + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + fillLevel = spaceRow - lowerNonZeroCount; + + // u(i,j) = w(j) for j = i + 1, .. , n // only the largest lfil - 1 elements + var upperNonZeroCount = 0; + count = 0; + for (var j = 0; j < sparseMatrix.RowCount - i; j++) + { + if ((count > fillLevel - 1) || (indexSorting[j] == -1)) + { + break; + } + + _upper[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + upperNonZeroCount += 1; + } + + // Simply copy the diagonal element. Next step is to see if we pivot + _upper[i, i] = workVector[i]; + + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + + // Check if we really need to pivot. If (i+1) >=(mCoefficientMatrix.Rows -1) then + // we are working on the last row. That means that there is only one number + // And pivoting is useless. Also the indexSorting array will only contain + // -1 values. + if ((i + 1) < (sparseMatrix.RowCount - 1)) + { + if (workVector[i].Magnitude < _pivotTolerance * workVector[indexSorting[0]].Magnitude) + { + // swap columns of u (which holds the values of A in the + // sections that haven't been partitioned yet. + SwapColumns(_upper, i, indexSorting[0]); + + // Update P + var temp = _pivots[i]; + _pivots[i] = _pivots[indexSorting[0]]; + _pivots[indexSorting[0]] = temp; + } + } + + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + spaceLeft -= lowerNonZeroCount + upperNonZeroCount; + } + + for (var i = 0; i < _lower.RowCount; i++) + { + _lower[i, i] = 1.0; + } + } + + /// + /// Pivot elements in the according to internal pivot array + /// + /// Row to pivot in + void PivotRow(Vector row) + { + var knownPivots = new Dictionary(); + + // pivot the row + for (var i = 0; i < row.Count; i++) + { + if ((_pivots[i] != i) && (!PivotMapFound(knownPivots, i))) + { + // store the pivots in the hashtable + knownPivots.Add(_pivots[i], i); + + var t = row[i]; + row[i] = row[_pivots[i]]; + row[_pivots[i]] = t; + } + } + } + + /// + /// Was pivoting already performed + /// + /// Pivots already done + /// Current item to pivot + /// true if performed, otherwise false + bool PivotMapFound(Dictionary knownPivots, int currentItem) + { + if (knownPivots.TryGetValue(_pivots[currentItem], out var knownPivot) && knownPivot.Equals(currentItem)) + { + return true; + } + + if (knownPivots.TryGetValue(currentItem, out var pivot) && pivot.Equals(_pivots[currentItem])) + { + return true; + } + + return false; + } + + /// + /// Swap columns in the + /// + /// Source . + /// First column index to swap + /// Second column index to swap + static void SwapColumns(Matrix matrix, int firstColumn, int secondColumn) + { + for (var i = 0; i < matrix.RowCount; i++) + { + var temp = matrix[i, firstColumn]; + matrix[i, firstColumn] = matrix[i, secondColumn]; + matrix[i, secondColumn] = temp; + } + } + + /// + /// Sort vector descending, not changing vector but placing sorted indices to + /// + /// Start sort form + /// Sort till upper bound + /// Array with sorted vector indices + /// Source + static void FindLargestItems(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + // Copy the indices for the values into the array + for (var i = 0; i < upperBound + 1 - lowerBound; i++) + { + sortedIndices[i] = lowerBound + i; + } + + for (var i = upperBound + 1 - lowerBound; i < sortedIndices.Length; i++) + { + sortedIndices[i] = -1; + } + + // Sort the first set of items. + // Sorting starts at index 0 because the index array + // starts at zero + // and ends at index upperBound - lowerBound + ILUTPElementSorter.SortDoubleIndicesDecreasing(0, upperBound - lowerBound, sortedIndices, values); + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_upper == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _upper.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rhs)); + } + + // Solve equation here + // Pivot(vector, result); + // Solve L*Y = B(piv,:) + var rowValues = new DenseVector(_lower.RowCount); + for (var i = 0; i < _lower.RowCount; i++) + { + _lower.Row(i, rowValues); + + var sum = Complex.Zero; + for (var j = 0; j < i; j++) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = rhs[i] - sum; + } + + // Solve U*X = Y; + for (var i = _upper.RowCount - 1; i > -1; i--) + { + _upper.Row(i, rowValues); + + var sum = Complex.Zero; + for (var j = _upper.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + + // We have a column pivot so we only need to pivot the + // end result not the incoming right hand side vector + var temp = lhs.Clone(); + + Pivot(temp, lhs); + } + + /// + /// Pivot elements in according to internal pivot array + /// + /// Source . + /// Result after pivoting. + void Pivot(Vector vector, Vector result) + { + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = vector[_pivots[i]]; + } + } +} + +/// +/// An element sort algorithm for the class. +/// +/// +/// This sort algorithm is used to sort the columns in a sparse matrix based on +/// the value of the element on the diagonal of the matrix. +/// +internal static class ILUTPElementSorter +{ + /// + /// Sorts the elements of the vector in decreasing + /// fashion. The vector itself is not affected. + /// + /// The starting index. + /// The stopping index. + /// An array that will contain the sorted indices once the algorithm finishes. + /// The that contains the values that need to be sorted. + public static void SortDoubleIndicesDecreasing(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + // Move all the indices that we're interested in to the beginning of the + // array. Ignore the rest of the indices. + if (lowerBound > 0) + { + for (var i = 0; i < (upperBound - lowerBound + 1); i++) + { + Exchange(sortedIndices, i, i + lowerBound); + } + + upperBound -= lowerBound; + lowerBound = 0; + } + + HeapSortDoublesIndices(lowerBound, upperBound, sortedIndices, values); + } + + /// + /// Sorts the elements of the vector in decreasing + /// fashion using heap sort algorithm. The vector itself is not affected. + /// + /// The starting index. + /// The stopping index. + /// An array that will contain the sorted indices once the algorithm finishes. + /// The that contains the values that need to be sorted. + private static void HeapSortDoublesIndices(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + var start = ((upperBound - lowerBound + 1) / 2) - 1 + lowerBound; + var end = (upperBound - lowerBound + 1) - 1 + lowerBound; + + BuildDoubleIndexHeap(start, upperBound - lowerBound + 1, sortedIndices, values); + + while (end >= lowerBound) + { + Exchange(sortedIndices, end, lowerBound); + SiftDoubleIndices(sortedIndices, values, lowerBound, end); + end -= 1; + } + } + + /// + /// Build heap for double indices + /// + /// Root position + /// Length of + /// Indices of + /// Target + private static void BuildDoubleIndexHeap(int start, int count, int[] sortedIndices, Vector values) + { + while (start >= 0) + { + SiftDoubleIndices(sortedIndices, values, start, count); + start -= 1; + } + } + + /// + /// Sift double indices + /// + /// Indices of + /// Target + /// Root position + /// Length of + private static void SiftDoubleIndices(int[] sortedIndices, Vector values, int begin, int count) + { + var root = begin; + + while (root * 2 < count) + { + var child = root * 2; + if ((child < count - 1) && (values[sortedIndices[child]].Magnitude > values[sortedIndices[child + 1]].Magnitude)) + { + child += 1; + } + + if (values[sortedIndices[root]].Magnitude <= values[sortedIndices[child]].Magnitude) + { + return; + } + + Exchange(sortedIndices, root, child); + root = child; + } + } + + /// + /// Sorts the given integers in a decreasing fashion. + /// + /// The values. + public static void SortIntegersDecreasing(int[] values) + { + HeapSortIntegers(values, values.Length); + } + + /// + /// Sort the given integers in a decreasing fashion using heapsort algorithm + /// + /// Array of values to sort + /// Length of + private static void HeapSortIntegers(int[] values, int count) + { + var start = (count / 2) - 1; + var end = count - 1; + + BuildHeap(values, start, count); + + while (end >= 0) + { + Exchange(values, end, 0); + Sift(values, 0, end); + end -= 1; + } + } + + /// + /// Build heap + /// + /// Target values array + /// Root position + /// Length of + private static void BuildHeap(int[] values, int start, int count) + { + while (start >= 0) + { + Sift(values, start, count); + start -= 1; + } + } + + /// + /// Sift values + /// + /// Target value array + /// Root position + /// Length of + private static void Sift(int[] values, int start, int count) + { + var root = start; + + while (root * 2 < count) + { + var child = root * 2; + if ((child < count - 1) && (values[child] > values[child + 1])) + { + child += 1; + } + + if (values[root] > values[child]) + { + Exchange(values, root, child); + root = child; + } + else + { + return; + } + } + } + + /// + /// Exchange values in array + /// + /// Target values array + /// First value to exchange + /// Second value to exchange + private static void Exchange(int[] values, int first, int second) + { + var t = values[first]; + values[first] = values[second]; + values[second] = t; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/MILU0Preconditioner.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/MILU0Preconditioner.cs new file mode 100644 index 0000000..6cd1979 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/MILU0Preconditioner.cs @@ -0,0 +1,263 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// A simple milu(0) preconditioner. +/// +/// +/// Original Fortran code by Yousef Saad (07 January 2004) +/// +public sealed class MILU0Preconditioner : IPreconditioner +{ + // Matrix stored in Modified Sparse Row (MSR) format containing the L and U + // factors together. + + // The diagonal (stored in alu(0:n-1) ) is inverted. Each i-th row of the matrix + // contains the i-th row of L (excluding the diagonal entry = 1) followed by + // the i-th row of U. + private Complex[] _alu; + + // The row pointers (stored in jlu(0:n) ) and column indices to off-diagonal elements. + private int[] _jlu; + + // Pointer to the diagonal elements in MSR storage (for faster LU solving). + private int[] _diag; + + /// Use modified or standard ILU(0) + public MILU0Preconditioner(bool modified = true) + { + UseModified = modified; + } + + /// + /// Gets or sets a value indicating whether to use modified or standard ILU(0). + /// + public bool UseModified { get; set; } + + /// + /// Gets a value indicating whether the preconditioner is initialized. + /// + public bool IsInitialized { get; private set; } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square or is not an + /// instance of SparseCompressedRowMatrixStorage. + public void Initialize(Matrix matrix) + { + var csr = matrix.Storage as SparseCompressedRowMatrixStorage; + if (csr == null) + { + throw new ArgumentException(Resources.MatrixMustBeSparse, nameof(matrix)); + } + + // Dimension of matrix + int n = csr.RowCount; + if (n != csr.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + // Original matrix compressed sparse row storage. + Complex[] a = csr.Values; + int[] ja = csr.ColumnIndices; + int[] ia = csr.RowPointers; + + _alu = new Complex[ia[n] + 1]; + _jlu = new int[ia[n] + 1]; + _diag = new int[n]; + + int code = Compute(n, a, ja, ia, _alu, _jlu, _diag, UseModified); + if (code > -1) + { + throw new NumericalBreakdownException("Zero pivot encountered on row " + code + " during ILU process"); + } + + IsInitialized = true; + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector b. + /// The left hand side vector x. + public void Approximate(Vector input, Vector result) + { + if (_alu == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((result.Count != input.Count) || (result.Count != _diag.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + int n = _diag.Length; + + // Forward solve. + for (int i = 0; i < n; i++) + { + result[i] = input[i]; + for (int k = _jlu[i]; k < _diag[i]; k++) + { + result[i] = result[i] - _alu[k] * result[_jlu[k]]; + } + } + + // Backward solve. + for (int i = n - 1; i >= 0; i--) + { + for (int k = _diag[i]; k < _jlu[i + 1]; k++) + { + result[i] = result[i] - _alu[k] * result[_jlu[k]]; + } + + result[i] = _alu[i] * result[i]; + } + } + + /// + /// MILU0 is a simple milu(0) preconditioner. + /// + /// Order of the matrix. + /// Matrix values in CSR format (input). + /// Column indices (input). + /// Row pointers (input). + /// Matrix values in MSR format (output). + /// Row pointers and column indices (output). + /// Pointer to diagonal elements (output). + /// True if the modified/MILU algorithm should be used (recommended) + /// Returns 0 on success or k > 0 if a zero pivot was encountered at step k. + private int Compute(int n, Complex[] a, int[] ja, int[] ia, Complex[] alu, int[] jlu, int[] ju, bool modified) + { + var iw = new int[n]; + int i; + + // Set initial pointer value. + int p = n + 1; + jlu[0] = p; + + // Initialize work vector. + for (i = 0; i < n; i++) + { + iw[i] = -1; + } + + // The main loop. + for (i = 0; i < n; i++) + { + int pold = p; + + // Generating row i of L and U. + int j; + for (j = ia[i]; j < ia[i + 1]; j++) + { + // Copy row i of A, JA, IA into row i of ALU, JLU (LU matrix). + int jcol = ja[j]; + + if (jcol == i) + { + alu[i] = a[j]; + iw[jcol] = i; + ju[i] = p; + } + else + { + alu[p] = a[j]; + jlu[p] = ja[j]; + iw[jcol] = p; + p = p + 1; + } + } + + jlu[i + 1] = p; + + Complex s = Complex.Zero; + + int k; + for (j = pold; j < ju[i]; j++) + { + int jrow = jlu[j]; + Complex tl = alu[j] * alu[jrow]; + alu[j] = tl; + + // Perform linear combination. + for (k = ju[jrow]; k < jlu[jrow + 1]; k++) + { + int jw = iw[jlu[k]]; + if (jw != -1) + { + alu[jw] = alu[jw] - tl * alu[k]; + } + else + { + // Accumulate fill-in values. + s = s + tl * alu[k]; + } + } + } + + if (modified) + { + alu[i] = alu[i] - s; + } + + if (alu[i] == Complex.Zero) + { + return i; + } + + // Invert and store diagonal element. + alu[i] = 1.0d / alu[i]; + + // Reset pointers in work array. + iw[i] = -1; + for (k = pold; k < p; k++) + { + iw[jlu[k]] = -1; + } + } + + return -1; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/MlkBiCgStab.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/MlkBiCgStab.cs new file mode 100644 index 0000000..3275659 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/MlkBiCgStab.cs @@ -0,0 +1,539 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// A Multiple-Lanczos Bi-Conjugate Gradient stabilized iterative matrix solver. +/// +/// +/// +/// The Multiple-Lanczos Bi-Conjugate Gradient stabilized (ML(k)-BiCGStab) solver is an 'improvement' +/// of the standard BiCgStab solver. +/// +/// +/// The algorithm was taken from:
+/// ML(k)BiCGSTAB: A BiCGSTAB variant based on multiple Lanczos starting vectors +///
+/// Man-Chung Yeung and Tony F. Chan +///
+/// SIAM Journal of Scientific Computing +///
+/// Volume 21, Number 4, pp. 1263 - 1290 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class MlkBiCgStab : IIterativeSolver +{ + /// + /// The default number of starting vectors. + /// + const int DefaultNumberOfStartingVectors = 50; + + /// + /// The collection of starting vectors which are used as the basis for the Krylov sub-space. + /// + IList> _startingVectors; + + /// + /// The number of starting vectors used by the algorithm + /// + int _numberOfStartingVectors = DefaultNumberOfStartingVectors; + + /// + /// Gets or sets the number of starting vectors. + /// + /// + /// Must be larger than 1 and smaller than the number of variables in the matrix that + /// for which this solver will be used. + /// + public int NumberOfStartingVectors + { + [DebuggerStepThrough] + get { return _numberOfStartingVectors; } + + [DebuggerStepThrough] + set + { + if (value <= 1) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfStartingVectors = value; + } + } + + /// + /// Resets the number of starting vectors to the default value. + /// + public void ResetNumberOfStartingVectors() + { + _numberOfStartingVectors = DefaultNumberOfStartingVectors; + } + + /// + /// Gets or sets a series of orthonormal vectors which will be used as basis for the + /// Krylov sub-space. + /// + public IList> StartingVectors + { + [DebuggerStepThrough] + get { return _startingVectors; } + + [DebuggerStepThrough] + set + { + if ((value == null) || (value.Count == 0)) + { + _startingVectors = null; + } + else + { + _startingVectors = value; + } + } + } + + /// + /// Gets the number of starting vectors to create + /// + /// Maximum number + /// Number of variables + /// Number of starting vectors to create + static int NumberOfStartingVectorsToCreate(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + return Math.Min(maximumNumberOfStartingVectors, (numberOfVariables - 1)); + } + + /// + /// Returns an array of starting vectors. + /// + /// The maximum number of starting vectors that should be created. + /// The number of variables. + /// + /// An array with starting vectors. The array will never be larger than the + /// but it may be smaller if + /// the is smaller than + /// the . + /// + static IList> CreateStartingVectors(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + // Get random values and then orthogonalize them with + // modified Gramm - Schmidt + var count = NumberOfStartingVectorsToCreate(maximumNumberOfStartingVectors, numberOfVariables); + + // Get a random set of samples based on the standard normal distribution with + // mean = 0 and sd = 1 + var distribution = new Normal(); + + var matrix = new DenseMatrix(numberOfVariables, count); + for (var i = 0; i < matrix.ColumnCount; i++) + { + var samples = new Complex[matrix.RowCount]; + var samplesRe = distribution.Samples().Take(matrix.RowCount).ToArray(); + var samplesIm = distribution.Samples().Take(matrix.RowCount).ToArray(); + for (int j = 0; j < matrix.RowCount; j++) + { + samples[j] = new Complex(samplesRe[j], samplesIm[j]); + } + + // Set the column + matrix.SetColumn(i, samples); + } + + // Compute the orthogonalization. + var gs = matrix.GramSchmidt(); + var orthogonalMatrix = gs.Q; + + // Now transfer this to vectors + var result = new List>(orthogonalMatrix.ColumnCount); + for (var i = 0; i < orthogonalMatrix.ColumnCount; i++) + { + result.Add(orthogonalMatrix.Column(i)); + + // Normalize the result vector + result[i].Multiply(1 / result[i].L2Norm(), result[i]); + } + + return result; + } + + /// + /// Create random vectors array + /// + /// Number of vectors + /// Size of each vector + /// Array of random vectors + static Vector[] CreateVectorArray(int arraySize, int vectorSize) + { + var result = new Vector[arraySize]; + for (var i = 0; i < result.Length; i++) + { + result[i] = new DenseVector(vectorSize); + } + + return result; + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Source A. + /// Residual data. + /// x data. + /// b data. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (input.Count != matrix.RowCount || result.Count != input.Count) + { + throw Matrix.DimensionsDontMatch(matrix, input, result); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // Choose an initial guess x_0 + // Take x_0 = 0 + var xtemp = new DenseVector(input.Count); + + // Choose k vectors q_1, q_2, ..., q_k + // Build a new set if: + // a) the stored set doesn't exist (i.e. == null) + // b) Is of an incorrect length (i.e. too long) + // c) The vectors are of an incorrect length (i.e. too long or too short) + var useOld = false; + if (_startingVectors != null) + { + // We don't accept collections with zero starting vectors so ... + if (_startingVectors.Count <= NumberOfStartingVectorsToCreate(_numberOfStartingVectors, input.Count)) + { + // Only check the first vector for sizing. If that matches we assume the + // other vectors match too. If they don't the process will crash + if (_startingVectors[0].Count == input.Count) + { + useOld = true; + } + } + } + + _startingVectors = useOld ? _startingVectors : CreateStartingVectors(_numberOfStartingVectors, input.Count); + + // Store the number of starting vectors. Not really necessary but easier to type :) + var k = _startingVectors.Count; + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary values + var c = new Complex[k]; + + // Define the temporary vectors + var gtemp = new DenseVector(residuals.Count); + + var u = new DenseVector(residuals.Count); + var utemp = new DenseVector(residuals.Count); + var temp = new DenseVector(residuals.Count); + var temp1 = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + + var zd = new DenseVector(residuals.Count); + var zg = new DenseVector(residuals.Count); + var zw = new DenseVector(residuals.Count); + + var d = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // g_0 = r_0 + var g = CreateVectorArray(_startingVectors.Count, residuals.Count); + residuals.CopyTo(g[k - 1]); + + var w = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // FOR (j = 0, 1, 2 ....) + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue) + { + // SOLVE M g~_((j-1)k+k) = g_((j-1)k+k) + preconditioner.Approximate(g[k - 1], gtemp); + + // w_((j-1)k+k) = A g~_((j-1)k+k) + matrix.Multiply(gtemp, w[k - 1]); + + // c_((j-1)k+k) = q^T_1 w_((j-1)k+k) + c[k - 1] = _startingVectors[0].ConjugateDotProduct(w[k - 1]); + if (c[k - 1].Real.AlmostEqualNumbersBetween(0, 1) && c[k - 1].Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // alpha_(jk+1) = q^T_1 r_((j-1)k+k) / c_((j-1)k+k) + var alpha = _startingVectors[0].ConjugateDotProduct(residuals) / c[k - 1]; + + // u_(jk+1) = r_((j-1)k+k) - alpha_(jk+1) w_((j-1)k+k) + w[k - 1].Multiply(-alpha, temp); + residuals.Add(temp, u); + + // SOLVE M u~_(jk+1) = u_(jk+1) + preconditioner.Approximate(u, temp1); + temp1.CopyTo(utemp); + + // rho_(j+1) = -u^t_(jk+1) A u~_(jk+1) / ||A u~_(jk+1)||^2 + matrix.Multiply(temp1, temp); + var rho = temp.ConjugateDotProduct(temp); + + // If rho is zero then temp is a zero vector and we're probably + // about to have zero residuals (i.e. an exact solution). + // So set rho to 1.0 because in the next step it will turn to zero. + if (rho.Real.AlmostEqualNumbersBetween(0, 1) && rho.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + rho = 1.0; + } + + rho = -u.ConjugateDotProduct(temp) / rho; + + // r_(jk+1) = rho_(j+1) A u~_(jk+1) + u_(jk+1) + u.CopyTo(residuals); + + // Reuse temp + temp.Multiply(rho, temp); + residuals.Add(temp, temp2); + temp2.CopyTo(residuals); + + // x_(jk+1) = x_((j-1)k_k) - rho_(j+1) u~_(jk+1) + alpha_(jk+1) g~_((j-1)k+k) + utemp.Multiply(-rho, temp); + xtemp.Add(temp, temp2); + temp2.CopyTo(xtemp); + + gtemp.Multiply(alpha, gtemp); + xtemp.Add(gtemp, temp2); + temp2.CopyTo(xtemp); + + // Check convergence and stop if we are converged. + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // We're all good now. + // Exit from the while loop. + break; + } + } + + // FOR (i = 1,2, ...., k) + for (var i = 0; i < k; i++) + { + // z_d = u_(jk+1) + u.CopyTo(zd); + + // z_g = r_(jk+i) + residuals.CopyTo(zg); + + // z_w = 0 + zw.Clear(); + + // FOR (s = i, ...., k-1) AND j >= 1 + Complex beta; + if (iterationNumber >= 1) + { + for (var s = i; s < k - 1; s++) + { + // beta^(jk+i)_((j-1)k+s) = -q^t_(s+1) z_d / c_((j-1)k+s) + beta = -_startingVectors[s + 1].ConjugateDotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_((j-1)k+s) d_((j-1)k+s) + d[s].Multiply(beta, temp); + zd.Add(temp, temp2); + temp2.CopyTo(zd); + + // z_g = z_g + beta^(jk+i)_((j-1)k+s) g_((j-1)k+s) + g[s].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + + // z_w = z_w + beta^(jk+i)_((j-1)k+s) w_((j-1)k+s) + w[s].Multiply(beta, temp); + zw.Add(temp, temp2); + temp2.CopyTo(zw); + } + } + + beta = rho * c[k - 1]; + if (beta.Real.AlmostEqualNumbersBetween(0, 1) && beta.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // beta^(jk+i)_((j-1)k+k) = -(q^T_1 (r_(jk+1) + rho_(j+1) z_w)) / (rho_(j+1) c_((j-1)k+k)) + zw.Multiply(rho, temp2); + residuals.Add(temp2, temp); + beta = -_startingVectors[0].ConjugateDotProduct(temp) / beta; + + // z_g = z_g + beta^(jk+i)_((j-1)k+k) g_((j-1)k+k) + g[k - 1].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + + // z_w = rho_(j+1) (z_w + beta^(jk+i)_((j-1)k+k) w_((j-1)k+k)) + w[k - 1].Multiply(beta, temp); + zw.Add(temp, temp2); + temp2.CopyTo(zw); + zw.Multiply(rho, zw); + + // z_d = r_(jk+i) + z_w + residuals.Add(zw, zd); + + // FOR (s = 1, ... i - 1) + for (var s = 0; s < i - 1; s++) + { + // beta^(jk+i)_(jk+s) = -q^T_s+1 z_d / c_(jk+s) + beta = -_startingVectors[s + 1].ConjugateDotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_(jk+s) * d_(jk+s) + d[s].Multiply(beta, temp); + zd.Add(temp, temp2); + temp2.CopyTo(zd); + + // z_g = z_g + beta^(jk+i)_(jk+s) * g_(jk+s) + g[s].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + } + + // d_(jk+i) = z_d - u_(jk+i) + zd.Subtract(u, d[i]); + + // g_(jk+i) = z_g + z_w + zg.Add(zw, g[i]); + + // IF (i < k - 1) + if (i < k - 1) + { + // c_(jk+1) = q^T_i+1 d_(jk+i) + c[i] = _startingVectors[i + 1].ConjugateDotProduct(d[i]); + if (c[i].Real.AlmostEqualNumbersBetween(0, 1) && c[i].Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // alpha_(jk+i+1) = q^T_(i+1) u_(jk+i) / c_(jk+i) + alpha = _startingVectors[i + 1].ConjugateDotProduct(u) / c[i]; + + // u_(jk+i+1) = u_(jk+i) - alpha_(jk+i+1) d_(jk+i) + d[i].Multiply(-alpha, temp); + u.Add(temp, temp2); + temp2.CopyTo(u); + + // SOLVE M g~_(jk+i) = g_(jk+i) + preconditioner.Approximate(g[i], gtemp); + + // x_(jk+i+1) = x_(jk+i) + rho_(j+1) alpha_(jk+i+1) g~_(jk+i) + gtemp.Multiply(rho * alpha, temp); + xtemp.Add(temp, temp2); + temp2.CopyTo(xtemp); + + // w_(jk+i) = A g~_(jk+i) + matrix.Multiply(gtemp, w[i]); + + // r_(jk+i+1) = r_(jk+i) - rho_(j+1) alpha_(jk+i+1) w_(jk+i) + w[i].Multiply(-rho * alpha, temp); + residuals.Add(temp, temp2); + temp2.CopyTo(residuals); + + // We can check the residuals here if they're close + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, xtemp, input); + } + } + } // END ITERATION OVER i + + iterationNumber++; + } + + // copy the temporary result to the real result vector + xtemp.CopyTo(result); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Solvers/TFQMR.cs b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/TFQMR.cs new file mode 100644 index 0000000..d3eee66 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Solvers/TFQMR.cs @@ -0,0 +1,274 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers; + +using Complex = System.Numerics.Complex; + +/// +/// A Transpose Free Quasi-Minimal Residual (TFQMR) iterative matrix solver. +/// +/// +/// +/// The TFQMR algorithm was taken from:
+/// Iterative methods for sparse linear systems. +///
+/// Yousef Saad +///
+/// Algorithm is described in Chapter 7, section 7.4.3, page 219 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class TFQMR : IIterativeSolver +{ + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Is even? + /// + /// Number to check + /// true if even, otherwise false + static bool IsEven(int number) + { + return number % 2 == 0; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (input.Count != matrix.RowCount || result.Count != input.Count) + { + throw Matrix.DimensionsDontMatch(matrix, input, result); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + var d = new DenseVector(input.Count); + var r = DenseVector.OfVector(input); + + var uodd = new DenseVector(input.Count); + var ueven = new DenseVector(input.Count); + + var v = new DenseVector(input.Count); + var pseudoResiduals = DenseVector.OfVector(input); + + var x = new DenseVector(input.Count); + var yodd = new DenseVector(input.Count); + var yeven = DenseVector.OfVector(input); + + // Temp vectors + var temp = new DenseVector(input.Count); + var temp1 = new DenseVector(input.Count); + var temp2 = new DenseVector(input.Count); + + // Define the scalars + Complex alpha = 0; + Complex eta = 0; + double theta = 0; + + // Initialize + var tau = input.L2Norm(); + Complex rho = tau * tau; + + // Calculate the initial values for v + // M temp = yEven + preconditioner.Approximate(yeven, temp); + + // v = A temp + matrix.Multiply(temp, v); + + // Set uOdd + v.CopyTo(ueven); + + // Start the iteration + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, result, input, pseudoResiduals) == IterationStatus.Continue) + { + // First part of the step, the even bit + if (IsEven(iterationNumber)) + { + // sigma = (v, r) + var sigma = r.ConjugateDotProduct(v); + if (sigma.Real.AlmostEqualNumbersBetween(0, 1) && sigma.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + // FAIL HERE + iterator.Cancel(); + break; + } + + // alpha = rho / sigma + alpha = rho / sigma; + + // yOdd = yEven - alpha * v + v.Multiply(-alpha, temp1); + yeven.Add(temp1, yodd); + + // Solve M temp = yOdd + preconditioner.Approximate(yodd, temp); + + // uOdd = A temp + matrix.Multiply(temp, uodd); + } + + // The intermediate step which is equal for both even and + // odd iteration steps. + // Select the correct vector + var uinternal = IsEven(iterationNumber) ? ueven : uodd; + var yinternal = IsEven(iterationNumber) ? yeven : yodd; + + // pseudoResiduals = pseudoResiduals - alpha * uOdd + uinternal.Multiply(-alpha, temp1); + pseudoResiduals.Add(temp1, temp2); + temp2.CopyTo(pseudoResiduals); + + // d = yOdd + theta * theta * eta / alpha * d + d.Multiply(theta * theta * eta / alpha, temp); + yinternal.Add(temp, d); + + // theta = ||pseudoResiduals||_2 / tau + theta = pseudoResiduals.L2Norm() / tau; + var c = 1 / Math.Sqrt(1 + (theta * theta)); + + // tau = tau * theta * c + tau *= theta * c; + + // eta = c^2 * alpha + eta = c * c * alpha; + + // x = x + eta * d + d.Multiply(eta, temp1); + x.Add(temp1, temp2); + temp2.CopyTo(x); + + // Check convergence and see if we can bail + if (iterator.DetermineStatus(iterationNumber, result, input, pseudoResiduals) != IterationStatus.Continue) + { + // Calculate the real values + preconditioner.Approximate(x, result); + + // Calculate the true residual. Use the temp vector for that + // so that we don't pollute the pseudoResidual vector for no + // good reason. + CalculateTrueResidual(matrix, temp, result, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, result, input, temp) != IterationStatus.Continue) + { + // We're all good now. + return; + } + } + + // The odd step + if (!IsEven(iterationNumber)) + { + if (rho.Real.AlmostEqualNumbersBetween(0, 1) && rho.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + // FAIL HERE + iterator.Cancel(); + break; + } + + var rhoNew = r.ConjugateDotProduct(pseudoResiduals); + var beta = rhoNew / rho; + + // Update rho for the next loop + rho = rhoNew; + + // yOdd = pseudoResiduals + beta * yOdd + yodd.Multiply(beta, temp1); + pseudoResiduals.Add(temp1, yeven); + + // Solve M temp = yOdd + preconditioner.Approximate(yeven, temp); + + // uOdd = A temp + matrix.Multiply(temp, ueven); + + // v = uEven + beta * (uOdd + beta * v) + v.Multiply(beta, temp1); + uodd.Add(temp1, temp); + + temp.Multiply(beta, temp1); + ueven.Add(temp1, v); + } + + // Calculate the real values + preconditioner.Approximate(x, result); + + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/SparseMatrix.cs b/MathNet.Numerics/LinearAlgebra/Complex/SparseMatrix.cs new file mode 100644 index 0000000..24fea48 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/SparseMatrix.cs @@ -0,0 +1,1562 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex; + +using Complex = System.Numerics.Complex; + +/// +/// A Matrix with sparse storage, intended for very large matrices where most of the cells are zero. +/// The underlying storage scheme is 3-array compressed-sparse-row (CSR) Format. +/// Wikipedia - CSR. +/// +[Serializable] +[DebuggerDisplay("SparseMatrix {RowCount}x{ColumnCount}-Complex {NonZerosCount}-NonZero")] +public class SparseMatrix : Matrix +{ + readonly SparseCompressedRowMatrixStorage _storage; + + /// + /// Gets the number of non zero elements in the matrix. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get { return _storage.ValueCount; } + } + + /// + /// Create a new sparse matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public SparseMatrix(SparseCompressedRowMatrixStorage storage) + : base(storage) + { + _storage = storage; + } + + /// + /// Create a new square sparse matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public SparseMatrix(int order) + : this(order, order) + { + } + + /// + /// Create a new sparse matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public SparseMatrix(int rows, int columns) + : this(new SparseCompressedRowMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new sparse matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfMatrix(Matrix matrix) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfArray(Complex[,] array) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfArray(array)); + } + + /// + /// Create a new sparse matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in row-major order (row by row). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + /// + public static SparseMatrix OfRowMajor(int rows, int columns, IEnumerable rowMajor) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowMajorEnumerable(rows, columns, rowMajor)); + } + + /// + /// Create a new sparse matrix with the given number of rows and columns as a copy of the given array. + /// The array is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + /// + public static SparseMatrix OfColumnMajor(int rows, int columns, IList columnMajor) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnMajorList(rows, columns, columnMajor)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumns(IEnumerable> data) + { + return OfColumnArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumns(int rows, int columns, IEnumerable> data) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnArrays(params Complex[][] columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnArrays(IEnumerable columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnArrays((columns as Complex[][]) ?? columns.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnVectors(IEnumerable> columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRows(IEnumerable> data) + { + return OfRowArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRows(int rows, int columns, IEnumerable> data) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowArrays(params Complex[][] rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowArrays(IEnumerable rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowArrays((rows as Complex[][]) ?? rows.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowVectors(IEnumerable> rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalVector(Vector diagonal) + { + var m = new SparseMatrix(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = new SparseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalArray(Complex[] diagonal) + { + var m = new SparseMatrix(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalArray(int rows, int columns, Complex[] diagonal) + { + var m = new SparseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix and initialize each value to the same provided value. + /// + public static SparseMatrix Create(int rows, int columns, Complex value) + { + if (value == Complex.Zero) + return new SparseMatrix(rows, columns); + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new sparse matrix and initialize each value using the provided init function. + /// + public static SparseMatrix Create(int rows, int columns, Func init) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value to the same provided value. + /// + public static SparseMatrix CreateDiagonal(int rows, int columns, Complex value) + { + if (value == Complex.Zero) + return new SparseMatrix(rows, columns); + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value using the provided init function. + /// + public static SparseMatrix CreateDiagonal(int rows, int columns, Func init) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static SparseMatrix CreateIdentity(int order) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + var result = Build.SameAs(this); + LowerTriangleImpl(result); + return result; + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + LowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + LowerTriangleImpl(result); + } + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void LowerTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row >= columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + var result = Build.SameAs(this); + UpperTriangleImpl(result); + return result; + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + UpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + UpperTriangleImpl(result); + } + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void UpperTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row <= columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + var result = Build.SameAs(this); + StrictlyLowerTriangleImpl(result); + return result; + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + StrictlyLowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyLowerTriangleImpl(result); + } + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyLowerTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row > columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + var result = Build.SameAs(this); + StrictlyUpperTriangleImpl(result); + return result; + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + StrictlyUpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyUpperTriangleImpl(result); + } + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyUpperTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row < columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + CopyTo(result); + DoMultiply(-1, result); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + var rowPointers = _storage.RowPointers; + var values = _storage.Values; + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + var startIndex = rowPointers[i]; + var endIndex = rowPointers[i + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var s = 0d; + for (var j = startIndex; j < endIndex; j++) + { + s += values[j].Magnitude; + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + var aat = (SparseCompressedRowMatrixStorage)(this * ConjugateTranspose()).Storage; + var norm = 0d; + for (var i = 0; i < aat.RowCount; i++) + { + var startIndex = aat.RowPointers[i]; + var endIndex = aat.RowPointers[i + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var j = startIndex; j < endIndex; j++) + { + if (i == aat.ColumnIndices[j]) + { + norm += aat.Values[j].Magnitude; + } + } + } + + return Math.Sqrt(norm); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther == null || sparseResult == null) + { + base.DoAdd(other, result); + return; + } + + if (ReferenceEquals(this, other)) + { + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + LinearAlgebraControl.Provider.ScaleArray(2.0, sparseResult._storage.Values, sparseResult._storage.Values); + return; + } + + SparseMatrix left; + + if (ReferenceEquals(sparseOther, sparseResult)) + { + left = this; + } + else if (ReferenceEquals(this, sparseResult)) + { + left = sparseOther; + } + else + { + CopyTo(sparseResult); + left = sparseOther; + } + + var leftStorage = left._storage; + for (var i = 0; i < leftStorage.RowCount; i++) + { + var endIndex = leftStorage.RowPointers[i + 1]; + for (var j = leftStorage.RowPointers[i]; j < endIndex; j++) + { + var columnIndex = leftStorage.ColumnIndices[j]; + var resVal = leftStorage.Values[j] + result.At(i, columnIndex); + result.At(i, columnIndex, resVal); + } + } + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract to this matrix. + /// The matrix to store the result of subtraction. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + + if (sparseOther == null || sparseResult == null) + { + base.DoSubtract(other, result); + return; + } + + if (ReferenceEquals(this, other)) + { + result.Clear(); + return; + } + + var otherStorage = sparseOther._storage; + + if (ReferenceEquals(this, sparseResult)) + { + for (var i = 0; i < otherStorage.RowCount; i++) + { + var endIndex = otherStorage.RowPointers[i + 1]; + for (var j = otherStorage.RowPointers[i]; j < endIndex; j++) + { + var columnIndex = otherStorage.ColumnIndices[j]; + var resVal = sparseResult.At(i, columnIndex) - otherStorage.Values[j]; + result.At(i, columnIndex, resVal); + } + } + } + else + { + if (!ReferenceEquals(sparseOther, sparseResult)) + { + sparseOther.CopyTo(sparseResult); + } + + sparseResult.Negate(sparseResult); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + var columnIndex = columnIndices[j]; + var resVal = sparseResult.At(i, columnIndex) + values[j]; + result.At(i, columnIndex, resVal); + } + } + } + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(Complex scalar, Matrix result) + { + if (scalar == 1.0) + { + CopyTo(result); + return; + } + + if (scalar == 0.0 || NonZerosCount == 0) + { + result.Clear(); + return; + } + + var sparseResult = result as SparseMatrix; + if (sparseResult == null) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + result.At(row, column, values[index] * scalar); + } + } + } + else + { + if (!ReferenceEquals(this, result)) + { + CopyTo(sparseResult); + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, sparseResult._storage.Values, sparseResult._storage.Values); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther != null && sparseResult != null) + { + DoMultiplySparse(sparseOther, sparseResult); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null && sparseResult != null) + { + var diagonal = diagonalOther.Data; + if (other.ColumnCount == other.RowCount) + { + Storage.MapIndexedTo(result.Storage, (i, j, x) => x * diagonal[j], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Storage.Clear(); + Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * diagonal[j], 0, 0, RowCount, 0, 0, ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + + return; + } + + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + // in this case we can directly address the underlying data-array + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var column = 0; column < other.ColumnCount; column++) + { + int otherColumnStartPosition = column * other.RowCount; + var sum = Complex.Zero; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * denseOther.Data[otherColumnStartPosition + columnIndices[index]]; + } + + result.At(row, column, sum); + } + } + + return; + } + + var columnVector = new DenseVector(other.RowCount); + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var column = 0; column < other.ColumnCount; column++) + { + // Multiply row of matrix A on column of matrix B + other.Column(column, columnVector); + + var sum = Complex.Zero; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * columnVector[columnIndices[index]]; + } + + result.At(row, column, sum); + } + } + } + + void DoMultiplySparse(SparseMatrix other, SparseMatrix result) + { + result.Clear(); + + var ax = _storage.Values; + var ap = _storage.RowPointers; + var ai = _storage.ColumnIndices; + + var bx = other._storage.Values; + var bp = other._storage.RowPointers; + var bi = other._storage.ColumnIndices; + + int rows = RowCount; + int cols = other.ColumnCount; + + int[] cp = result._storage.RowPointers; + + var marker = new int[cols]; + for (int ib = 0; ib < cols; ib++) + { + marker[ib] = -1; + } + + int count = 0; + for (int i = 0; i < rows; i++) + { + // For each row of A + for (int j = ap[i]; j < ap[i + 1]; j++) + { + // Row number to be added + int a = ai[j]; + for (int k = bp[a]; k < bp[a + 1]; k++) + { + int b = bi[k]; + if (marker[b] != i) + { + marker[b] = i; + count++; + } + } + } + + // Record non-zero count. + cp[i + 1] = count; + } + + var ci = new int[count]; + var cx = new Complex[count]; + + for (int ib = 0; ib < cols; ib++) + { + marker[ib] = -1; + } + + count = 0; + for (int i = 0; i < rows; i++) + { + int rowStart = cp[i]; + for (int j = ap[i]; j < ap[i + 1]; j++) + { + int a = ai[j]; + Complex aEntry = ax[j]; + for (int k = bp[a]; k < bp[a + 1]; k++) + { + int b = bi[k]; + Complex bEntry = bx[k]; + if (marker[b] < rowStart) + { + marker[b] = count; + ci[marker[b]] = b; + cx[marker[b]] = aEntry * bEntry; + count++; + } + else + { + cx[marker[b]] += aEntry * bEntry; + } + } + } + } + + result._storage.Values = cx; + result._storage.ColumnIndices = ci; + result._storage.Normalize(); + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var sum = Complex.Zero; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * rightSide[columnIndices[index]]; + } + + result[row] = sum; + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var otherSparse = other as SparseMatrix; + var resultSparse = result as SparseMatrix; + + if (otherSparse == null || resultSparse == null) + { + base.DoTransposeAndMultiply(other, result); + return; + } + + resultSparse.Clear(); + + var rowPointers = _storage.RowPointers; + var values = _storage.Values; + + var otherStorage = otherSparse._storage; + + for (var j = 0; j < RowCount; j++) + { + var startIndexOther = otherStorage.RowPointers[j]; + var endIndexOther = otherStorage.RowPointers[j + 1]; + + if (startIndexOther == endIndexOther) + { + continue; + } + + for (var i = 0; i < RowCount; i++) + { + // Multiply row of matrix A on row of matrix B + + var startIndexThis = rowPointers[i]; + var endIndexThis = rowPointers[i + 1]; + + if (startIndexThis == endIndexThis) + { + continue; + } + + var sum = Complex.Zero; + for (var index = startIndexOther; index < endIndexOther; index++) + { + var ind = _storage.FindItem(i, otherStorage.ColumnIndices[index]); + if (ind >= 0) + { + sum += otherStorage.Values[index] * values[ind]; + } + } + + resultSparse._storage.At(i, j, sum + result.At(i, j)); + } + } + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var rightSideValue = rightSide[row]; + for (var index = startIndex; index < endIndex; index++) + { + result[columnIndices[index]] += values[index] * rightSideValue; + } + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + var resVal = values[j] * other.At(i, columnIndices[j]); + if (!resVal.IsZero()) + { + result.At(i, columnIndices[j], resVal); + } + } + } + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + if (!values[j].IsZero()) + { + result.At(i, columnIndices[j], values[j] / divisor.At(i, columnIndices[j])); + } + } + } + } + + public override void KroneckerProduct(Matrix other, Matrix result) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != (RowCount * other.RowCount) || result.ColumnCount != (ColumnCount * other.ColumnCount)) + { + throw DimensionsDontMatch(this, other, result); + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + if (!values[j].IsZero()) + { + result.SetSubMatrix(i * other.RowCount, other.RowCount, columnIndices[j] * other.ColumnCount, other.ColumnCount, values[j] * other); + } + } + } + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public override bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + if (!values[index].Equals(At(column, row))) + { + return false; + } + } + } + + return true; + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public override bool IsHermitian() + { + if (RowCount != ColumnCount) + { + return false; + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + if (!values[index].Equals(At(column, row).Conjugate())) + { + return false; + } + } + } + + return true; + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static SparseMatrix operator +(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.RowCount != rightSide.RowCount || leftSide.ColumnCount != rightSide.ColumnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Add(rightSide); + } + + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static SparseMatrix operator +(SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Clone(); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static SparseMatrix operator -(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.RowCount != rightSide.RowCount || leftSide.ColumnCount != rightSide.ColumnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Subtract(rightSide); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static SparseMatrix operator -(SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Negate(); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator *(SparseMatrix leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator *(Complex leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static SparseMatrix operator *(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide.ColumnCount != rightSide.RowCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static SparseVector operator *(SparseMatrix leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static SparseVector operator *(SparseVector leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.LeftMultiply(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator %(SparseMatrix leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseMatrix)leftSide.Remainder(rightSide); + } + + public override string ToTypeString() + { + return string.Format("SparseMatrix {0}x{1}-Complex {2:P2} Filled", RowCount, ColumnCount, NonZerosCount / (RowCount * (double)ColumnCount)); + } +} + diff --git a/MathNet.Numerics/LinearAlgebra/Complex/SparseVector.cs b/MathNet.Numerics/LinearAlgebra/Complex/SparseVector.cs new file mode 100644 index 0000000..52b03f5 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/SparseVector.cs @@ -0,0 +1,958 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace MathNet.Numerics.LinearAlgebra.Complex; + +using Complex = System.Numerics.Complex; + +/// +/// A vector with sparse storage, intended for very large vectors where most of the cells are zero. +/// +/// The sparse vector is not thread safe. +[Serializable] +[DebuggerDisplay("SparseVector {Count}-Complex {NonZerosCount}-NonZero")] +public class SparseVector : Vector +{ + readonly SparseVectorStorage _storage; + + /// + /// Gets the number of non zero elements in the vector. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get { return _storage.ValueCount; } + } + + /// + /// Create a new sparse vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public SparseVector(SparseVectorStorage storage) + : base(storage) + { + _storage = storage; + } + + /// + /// Create a new sparse vector with the given length. + /// All cells of the vector will be initialized to zero. + /// Zero-length vectors are not supported. + /// + /// If length is less than one. + public SparseVector(int length) + : this(new SparseVectorStorage(length)) + { + } + + /// + /// Create a new sparse vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfVector(Vector vector) + { + return new SparseVector(SparseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new sparse vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfEnumerable(IEnumerable enumerable) + { + return new SparseVector(SparseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new sparse vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfIndexedEnumerable(int length, IEnumerable> enumerable) + { + return new SparseVector(SparseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided value. + /// + public static SparseVector Create(int length, Complex value) + { + return new SparseVector(SparseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided init function. + /// + public static SparseVector Create(int length, Func init) + { + return new SparseVector(SparseVectorStorage.OfInit(length, init)); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// Warning, the new 'sparse vector' with a non-zero scalar added to it will be a 100% filled + /// sparse vector and very inefficient. Would be better to work with a dense vector instead. + /// + /// + /// The scalar to add. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Complex scalar, Vector result) + { + if (scalar == Complex.Zero) + { + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + return; + } + + if (ReferenceEquals(this, result)) + { + //populate a new vector with the scalar + var vnonZeroValues = new Complex[Count]; + var vnonZeroIndices = new int[Count]; + for (int index = 0; index < Count; index++) + { + vnonZeroIndices[index] = index; + vnonZeroValues[index] = scalar; + } + + //populate the non zero values from this + var indices = _storage.Indices; + var values = _storage.Values; + for (int j = 0; j < _storage.ValueCount; j++) + { + vnonZeroValues[indices[j]] = values[j] + scalar; + } + + //assign this vectors array to the new arrays. + _storage.Values = vnonZeroValues; + _storage.Indices = vnonZeroIndices; + _storage.ValueCount = Count; + } + else + { + for (var index = 0; index < Count; index++) + { + result.At(index, At(index) + scalar); + } + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to add to this one. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Vector other, Vector result) + { + var otherSparse = other as SparseVector; + if (otherSparse == null) + { + base.DoAdd(other, result); + return; + } + + var resultSparse = result as SparseVector; + if (resultSparse == null) + { + base.DoAdd(other, result); + return; + } + + // TODO (ruegg, 2011-10-11): Options to optimize? + + var otherStorage = otherSparse._storage; + if (ReferenceEquals(this, resultSparse)) + { + int i = 0, j = 0; + while (j < otherStorage.ValueCount) + { + if (i >= _storage.ValueCount || _storage.Indices[i] > otherStorage.Indices[j]) + { + var otherValue = otherStorage.Values[j]; + if (!Complex.Zero.Equals(otherValue)) + { + _storage.InsertAtIndexUnchecked(i++, otherStorage.Indices[j], otherValue); + } + + j++; + } + else if (_storage.Indices[i] == otherStorage.Indices[j]) + { + // TODO: result can be zero, remove? + _storage.Values[i++] += otherStorage.Values[j++]; + } + else + { + i++; + } + } + } + else + { + result.Clear(); + int i = 0, j = 0, last = -1; + while (i < _storage.ValueCount || j < otherStorage.ValueCount) + { + if (j >= otherStorage.ValueCount || i < _storage.ValueCount && _storage.Indices[i] <= otherStorage.Indices[j]) + { + var next = _storage.Indices[i]; + if (next != last) + { + last = next; + result.At(next, _storage.Values[i] + otherSparse.At(next)); + } + + i++; + } + else + { + var next = otherStorage.Indices[j]; + if (next != last) + { + last = next; + result.At(next, At(next) + otherStorage.Values[j]); + } + + j++; + } + } + } + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to subtract. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Complex scalar, Vector result) + { + DoAdd(-scalar, result); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to subtract from this one. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Vector other, Vector result) + { + if (ReferenceEquals(this, other)) + { + result.Clear(); + return; + } + + var otherSparse = other as SparseVector; + if (otherSparse == null) + { + base.DoSubtract(other, result); + return; + } + + var resultSparse = result as SparseVector; + if (resultSparse == null) + { + base.DoSubtract(other, result); + return; + } + + // TODO (ruegg, 2011-10-11): Options to optimize? + + var otherStorage = otherSparse._storage; + if (ReferenceEquals(this, resultSparse)) + { + int i = 0, j = 0; + while (j < otherStorage.ValueCount) + { + if (i >= _storage.ValueCount || _storage.Indices[i] > otherStorage.Indices[j]) + { + var otherValue = otherStorage.Values[j]; + if (!Complex.Zero.Equals(otherValue)) + { + _storage.InsertAtIndexUnchecked(i++, otherStorage.Indices[j], -otherValue); + } + + j++; + } + else if (_storage.Indices[i] == otherStorage.Indices[j]) + { + // TODO: result can be zero, remove? + _storage.Values[i++] -= otherStorage.Values[j++]; + } + else + { + i++; + } + } + } + else + { + result.Clear(); + int i = 0, j = 0, last = -1; + while (i < _storage.ValueCount || j < otherStorage.ValueCount) + { + if (j >= otherStorage.ValueCount || i < _storage.ValueCount && _storage.Indices[i] <= otherStorage.Indices[j]) + { + var next = _storage.Indices[i]; + if (next != last) + { + last = next; + result.At(next, _storage.Values[i] - otherSparse.At(next)); + } + + i++; + } + else + { + var next = otherStorage.Indices[j]; + if (next != last) + { + last = next; + result.At(next, At(next) - otherStorage.Values[j]); + } + + j++; + } + } + } + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult == null) + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], -_storage.Values[index]); + } + + return; + } + + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new Complex[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ScaleArray(-Complex.One, sparseResult._storage.Values, sparseResult._storage.Values); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + protected override void DoConjugate(Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult != null) + { + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new Complex[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ConjugateArray(sparseResult._storage.Values, sparseResult._storage.Values); + return; + } + + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], _storage.Values[index].Conjugate()); + } + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to multiply. + /// + /// + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(Complex scalar, Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult == null) + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], scalar * _storage.Values[index]); + } + } + else + { + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new Complex[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, sparseResult._storage.Values, sparseResult._storage.Values); + } + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override Complex DoDotProduct(Vector other) + { + var result = Complex.Zero; + if (ReferenceEquals(this, other)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i] * _storage.Values[i]; + } + } + else + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i] * other.At(_storage.Indices[i]); + } + } + + return result; + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected override Complex DoConjugateDotProduct(Vector other) + { + var result = Complex.Zero; + if (ReferenceEquals(this, other)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i].Conjugate() * _storage.Values[i]; + } + } + else + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i].Conjugate() * other.At(_storage.Indices[i]); + } + } + + return result; + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static SparseVector operator +(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Add(rightSide); + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static SparseVector operator -(SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static SparseVector operator -(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Subtract(rightSide); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The vector to scale. + /// The complex value. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(SparseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The complex value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(Complex leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static Complex operator *(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a complex. + /// + /// The vector to divide. + /// The complex value. + /// The result of the division. + /// If is . + public static SparseVector operator /(SparseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Divide(rightSide); + } + + /// + /// Computes the modulus of each element of the vector of the given divisor. + /// + /// The vector whose elements we want to compute the modulus of. + /// The divisor to use, + /// The result of the calculation + /// If is . + public static SparseVector operator %(SparseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Modulus(rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + if (_storage.ValueCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var min = _storage.Values[index].Magnitude; + for (var i = 1; i < _storage.ValueCount; i++) + { + var test = _storage.Values[i].Magnitude; + if (test < min) + { + index = i; + min = test; + } + } + + return _storage.Indices[index]; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + if (_storage.ValueCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var max = _storage.Values[index].Magnitude; + for (var i = 1; i < _storage.ValueCount; i++) + { + var test = _storage.Values[i].Magnitude; + if (test > max) + { + index = i; + max = test; + } + } + + return _storage.Indices[index]; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override Complex Sum() + { + var result = Complex.Zero; + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i]; + } + + return result; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double result = 0d; + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i].Magnitude; + } + + return result; + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(0, _storage.ValueCount, i => _storage.Values[i].Magnitude, Math.Max, 0d); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = ( ∑|this[i]|^p )^(1/p) + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (_storage.ValueCount == 0) + { + return 0d; + } + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var index = 0; index < _storage.ValueCount; index++) + { + sum += Math.Pow(_storage.Values[index].Magnitude, p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + if (ReferenceEquals(this, other) && ReferenceEquals(this, result)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + _storage.Values[i] *= _storage.Values[i]; + } + } + else + { + base.DoPointwiseMultiply(other, result); + } + } + + #region Parse Functions + + /// + /// Creates a double sparse vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n;n;..', '(n;n;..)', '[n;n;...]', where n is a Complex. + /// + /// + /// A double sparse vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static SparseVector Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // parsing + var strongTokens = value.Split(new[] { formatProvider.GetTextInfo().ListSeparator }, StringSplitOptions.RemoveEmptyEntries); + var data = new List(); + foreach (string strongToken in strongTokens) + { + var weakTokens = strongToken.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); + string current = string.Empty; + for (int i = 0; i < weakTokens.Length; i++) + { + current += weakTokens[i]; + if (current.EndsWith("+") || current.EndsWith("-") || current.StartsWith("(") && !current.EndsWith(")")) + { + continue; + } + + var ahead = i < weakTokens.Length - 1 ? weakTokens[i + 1] : string.Empty; + if (ahead.StartsWith("+") || ahead.StartsWith("-")) + { + continue; + } + + data.Add(current.ToComplex(formatProvider)); + current = string.Empty; + } + + if (current != string.Empty) + { + throw new FormatException(); + } + } + + if (data.Count == 0) + { + throw new FormatException(); + } + + return OfEnumerable(data); + } + + /// + /// Converts the string representation of a complex sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out SparseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a complex sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out SparseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + #endregion + + public override string ToTypeString() + { + return string.Format("SparseVector {0}-Complex {1:P2} Filled", Count, NonZerosCount / (double)Count); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex/Vector.cs b/MathNet.Numerics/LinearAlgebra/Complex/Vector.cs new file mode 100644 index 0000000..d54b889 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex/Vector.cs @@ -0,0 +1,627 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex; + +using Complex = System.Numerics.Complex; + +/// +/// Complex version of the class. +/// +[Serializable] +public abstract class Vector : Vector +{ + /// + /// Initializes a new instance of the Vector class. + /// + protected Vector(VectorStorage storage) + : base(storage) + { + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero. + /// + public override void CoerceZero(double threshold) + { + MapInplace(x => x.Magnitude < threshold ? Complex.Zero : x, Zeros.AllowSkip); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + protected override void DoConjugate(Vector result) + { + Map(Complex.Conjugate, result, Zeros.AllowSkip); + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + Map(Complex.Negate, result, Zeros.AllowSkip); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to add. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Complex scalar, Vector result) + { + Map(x => x + scalar, result, Zeros.Include); + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to add to this one. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Vector other, Vector result) + { + Map2(Complex.Add, other, result, Zeros.AllowSkip); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to subtract. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Complex scalar, Vector result) + { + Map(x => x - scalar, result, Zeros.Include); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to subtract from this one. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Vector other, Vector result) + { + Map2(Complex.Subtract, other, result, Zeros.AllowSkip); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to multiply. + /// + /// + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(Complex scalar, Vector result) + { + Map(x => x * scalar, result, Zeros.AllowSkip); + } + + /// + /// Divides each element of the vector by a scalar and stores the result in the result vector. + /// + /// + /// The scalar to divide with. + /// + /// + /// The vector to store the result of the division. + /// + protected override void DoDivide(Complex divisor, Vector result) + { + Map(x => x / divisor, result, divisor.IsZero() ? Zeros.Include : Zeros.AllowSkip); + } + + /// + /// Divides a scalar by each element of the vector and stores the result in the result vector. + /// + /// The scalar to divide. + /// The vector to store the result of the division. + protected override void DoDivideByThis(Complex dividend, Vector result) + { + Map(x => dividend / x, result, Zeros.Include); + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + Map2(Complex.Multiply, other, result, Zeros.AllowSkip); + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + protected override void DoPointwiseDivide(Vector divisor, Vector result) + { + Map2(Complex.Divide, divisor, result, Zeros.Include); + } + + /// + /// Pointwise raise this vector to an exponent and store the result into the result vector. + /// + /// The exponent to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Complex exponent, Vector result) + { + Map(x => x.Power(exponent), result, Zeros.Include); + } + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Vector exponent, Vector result) + { + Map2(Complex.Pow, exponent, result, Zeros.Include); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected sealed override void DoPointwiseModulus(Vector divisor, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected sealed override void DoPointwiseRemainder(Vector divisor, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected override void DoPointwiseExp(Vector result) + { + Map(Complex.Exp, result, Zeros.Include); + } + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected override void DoPointwiseLog(Vector result) + { + Map(Complex.Log, result, Zeros.Include); + } + + protected override void DoPointwiseAbs(Vector result) + { + Map(x => (Complex)Complex.Abs(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAcos(Vector result) + { + Map(Complex.Acos, result, Zeros.Include); + } + protected override void DoPointwiseAsin(Vector result) + { + Map(Complex.Asin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan(Vector result) + { + Map(Complex.Atan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan2(Vector other, Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseAtan2(Complex scalar, Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseCeiling(Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseCos(Vector result) + { + Map(Complex.Cos, result, Zeros.Include); + } + protected override void DoPointwiseCosh(Vector result) + { + Map(Complex.Cosh, result, Zeros.Include); + } + protected override void DoPointwiseFloor(Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseLog10(Vector result) + { + Map(Complex.Log10, result, Zeros.Include); + } + protected override void DoPointwiseRound(Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseSign(Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseSin(Vector result) + { + Map(Complex.Sin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSinh(Vector result) + { + Map(Complex.Sinh, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSqrt(Vector result) + { + Map(Complex.Sqrt, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTan(Vector result) + { + Map(Complex.Tan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTanh(Vector result) + { + Map(Complex.Tanh, result, Zeros.AllowSkip); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override Complex DoDotProduct(Vector other) + { + var dot = Complex.Zero; + for (var i = 0; i < Count; i++) + { + dot += At(i) * other.At(i); + } + + return dot; + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected override Complex DoConjugateDotProduct(Vector other) + { + var dot = Complex.Zero; + for (var i = 0; i < Count; i++) + { + dot += At(i).Conjugate() * other.At(i); + } + + return dot; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected sealed override void DoModulus(Complex divisor, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected sealed override void DoModulusByThis(Complex dividend, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected sealed override void DoRemainder(Complex divisor, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected sealed override void DoRemainderByThis(Complex dividend, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMinimum(Complex scalar, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMaximum(Complex scalar, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseAbsoluteMinimum(Complex scalar, Vector result) + { + double absolute = scalar.Magnitude; + Map(x => Math.Min(absolute, x.Magnitude), result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Complex scalar, Vector result) + { + double absolute = scalar.Magnitude; + Map(x => Math.Max(absolute, x.Magnitude), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(Vector other, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMaximum(Vector other, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseAbsoluteMinimum(Vector other, Vector result) + { + Map2((x, y) => Math.Min(x.Magnitude, y.Magnitude), other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Vector other, Vector result) + { + Map2((x, y) => Math.Max(x.Magnitude, y.Magnitude), other, result, Zeros.AllowSkip); + } + + /// + /// Returns the value of the absolute minimum element. + /// + /// The value of the absolute minimum element. + public sealed override Complex AbsoluteMinimum() + { + return At(AbsoluteMinimumIndex()).Magnitude; + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = At(index).Magnitude; + for (var i = 1; i < Count; i++) + { + var test = At(i).Magnitude; + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the value of the absolute maximum element. + /// + /// The value of the absolute maximum element. + public override Complex AbsoluteMaximum() + { + return At(AbsoluteMaximumIndex()).Magnitude; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = At(index).Magnitude; + for (var i = 1; i < Count; i++) + { + var test = At(i).Magnitude; + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override Complex Sum() + { + var sum = Complex.Zero; + for (var i = 0; i < Count; i++) + { + sum += At(i); + } + + return sum; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double sum = 0d; + for (var i = 0; i < Count; i++) + { + sum += At(i).Magnitude; + } + + return sum; + } + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public override double L2Norm() + { + return DoConjugateDotProduct(this).SquareRoot().Real; + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(0, Count, i => At(i).Magnitude, Math.Max, 0d); + } + + /// + /// Computes the p-Norm. + /// + /// + /// The p value. + /// + /// + /// Scalar ret = ( ∑|At(i)|^p )^(1/p) + /// + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var index = 0; index < Count; index++) + { + sum += Math.Pow(At(index).Magnitude, p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public override int MaximumIndex() + { + throw new NotSupportedException(); + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + throw new NotSupportedException(); + } + + /// + /// Normalizes this vector to a unit vector with respect to the p-norm. + /// + /// + /// The p value. + /// + /// + /// This vector normalized to a unit vector with respect to the p-norm. + /// + public override Vector Normalize(double p) + { + if (p < 0d) + { + throw new ArgumentOutOfRangeException(nameof(p)); + } + + double norm = Norm(p); + var clone = Clone(); + if (norm == 0d) + { + return clone; + } + + clone.Multiply(1d / norm, clone); + + return clone; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/DenseMatrix.cs b/MathNet.Numerics/LinearAlgebra/Complex32/DenseMatrix.cs new file mode 100644 index 0000000..f9ae38d --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/DenseMatrix.cs @@ -0,0 +1,1345 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Complex32.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex32; + +using Numerics; + +/// +/// A Matrix class with dense storage. The underlying storage is a one dimensional array in column-major order (column by column). +/// +[Serializable] +[DebuggerDisplay("DenseMatrix {RowCount}x{ColumnCount}-Complex32")] +public class DenseMatrix : Matrix +{ + /// + /// Number of rows. + /// + /// Using this instead of the RowCount property to speed up calculating + /// a matrix index in the data array. + private readonly int _rowCount; + + /// + /// Number of columns. + /// + /// Using this instead of the ColumnCount property to speed up calculating + /// a matrix index in the data array. + private readonly int _columnCount; + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + readonly Complex32[] _values; + + /// + /// Create a new dense matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DenseMatrix(DenseColumnMajorMatrixStorage storage) + : base(storage) + { + _rowCount = storage.RowCount; + _columnCount = storage.ColumnCount; + _values = storage.Data; + } + + /// + /// Create a new square dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public DenseMatrix(int order) + : this(new DenseColumnMajorMatrixStorage(order, order)) + { + } + + /// + /// Create a new dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DenseMatrix(int rows, int columns) + : this(new DenseColumnMajorMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new dense matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to be in column-major order (column by column) and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + /// + public DenseMatrix(int rows, int columns, Complex32[] storage) + : this(new DenseColumnMajorMatrixStorage(rows, columns, storage)) + { + } + + /// + /// Create a new dense matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfMatrix(Matrix matrix) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new dense matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfArray(Complex32[,] array) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfArray(array)); + } + + /// + /// Create a new dense matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnMajor(int rows, int columns, IEnumerable columnMajor) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnMajorEnumerable(rows, columns, columnMajor)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumns(IEnumerable> data) + { + return OfColumnArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumns(int rows, int columns, IEnumerable> data) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnArrays(params Complex32[][] columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new dense matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnArrays(IEnumerable columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnArrays((columns as Complex32[][]) ?? columns.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnVectors(IEnumerable> columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRows(IEnumerable> data) + { + return OfRowArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRows(int rows, int columns, IEnumerable> data) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowArrays(params Complex32[][] rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new dense matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowArrays(IEnumerable rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowArrays((rows as Complex32[][]) ?? rows.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowVectors(IEnumerable> rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalVector(Vector diagonal) + { + var m = new DenseMatrix(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = new DenseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalArray(Complex32[] diagonal) + { + var m = new DenseMatrix(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalArray(int rows, int columns, Complex32[] diagonal) + { + var m = new DenseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix and initialize each value to the same provided value. + /// + public static DenseMatrix Create(int rows, int columns, Complex32 value) + { + if (value == Complex32.Zero) + return new DenseMatrix(rows, columns); + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new dense matrix and initialize each value using the provided init function. + /// + public static DenseMatrix Create(int rows, int columns, Func init) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value to the same provided value. + /// + public static DenseMatrix CreateDiagonal(int rows, int columns, Complex32 value) + { + if (value == Complex32.Zero) + return new DenseMatrix(rows, columns); + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value using the provided init function. + /// + public static DenseMatrix CreateDiagonal(int rows, int columns, Func init) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static DenseMatrix CreateIdentity(int order) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Create a new dense matrix with values sampled from the provided random distribution. + /// + public static DenseMatrix CreateRandom(int rows, int columns, IContinuousDistribution distribution) + { + return new DenseMatrix(new DenseColumnMajorMatrixStorage(rows, columns, Generate.RandomComplex32(rows * columns, distribution))); + } + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + public Complex32[] Values + { + get { return _values; } + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.OneNorm, _rowCount, _columnCount, _values); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.InfinityNorm, _rowCount, _columnCount, _values); + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.FrobeniusNorm, _rowCount, _columnCount, _values); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(-1, _values, denseResult._values); + return; + } + + base.DoNegate(result); + } + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected override void DoConjugate(Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult != null) + { + LinearAlgebraControl.Provider.ConjugateArray(_values, denseResult._values); + return; + } + + base.DoConjugate(result); + } + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected override void DoAdd(Complex32 scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoAdd(scalar, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = _values[i] + scalar; + } + }); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of add + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + // dense + dense = dense + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + var denseResult = result.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.AddArrays(_values, denseOther.Data, denseResult.Data); + return; + } + + // dense + diagonal = any + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + Storage.CopyToUnchecked(result.Storage, ExistingData.Clear); + var diagonal = diagonalOther.Data; + for (int i = 0; i < diagonal.Length; i++) + { + result.At(i, i, result.At(i, i) + diagonal[i]); + } + + return; + } + + base.DoAdd(other, result); + } + + /// + /// Subtracts a scalar from each element of the matrix and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(Complex32 scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoSubtract(scalar, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = _values[i] - scalar; + } + }); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(Matrix other, Matrix result) + { + // dense + dense = dense + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + var denseResult = result.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.SubtractArrays(_values, denseOther.Data, denseResult.Data); + return; + } + + // dense + diagonal = matrix + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + CopyTo(result); + var diagonal = diagonalOther.Data; + for (int i = 0; i < diagonal.Length; i++) + { + result.At(i, i, result.At(i, i) - diagonal[i]); + } + + return; + } + + base.DoSubtract(other, result); + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(Complex32 scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoMultiply(scalar, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(scalar, _values, denseResult._values); + } + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + + if (denseRight == null || denseResult == null) + { + base.DoMultiply(rightSide, result); + } + else + { + LinearAlgebraControl.Provider.MatrixMultiply( + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + denseResult.Values); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiply( + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(ColumnCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.ColumnCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoMultiply(other, result); + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.DontTranspose, + Providers.LinearAlgebra.Transpose.Transpose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0f, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(ColumnCount, other.RowCount); + if (d < other.RowCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.RowCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.DontTranspose, + Providers.LinearAlgebra.Transpose.ConjugateTranspose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0f, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var conjugateDiagonal = new Complex32[diagonal.Length]; + for (int i = 0; i < diagonal.Length; i++) + { + conjugateDiagonal[i] = diagonal[i].Conjugate(); + } + + var d = Math.Min(ColumnCount, other.RowCount); + if (d < other.RowCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.RowCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * conjugateDiagonal[j]); + index++; + } + } + + return; + } + + base.DoConjugateTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + + if (denseRight == null || denseResult == null) + { + base.DoTransposeThisAndMultiply(rightSide, result); + } + else + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.Transpose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + 0.0f, + denseResult.Values); + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + if (denseRight != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.ConjugateTranspose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + 0.0f, + denseResult.Values); + return; + } + + base.DoConjugateTransposeThisAndMultiply(rightSide, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.Transpose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0f, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(RowCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, ColumnCount, RowCount, other.ColumnCount - RowCount); + } + + int index = 0; + for (int i = 0; i < ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + + index += (RowCount - d); + } + + return; + } + + base.DoTransposeThisAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.ConjugateTranspose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0f, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(RowCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, ColumnCount, RowCount, other.ColumnCount - RowCount); + } + + int index = 0; + for (int i = 0; i < ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(i, j, _values[index].Conjugate() * diagonal[j]); + index++; + } + + index += (RowCount - d); + } + + return; + } + + base.DoConjugateTransposeThisAndMultiply(other, result); + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(Complex32 divisor, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(1.0f / divisor, _values, denseResult._values); + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseMultiply(other, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + var denseDivisor = divisor as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseDivisor == null || denseResult == null) + { + base.DoPointwiseDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseDivideArrays(_values, denseDivisor._values, denseResult._values); + } + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Matrix exponent, Matrix result) + { + var denseExponent = exponent as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseExponent == null || denseResult == null) + { + base.DoPointwisePower(exponent, result); + } + else + { + LinearAlgebraControl.Provider.PointWisePowerArrays(_values, denseExponent._values, denseResult._values); + } + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public override Complex32 Trace() + { + if (_rowCount != _columnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var sum = Complex32.Zero; + for (var i = 0; i < _rowCount; i++) + { + sum += _values[(i * _rowCount) + i]; + } + + return sum; + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static DenseMatrix operator +(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide._rowCount != rightSide._rowCount || leftSide._columnCount != rightSide._columnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Add(rightSide); + } + + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static DenseMatrix operator +(DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Clone(); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static DenseMatrix operator -(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide._rowCount != rightSide._rowCount || leftSide._columnCount != rightSide._columnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Subtract(rightSide); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static DenseMatrix operator -(DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Negate(); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator *(DenseMatrix leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator *(Complex32 leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static DenseMatrix operator *(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide._columnCount != rightSide._rowCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static DenseVector operator *(DenseMatrix leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static DenseVector operator *(DenseVector leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.LeftMultiply(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator %(DenseMatrix leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseMatrix)leftSide.Remainder(rightSide); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public override bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = j + 1; i < RowCount; i++) + { + if (_values[(i * ColumnCount) + j] != _values[index + i]) + { + return false; + } + } + } + + return true; + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public override bool IsHermitian() + { + if (RowCount != ColumnCount) + { + return false; + } + + int stride = RowCount + 1; + for (var k = 0; k < _values.Length; k += stride) + { + if (!_values[k].IsReal()) + { + return false; + } + } + + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = j + 1; i < RowCount; i++) + { + if (_values[(i * ColumnCount) + j] != _values[index + i].Conjugate()) + { + return false; + } + } + } + + return true; + } + + public override Cholesky Cholesky() + { + return DenseCholesky.Create(this); + } + + public override LU LU() + { + return DenseLU.Create(this); + } + + public override QR QR(QRMethod method = QRMethod.Thin) + { + return DenseQR.Create(this, method); + } + + public override GramSchmidt GramSchmidt() + { + return DenseGramSchmidt.Create(this); + } + + public override Svd Svd(bool computeVectors = true) + { + return DenseSvd.Create(this, computeVectors); + } + + public override Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown) + { + return DenseEvd.Create(this, symmetricity); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/DenseVector.cs b/MathNet.Numerics/LinearAlgebra/Complex32/DenseVector.cs new file mode 100644 index 0000000..137216f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/DenseVector.cs @@ -0,0 +1,825 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex32; + +using Numerics; + +/// +/// A vector using dense storage. +/// +[Serializable] +[DebuggerDisplay("DenseVector {" + nameof(Count) + "}-Complex32")] +public class DenseVector : Vector +{ + /// + /// Number of elements + /// + readonly int _length; + + /// + /// Gets the vector's data. + /// + readonly Complex32[] _values; + + /// + /// Create a new dense vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DenseVector(DenseVectorStorage storage) + : base(storage) + { + _length = storage.Length; + _values = storage.Data; + } + + /// + /// Create a new dense vector with the given length. + /// All cells of the vector will be initialized to zero. + /// Zero-length vectors are not supported. + /// + /// If length is less than one. + public DenseVector(int length) + : this(new DenseVectorStorage(length)) + { + } + + /// + /// Create a new dense vector directly binding to a raw array. + /// The array is used directly without copying. + /// Very efficient, but changes to the array and the vector will affect each other. + /// + public DenseVector(Complex32[] storage) + : this(new DenseVectorStorage(storage.Length, storage)) + { + } + + /// + /// Create a new dense vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfVector(Vector vector) + { + return new DenseVector(DenseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new dense vector as a copy of the given array. + /// This new vector will be independent from the array. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfArray(Complex32[] array) + { + return new DenseVector(DenseVectorStorage.OfVector(new DenseVectorStorage(array.Length, array))); + } + + /// + /// Create a new dense vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfEnumerable(IEnumerable enumerable) + { + return new DenseVector(DenseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new dense vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfIndexedEnumerable(int length, IEnumerable> enumerable) + { + return new DenseVector(DenseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new dense vector and initialize each value using the provided value. + /// + public static DenseVector Create(int length, Complex32 value) + { + if (value == Complex32.Zero) + return new DenseVector(length); + return new DenseVector(DenseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new dense vector and initialize each value using the provided init function. + /// + public static DenseVector Create(int length, Func init) + { + return new DenseVector(DenseVectorStorage.OfInit(length, init)); + } + + /// + /// Create a new dense vector with values sampled from the provided random distribution. + /// + public static DenseVector CreateRandom(int length, IContinuousDistribution distribution) + { + var samples = Generate.RandomComplex32(length, distribution); + return new DenseVector(new DenseVectorStorage(length, samples)); + } + + /// + /// Gets the vector's data. + /// + /// The vector's data. + public Complex32[] Values + { + get { return _values; } + } + + /// + /// Returns a reference to the internal data structure. + /// + /// The DenseVector whose internal data we are + /// returning. + /// + /// A reference to the internal date of the given vector. + /// + public static explicit operator Complex32[](DenseVector vector) + { + if (vector == null) + { + throw new ArgumentNullException(nameof(vector)); + } + + return vector.Values; + } + + /// + /// Returns a vector bound directly to a reference of the provided array. + /// + /// The array to bind to the DenseVector object. + /// + /// A DenseVector whose values are bound to the given array. + /// + public static implicit operator DenseVector(Complex32[] array) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + return new DenseVector(array); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to add. + /// The vector to store the result of the addition. + protected override void DoAdd(Complex32 scalar, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoAdd(scalar, result); + } + else + { + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] + scalar; + } + }); + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + protected override void DoAdd(Vector other, Vector result) + { + var otherDense = other as DenseVector; + var resultDense = result as DenseVector; + + if (otherDense == null || resultDense == null) + { + base.DoAdd(other, result); + } + else + { + LinearAlgebraControl.Provider.AddArrays(_values, otherDense._values, resultDense._values); + } + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static DenseVector operator +(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Add(rightSide); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The vector to store the result of the subtraction. + protected override void DoSubtract(Complex32 scalar, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoSubtract(scalar, result); + } + else + { + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] - scalar; + } + }); + } + } + + /// + /// Subtracts another vector from this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + protected override void DoSubtract(Vector other, Vector result) + { + var otherDense = other as DenseVector; + var resultDense = result as DenseVector; + + if (otherDense == null || resultDense == null) + { + base.DoSubtract(other, result); + } + else + { + LinearAlgebraControl.Provider.SubtractArrays(_values, otherDense._values, resultDense._values); + } + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static DenseVector operator -(DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static DenseVector operator -(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Subtract(rightSide); + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + var denseResult = result as DenseVector; + if (denseResult == null) + { + base.DoNegate(result); + return; + } + + LinearAlgebraControl.Provider.ScaleArray(-Complex32.One, _values, denseResult.Values); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + protected override void DoConjugate(Vector result) + { + var resultDense = result as DenseVector; + if (resultDense == null) + { + base.DoConjugate(result); + return; + } + + LinearAlgebraControl.Provider.ConjugateArray(_values, resultDense._values); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to multiply. + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(Complex32 scalar, Vector result) + { + var denseResult = result as DenseVector; + if (denseResult == null) + { + base.DoMultiply(scalar, result); + return; + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, _values, denseResult.Values); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override Complex32 DoDotProduct(Vector other) + { + var denseVector = other as DenseVector; + return denseVector == null + ? base.DoDotProduct(other) + : LinearAlgebraControl.Provider.DotProduct(_values, denseVector.Values); + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected override Complex32 DoConjugateDotProduct(Vector other) + { + var denseVector = other as DenseVector; + if (denseVector == null) + return base.DoConjugateDotProduct(other); + + // TODO: provide native cdotc routine + var dot = Complex32.Zero; + for (var i = 0; i < _values.Length; i++) + { + dot += _values[i].Conjugate() * denseVector._values[i]; + } + + return dot; + } + + /// + /// Multiplies a vector with a complex. + /// + /// The vector to scale. + /// The Complex32 value. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(DenseVector leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The Complex32 value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(Complex32 leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static Complex32 operator *(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a complex. + /// + /// The vector to divide. + /// The Complex32 value. + /// The result of the division. + /// If is . + public static DenseVector operator /(DenseVector leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Divide(rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = _values[index].Magnitude; + for (var i = 1; i < _length; i++) + { + var test = _values[i].Magnitude; + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = _values[index].Magnitude; + for (var i = 1; i < _length; i++) + { + var test = _values[i].Magnitude; + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override Complex32 Sum() + { + var sum = Complex32.Zero; + for (var i = 0; i < _length; i++) + { + sum += _values[i]; + } + + return sum; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double sum = 0d; + for (var i = 0; i < _length; i++) + { + sum += _values[i].Magnitude; + } + + return sum; + } + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public override double L2Norm() + { + // TODO: native provider + return _values.Aggregate(Complex32.Zero, SpecialFunctions.Hypotenuse).Magnitude; + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(_values, (i, v) => v.Magnitude, Math.Max, 0f); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = ( ∑|this[i]|^p )^(1/p) + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var i = 0; i < _length; i++) + { + sum += Math.Pow(_values[i].Magnitude, p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + var denseOther = other as DenseVector; + var denseResult = result as DenseVector; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseMultiply(other, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + /// + protected override void DoPointwiseDivide(Vector divisor, Vector result) + { + var denseOther = divisor as DenseVector; + var denseResult = result as DenseVector; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseDivideArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Vector exponent, Vector result) + { + var denseExponent = exponent as DenseVector; + var denseResult = result as DenseVector; + + if (denseExponent == null || denseResult == null) + { + base.DoPointwisePower(exponent, result); + } + else + { + LinearAlgebraControl.Provider.PointWisePowerArrays(_values, denseExponent._values, denseResult._values); + } + } + + #region Parse Functions + + /// + /// Creates a Complex32 dense vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n;n;..', '(n;n;..)', '[n;n;...]', where n is a double. + /// + /// + /// A Complex32 dense vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static DenseVector Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // parsing + var strongTokens = value.Split(new[] { formatProvider.GetTextInfo().ListSeparator }, StringSplitOptions.RemoveEmptyEntries); + var data = new List(); + foreach (string strongToken in strongTokens) + { + var weakTokens = strongToken.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); + string current = string.Empty; + for (int i = 0; i < weakTokens.Length; i++) + { + current += weakTokens[i]; + if (current.EndsWith("+") || current.EndsWith("-") || current.StartsWith("(") && !current.EndsWith(")")) + { + continue; + } + + var ahead = i < weakTokens.Length - 1 ? weakTokens[i + 1] : string.Empty; + if (ahead.StartsWith("+") || ahead.StartsWith("-")) + { + continue; + } + + data.Add(current.ToComplex32(formatProvider)); + current = string.Empty; + } + + if (current != string.Empty) + { + throw new FormatException(); + } + } + + if (data.Count == 0) + { + throw new FormatException(); + } + + return new DenseVector(data.ToArray()); + } + + /// + /// Converts the string representation of a complex dense vector to double-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out DenseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a complex dense vector to double-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out DenseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + + #endregion +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/DiagonalMatrix.cs b/MathNet.Numerics/LinearAlgebra/Complex32/DiagonalMatrix.cs new file mode 100644 index 0000000..6cf348a --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/DiagonalMatrix.cs @@ -0,0 +1,1038 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex32; + +using Numerics; + +/// +/// A matrix type for diagonal matrices. +/// +/// +/// Diagonal matrices can be non-square matrices but the diagonal always starts +/// at element 0,0. A diagonal matrix will throw an exception if non diagonal +/// entries are set. The exception to this is when the off diagonal elements are +/// 0.0 or NaN; these settings will cause no change to the diagonal matrix. +/// +[Serializable] +[DebuggerDisplay("DiagonalMatrix {RowCount}x{ColumnCount}-Complex32")] +public class DiagonalMatrix : Matrix +{ + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + readonly Complex32[] _data; + + /// + /// Create a new diagonal matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DiagonalMatrix(DiagonalMatrixStorage storage) + : base(storage) + { + _data = storage.Data; + } + + /// + /// Create a new square diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public DiagonalMatrix(int order) + : this(new DiagonalMatrixStorage(order, order)) + { + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DiagonalMatrix(int rows, int columns) + : this(new DiagonalMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All diagonal cells of the matrix will be initialized to the provided value, all non-diagonal ones to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DiagonalMatrix(int rows, int columns, Complex32 diagonalValue) + : this(rows, columns) + { + for (var i = 0; i < _data.Length; i++) + { + _data[i] = diagonalValue; + } + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to contain the diagonal elements only and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + public DiagonalMatrix(int rows, int columns, Complex32[] diagonalStorage) + : this(new DiagonalMatrixStorage(rows, columns, diagonalStorage)) + { + } + + /// + /// Create a new diagonal matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// The matrix to copy from must be diagonal as well. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfMatrix(Matrix matrix) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new diagonal matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// The array to copy from must be diagonal as well. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfArray(Complex32[,] array) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfArray(array)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value from the provided indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfIndexedDiagonal(int rows, int columns, IEnumerable> diagonal) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfIndexedEnumerable(rows, columns, diagonal)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value from the provided enumerable. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfDiagonal(int rows, int columns, IEnumerable diagonal) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfEnumerable(rows, columns, diagonal)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value using the provided init function. + /// + public static DiagonalMatrix Create(int rows, int columns, Func init) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static DiagonalMatrix CreateIdentity(int order) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfValue(order, order, One)); + } + + /// + /// Create a new diagonal matrix with diagonal values sampled from the provided random distribution. + /// + public static DiagonalMatrix CreateRandom(int rows, int columns, IContinuousDistribution distribution) + { + return new DiagonalMatrix(new DiagonalMatrixStorage(rows, columns, Generate.RandomComplex32(Math.Min(rows, columns), distribution))); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(-1, _data, diagResult._data); + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, -_data[i]); + } + } + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected override void DoConjugate(Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ConjugateArray(_data, diagResult._data); + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i].Conjugate()); + } + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + // diagonal + diagonal = diagonal + var diagOther = other as DiagonalMatrix; + var diagResult = result as DiagonalMatrix; + if (diagOther != null && diagResult != null) + { + LinearAlgebraControl.Provider.AddArrays(_data, diagOther._data, diagResult._data); + return; + } + + other.CopyTo(result); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, result.At(i, i) + _data[i]); + } + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + // diagonal - diagonal = diagonal + var diagOther = other as DiagonalMatrix; + var diagResult = result as DiagonalMatrix; + if (diagOther != null && diagResult != null) + { + LinearAlgebraControl.Provider.SubtractArrays(_data, diagOther._data, diagResult._data); + return; + } + + other.Negate(result); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, result.At(i, i) + _data[i]); + } + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(Complex32 scalar, Matrix result) + { + if (scalar.IsZero()) + { + result.Clear(); + return; + } + + if (scalar.IsOne()) + { + CopyTo(result); + return; + } + + var diagResult = result as DiagonalMatrix; + if (diagResult == null) + { + base.DoMultiply(scalar, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(scalar, _data, diagResult._data); + } + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubVector(ColumnCount, RowCount - ColumnCount); + } + + if (d == ColumnCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i] * rightSide.At(i)); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex32[diagonalResult._data.Length]; + var otherDataCopy = new Complex32[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.RowCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.RowCount, RowCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + if (ColumnCount == RowCount) + { + other.Storage.MapIndexedTo(result.Storage, (i, j, x) => x * _data[i], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Clear(); + other.Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * _data[i], 0, 0, other.RowCount, 0, 0, other.ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex32[diagonalResult._data.Length]; + var otherDataCopy = new Complex32[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.ColumnCount, RowCount - denseOther.ColumnCount, 0, denseOther.RowCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < denseOther.RowCount; i++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex32[diagonalResult._data.Length]; + var otherDataCopy = new Complex32[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + // TODO: merge/MulByConj + LinearAlgebraControl.Provider.ConjugateArray(otherDataCopy, otherDataCopy); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.ColumnCount, RowCount - denseOther.ColumnCount, 0, denseOther.RowCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < denseOther.RowCount; i++) + { + result.At(j, i, dense[index].Conjugate() * diagonal[j]); + index++; + } + } + + return; + } + + base.DoConjugateTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex32[diagonalResult._data.Length]; + var otherDataCopy = new Complex32[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.RowCount, ColumnCount); + if (d < ColumnCount) + { + result.ClearSubMatrix(denseOther.RowCount, ColumnCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + if (ColumnCount == RowCount) + { + other.Storage.MapIndexedTo(result.Storage, (i, j, x) => x * _data[i], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Clear(); + other.Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * _data[i], 0, 0, other.RowCount, 0, 0, other.ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new Complex32[diagonalResult._data.Length]; + var otherDataCopy = new Complex32[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + // TODO: merge/MulByConj + LinearAlgebraControl.Provider.ConjugateArray(thisDataCopy, thisDataCopy); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var conjugateDiagonal = new Complex32[_data.Length]; + for (int i = 0; i < _data.Length; i++) + { + conjugateDiagonal[i] = _data[i].Conjugate(); + } + + var d = Math.Min(denseOther.RowCount, ColumnCount); + if (d < ColumnCount) + { + result.ClearSubMatrix(denseOther.RowCount, ColumnCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * conjugateDiagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + base.DoConjugateTransposeThisAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < ColumnCount) + { + result.ClearSubVector(RowCount, ColumnCount - RowCount); + } + + if (d == RowCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i] * rightSide.At(i)); + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < ColumnCount) + { + result.ClearSubVector(RowCount, ColumnCount - RowCount); + } + + if (d == RowCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + // TODO: merge/MulByConj + LinearAlgebraControl.Provider.ConjugateArray(_data, denseResult.Data); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(denseResult.Data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i].Conjugate() * rightSide.At(i)); + } + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(Complex32 divisor, Matrix result) + { + if (divisor == Complex32.One) + { + CopyTo(result); + return; + } + + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(1.0f / divisor, _data, diagResult._data); + return; + } + + result.Clear(); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i] / divisor); + } + } + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to add. + /// The matrix to store the result of the division. + protected override void DoDivideByThis(Complex32 dividend, Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + var resultData = diagResult._data; + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + resultData[i] = dividend / _data[i]; + } + }); + return; + } + + result.Clear(); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, dividend / _data[i]); + } + } + + /// + /// Computes the determinant of this matrix. + /// + /// The determinant of this matrix. + public override Complex32 Determinant() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + return _data.Aggregate(Complex32.One, (current, t) => current * t); + } + + /// + /// Returns the elements of the diagonal in a . + /// + /// The elements of the diagonal. + /// For non-square matrices, the method returns Min(Rows, Columns) elements where + /// i == j (i is the row index, and j is the column index). + public override Vector Diagonal() + { + return new DenseVector(_data).Clone(); + } + + /// + /// Copies the values of the given array to the diagonal. + /// + /// The array to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(Complex32[] source) + { + if (source.Length != _data.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(source)); + } + + Array.Copy(source, 0, _data, 0, source.Length); + } + + /// + /// Copies the values of the given to the diagonal. + /// + /// The vector to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(Vector source) + { + var denseSource = source as DenseVector; + if (denseSource == null) + { + base.SetDiagonal(source); + return; + } + + if (_data.Length != denseSource.Values.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(source)); + } + + Array.Copy(denseSource.Values, 0, _data, 0, denseSource.Values.Length); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + return _data.Aggregate(0f, (current, t) => Math.Max(current, t.Magnitude)); + } + + /// Calculates the induced L2 norm of the matrix. + /// The largest singular value of the matrix. + public override double L2Norm() + { + return _data.Aggregate(0f, (current, t) => Math.Max(current, t.Magnitude)); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + return L1Norm(); + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + return Math.Sqrt(_data.Sum(t => t.Magnitude * t.Magnitude)); + } + + /// Calculates the condition number of this matrix. + /// The condition number of the matrix. + public override Complex32 ConditionNumber() + { + var maxSv = float.NegativeInfinity; + var minSv = float.PositiveInfinity; + foreach (var t in _data) + { + maxSv = Math.Max(maxSv, t.Magnitude); + minSv = Math.Min(minSv, t.Magnitude); + } + + return maxSv / minSv; + } + + /// Computes the inverse of this matrix. + /// If is not a square matrix. + /// If is singular. + /// The inverse of this matrix. + public override Matrix Inverse() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var inverse = (DiagonalMatrix)Clone(); + for (var i = 0; i < _data.Length; i++) + { + if (_data[i] != 0.0f) + { + inverse._data[i] = 1.0f / _data[i]; + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixNotSingular); + } + } + + return inverse; + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + return Clone(); + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i]); + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + return Clone(); + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i]); + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + } + + /// + /// Creates a matrix that contains the values from the requested sub-matrix. + /// + /// The row to start copying from. + /// The number of rows to copy. Must be positive. + /// The column to start copying from. + /// The number of columns to copy. Must be positive. + /// The requested sub-matrix. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + /// If or + /// is not positive. + public override Matrix SubMatrix(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + var target = rowIndex == columnIndex + ? (Matrix)new DiagonalMatrix(rowCount, columnCount) + : new SparseMatrix(rowCount, columnCount); + + Storage.CopySubMatrixTo(target.Storage, rowIndex, 0, rowCount, columnIndex, 0, columnCount, ExistingData.AssumeZeros); + return target; + } + + /// + /// Permute the columns of a matrix according to a permutation. + /// + /// The column permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteColumns(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Permute the rows of a matrix according to a permutation. + /// + /// The row permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteRows(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public sealed override bool IsSymmetric() + { + return true; + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public sealed override bool IsHermitian() + { + for (var k = 0; k < _data.Length; k++) + { + if (!_data[k].IsReal()) + { + return false; + } + } + + return true; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Cholesky.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Cholesky.cs new file mode 100644 index 0000000..4f52db7 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Cholesky.cs @@ -0,0 +1,86 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal abstract class Cholesky : Cholesky +{ + protected Cholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Gets the determinant of the matrix for which the Cholesky matrix was computed. + /// + public override Complex32 Determinant + { + get + { + var det = Complex32.One; + for (var j = 0; j < Factor.RowCount; j++) + { + var d = Factor.At(j, j); + det *= d * d; + } + + return det; + } + } + + /// + /// Gets the log determinant of the matrix for which the Cholesky matrix was computed. + /// + public override Complex32 DeterminantLn + { + get + { + var det = Complex32.Zero; + for (var j = 0; j < Factor.RowCount; j++) + { + det += 2.0f * Factor.At(j, j).NaturalLogarithm(); + } + + return det; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseCholesky.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseCholesky.cs new file mode 100644 index 0000000..1305f8f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseCholesky.cs @@ -0,0 +1,189 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization for dense matrices. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal sealed class DenseCholesky : Cholesky +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public static DenseCholesky Create(DenseMatrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var factor = (DenseMatrix)matrix.Clone(); + LinearAlgebraControl.Provider.CholeskyFactor(factor.Values, factor.RowCount); + return new DenseCholesky(factor); + } + + DenseCholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + Array.Copy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)Factor; + LinearAlgebraControl.Provider.CholeskySolveFactored(dfactor.Values, dfactor.RowCount, dresult.Values, dresult.ColumnCount); + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + Array.Copy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)Factor; + LinearAlgebraControl.Provider.CholeskySolveFactored(dfactor.Values, dfactor.RowCount, dresult.Values, 1); + } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + /// If does not have the same dimensions as the existing factor. + public override void Factorize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + if (matrix.RowCount != Factor.RowCount || matrix.ColumnCount != Factor.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix, Factor); + } + + var dmatrix = matrix as DenseMatrix; + if (dmatrix == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dfactor = (DenseMatrix)Factor; + + // Overwrite the existing Factor matrix with the input. + Array.Copy(dmatrix.Values, 0, dfactor.Values, 0, dmatrix.Values.Length); + + // Perform factorization (while overwriting). + LinearAlgebraControl.Provider.CholeskyFactor(dfactor.Values, dfactor.RowCount); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseEvd.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseEvd.cs new file mode 100644 index 0000000..6f3a224 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseEvd.cs @@ -0,0 +1,947 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a complex matrix. +/// +/// +/// If A is Hermitian, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is Hermitian. +/// I.e. A = V*D*V' and V*VH=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal sealed class DenseEvd : Evd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If it is known whether the matrix is symmetric or not the routine can skip checking it itself. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public static DenseEvd Create(DenseMatrix matrix, Symmetricity symmetricity) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matrices for eigenvalues and eigenvectors + var eigenVectors = DenseMatrix.CreateIdentity(order); + var blockDiagonal = new DenseMatrix(order); + var eigenValues = new LinearAlgebra.Complex.DenseVector(order); + + bool isSymmetric; + switch (symmetricity) + { + case Symmetricity.Hermitian: + isSymmetric = true; + break; + case Symmetricity.Asymmetric: + isSymmetric = false; + break; + default: + isSymmetric = matrix.IsHermitian(); + break; + } + + LinearAlgebraControl.Provider.EigenDecomp(isSymmetric, order, matrix.Values, eigenVectors.Values, eigenValues.Values, blockDiagonal.Values); + + return new DenseEvd(eigenVectors, eigenValues, blockDiagonal, isSymmetric); + } + + DenseEvd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Reduces a complex Hermitian matrix to a real symmetric tridiagonal matrix using unitary similarity transformations. + /// + /// Source matrix to reduce + /// Output: Arrays for internal storage of real parts of eigenvalues + /// Output: Arrays for internal storage of imaginary parts of eigenvalues + /// Output: Arrays that contains further information about the transformations. + /// Order of initial matrix + /// This is derived from the Algol procedures HTRIDI by + /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + internal static void SymmetricTridiagonalize(Complex32[] matrixA, float[] d, float[] e, Complex32[] tau, int order) + { + float hh; + tau[order - 1] = Complex32.One; + + for (var i = 0; i < order; i++) + { + d[i] = matrixA[i * order + i].Real; + } + + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0f; + var h = 0.0f; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(matrixA[k * order + i].Real) + Math.Abs(matrixA[k * order + i].Imaginary); + } + + if (scale == 0.0f) + { + tau[i - 1] = Complex32.One; + e[i] = 0.0f; + } + else + { + for (var k = 0; k < i; k++) + { + matrixA[k * order + i] /= scale; + h += matrixA[k * order + i].MagnitudeSquared; + } + + Complex32 g = (float)Math.Sqrt(h); + e[i] = scale * g.Real; + + Complex32 temp; + var im1Oi = (i - 1) * order + i; + var f = matrixA[im1Oi]; + if (f.Magnitude != 0.0f) + { + temp = -(matrixA[im1Oi].Conjugate() * tau[i].Conjugate()) / f.Magnitude; + h += f.Magnitude * g.Real; + g = 1.0f + (g / f.Magnitude); + matrixA[im1Oi] *= g; + } + else + { + temp = -tau[i].Conjugate(); + matrixA[im1Oi] = g; + } + + if ((f.Magnitude == 0.0f) || (i != 1)) + { + f = Complex32.Zero; + for (var j = 0; j < i; j++) + { + var tmp = Complex32.Zero; + var jO = j * order; + // Form element of A*U. + for (var k = 0; k <= j; k++) + { + tmp += matrixA[k * order + j] * matrixA[k * order + i].Conjugate(); + } + + for (var k = j + 1; k <= i - 1; k++) + { + tmp += matrixA[jO + k].Conjugate() * matrixA[k * order + i].Conjugate(); + } + + // Form element of P + tau[j] = tmp / h; + f += (tmp / h) * matrixA[jO + i]; + } + + hh = f.Real / (h + h); + + // Form the reduced A. + for (var j = 0; j < i; j++) + { + f = matrixA[j * order + i].Conjugate(); + g = tau[j] - (hh * f); + tau[j] = g.Conjugate(); + + for (var k = 0; k <= j; k++) + { + matrixA[k * order + j] -= (f * tau[k]) + (g * matrixA[k * order + i]); + } + } + } + + for (var k = 0; k < i; k++) + { + matrixA[k * order + i] *= scale; + } + + tau[i - 1] = temp.Conjugate(); + } + + hh = d[i]; + d[i] = matrixA[i * order + i].Real; + matrixA[i * order + i] = new Complex32(hh, scale * (float)Math.Sqrt(h)); + } + + hh = d[0]; + d[0] = matrixA[0].Real; + matrixA[0] = hh; + e[0] = 0.0f; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + internal static void SymmetricDiagonalize(Complex32[] dataEv, float[] d, float[] e, int order) + { + const int maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0f; + + var f = 0.0f; + var tst1 = 0.0f; + var eps = Precision.DoublePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0f * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0f); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0f; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0f; + var s2 = 0.0f; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = dataEv[((i + 1) * order) + k].Real; + dataEv[((i + 1) * order) + k] = (s * dataEv[(i * order) + k].Real) + (c * h); + dataEv[(i * order) + k] = (c * dataEv[(i * order) + k].Real) - (s * h); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + } while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0f; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = dataEv[(i * order) + j].Real; + dataEv[(i * order) + j] = dataEv[(k * order) + j]; + dataEv[(k * order) + j] = p; + } + } + } + } + + /// + /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation + /// + /// Data array of matrix V (eigenvectors) + /// Previously tridiagonalized matrix by . + /// Contains further information about the transformations + /// Input matrix order + /// This is derived from the Algol procedures HTRIBK, by + /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + internal static void SymmetricUntridiagonalize(Complex32[] dataEv, Complex32[] matrixA, Complex32[] tau, int order) + { + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + dataEv[(j * order) + i] = dataEv[(j * order) + i].Real * tau[i].Conjugate(); + } + } + + // Recover and apply the Householder matrices. + for (var i = 1; i < order; i++) + { + var h = matrixA[i * order + i].Imaginary; + if (h != 0) + { + for (var j = 0; j < order; j++) + { + var s = Complex32.Zero; + for (var k = 0; k < i; k++) + { + s += dataEv[(j * order) + k] * matrixA[k * order + i]; + } + + s = (s / h) / h; + + for (var k = 0; k < i; k++) + { + dataEv[(j * order) + k] -= s * matrixA[k * order + i].Conjugate(); + } + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + internal static void NonsymmetricReduceToHessenberg(Complex32[] dataEv, Complex32[] matrixH, int order) + { + var ort = new Complex32[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0f; + var mm1O = (m - 1) * order; + for (var i = m; i < order; i++) + { + scale += Math.Abs(matrixH[mm1O + i].Real) + Math.Abs(matrixH[mm1O + i].Imaginary); + } + + if (scale != 0.0f) + { + // Compute Householder transformation. + var h = 0.0f; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[mm1O + i] / scale; + h += ort[i].MagnitudeSquared; + } + + var g = (float)Math.Sqrt(h); + if (ort[m].Magnitude != 0) + { + h = h + (ort[m].Magnitude * g); + g /= ort[m].Magnitude; + ort[m] = (1.0f + g) * ort[m]; + } + else + { + ort[m] = g; + matrixH[mm1O + m] = scale; + } + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = Complex32.Zero; + var jO = j * order; + for (var i = order - 1; i >= m; i--) + { + f += ort[i].Conjugate() * matrixH[jO + i]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[jO + i] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = Complex32.Zero; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[j * order + i]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[j * order + i] -= f * ort[j].Conjugate(); + } + } + + ort[m] = scale * ort[m]; + matrixH[mm1O + m] *= -g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + dataEv[(j * order) + i] = i == j ? Complex32.One : Complex32.Zero; + } + } + + for (var m = order - 2; m >= 1; m--) + { + var mm1O = (m - 1) * order; + var mm1Om = mm1O + m; + if (matrixH[mm1Om] != Complex32.Zero && ort[m] != Complex32.Zero) + { + var norm = (matrixH[mm1Om].Real * ort[m].Real) + (matrixH[mm1Om].Imaginary * ort[m].Imaginary); + + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[mm1O + i]; + } + + for (var j = m; j < order; j++) + { + var g = Complex32.Zero; + for (var i = m; i < order; i++) + { + g += ort[i].Conjugate() * dataEv[(j * order) + i]; + } + + // Double division avoids possible underflow + g /= norm; + for (var i = m; i < order; i++) + { + dataEv[(j * order) + i] += g * ort[i]; + } + } + } + } + + // Create real subdiagonal elements. + for (var i = 1; i < order; i++) + { + var im1 = i - 1; + var im1O = im1 * order; + var im1Oi = im1O + i; + var iO = i * order; + if (matrixH[im1Oi].Imaginary != 0.0f) + { + var y = matrixH[im1Oi] / matrixH[im1Oi].Magnitude; + matrixH[im1Oi] = matrixH[im1Oi].Magnitude; + for (var j = i; j < order; j++) + { + matrixH[j * order + i] *= y.Conjugate(); + } + + for (var j = 0; j <= Math.Min(i + 1, order - 1); j++) + { + matrixH[iO + j] *= y; + } + + for (var j = 0; j < order; j++) + { + dataEv[(i * order) + j] *= y; + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// Data array of the eigenvectors + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + internal static void NonsymmetricReduceHessenberToRealSchur(Complex32[] vectorV, Complex32[] dataEv, Complex32[] matrixH, int order) + { + // Initialize + var n = order - 1; + var eps = (float)Precision.SinglePrecision; + + float norm; + Complex32 x, y, z, exshift = Complex32.Zero; + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + var lm1 = l - 1; + var lm1O = lm1 * order; + var lO = l * order; + var tst1 = Math.Abs(matrixH[lm1O + lm1].Real) + Math.Abs(matrixH[lm1O + lm1].Imaginary) + Math.Abs(matrixH[lO + l].Real) + Math.Abs(matrixH[lO + l].Imaginary); + if (Math.Abs(matrixH[lm1O + l].Real) < eps * tst1) + { + break; + } + + l--; + } + + var nm1 = n - 1; + var nm1O = nm1 * order; + var nO = n * order; + var nOn = nO + n; + // Check for convergence + // One root found + if (l == n) + { + matrixH[nOn] += exshift; + vectorV[n] = matrixH[nOn]; + n--; + iter = 0; + } + else + { + // Form shift + Complex32 s; + if (iter != 10 && iter != 20) + { + s = matrixH[nOn]; + x = matrixH[nO + nm1] * matrixH[nm1O + n].Real; + + if (x.Real != 0.0f || x.Imaginary != 0.0f) + { + y = (matrixH[nm1O + nm1] - s) / 2.0f; + z = ((y * y) + x).SquareRoot(); + if ((y.Real * z.Real) + (y.Imaginary * z.Imaginary) < 0.0) + { + z *= -1.0f; + } + + x /= y + z; + s = s - x; + } + } + else + { + // Form exceptional shift + s = Math.Abs(matrixH[nm1O + n].Real) + Math.Abs(matrixH[(n - 2) * order + nm1].Real); + } + + for (var i = 0; i <= n; i++) + { + matrixH[i * order + i] -= s; + } + + exshift += s; + iter++; + + // Reduce to triangle (rows) + for (var i = l + 1; i <= n; i++) + { + var im1 = i - 1; + var im1O = im1 * order; + var im1Oim1 = im1O + im1; + s = matrixH[im1O + i].Real; + norm = SpecialFunctions.Hypotenuse(matrixH[im1Oim1].Magnitude, s.Real); + x = matrixH[im1Oim1] / norm; + vectorV[i - 1] = x; + matrixH[im1Oim1] = norm; + matrixH[im1O + i] = new Complex32(0.0f, s.Real / norm); + + for (var j = i; j < order; j++) + { + var jO = j * order; + y = matrixH[jO + im1]; + z = matrixH[jO + i]; + matrixH[jO + im1] = (x.Conjugate() * y) + (matrixH[im1O + i].Imaginary * z); + matrixH[jO + i] = (x * z) - (matrixH[im1O + i].Imaginary * y); + } + } + + s = matrixH[nOn]; + if (s.Imaginary != 0.0f) + { + s /= matrixH[nOn].Magnitude; + matrixH[nOn] = matrixH[nOn].Magnitude; + + for (var j = n + 1; j < order; j++) + { + matrixH[j * order + n] *= s.Conjugate(); + } + } + + // Inverse operation (columns). + for (var j = l + 1; j <= n; j++) + { + x = vectorV[j - 1]; + var jO = j * order; + var jm1 = j - 1; + var jm1O = jm1 * order; + var jm1Oj = jm1O + j; + for (var i = 0; i <= j; i++) + { + var jm1Oi = jm1O + i; + z = matrixH[jO + i]; + if (i != j) + { + y = matrixH[jm1Oi]; + matrixH[jm1Oi] = (x * y) + (matrixH[jm1O + j].Imaginary * z); + } + else + { + y = matrixH[jm1Oi].Real; + matrixH[jm1Oi] = new Complex32((x.Real * y.Real) - (x.Imaginary * y.Imaginary) + (matrixH[jm1O + j].Imaginary * z.Real), matrixH[jm1Oi].Imaginary); + } + + matrixH[jO + i] = (x.Conjugate() * z) - (matrixH[jm1O + j].Imaginary * y); + } + + for (var i = 0; i < order; i++) + { + y = dataEv[((j - 1) * order) + i]; + z = dataEv[(j * order) + i]; + dataEv[jm1O + i] = (x * y) + (matrixH[jm1Oj].Imaginary * z); + dataEv[jO + i] = (x.Conjugate() * z) - (matrixH[jm1Oj].Imaginary * y); + } + } + + if (s.Imaginary != 0.0f) + { + for (var i = 0; i <= n; i++) + { + matrixH[nO + i] *= s; + } + + for (var i = 0; i < order; i++) + { + dataEv[nO + i] *= s; + } + } + } + } + + // All roots found. + // Backsubstitute to find vectors of upper triangular form + norm = 0.0f; + for (var i = 0; i < order; i++) + { + for (var j = i; j < order; j++) + { + norm = Math.Max(norm, Math.Abs(matrixH[j * order + i].Real) + Math.Abs(matrixH[j * order + i].Imaginary)); + } + } + + if (order == 1) + { + return; + } + + if (norm == 0.0) + { + return; + } + + for (n = order - 1; n > 0; n--) + { + var nO = n * order; + var nOn = nO + n; + x = vectorV[n]; + matrixH[nOn] = 1.0f; + + for (var i = n - 1; i >= 0; i--) + { + z = 0.0f; + for (var j = i + 1; j <= n; j++) + { + z += matrixH[j * order + i] * matrixH[nO + j]; + } + + y = x - vectorV[i]; + if (y.Real == 0.0f && y.Imaginary == 0.0f) + { + y = eps * norm; + } + + matrixH[nO + i] = z / y; + + // Overflow control + var tr = Math.Abs(matrixH[nO + i].Real) + Math.Abs(matrixH[nO + i].Imaginary); + if ((eps * tr) * tr > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[nO + j] = matrixH[nO + j] / tr; + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j > 0; j--) + { + var jO = j * order; + for (var i = 0; i < order; i++) + { + z = Complex32.Zero; + for (var k = 0; k <= j; k++) + { + z += dataEv[(k * order) + i] * matrixH[jO + k]; + } + + dataEv[jO + i] = z; + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (EigenValues.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (EigenValues.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = EigenValues.Count; + var tmp = new Complex32[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + Complex32 value = 0.0f; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(j * order) + i].Conjugate() * input.At(i, k); + } + + value /= (float)EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + Complex32 value = 0.0f; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(i * order) + j] * tmp[i]; + } + + result.At(j, k, value); + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (EigenValues.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (EigenValues.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VH * b; + var order = EigenValues.Count; + var tmp = new Complex32[order]; + Complex32 value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(j * order) + i].Conjugate() * input[i]; + } + + value /= (float)EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(i * order) + j] * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseGramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseGramSchmidt.cs new file mode 100644 index 0000000..1d277af --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseGramSchmidt.cs @@ -0,0 +1,200 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any complex square matrix A may be decomposed as A = QR where Q is an unitary mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal sealed class DenseGramSchmidt : GramSchmidt +{ + /// + /// Initializes a new instance of the class. This object creates an unitary matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public static DenseGramSchmidt Create(Matrix matrix) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var q = (DenseMatrix)matrix.Clone(); + var r = new DenseMatrix(matrix.ColumnCount, matrix.ColumnCount); + Factorize(q.Values, q.RowCount, q.ColumnCount, r.Values); + + return new DenseGramSchmidt(q, r); + } + + DenseGramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Factorize matrix using the modified Gram-Schmidt method. + /// + /// Initial matrix. On exit is replaced by Q. + /// Number of rows in Q. + /// Number of columns in Q. + /// On exit is filled by R. + private static void Factorize(Complex32[] q, int rowsQ, int columnsQ, Complex32[] r) + { + for (var k = 0; k < columnsQ; k++) + { + var norm = 0.0f; + for (var i = 0; i < rowsQ; i++) + { + norm += q[(k * rowsQ) + i].Magnitude * q[(k * rowsQ) + i].Magnitude; + } + + norm = (float)Math.Sqrt(norm); + if (norm == 0.0f) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r[(k * columnsQ) + k] = norm; + for (var i = 0; i < rowsQ; i++) + { + q[(k * rowsQ) + i] /= norm; + } + + for (var j = k + 1; j < columnsQ; j++) + { + var k1 = k; + var j1 = j; + + var dot = Complex32.Zero; + for (var index = 0; index < rowsQ; index++) + { + dot += q[(k1 * rowsQ) + index].Conjugate() * q[(j1 * rowsQ) + index]; + } + + r[(j * columnsQ) + k] = dot; + for (var i = 0; i < rowsQ; i++) + { + var value = q[(j * rowsQ) + i] - (q[(k * rowsQ) + i] * dot); + q[(j * rowsQ) + i] = value; + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (Q.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, null, dinput.Values, input.ColumnCount, dresult.Values, QRMethod.Thin); + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (Q.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(Q, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, null, dinput.Values, 1, dresult.Values, QRMethod.Thin); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseLU.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseLU.cs new file mode 100644 index 0000000..380a5ed --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseLU.cs @@ -0,0 +1,196 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal sealed class DenseLU : LU +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public static DenseLU Create(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var pivots = new int[matrix.RowCount]; + + // Create a new matrix for the LU factors, then perform factorization (while overwriting). + var factors = (DenseMatrix)matrix.Clone(); + LinearAlgebraControl.Provider.LUFactor(factors.Values, factors.RowCount, pivots); + + return new DenseLU(factors, pivots); + } + + DenseLU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do LU factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do LU factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + Array.Copy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + LinearAlgebraControl.Provider.LUSolveFactored(input.ColumnCount, dfactors.Values, dfactors.RowCount, Pivots, dresult.Values); + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do LU factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do LU factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + Array.Copy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + LinearAlgebraControl.Provider.LUSolveFactored(1, dfactors.Values, dfactors.RowCount, Pivots, dresult.Values); + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var result = (DenseMatrix)Factors.Clone(); + LinearAlgebraControl.Provider.LUInverseFactored(result.Values, result.RowCount, Pivots); + return result; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseQR.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseQR.cs new file mode 100644 index 0000000..3d11a5d --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseQR.cs @@ -0,0 +1,171 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// +internal sealed class DenseQR : QR +{ + /// + /// Gets or sets Tau vector. Contains additional information on Q - used for native solver. + /// + Complex32[] Tau { get; set; } + + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// The QR factorization method to use. + /// If is null. + /// If row count is less then column count + public static DenseQR Create(DenseMatrix matrix, QRMethod method = QRMethod.Full) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var tau = new Complex32[Math.Min(matrix.RowCount, matrix.ColumnCount)]; + Matrix q; + Matrix r; + + if (method == QRMethod.Full) + { + r = matrix.Clone(); + q = new DenseMatrix(matrix.RowCount); + LinearAlgebraControl.Provider.QRFactor(((DenseMatrix)r).Values, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)q).Values, tau); + } + else + { + q = matrix.Clone(); + r = new DenseMatrix(matrix.ColumnCount); + LinearAlgebraControl.Provider.ThinQRFactor(((DenseMatrix)q).Values, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)r).Values, tau); + } + + return new DenseQR(q, r, method, tau); + } + + DenseQR(Matrix q, Matrix rFull, QRMethod method, Complex32[] tau) + : base(q, rFull, method) + { + Tau = tau; + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (FullR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do QR factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do QR factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, Tau, dinput.Values, input.ColumnCount, dresult.Values, Method); + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (FullR.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(FullR, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do QR factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do QR factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, Tau, dinput.Values, 1, dresult.Values, Method); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseSvd.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseSvd.cs new file mode 100644 index 0000000..4ea701f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/DenseSvd.cs @@ -0,0 +1,163 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD) for . +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal sealed class DenseSvd : Svd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// If SVD algorithm failed to converge with matrix . + public static DenseSvd Create(DenseMatrix matrix, bool computeVectors) + { + var nm = Math.Min(matrix.RowCount, matrix.ColumnCount); + var s = new DenseVector(nm); + var u = new DenseMatrix(matrix.RowCount); + var vt = new DenseMatrix(matrix.ColumnCount); + LinearAlgebraControl.Provider.SingularValueDecomposition(computeVectors, ((DenseMatrix)matrix.Clone()).Values, matrix.RowCount, matrix.ColumnCount, s.Values, u.Values, vt.Values); + + return new DenseSvd(s, u, vt, computeVectors); + } + + DenseSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (U.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.SvdSolveFactored(U.RowCount, VT.ColumnCount, ((DenseVector)S).Values, ((DenseMatrix)U).Values, ((DenseMatrix)VT).Values, dinput.Values, input.ColumnCount, dresult.Values); + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (U.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VT.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(VT, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.SvdSolveFactored(U.RowCount, VT.ColumnCount, ((DenseVector)S).Values, ((DenseMatrix)U).Values, ((DenseMatrix)VT).Values, dinput.Values, 1, dresult.Values); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Evd.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Evd.cs new file mode 100644 index 0000000..c37281f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Evd.cs @@ -0,0 +1,126 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal abstract class Evd : Evd +{ + protected Evd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Gets the absolute value of determinant of the square matrix for which the EVD was computed. + /// + public override Complex32 Determinant + { + get + { + var det = Complex.One; + for (var i = 0; i < EigenValues.Count; i++) + { + det *= EigenValues[i]; + + if (((Complex32)EigenValues[i]).AlmostEqual(Complex32.Zero)) + { + return 0; + } + } + + return new Complex32(Convert.ToSingle(det.Magnitude), 0.0f); + } + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public override int Rank + { + get + { + var rank = 0; + for (var i = 0; i < EigenValues.Count; i++) + { + if (((Complex32)EigenValues[i]).AlmostEqual(Complex32.Zero)) + { + continue; + } + + rank++; + } + + return rank; + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < EigenValues.Count; i++) + { + if (EigenValues[i].AlmostEqual(Complex.Zero)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/GramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/GramSchmidt.cs new file mode 100644 index 0000000..0fb60f9 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/GramSchmidt.cs @@ -0,0 +1,98 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal abstract class GramSchmidt : GramSchmidt +{ + protected GramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public override Complex32 Determinant + { + get + { + if (FullR.RowCount != FullR.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = Complex32.One; + for (var i = 0; i < FullR.ColumnCount; i++) + { + det *= FullR.At(i, i); + if (FullR.At(i, i).Magnitude.AlmostEqual(0.0f)) + { + return 0; + } + } + + return det.Magnitude; + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < FullR.ColumnCount; i++) + { + if (FullR.At(i, i).Magnitude.AlmostEqual(0.0f)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/LU.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/LU.cs new file mode 100644 index 0000000..3b6b498 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/LU.cs @@ -0,0 +1,76 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// In the Math.Net implementation we also store a set of pivot elements for increased +/// numerical stability. The pivot elements encode a permutation matrix P such that P*A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal abstract class LU : LU +{ + protected LU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Gets the determinant of the matrix for which the LU factorization was computed. + /// + public override Complex32 Determinant + { + get + { + var det = Complex32.One; + for (var j = 0; j < Factors.RowCount; j++) + { + if (Pivots[j] != j) + { + det *= -Factors.At(j, j); + } + else + { + det *= Factors.At(j, j); + } + } + + return det; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/QR.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/QR.cs new file mode 100644 index 0000000..90dfdf6 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/QR.cs @@ -0,0 +1,103 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A (m x n) may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// If a factorization is performed, the resulting Q matrix is an m x m matrix +/// and the R matrix is an m x n matrix. If a factorization is performed, the +/// resulting Q matrix is an m x n matrix and the R matrix is an n x n matrix. +/// +internal abstract class QR : QR +{ + protected QR(Matrix q, Matrix rFull, QRMethod method) + : base(q, rFull, method) + { + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public override Complex32 Determinant + { + get + { + if (FullR.RowCount != FullR.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = Complex32.One; + for (var i = 0; i < FullR.ColumnCount; i++) + { + det *= FullR.At(i, i); + if (FullR.At(i, i).Magnitude.AlmostEqual(0.0f)) + { + return 0; + } + } + + return det.Magnitude; + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < FullR.ColumnCount; i++) + { + if (FullR.At(i, i).Magnitude.AlmostEqual(0.0f)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Svd.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Svd.cs new file mode 100644 index 0000000..0fe544b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/Svd.cs @@ -0,0 +1,124 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD). +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal abstract class Svd : Svd +{ + protected Svd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public override int Rank + { + get + { + double tolerance = Precision.EpsilonOf(S.AbsoluteMaximum().Magnitude) * Math.Max(U.RowCount, VT.RowCount); + return S.Count(t => t.Magnitude > tolerance); + } + } + + /// + /// Gets the two norm of the . + /// + /// The 2-norm of the . + public override double L2Norm + { + get + { + return S[0].Magnitude; + } + } + + /// + /// Gets the condition number max(S) / min(S) + /// + /// The condition number. + public override Complex32 ConditionNumber + { + get + { + var tmp = Math.Min(U.RowCount, VT.ColumnCount) - 1; + return S[0].Magnitude / S[tmp].Magnitude; + } + } + + /// + /// Gets the determinant of the square matrix for which the SVD was computed. + /// + public override Complex32 Determinant + { + get + { + if (U.RowCount != VT.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = Complex32.One; + foreach (var value in S) + { + det *= value; + if (value.Magnitude.AlmostEqual(0.0f)) + { + return 0; + } + } + + return det.Magnitude; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserCholesky.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserCholesky.cs new file mode 100644 index 0000000..f2a8adf --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserCholesky.cs @@ -0,0 +1,276 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization for user matrices. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal sealed class UserCholesky : Cholesky +{ + /// + /// Computes the Cholesky factorization in-place. + /// + /// On entry, the matrix to factor. On exit, the Cholesky factor matrix + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + static void DoCholesky(Matrix factor) + { + if (factor.RowCount != factor.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var tmpColumn = new Complex32[factor.RowCount]; + + // Main loop - along the diagonal + for (var ij = 0; ij < factor.RowCount; ij++) + { + // "Pivot" element + var tmpVal = factor.At(ij, ij); + + if (tmpVal.Real > 0.0) + { + tmpVal = tmpVal.SquareRoot(); + factor.At(ij, ij, tmpVal); + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < factor.RowCount; i++) + { + factor.At(i, ij, factor.At(i, ij) / tmpVal); + tmpColumn[i] = factor.At(i, ij); + } + + // Remaining columns, below the diagonal + DoCholeskyStep(factor, factor.RowCount, ij + 1, factor.RowCount, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (var i = ij + 1; i < factor.RowCount; i++) + { + factor.At(ij, i, Complex32.Zero); + } + } + } + + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public static UserCholesky Create(Matrix matrix) + { + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var factor = matrix.Clone(); + DoCholesky(factor); + return new UserCholesky(factor); + } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + /// If does not have the same dimensions as the existing factor. + public override void Factorize(Matrix matrix) + { + if (matrix.RowCount != Factor.RowCount || matrix.ColumnCount != Factor.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix, Factor); + } + + matrix.CopyTo(Factor); + DoCholesky(Factor); + } + + UserCholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(Matrix data, int rowDim, int firstCol, int colLimit, Complex32[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data.At(i, j, data.At(i, j) - (multipliers[i] * tmpVal.Conjugate())); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + input.CopyTo(result); + var order = Factor.RowCount; + + for (var c = 0; c < result.ColumnCount; c++) + { + // Solve L*Y = B; + Complex32 sum; + for (var i = 0; i < order; i++) + { + sum = result.At(i, c); + for (var k = i - 1; k >= 0; k--) + { + sum -= Factor.At(i, k) * result.At(k, c); + } + + result.At(i, c, sum / Factor.At(i, i)); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result.At(i, c); + for (var k = i + 1; k < order; k++) + { + sum -= Factor.At(k, i).Conjugate() * result.At(k, c); + } + + result.At(i, c, sum / Factor.At(i, i)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + input.CopyTo(result); + var order = Factor.RowCount; + + // Solve L*Y = B; + Complex32 sum; + for (var i = 0; i < order; i++) + { + sum = result[i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= Factor.At(i, k) * result[k]; + } + + result[i] = sum / Factor.At(i, i); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result[i]; + for (var k = i + 1; k < order; k++) + { + sum -= Factor.At(k, i).Conjugate() * result[k]; + } + + result[i] = sum / Factor.At(i, i); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs new file mode 100644 index 0000000..c9b75e4 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs @@ -0,0 +1,944 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a complex matrix. +/// +/// +/// If A is Hermitian, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is Hermitian. +/// I.e. A = V*D*V' and V*VH=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal sealed class UserEvd : Evd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If it is known whether the matrix is symmetric or not the routine can skip checking it itself. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public static UserEvd Create(Matrix matrix, Symmetricity symmetricity) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matrices for eigenvalues and eigenvectors + var eigenVectors = DenseMatrix.CreateIdentity(order); + var blockDiagonal = Matrix.Build.SameAs(matrix, order, order); + var eigenValues = new LinearAlgebra.Complex.DenseVector(order); + + bool isSymmetric; + switch (symmetricity) + { + case Symmetricity.Hermitian: + isSymmetric = true; + break; + case Symmetricity.Asymmetric: + isSymmetric = false; + break; + default: + isSymmetric = matrix.IsHermitian(); + break; + } + + if (isSymmetric) + { + var matrixCopy = matrix.ToArray(); + var tau = new Complex32[order]; + var d = new float[order]; + var e = new float[order]; + + SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + SymmetricDiagonalize(eigenVectors, d, e, order); + SymmetricUntridiagonalize(eigenVectors, matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + eigenValues[i] = new Complex(d[i], e[i]); + } + } + else + { + var matrixH = matrix.ToArray(); + NonsymmetricReduceToHessenberg(eigenVectors, matrixH, order); + NonsymmetricReduceHessenberToRealSchur(eigenVectors, eigenValues, matrixH, order); + } + + for (var i = 0; i < eigenValues.Count; i++) + { + blockDiagonal.At(i, i, (Complex32)eigenValues[i]); + } + + return new UserEvd(eigenVectors, eigenValues, blockDiagonal, isSymmetric); + } + + UserEvd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Reduces a complex Hermitian matrix to a real symmetric tridiagonal matrix using unitary similarity transformations. + /// + /// Source matrix to reduce + /// Output: Arrays for internal storage of real parts of eigenvalues + /// Output: Arrays for internal storage of imaginary parts of eigenvalues + /// Output: Arrays that contains further information about the transformations. + /// Order of initial matrix + /// This is derived from the Algol procedures HTRIDI by + /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void SymmetricTridiagonalize(Complex32[,] matrixA, float[] d, float[] e, Complex32[] tau, int order) + { + float hh; + tau[order - 1] = Complex32.One; + + for (var i = 0; i < order; i++) + { + d[i] = matrixA[i, i].Real; + } + + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0f; + var h = 0.0f; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(matrixA[i, k].Real) + Math.Abs(matrixA[i, k].Imaginary); + } + + if (scale == 0.0f) + { + tau[i - 1] = Complex32.One; + e[i] = 0.0f; + } + else + { + for (var k = 0; k < i; k++) + { + matrixA[i, k] /= scale; + h += matrixA[i, k].MagnitudeSquared; + } + + Complex32 g = (float)Math.Sqrt(h); + e[i] = scale * g.Real; + + Complex32 temp; + var f = matrixA[i, i - 1]; + if (f.Magnitude != 0) + { + temp = -(matrixA[i, i - 1].Conjugate() * tau[i].Conjugate()) / f.Magnitude; + h += f.Magnitude * g.Real; + g = 1.0f + (g / f.Magnitude); + matrixA[i, i - 1] *= g; + } + else + { + temp = -tau[i].Conjugate(); + matrixA[i, i - 1] = g; + } + + if ((f.Magnitude == 0) || (i != 1)) + { + f = Complex32.Zero; + for (var j = 0; j < i; j++) + { + var tmp = Complex32.Zero; + + // Form element of A*U. + for (var k = 0; k <= j; k++) + { + tmp += matrixA[j, k] * matrixA[i, k].Conjugate(); + } + + for (var k = j + 1; k <= i - 1; k++) + { + tmp += matrixA[k, j].Conjugate() * matrixA[i, k].Conjugate(); + } + + // Form element of P + tau[j] = tmp / h; + f += (tmp / h) * matrixA[i, j]; + } + + hh = f.Real / (h + h); + + // Form the reduced A. + for (var j = 0; j < i; j++) + { + f = matrixA[i, j].Conjugate(); + g = tau[j] - (hh * f); + tau[j] = g.Conjugate(); + + for (var k = 0; k <= j; k++) + { + matrixA[j, k] -= (f * tau[k]) + (g * matrixA[i, k]); + } + } + } + + for (var k = 0; k < i; k++) + { + matrixA[i, k] *= scale; + } + + tau[i - 1] = temp.Conjugate(); + } + + hh = d[i]; + d[i] = matrixA[i, i].Real; + matrixA[i, i] = new Complex32(hh, scale * (float)Math.Sqrt(h)); + } + + hh = d[0]; + d[0] = matrixA[0, 0].Real; + matrixA[0, 0] = hh; + e[0] = 0.0f; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// The eigen vectors to work on. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + static void SymmetricDiagonalize(Matrix eigenVectors, float[] d, float[] e, int order) + { + const int maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0f; + + var f = 0.0f; + var tst1 = 0.0f; + var eps = Precision.DoublePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0f * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0f); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0f; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0f; + var s2 = 0.0f; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = eigenVectors.At(k, i + 1).Real; + eigenVectors.At(k, i + 1, (s * eigenVectors.At(k, i).Real) + (c * h)); + eigenVectors.At(k, i, (c * eigenVectors.At(k, i).Real) - (s * h)); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + } while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0f; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = eigenVectors.At(j, i).Real; + eigenVectors.At(j, i, eigenVectors.At(j, k)); + eigenVectors.At(j, k, p); + } + } + } + } + + /// + /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation + /// + /// The eigen vectors to work on. + /// Previously tridiagonalized matrix by . + /// Contains further information about the transformations + /// Input matrix order + /// This is derived from the Algol procedures HTRIBK, by + /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void SymmetricUntridiagonalize(Matrix eigenVectors, Complex32[,] matrixA, Complex32[] tau, int order) + { + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + eigenVectors.At(i, j, eigenVectors.At(i, j).Real * tau[i].Conjugate()); + } + } + + // Recover and apply the Householder matrices. + for (var i = 1; i < order; i++) + { + var h = matrixA[i, i].Imaginary; + if (h != 0) + { + for (var j = 0; j < order; j++) + { + var s = Complex32.Zero; + for (var k = 0; k < i; k++) + { + s += eigenVectors.At(k, j) * matrixA[i, k]; + } + + s = (s / h) / h; + + for (var k = 0; k < i; k++) + { + eigenVectors.At(k, j, eigenVectors.At(k, j) - s * matrixA[i, k].Conjugate()); + } + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// The eigen vectors to work on. + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + static void NonsymmetricReduceToHessenberg(Matrix eigenVectors, Complex32[,] matrixH, int order) + { + var ort = new Complex32[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0f; + for (var i = m; i < order; i++) + { + scale += Math.Abs(matrixH[i, m - 1].Real) + Math.Abs(matrixH[i, m - 1].Imaginary); + } + + if (scale != 0.0f) + { + // Compute Householder transformation. + var h = 0.0f; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i].MagnitudeSquared; + } + + var g = (float)Math.Sqrt(h); + if (ort[m].Magnitude != 0) + { + h = h + (ort[m].Magnitude * g); + g /= ort[m].Magnitude; + ort[m] = (1.0f + g) * ort[m]; + } + else + { + ort[m] = g; + matrixH[m, m - 1] = scale; + } + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = Complex32.Zero; + for (var i = order - 1; i >= m; i--) + { + f += ort[i].Conjugate() * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = Complex32.Zero; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[i, j] -= f * ort[j].Conjugate(); + } + } + + ort[m] = scale * ort[m]; + matrixH[m, m - 1] *= -g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + eigenVectors.At(i, j, i == j ? Complex32.One : Complex32.Zero); + } + } + + for (var m = order - 2; m >= 1; m--) + { + if (matrixH[m, m - 1] != Complex32.Zero && ort[m] != Complex32.Zero) + { + var norm = (matrixH[m, m - 1].Real * ort[m].Real) + (matrixH[m, m - 1].Imaginary * ort[m].Imaginary); + + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j < order; j++) + { + var g = Complex32.Zero; + for (var i = m; i < order; i++) + { + g += ort[i].Conjugate() * eigenVectors.At(i, j); + } + + // Double division avoids possible underflow + g /= norm; + for (var i = m; i < order; i++) + { + eigenVectors.At(i, j, eigenVectors.At(i, j) + g * ort[i]); + } + } + } + } + + // Create real subdiagonal elements. + for (var i = 1; i < order; i++) + { + if (matrixH[i, i - 1].Imaginary != 0.0f) + { + var y = matrixH[i, i - 1] / matrixH[i, i - 1].Magnitude; + matrixH[i, i - 1] = matrixH[i, i - 1].Magnitude; + for (var j = i; j < order; j++) + { + matrixH[i, j] *= y.Conjugate(); + } + + for (var j = 0; j <= Math.Min(i + 1, order - 1); j++) + { + matrixH[j, i] *= y; + } + + for (var j = 0; j < order; j++) + { + eigenVectors.At(j, i, eigenVectors.At(j, i) * y); + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// The eigen vectors to work on. + /// The eigen values to work on. + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void NonsymmetricReduceHessenberToRealSchur(Matrix eigenVectors, Vector eigenValues, Complex32[,] matrixH, int order) + { + // Initialize + var n = order - 1; + var eps = (float)Precision.SinglePrecision; + + float norm; + Complex32 x, y, z, exshift = Complex32.Zero; + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + var tst1 = Math.Abs(matrixH[l - 1, l - 1].Real) + Math.Abs(matrixH[l - 1, l - 1].Imaginary) + Math.Abs(matrixH[l, l].Real) + Math.Abs(matrixH[l, l].Imaginary); + if (Math.Abs(matrixH[l, l - 1].Real) < eps * tst1) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] += exshift; + eigenValues[n] = matrixH[n, n].ToComplex(); + n--; + iter = 0; + } + else + { + // Form shift + Complex32 s; + if (iter != 10 && iter != 20) + { + s = matrixH[n, n]; + x = matrixH[n - 1, n] * matrixH[n, n - 1].Real; + + if (x.Real != 0.0f || x.Imaginary != 0.0f) + { + y = (matrixH[n - 1, n - 1] - s) / 2.0f; + z = ((y * y) + x).SquareRoot(); + if ((y.Real * z.Real) + (y.Imaginary * z.Imaginary) < 0.0f) + { + z *= -1.0f; + } + + x /= y + z; + s = s - x; + } + } + else + { + // Form exceptional shift + s = Math.Abs(matrixH[n, n - 1].Real) + Math.Abs(matrixH[n - 1, n - 2].Real); + } + + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + iter++; + + // Reduce to triangle (rows) + for (var i = l + 1; i <= n; i++) + { + s = matrixH[i, i - 1].Real; + norm = SpecialFunctions.Hypotenuse(matrixH[i - 1, i - 1].Magnitude, s.Real); + x = matrixH[i - 1, i - 1] / norm; + eigenValues[i - 1] = x.ToComplex(); + matrixH[i - 1, i - 1] = norm; + matrixH[i, i - 1] = new Complex32(0.0f, s.Real / norm); + + for (var j = i; j < order; j++) + { + y = matrixH[i - 1, j]; + z = matrixH[i, j]; + matrixH[i - 1, j] = (x.Conjugate() * y) + (matrixH[i, i - 1].Imaginary * z); + matrixH[i, j] = (x * z) - (matrixH[i, i - 1].Imaginary * y); + } + } + + s = matrixH[n, n]; + if (s.Imaginary != 0.0f) + { + s /= matrixH[n, n].Magnitude; + matrixH[n, n] = matrixH[n, n].Magnitude; + + for (var j = n + 1; j < order; j++) + { + matrixH[n, j] *= s.Conjugate(); + } + } + + // Inverse operation (columns). + for (var j = l + 1; j <= n; j++) + { + x = (Complex32)eigenValues[j - 1]; + for (var i = 0; i <= j; i++) + { + z = matrixH[i, j]; + if (i != j) + { + y = matrixH[i, j - 1]; + matrixH[i, j - 1] = (x * y) + (matrixH[j, j - 1].Imaginary * z); + } + else + { + y = matrixH[i, j - 1].Real; + matrixH[i, j - 1] = new Complex32((x.Real * y.Real) - (x.Imaginary * y.Imaginary) + (matrixH[j, j - 1].Imaginary * z.Real), matrixH[i, j - 1].Imaginary); + } + + matrixH[i, j] = (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y); + } + + for (var i = 0; i < order; i++) + { + y = eigenVectors.At(i, j - 1); + z = eigenVectors.At(i, j); + eigenVectors.At(i, j - 1, (x * y) + (matrixH[j, j - 1].Imaginary * z)); + eigenVectors.At(i, j, (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y)); + } + } + + if (s.Imaginary != 0.0f) + { + for (var i = 0; i <= n; i++) + { + matrixH[i, n] *= s; + } + + for (var i = 0; i < order; i++) + { + eigenVectors.At(i, n, eigenVectors.At(i, n) * s); + } + } + } + } + + // All roots found. + // Backsubstitute to find vectors of upper triangular form + norm = 0.0f; + for (var i = 0; i < order; i++) + { + for (var j = i; j < order; j++) + { + norm = Math.Max(norm, Math.Abs(matrixH[i, j].Real) + Math.Abs(matrixH[i, j].Imaginary)); + } + } + + if (order == 1) + { + return; + } + + if (norm == 0.0f) + { + return; + } + + for (n = order - 1; n > 0; n--) + { + x = (Complex32)eigenValues[n]; + matrixH[n, n] = 1.0f; + + for (var i = n - 1; i >= 0; i--) + { + z = 0.0f; + for (var j = i + 1; j <= n; j++) + { + z += matrixH[i, j] * matrixH[j, n]; + } + + y = x - (Complex32)eigenValues[i]; + if (y.Real == 0.0f && y.Imaginary == 0.0f) + { + y = eps * norm; + } + + matrixH[i, n] = z / y; + + // Overflow control + var tr = Math.Abs(matrixH[i, n].Real) + Math.Abs(matrixH[i, n].Imaginary); + if ((eps * tr) * tr > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / tr; + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j > 0; j--) + { + for (var i = 0; i < order; i++) + { + z = Complex32.Zero; + for (var k = 0; k <= j; k++) + { + z += eigenVectors.At(i, k) * matrixH[k, j]; + } + + eigenVectors.At(i, j, z); + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (EigenValues.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (EigenValues.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = EigenValues.Count; + var tmp = new Complex32[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + Complex32 value = 0.0f; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(i, j).Conjugate() * input.At(i, k); + } + + value /= (float)EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + Complex32 value = 0.0f; + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(j, i) * tmp[i]; + } + + result.At(j, k, value); + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (EigenValues.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (EigenValues.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VH * b; + var order = EigenValues.Count; + var tmp = new Complex32[order]; + Complex32 value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(i, j).Conjugate() * input[i]; + } + + value /= (float)EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (int i = 0; i < order; i++) + { + value += EigenVectors.At(j, i) * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserGramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserGramSchmidt.cs new file mode 100644 index 0000000..993b59b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserGramSchmidt.cs @@ -0,0 +1,233 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any complex square matrix A may be decomposed as A = QR where Q is an unitary mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal sealed class UserGramSchmidt : GramSchmidt +{ + /// + /// Initializes a new instance of the class. This object creates an unitary matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public static UserGramSchmidt Create(Matrix matrix) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var q = matrix.Clone(); + var r = Matrix.Build.SameAs(matrix, matrix.ColumnCount, matrix.ColumnCount, fullyMutable: true); + + for (var k = 0; k < q.ColumnCount; k++) + { + var norm = (float)q.Column(k).L2Norm(); + if (norm == 0f) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r.At(k, k, norm); + for (var i = 0; i < q.RowCount; i++) + { + q.At(i, k, (q.At(i, k) / norm)); + } + + for (var j = k + 1; j < q.ColumnCount; j++) + { + var dot = Complex32.Zero; + for (int i = 0; i < q.RowCount; i++) + { + dot += q.Column(k)[i].Conjugate() * q.Column(j)[i]; + } + + r.At(k, j, dot); + for (var i = 0; i < q.RowCount; i++) + { + var value = q.At(i, j) - (q.At(i, k) * dot); + q.At(i, j, value); + } + } + } + + return new UserGramSchmidt(q, r); + } + + UserGramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (Q.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new Complex32[Q.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < Q.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < Q.ColumnCount; i++) + { + var s = Complex32.Zero; + for (var k = 0; k < Q.RowCount; k++) + { + s += Q.At(k, i).Conjugate() * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = Q.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / FullR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * FullR.At(i, k))); + } + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (Q.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(Q, result); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new Complex32[Q.RowCount]; + for (var k = 0; k < Q.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < Q.ColumnCount; i++) + { + var s = Complex32.Zero; + for (var k = 0; k < Q.RowCount; k++) + { + s += Q.At(k, i).Conjugate() * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = Q.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= FullR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * FullR.At(i, k); + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserLU.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserLU.cs new file mode 100644 index 0000000..1354502 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserLU.cs @@ -0,0 +1,308 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal sealed class UserLU : LU +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public static UserLU Create(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var order = matrix.RowCount; + var factors = matrix.Clone(); + var pivots = new int[order]; + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + pivots[i] = i; + } + + var vectorLUcolj = new Complex32[order]; + for (var j = 0; j < order; j++) + { + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vectorLUcolj[i] = factors.At(i, j); + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + var kmax = Math.Min(i, j); + var s = Complex32.Zero; + for (var k = 0; k < kmax; k++) + { + s += factors.At(i, k) * vectorLUcolj[k]; + } + + vectorLUcolj[i] -= s; + factors.At(i, j, vectorLUcolj[i]); + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (vectorLUcolj[i].Magnitude > vectorLUcolj[p].Magnitude) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var temp = factors.At(p, k); + factors.At(p, k, factors.At(j, k)); + factors.At(j, k, temp); + } + + pivots[j] = p; + } + + // Compute multipliers. + if (j < order & factors.At(j, j) != 0.0f) + { + for (var i = j + 1; i < order; i++) + { + factors.At(i, j, (factors.At(i, j) / factors.At(j, j))); + } + } + } + + return new UserLU(factors, pivots); + } + + UserLU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(p, j); + result.At(p, j, result.At(i, j)); + result.At(i, j, temp); + } + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + for (var j = 0; j < result.ColumnCount; j++) + { + result.At(k, j, (result.At(k, j) / Factors.At(k, k))); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + var temp = result[p]; + result[p] = result[i]; + result[i] = temp; + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + result[k] /= Factors.At(k, k); + for (var i = 0; i < k; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var order = Factors.RowCount; + var inverse = Matrix.Build.SameAs(Factors, order, order); + for (var i = 0; i < order; i++) + { + inverse.At(i, i, 1.0f); + } + + return Solve(inverse); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserQR.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserQR.cs new file mode 100644 index 0000000..30a173a --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserQR.cs @@ -0,0 +1,351 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// +internal sealed class UserQR : QR +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// The QR factorization method to use. + /// If is null. + public static UserQR Create(Matrix matrix, QRMethod method = QRMethod.Full) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + Matrix q; + Matrix r; + + var minmn = Math.Min(matrix.RowCount, matrix.ColumnCount); + var u = new Complex32[minmn][]; + + if (method == QRMethod.Full) + { + r = matrix.Clone(); + q = Matrix.Build.SameAs(matrix, matrix.RowCount, matrix.RowCount, fullyMutable: true); + + for (var i = 0; i < matrix.RowCount; i++) + { + q.At(i, i, 1.0f); + } + + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(r, i, i); + ComputeQR(u[i], r, i, matrix.RowCount, i + 1, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], q, i, matrix.RowCount, i, matrix.RowCount, Control.MaxDegreeOfParallelism); + } + } + else + { + q = matrix.Clone(); + + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(q, i, i); + ComputeQR(u[i], q, i, matrix.RowCount, i + 1, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + + r = q.SubMatrix(0, matrix.ColumnCount, 0, matrix.ColumnCount); + q.Clear(); + + for (var i = 0; i < matrix.ColumnCount; i++) + { + q.At(i, i, 1.0f); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], q, i, matrix.RowCount, i, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + } + + return new UserQR(q, r, method); + } + + UserQR(Matrix q, Matrix rFull, QRMethod method) + : base(q, rFull, method) + { + } + + /// + /// Generate column from initial matrix to work array + /// + /// Initial matrix + /// The first row + /// Column index + /// Generated vector + static Complex32[] GenerateColumn(Matrix a, int row, int column) + { + var ru = a.RowCount - row; + var u = new Complex32[ru]; + + for (var i = row; i < a.RowCount; i++) + { + u[i - row] = a.At(i, column); + a.At(i, column, 0.0f); + } + + var norm = u.Aggregate(Complex32.Zero, (current, t) => current + (t.Magnitude * t.Magnitude)); + norm = norm.SquareRoot(); + + if (row == a.RowCount - 1 || norm.Magnitude == 0) + { + a.At(row, column, -u[0]); + u[0] = (float)Constants.Sqrt2; + return u; + } + + if (u[0].Magnitude != 0.0f) + { + norm = norm.Magnitude * (u[0] / u[0].Magnitude); + } + + a.At(row, column, -norm); + + for (var i = 0; i < ru; i++) + { + u[i] /= norm; + } + + u[0] += 1.0f; + + var s = (1.0f / u[0]).SquareRoot(); + for (var i = 0; i < ru; i++) + { + u[i] = u[i].Conjugate() * s; + } + + return u; + } + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Q or R matrices + /// The first row + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(Complex32[] u, Matrix a, int rowStart, int rowDim, int columnStart, int columnDim, int availableCores) + { + if (rowDim < rowStart || columnDim < columnStart) + { + return; + } + + var tmpColCount = columnDim - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(u, a, rowStart, rowDim, columnStart, tmpSplit, tmpCores), + () => ComputeQR(u, a, rowStart, rowDim, tmpSplit, columnDim, tmpCores)); + } + else + { + for (var j = columnStart; j < columnDim; j++) + { + var scale = Complex32.Zero; + for (var i = rowStart; i < rowDim; i++) + { + scale += u[i - rowStart] * a.At(i, j); + } + + for (var i = rowStart; i < rowDim; i++) + { + a.At(i, j, a.At(i, j) - (u[i - rowStart].Conjugate() * scale)); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (FullR.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (FullR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new Complex32[FullR.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < FullR.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < FullR.RowCount; i++) + { + var s = Complex32.Zero; + for (var k = 0; k < FullR.RowCount; k++) + { + s += Q.At(k, i).Conjugate() * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = FullR.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / FullR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * FullR.At(i, k))); + } + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + for (var j = 0; j < inputCopy.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (FullR.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (FullR.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(FullR, result); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new Complex32[FullR.RowCount]; + for (var k = 0; k < FullR.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < FullR.RowCount; i++) + { + var s = Complex32.Zero; + for (var k = 0; k < FullR.RowCount; k++) + { + s += Q.At(k, i).Conjugate() * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = FullR.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= FullR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * FullR.At(i, k); + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserSvd.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserSvd.cs new file mode 100644 index 0000000..5b623fb --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Factorization/UserSvd.cs @@ -0,0 +1,919 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization; + +using Numerics; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD) for . +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal sealed class UserSvd : Svd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// + public static UserSvd Create(Matrix matrix, bool computeVectors) + { + var nm = Math.Min(matrix.RowCount + 1, matrix.ColumnCount); + var matrixCopy = matrix.Clone(); + + var s = Vector.Build.SameAs(matrixCopy, nm); + var u = Matrix.Build.SameAs(matrixCopy, matrixCopy.RowCount, matrixCopy.RowCount, fullyMutable: true); + var vt = Matrix.Build.SameAs(matrixCopy, matrixCopy.ColumnCount, matrixCopy.ColumnCount, fullyMutable: true); + + const int maxiter = 1000; + var e = new Complex32[matrixCopy.ColumnCount]; + var work = new Complex32[matrixCopy.RowCount]; + + int i, j; + int l, lp1; + Complex32 t; + + var ncu = matrixCopy.RowCount; + + // Reduce matrixCopy to bidiagonal form, storing the diagonal elements + // In s and the super-diagonal elements in e. + var nct = Math.Min(matrixCopy.RowCount - 1, matrixCopy.ColumnCount); + var nrt = Math.Max(0, Math.Min(matrixCopy.ColumnCount - 2, matrixCopy.RowCount)); + var lu = Math.Max(nct, nrt); + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and place the l-th diagonal in VectorS[l]. + s[l] = Cnrm2Column(matrixCopy, matrixCopy.RowCount, l, l); + if (s[l].Magnitude != 0.0f) + { + if (matrixCopy.At(l, l).Magnitude != 0.0f) + { + s[l] = Csign(s[l], matrixCopy.At(l, l)); + } + + CscalColumn(matrixCopy, matrixCopy.RowCount, l, l, 1.0f / s[l]); + matrixCopy.At(l, l, (Complex32.One + matrixCopy.At(l, l))); + } + + s[l] = -s[l]; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (l < nct) + { + if (s[l].Magnitude != 0.0f) + { + // Apply the transformation. + t = -Cdotc(matrixCopy, matrixCopy.RowCount, l, j, l) / matrixCopy.At(l, l); + if (t != Complex32.Zero) + { + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (t * matrixCopy.At(ii, l))); + } + } + } + } + + // Place the l-th row of matrixCopy into e for the + // Subsequent calculation of the row transformation. + e[j] = matrixCopy.At(l, j).Conjugate(); + } + + if (computeVectors && l < nct) + { + // Place the transformation in u for subsequent back multiplication. + for (i = l; i < matrixCopy.RowCount; i++) + { + u.At(i, l, matrixCopy.At(i, l)); + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = Cnrm2Vector(e, lp1); + e[l] = enorm; + if (e[l].Magnitude != 0.0f) + { + if (e[lp1].Magnitude != 0.0f) + { + e[l] = Csign(e[l], e[lp1]); + } + + CscalVector(e, lp1, 1.0f / e[l]); + e[lp1] = Complex32.One + e[lp1]; + } + + e[l] = -e[l].Conjugate(); + if (lp1 < matrixCopy.RowCount && e[l].Magnitude != 0.0f) + { + // Apply the transformation. + for (i = lp1; i < matrixCopy.RowCount; i++) + { + work[i] = Complex32.Zero; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (e[j] != Complex32.Zero) + { + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + work[ii] += e[j] * matrixCopy.At(ii, j); + } + } + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + var ww = (-e[j] / e[lp1]).Conjugate(); + if (ww != Complex32.Zero) + { + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (ww * work[ii])); + } + } + } + } + + if (computeVectors) + { + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < matrixCopy.ColumnCount; i++) + { + vt.At(i, l, e[i]); + } + } + } + + // Set up the final bidiagonal matrixCopy or order m. + var m = Math.Min(matrixCopy.ColumnCount, matrixCopy.RowCount + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < matrixCopy.ColumnCount) + { + s[nctp1 - 1] = matrixCopy.At((nctp1 - 1), (nctp1 - 1)); + } + + if (matrixCopy.RowCount < m) + { + s[m - 1] = Complex32.Zero; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = matrixCopy.At((nrtp1 - 1), (m - 1)); + } + + e[m - 1] = Complex32.Zero; + + // If required, generate u. + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + u.At(i, j, Complex32.Zero); + } + + u.At(j, j, Complex32.One); + } + + for (l = nct - 1; l >= 0; l--) + { + if (s[l].Magnitude != 0.0f) + { + for (j = l + 1; j < ncu; j++) + { + t = -Cdotc(u, matrixCopy.RowCount, l, j, l) / u.At(l, l); + if (t != Complex32.Zero) + { + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + u.At(ii, j, u.At(ii, j) + (t * u.At(ii, l))); + } + } + } + + CscalColumn(u, matrixCopy.RowCount, l, l, -1.0f); + u.At(l, l, Complex32.One + u.At(l, l)); + for (i = 0; i < l; i++) + { + u.At(i, l, Complex32.Zero); + } + } + else + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + u.At(i, l, Complex32.Zero); + } + + u.At(l, l, Complex32.One); + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = matrixCopy.ColumnCount - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l].Magnitude != 0.0f) + { + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + t = -Cdotc(vt, matrixCopy.ColumnCount, l, j, lp1) / vt.At(lp1, l); + if (t != Complex32.Zero) + { + for (var ii = l; ii < matrixCopy.ColumnCount; ii++) + { + vt.At(ii, j, vt.At(ii, j) + (t * vt.At(ii, l))); + } + } + } + } + } + + for (i = 0; i < matrixCopy.ColumnCount; i++) + { + vt.At(i, l, Complex32.Zero); + } + + vt.At(l, l, Complex32.One); + } + } + + // Transform s and e so that they are real . + for (i = 0; i < m; i++) + { + Complex32 r; + if (s[i].Magnitude != 0.0f) + { + t = s[i].Magnitude; + r = s[i] / t; + s[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + CscalColumn(u, matrixCopy.RowCount, i, 0, r); + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i].Magnitude != 0.0f) + { + t = e[i].Magnitude; + r = t / e[i]; + e[i] = t; + s[i + 1] = s[i + 1] * r; + if (computeVectors) + { + CscalColumn(vt, matrixCopy.ColumnCount, i + 1, 0, r); + } + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays. On + // completion the variables case and l are set as follows. + // Case = 1 if VectorS[m] and e[l-1] are negligible and l < m + // Case = 2 if VectorS[l] is negligible and l < m + // Case = 3 if e[l-1] is negligible, l < m, and VectorS[l, ..., VectorS[m] are not negligible (qr step). + // Case = 4 if e[m-1] is negligible (convergence). + float ztest; + float test; + for (l = m - 2; l >= 0; l--) + { + test = s[l].Magnitude + s[l + 1].Magnitude; + ztest = test + e[l].Magnitude; + if (ztest.AlmostEqualRelative(test, 7)) + { + e[l] = Complex32.Zero; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0f; + if (ls != m - 1) + { + test = test + e[ls].Magnitude; + } + + if (ls != l + 1) + { + test = test + e[ls - 1].Magnitude; + } + + ztest = test + s[ls].Magnitude; + if (ztest.AlmostEqualRelative(test, 7)) + { + s[ls] = Complex32.Zero; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + float f; + float sn; + float cs; + switch (kase) + { + // Deflate negligible VectorS[m]. + case 1: + f = e[m - 2].Real; + e[m - 2] = Complex32.Zero; + float t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = s[k].Real; + Srotg(ref t1, ref f, out cs, out sn); + s[k] = t1; + if (k != l) + { + f = -sn * e[k - 1].Real; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + Csrot(vt, matrixCopy.ColumnCount, k, m - 1, cs, sn); + } + } + + break; + + // Split at negligible VectorS[l]. + case 2: + f = e[l - 1].Real; + e[l - 1] = Complex32.Zero; + for (k = l; k < m; k++) + { + t1 = s[k].Real; + Srotg(ref t1, ref f, out cs, out sn); + s[k] = t1; + f = -sn * e[k].Real; + e[k] = cs * e[k]; + if (computeVectors) + { + Csrot(u, matrixCopy.RowCount, k, l - 1, cs, sn); + } + } + + break; + + // Perform one qr step. + case 3: + // Calculate the shift. + var scale = 0.0f; + scale = Math.Max(scale, s[m - 1].Magnitude); + scale = Math.Max(scale, s[m - 2].Magnitude); + scale = Math.Max(scale, e[m - 2].Magnitude); + scale = Math.Max(scale, s[l].Magnitude); + scale = Math.Max(scale, e[l].Magnitude); + var sm = s[m - 1].Real / scale; + var smm1 = s[m - 2].Real / scale; + var emm1 = e[m - 2].Real / scale; + var sl = s[l].Real / scale; + var el = e[l].Real / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0f; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0f; + + if (b != 0.0f || c != 0.0f) + { + shift = (float)Math.Sqrt((b * b) + c); + if (b < 0.0f) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros. + for (k = l; k < m - 1; k++) + { + Srotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * s[k].Real) + (sn * e[k].Real); + e[k] = (cs * e[k]) - (sn * s[k]); + g = sn * s[k + 1].Real; + s[k + 1] = cs * s[k + 1]; + if (computeVectors) + { + Csrot(vt, matrixCopy.ColumnCount, k, k + 1, cs, sn); + } + + Srotg(ref f, ref g, out cs, out sn); + s[k] = f; + f = (cs * e[k].Real) + (sn * s[k + 1].Real); + s[k + 1] = (-sn * e[k]) + (cs * s[k + 1]); + g = sn * e[k + 1].Real; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < matrixCopy.RowCount) + { + Csrot(u, matrixCopy.RowCount, k, k + 1, cs, sn); + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence. + case 4: + // Make the singular value positive + if (s[l].Real < 0.0f) + { + s[l] = -s[l]; + if (computeVectors) + { + CscalColumn(vt, matrixCopy.ColumnCount, l, 0, -1.0f); + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (s[l].Real >= s[l + 1].Real) + { + break; + } + + t = s[l]; + s[l] = s[l + 1]; + s[l + 1] = t; + if (computeVectors && l < matrixCopy.ColumnCount) + { + Swap(vt, matrixCopy.ColumnCount, l, l + 1); + } + + if (computeVectors && l < matrixCopy.RowCount) + { + Swap(u, matrixCopy.RowCount, l, l + 1); + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + vt = vt.ConjugateTranspose(); + } + + // Adjust the size of s if rows < columns. We are using ported copy of linpack's svd code and it uses + // a singular vector of length mRows+1 when mRows < mColumns. The last element is not used and needs to be removed. + // we should port lapack's svd routine to remove this problem. + if (matrixCopy.RowCount < matrixCopy.ColumnCount) + { + nm--; + var tmp = Vector.Build.SameAs(matrixCopy, nm); + for (i = 0; i < nm; i++) + { + tmp[i] = s[i]; + } + + s = tmp; + } + + return new UserSvd(s, u, vt, computeVectors); + } + + UserSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Calculates absolute value of multiplied on signum function of + /// + /// Complex32 value z1 + /// Complex32 value z2 + /// Result multiplication of signum function and absolute value + static Complex32 Csign(Complex32 z1, Complex32 z2) + { + return z1.Magnitude * (z2 / z2.Magnitude); + } + + /// + /// Interchanges two vectors and + /// + /// Source matrix + /// The number of rows in + /// Column A index to swap + /// Column B index to swap + static void Swap(Matrix a, int rowCount, int columnA, int columnB) + { + for (var i = 0; i < rowCount; i++) + { + var z = a.At(i, columnA); + a.At(i, columnA, a.At(i, columnB)); + a.At(i, columnB, z); + } + } + + /// + /// Scale column by starting from row + /// + /// Source matrix + /// The number of rows in + /// Column to scale + /// Row to scale from + /// Scale value + static void CscalColumn(Matrix a, int rowCount, int column, int rowStart, Complex32 z) + { + for (var i = rowStart; i < rowCount; i++) + { + a.At(i, column, a.At(i, column) * z); + } + } + + /// + /// Scale vector by starting from index + /// + /// Source vector + /// Row to scale from + /// Scale value + static void CscalVector(Complex32[] a, int start, Complex32 z) + { + for (var i = start; i < a.Length; i++) + { + a[i] = a[i] * z; + } + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + static void Srotg(ref float da, ref float db, out float c, out float s) + { + float r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0f) + { + c = 1.0f; + s = 0.0f; + r = 0.0f; + z = 0.0f; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * (float)Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0f) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0f; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0f) + { + z = 1.0f / c; + } + } + + da = r; + db = z; + } + + /// + /// Calculate Norm 2 of the column in matrix starting from row + /// + /// Source matrix + /// The number of rows in + /// Column index + /// Start row index + /// Norm2 (Euclidean norm) of the column + static float Cnrm2Column(Matrix a, int rowCount, int column, int rowStart) + { + var s = 0.0f; + for (var i = rowStart; i < rowCount; i++) + { + s += a.At(i, column).Magnitude * a.At(i, column).Magnitude; + } + + return (float)Math.Sqrt(s); + } + + /// + /// Calculate Norm 2 of the vector starting from index + /// + /// Source vector + /// Start index + /// Norm2 (Euclidean norm) of the vector + static float Cnrm2Vector(Complex32[] a, int rowStart) + { + var s = 0.0f; + for (var i = rowStart; i < a.Length; i++) + { + s += a[i].Magnitude * a[i].Magnitude; + } + + return (float)Math.Sqrt(s); + } + + /// + /// Calculate dot product of and conjugating the first vector. + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Starting row index + /// Dot product value + static Complex32 Cdotc(Matrix a, int rowCount, int columnA, int columnB, int rowStart) + { + var z = Complex32.Zero; + for (var i = rowStart; i < rowCount; i++) + { + z += a.At(i, columnA).Conjugate() * a.At(i, columnB); + } + + return z; + } + + /// + /// Performs rotation of points in the plane. Given two vectors x and y , + /// each vector element of these vectors is replaced as follows: x(i) = c*x(i) + s*y(i); y(i) = c*y(i) - s*x(i) + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// scalar cos value + /// scalar sin value + static void Csrot(Matrix a, int rowCount, int columnA, int columnB, float c, float s) + { + for (var i = 0; i < rowCount; i++) + { + var z = (c * a.At(i, columnA)) + (s * a.At(i, columnB)); + var tmp = (c * a.At(i, columnB)) - (s * a.At(i, columnA)); + a.At(i, columnB, tmp); + a.At(i, columnA, z); + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (U.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var mn = Math.Min(U.RowCount, VT.ColumnCount); + var bn = input.ColumnCount; + + var tmp = new Complex32[VT.ColumnCount]; + + for (var k = 0; k < bn; k++) + { + for (var j = 0; j < VT.ColumnCount; j++) + { + var value = Complex32.Zero; + if (j < mn) + { + for (var i = 0; i < U.RowCount; i++) + { + value += U.At(i, j).Conjugate() * input.At(i, k); + } + + value /= S[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < VT.ColumnCount; j++) + { + var value = Complex32.Zero; + for (var i = 0; i < VT.ColumnCount; i++) + { + value += VT.At(i, j).Conjugate() * tmp[i]; + } + + result.At(j, k, value); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (U.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VT.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(VT, result); + } + + var mn = Math.Min(U.RowCount, VT.ColumnCount); + var tmp = new Complex32[VT.ColumnCount]; + for (var j = 0; j < VT.ColumnCount; j++) + { + var value = Complex32.Zero; + if (j < mn) + { + for (var i = 0; i < U.RowCount; i++) + { + value += U.At(i, j).Conjugate() * input[i]; + } + + value /= S[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < VT.ColumnCount; j++) + { + var value = Complex32.Zero; + for (var i = 0; i < VT.ColumnCount; i++) + { + value += VT.At(i, j).Conjugate() * tmp[i]; + } + + result[j] = value; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Matrix.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Matrix.cs new file mode 100644 index 0000000..e4aeb5a --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Matrix.cs @@ -0,0 +1,871 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Complex32.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32; + +using Numerics; + +/// +/// Complex32 version of the class. +/// +[Serializable] +public abstract class Matrix : Matrix +{ + /// + /// Initializes a new instance of the Matrix class. + /// + protected Matrix(MatrixStorage storage) + : base(storage) + { + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero. + /// + public override void CoerceZero(double threshold) + { + MapInplace(x => x.Magnitude < threshold ? Complex32.Zero : x, Zeros.AllowSkip); + } + + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public sealed override Matrix ConjugateTranspose() + { + var ret = Transpose(); + ret.MapInplace(c => c.Conjugate(), Zeros.AllowSkip); + return ret; + } + + /// + /// Puts the conjugate transpose of this matrix into the result matrix. + /// + public sealed override void ConjugateTranspose(Matrix result) + { + Transpose(result); + result.MapInplace(c => c.Conjugate(), Zeros.AllowSkip); + } + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected override void DoConjugate(Matrix result) + { + Map(Complex32.Conjugate, result, Zeros.AllowSkip); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + Map(Complex32.Negate, result, Zeros.AllowSkip); + } + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected override void DoAdd(Complex32 scalar, Matrix result) + { + Map(x => x + scalar, result, Zeros.Include); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + Map2(Complex32.Add, other, result, Zeros.AllowSkip); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(Complex32 scalar, Matrix result) + { + Map(x => x - scalar, result, Zeros.Include); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract to this matrix. + /// The matrix to store the result of subtraction. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + Map2(Complex32.Subtract, other, result, Zeros.AllowSkip); + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(Complex32 scalar, Matrix result) + { + Map(x => x * scalar, result, Zeros.AllowSkip); + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + for (var i = 0; i < RowCount; i++) + { + var s = Complex32.Zero; + for (var j = 0; j < ColumnCount; j++) + { + s += At(i, j) * rightSide[j]; + } + + result[i] = s; + } + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(Complex32 divisor, Matrix result) + { + Map(x => x / divisor, result, divisor.IsZero() ? Zeros.Include : Zeros.AllowSkip); + } + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to divide by each element of the matrix. + /// The matrix to store the result of the division. + protected override void DoDivideByThis(Complex32 dividend, Matrix result) + { + Map(x => dividend / x, result, Zeros.Include); + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + for (var i = 0; i < RowCount; i++) + { + for (var j = 0; j != other.ColumnCount; j++) + { + var s = Complex32.Zero; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.RowCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + var s = Complex32.Zero; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(j, k); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.RowCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + var s = Complex32.Zero; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(j, k).Conjugate(); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.ColumnCount; j++) + { + for (var i = 0; i < ColumnCount; i++) + { + var s = Complex32.Zero; + for (var k = 0; k < RowCount; k++) + { + s += At(k, i) * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.ColumnCount; j++) + { + for (var i = 0; i < ColumnCount; i++) + { + var s = Complex32.Zero; + for (var k = 0; k < RowCount; k++) + { + s += At(k, i).Conjugate() * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + for (var j = 0; j < ColumnCount; j++) + { + var s = Complex32.Zero; + for (var i = 0; i < RowCount; i++) + { + s += At(i, j) * rightSide[i]; + } + + result[j] = s; + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + for (var j = 0; j < ColumnCount; j++) + { + var s = Complex32.Zero; + for (var i = 0; i < RowCount; i++) + { + s += At(i, j).Conjugate() * rightSide[i]; + } + + result[j] = s; + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + Map2(Complex32.Multiply, other, result, Zeros.AllowSkip); + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + Map2(Complex32.Divide, divisor, result, Zeros.Include); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The matrix to store the result of the pointwise power. + protected override void DoPointwisePower(Complex32 exponent, Matrix result) + { + Map(x => x.Power(exponent), result, Zeros.Include); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Matrix exponent, Matrix result) + { + Map2(Complex32.Pow, result, Zeros.Include); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected sealed override void DoPointwiseModulus(Matrix divisor, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected sealed override void DoPointwiseRemainder(Matrix divisor, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected sealed override void DoModulus(Complex32 divisor, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected sealed override void DoModulusByThis(Complex32 dividend, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected sealed override void DoRemainder(Complex32 divisor, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected sealed override void DoRemainderByThis(Complex32 dividend, Matrix result) + { + throw new NotSupportedException(); + } + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected override void DoPointwiseExp(Matrix result) + { + Map(Complex32.Exp, result, Zeros.Include); + } + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected override void DoPointwiseLog(Matrix result) + { + Map(Complex32.Log, result, Zeros.Include); + } + + protected override void DoPointwiseAbs(Matrix result) + { + Map(x => (Complex32)Complex32.Abs(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAcos(Matrix result) + { + Map(Complex32.Acos, result, Zeros.Include); + } + protected override void DoPointwiseAsin(Matrix result) + { + Map(Complex32.Asin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan(Matrix result) + { + Map(Complex32.Atan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan2(Matrix other, Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseCeiling(Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseCos(Matrix result) + { + Map(Complex32.Cos, result, Zeros.Include); + } + protected override void DoPointwiseCosh(Matrix result) + { + Map(Complex32.Cosh, result, Zeros.Include); + } + protected override void DoPointwiseFloor(Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseLog10(Matrix result) + { + Map(Complex32.Log10, result, Zeros.Include); + } + protected override void DoPointwiseRound(Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseSign(Matrix result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseSin(Matrix result) + { + Map(Complex32.Sin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSinh(Matrix result) + { + Map(Complex32.Sinh, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSqrt(Matrix result) + { + Map(Complex32.Sqrt, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTan(Matrix result) + { + Map(Complex32.Tan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTanh(Matrix result) + { + Map(Complex32.Tanh, result, Zeros.AllowSkip); + } + + /// + /// Computes the Moore-Penrose Pseudo-Inverse of this matrix. + /// + public override Matrix PseudoInverse() + { + var svd = Svd(true); + var w = svd.W; + var s = svd.S; + float tolerance = (float)(Math.Max(RowCount, ColumnCount) * svd.L2Norm * Precision.SinglePrecision); + + for (int i = 0; i < s.Count; i++) + { + s[i] = s[i].Magnitude < tolerance ? 0 : 1 / s[i]; + } + + w.SetDiagonal(s); + return (svd.U * w * svd.VT).Transpose(); + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public override Complex32 Trace() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var sum = Complex32.Zero; + for (var i = 0; i < RowCount; i++) + { + sum += At(i, i); + } + + return sum; + } + + protected override void DoPointwiseMinimum(Complex32 scalar, Matrix result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMaximum(Complex32 scalar, Matrix result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseAbsoluteMinimum(Complex32 scalar, Matrix result) + { + float absolute = scalar.Magnitude; + Map(x => Math.Min(absolute, x.Magnitude), result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Complex32 scalar, Matrix result) + { + float absolute = scalar.Magnitude; + Map(x => Math.Max(absolute, x.Magnitude), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(Matrix other, Matrix result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMaximum(Matrix other, Matrix result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseAbsoluteMinimum(Matrix other, Matrix result) + { + Map2((x, y) => Math.Min(x.Magnitude, y.Magnitude), other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Matrix other, Matrix result) + { + Map2((x, y) => Math.Max(x.Magnitude, y.Magnitude), other, result, Zeros.AllowSkip); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + var norm = 0d; + for (var j = 0; j < ColumnCount; j++) + { + var s = 0d; + for (var i = 0; i < RowCount; i++) + { + s += At(i, j).Magnitude; + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + var s = 0d; + for (var j = 0; j < ColumnCount; j++) + { + s += At(i, j).Magnitude; + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + var transpose = ConjugateTranspose(); + var aat = this * transpose; + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + norm += aat.At(i, i).Magnitude; + } + + return Math.Sqrt(norm); + } + + /// + /// Calculates the p-norms of all row vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public override Vector RowNorms(double norm) + { + if (norm <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(norm), Resources.ArgumentMustBePositive); + } + + var ret = new double[RowCount]; + if (norm == 2.0) + { + Storage.FoldByRowUnchecked(ret, (s, x) => s + x.MagnitudeSquared, (x, c) => Math.Sqrt(x), ret, Zeros.AllowSkip); + } + else if (norm == 1.0) + { + Storage.FoldByRowUnchecked(ret, (s, x) => s + x.Magnitude, (x, c) => x, ret, Zeros.AllowSkip); + } + else if (double.IsPositiveInfinity(norm)) + { + Storage.FoldByRowUnchecked(ret, (s, x) => Math.Max(s, x.Magnitude), (x, c) => x, ret, Zeros.AllowSkip); + } + else + { + double invnorm = 1.0 / norm; + Storage.FoldByRowUnchecked(ret, (s, x) => s + Math.Pow(x.Magnitude, norm), (x, c) => Math.Pow(x, invnorm), ret, Zeros.AllowSkip); + } + + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the p-norms of all column vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public override Vector ColumnNorms(double norm) + { + if (norm <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(norm), Resources.ArgumentMustBePositive); + } + + var ret = new double[ColumnCount]; + if (norm == 2.0) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x.MagnitudeSquared, (x, c) => Math.Sqrt(x), ret, Zeros.AllowSkip); + } + else if (norm == 1.0) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x.Magnitude, (x, c) => x, ret, Zeros.AllowSkip); + } + else if (double.IsPositiveInfinity(norm)) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => Math.Max(s, x.Magnitude), (x, c) => x, ret, Zeros.AllowSkip); + } + else + { + double invnorm = 1.0 / norm; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + Math.Pow(x.Magnitude, norm), (x, c) => Math.Pow(x, invnorm), ret, Zeros.AllowSkip); + } + + return Vector.Build.Dense(ret); + } + + /// + /// Normalizes all row vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public sealed override Matrix NormalizeRows(double norm) + { + var norminv = ((DenseVectorStorage)RowNorms(norm).Storage).Data; + for (int i = 0; i < norminv.Length; i++) + { + norminv[i] = norminv[i] == 0d ? 1d : 1d / norminv[i]; + } + + var result = Build.SameAs(this, RowCount, ColumnCount); + Storage.MapIndexedTo(result.Storage, (i, j, x) => ((float)norminv[i]) * x, Zeros.AllowSkip, ExistingData.AssumeZeros); + return result; + } + + /// + /// Normalizes all column vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public sealed override Matrix NormalizeColumns(double norm) + { + var norminv = ((DenseVectorStorage)ColumnNorms(norm).Storage).Data; + for (int i = 0; i < norminv.Length; i++) + { + norminv[i] = norminv[i] == 0d ? 1d : 1d / norminv[i]; + } + + var result = Build.SameAs(this, RowCount, ColumnCount); + Storage.MapIndexedTo(result.Storage, (i, j, x) => ((float)norminv[j]) * x, Zeros.AllowSkip, ExistingData.AssumeZeros); + return result; + } + + /// + /// Calculates the value sum of each row vector. + /// + public override Vector RowSums() + { + var ret = new Complex32[RowCount]; + Storage.FoldByRowUnchecked(ret, (s, x) => s + x, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the absolute value sum of each row vector. + /// + public override Vector RowAbsoluteSums() + { + var ret = new Complex32[RowCount]; + Storage.FoldByRowUnchecked(ret, (s, x) => s + x.Magnitude, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the value sum of each column vector. + /// + public override Vector ColumnSums() + { + var ret = new Complex32[ColumnCount]; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the absolute value sum of each column vector. + /// + public override Vector ColumnAbsoluteSums() + { + var ret = new Complex32[ColumnCount]; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x.Magnitude, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public override bool IsHermitian() + { + if (RowCount != ColumnCount) + { + return false; + } + + for (var k = 0; k < RowCount; k++) + { + if (!At(k, k).IsReal()) + { + return false; + } + } + + for (var row = 0; row < RowCount; row++) + { + for (var column = row + 1; column < ColumnCount; column++) + { + if (!At(row, column).Equals(At(column, row).Conjugate())) + { + return false; + } + } + } + + return true; + } + + public override Cholesky Cholesky() + { + return UserCholesky.Create(this); + } + + public override LU LU() + { + return UserLU.Create(this); + } + + public override QR QR(QRMethod method = QRMethod.Thin) + { + return UserQR.Create(this, method); + } + + public override GramSchmidt GramSchmidt() + { + return UserGramSchmidt.Create(this); + } + + public override Svd Svd(bool computeVectors = true) + { + return UserSvd.Create(this, computeVectors); + } + + public override Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown) + { + return UserEvd.Create(this, symmetricity); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/BiCgStab.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/BiCgStab.cs new file mode 100644 index 0000000..a01d2c1 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/BiCgStab.cs @@ -0,0 +1,273 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +/// +/// A Bi-Conjugate Gradient stabilized iterative matrix solver. +/// +/// +/// +/// The Bi-Conjugate Gradient Stabilized (BiCGStab) solver is an 'improvement' +/// of the standard Conjugate Gradient (CG) solver. Unlike the CG solver the +/// BiCGStab can be used on non-symmetric matrices.
+/// Note that much of the success of the solver depends on the selection of the +/// proper preconditioner. +///
+/// +/// The Bi-CGSTAB algorithm was taken from:
+/// Templates for the solution of linear systems: Building blocks +/// for iterative methods +///
+/// Richard Barrett, Michael Berry, Tony F. Chan, James Demmel, +/// June M. Donato, Jack Dongarra, Victor Eijkhout, Roldan Pozo, +/// Charles Romine and Henk van der Vorst +///
+/// Url: http://www.netlib.org/templates/Templates.html +///
+/// Algorithm is described in Chapter 2, section 2.3.8, page 27 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class BiCgStab : IIterativeSolver +{ + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + + // Do not use residual = residual.Negate() because it creates another object + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient , A. + /// The solution , b. + /// The result , x. + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // Compute r_0 = b - Ax_0 for some initial guess x_0 + // In this case we take x_0 = vector + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, result, input); + + // Choose r~ (for example, r~ = r_0) + var tempResiduals = residuals.Clone(); + + // create seven temporary vectors needed to hold temporary + // coefficients. All vectors are mangled in each iteration. + // These are defined here to prevent stressing the garbage collector + var vecP = new DenseVector(residuals.Count); + var vecPdash = new DenseVector(residuals.Count); + var nu = new DenseVector(residuals.Count); + var vecS = new DenseVector(residuals.Count); + var vecSdash = new DenseVector(residuals.Count); + var temp = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + + // create some temporary float variables that are needed + // to hold values in between iterations + Numerics.Complex32 currentRho = 0; + Numerics.Complex32 alpha = 0; + Numerics.Complex32 omega = 0; + + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, result, input, residuals) == IterationStatus.Continue) + { + // rho_(i-1) = r~^T r_(i-1) // dotproduct r~ and r_(i-1) + var oldRho = currentRho; + currentRho = tempResiduals.ConjugateDotProduct(residuals); + + // if (rho_(i-1) == 0) // METHOD FAILS + // If rho is only 1 ULP from zero then we fail. + if (currentRho.Real.AlmostEqualNumbersBetween(0, 1) && currentRho.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + // Rho-type breakdown + throw new NumericalBreakdownException(); + } + + if (iterationNumber != 0) + { + // beta_(i-1) = (rho_(i-1)/rho_(i-2))(alpha_(i-1)/omega(i-1)) + var beta = (currentRho / oldRho) * (alpha / omega); + + // p_i = r_(i-1) + beta_(i-1)(p_(i-1) - omega_(i-1) * nu_(i-1)) + nu.Multiply(-omega, temp); + vecP.Add(temp, temp2); + temp2.CopyTo(vecP); + + vecP.Multiply(beta, vecP); + vecP.Add(residuals, temp2); + temp2.CopyTo(vecP); + } + else + { + // p_i = r_(i-1) + residuals.CopyTo(vecP); + } + + // SOLVE Mp~ = p_i // M = preconditioner + preconditioner.Approximate(vecP, vecPdash); + + // nu_i = Ap~ + matrix.Multiply(vecPdash, nu); + + // alpha_i = rho_(i-1)/ (r~^T nu_i) = rho / dotproduct(r~ and nu_i) + alpha = currentRho * 1 / tempResiduals.ConjugateDotProduct(nu); + + // s = r_(i-1) - alpha_i nu_i + nu.Multiply(-alpha, temp); + residuals.Add(temp, vecS); + + // Check if we're converged. If so then stop. Otherwise continue; + // Calculate the temporary result. + // Be careful not to change any of the temp vectors, except for + // temp. Others will be used in the calculation later on. + // x_i = x_(i-1) + alpha_i * p^_i + s^_i + vecPdash.Multiply(alpha, temp); + temp.Add(vecSdash, temp2); + temp2.CopyTo(temp); + temp.Add(result, temp2); + temp2.CopyTo(temp); + + // Check convergence and stop if we are converged. + if (iterator.DetermineStatus(iterationNumber, temp, input, vecS) != IterationStatus.Continue) + { + temp.CopyTo(result); + + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, result, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // We're all good now. + return; + } + + // Continue the calculation + iterationNumber++; + continue; + } + + // SOLVE Ms~ = s + preconditioner.Approximate(vecS, vecSdash); + + // temp = As~ + matrix.Multiply(vecSdash, temp); + + // omega_i = temp^T s / temp^T temp + omega = temp.ConjugateDotProduct(vecS) / temp.ConjugateDotProduct(temp); + + // x_i = x_(i-1) + alpha_i p^ + omega_i s^ + temp.Multiply(-omega, residuals); + residuals.Add(vecS, temp2); + temp2.CopyTo(residuals); + + vecSdash.Multiply(omega, temp); + result.Add(temp, temp2); + temp2.CopyTo(result); + + vecPdash.Multiply(alpha, temp); + result.Add(temp, temp2); + temp2.CopyTo(result); + + // for continuation it is necessary that omega_i != 0.0f + // If omega is only 1 ULP from zero then we fail. + if (omega.Real.AlmostEqualNumbersBetween(0, 1) && omega.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + // Omega-type breakdown + throw new NumericalBreakdownException(); + } + + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + // The residual calculation based on omega_i * s can be off by a factor 10. So here + // we calculate the real residual (which can be expensive) but we only do it if we're + // sufficiently close to the finish. + CalculateTrueResidual(matrix, residuals, result, input); + } + + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/CompositeSolver.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/CompositeSolver.cs new file mode 100644 index 0000000..b7ac3e4 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/CompositeSolver.cs @@ -0,0 +1,152 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +/// +/// A composite matrix solver. The actual solver is made by a sequence of +/// matrix solvers. +/// +/// +/// +/// Solver based on:
+/// Faster PDE-based simulations using robust composite linear solvers
+/// S. Bhowmicka, P. Raghavan a,*, L. McInnes b, B. Norris
+/// Future Generation Computer Systems, Vol 20, 2004, pp 373387
+///
+/// +/// Note that if an iterator is passed to this solver it will be used for all the sub-solvers. +/// +///
+public sealed class CompositeSolver : IIterativeSolver +{ + /// + /// The collection of solvers that will be used + /// + readonly List, IPreconditioner>> _solvers; + + public CompositeSolver(IEnumerable> solvers) + { + _solvers = solvers.Select(setup => new Tuple, IPreconditioner>(setup.CreateSolver(), setup.CreatePreconditioner() ?? new UnitPreconditioner())).ToList(); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + // Create a copy of the solution and result vectors so we can use them + // later on + var internalInput = input.Clone(); + var internalResult = result.Clone(); + + foreach (var solver in _solvers) + { + // Store a reference to the solver so we can stop it. + + IterationStatus status; + try + { + // Reset the iterator and pass it to the solver + iterator.Reset(); + + // Start the solver + solver.Item1.Solve(matrix, internalInput, internalResult, iterator, solver.Item2 ?? preconditioner); + status = iterator.Status; + } + catch (Exception) + { + // The solver broke down. + // Log a message about this + // Switch to the next preconditioner. + // Reset the solution vector to the previous solution + input.CopyTo(internalInput); + continue; + } + + // There was no fatal breakdown so check the status + if (status == IterationStatus.Converged) + { + // We're done + internalResult.CopyTo(result); + break; + } + + // We're not done + // Either: + // - calculation finished without convergence + if (status == IterationStatus.StoppedWithoutConvergence) + { + // Copy the internal result to the result vector and + // continue with the calculation. + internalResult.CopyTo(result); + } + else + { + // - calculation failed --> restart with the original vector + // - calculation diverged --> restart with the original vector + // - Some unknown status occurred --> To be safe restart. + input.CopyTo(internalInput); + } + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/DiagonalPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/DiagonalPreconditioner.cs new file mode 100644 index 0000000..39762c0 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/DiagonalPreconditioner.cs @@ -0,0 +1,108 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +using Numerics; + +/// +/// A diagonal preconditioner. The preconditioner uses the inverse +/// of the matrix diagonal as preconditioning values. +/// +public sealed class DiagonalPreconditioner : IPreconditioner +{ + /// + /// The inverse of the matrix diagonal. + /// + Complex32[] _inverseDiagonals; + + /// + /// Returns the decomposed matrix diagonal. + /// + /// The matrix diagonal. + internal DiagonalMatrix DiagonalEntries() + { + var result = new DiagonalMatrix(_inverseDiagonals.Length); + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + result[i, i] = 1 / _inverseDiagonals[i]; + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _inverseDiagonals = new Complex32[matrix.RowCount]; + for (var i = 0; i < matrix.RowCount; i++) + { + _inverseDiagonals[i] = 1 / matrix[i, i]; + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_inverseDiagonals == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _inverseDiagonals.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rhs)); + } + + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + lhs[i] = rhs[i] * _inverseDiagonals[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/GpBiCg.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/GpBiCg.cs new file mode 100644 index 0000000..adef106 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/GpBiCg.cs @@ -0,0 +1,377 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +/// +/// A Generalized Product Bi-Conjugate Gradient iterative matrix solver. +/// +/// +/// +/// The Generalized Product Bi-Conjugate Gradient (GPBiCG) solver is an +/// alternative version of the Bi-Conjugate Gradient stabilized (CG) solver. +/// Unlike the CG solver the GPBiCG solver can be used on +/// non-symmetric matrices.
+/// Note that much of the success of the solver depends on the selection of the +/// proper preconditioner. +///
+/// +/// The GPBiCG algorithm was taken from:
+/// GPBiCG(m,l): A hybrid of BiCGSTAB and GPBiCG methods with +/// efficiency and robustness +///
+/// S. Fujino +///
+/// Applied Numerical Mathematics, Volume 41, 2002, pp 107 - 117 +///
+///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class GpBiCg : IIterativeSolver +{ + /// + /// Indicates the number of BiCGStab steps should be taken + /// before switching. + /// + int _numberOfBiCgStabSteps = 1; + + /// + /// Indicates the number of GPBiCG steps should be taken + /// before switching. + /// + int _numberOfGpbiCgSteps = 4; + + /// + /// Gets or sets the number of steps taken with the BiCgStab algorithm + /// before switching over to the GPBiCG algorithm. + /// + public int NumberOfBiCgStabSteps + { + get { return _numberOfBiCgStabSteps; } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfBiCgStabSteps = value; + } + } + + /// + /// Gets or sets the number of steps taken with the GPBiCG algorithm + /// before switching over to the BiCgStab algorithm. + /// + public int NumberOfGpBiCgSteps + { + get { return _numberOfGpbiCgSteps; } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfGpbiCgSteps = value; + } + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Decide if to do steps with BiCgStab + /// + /// Number of iteration + /// true if yes, otherwise false + bool ShouldRunBiCgStabSteps(int iterationNumber) + { + // Run the first steps as BiCGStab + // The number of steps past a whole iteration set + var difference = iterationNumber % (_numberOfBiCgStabSteps + _numberOfGpbiCgSteps); + + // Do steps with BiCGStab if: + // - The difference is zero or more (i.e. we have done zero or more complete cycles) + // - The difference is less than the number of BiCGStab steps that should be taken + return (difference >= 0) && (difference < _numberOfBiCgStabSteps); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // x_0 is initial guess + // Take x_0 = 0 + var xtemp = new DenseVector(input.Count); + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary scalars + Numerics.Complex32 beta = 0; + + // Define the temporary vectors + // rDash_0 = r_0 + var rdash = DenseVector.OfVector(residuals); + + // t_-1 = 0 + var t = new DenseVector(residuals.Count); + var t0 = new DenseVector(residuals.Count); + + // w_-1 = 0 + var w = new DenseVector(residuals.Count); + + // Define the remaining temporary vectors + var c = new DenseVector(residuals.Count); + var p = new DenseVector(residuals.Count); + var s = new DenseVector(residuals.Count); + var u = new DenseVector(residuals.Count); + var y = new DenseVector(residuals.Count); + var z = new DenseVector(residuals.Count); + + var temp = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + var temp3 = new DenseVector(residuals.Count); + + // for (k = 0, 1, .... ) + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue) + { + // p_k = r_k + beta_(k-1) * (p_(k-1) - u_(k-1)) + p.Subtract(u, temp); + + temp.Multiply(beta, temp2); + residuals.Add(temp2, p); + + // Solve M b_k = p_k + preconditioner.Approximate(p, temp); + + // s_k = A b_k + matrix.Multiply(temp, s); + + // alpha_k = (r*_0 * r_k) / (r*_0 * s_k) + var alpha = rdash.ConjugateDotProduct(residuals) / rdash.ConjugateDotProduct(s); + + // y_k = t_(k-1) - r_k - alpha_k * w_(k-1) + alpha_k s_k + s.Subtract(w, temp); + t.Subtract(residuals, y); + + temp.Multiply(alpha, temp2); + y.Add(temp2, temp3); + temp3.CopyTo(y); + + // Store the old value of t in t0 + t.CopyTo(t0); + + // t_k = r_k - alpha_k s_k + s.Multiply(-alpha, temp2); + residuals.Add(temp2, t); + + // Solve M d_k = t_k + preconditioner.Approximate(t, temp); + + // c_k = A d_k + matrix.Multiply(temp, c); + var cdot = c.ConjugateDotProduct(c); + + // cDot can only be zero if c is a zero vector + // We'll set cDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // c.DotProduct(t) will be zero and so will c.DotProduct(y) + if (cdot.Real.AlmostEqualNumbersBetween(0, 1) && cdot.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + cdot = 1.0f; + } + + // Even if we don't want to do any BiCGStab steps we'll still have + // to do at least one at the start to initialize the + // system, but we'll only have to take special measures + // if we don't do any so ... + var ctdot = c.ConjugateDotProduct(t); + Numerics.Complex32 eta; + Numerics.Complex32 sigma; + if (((_numberOfBiCgStabSteps == 0) && (iterationNumber == 0)) || ShouldRunBiCgStabSteps(iterationNumber)) + { + // sigma_k = (c_k * t_k) / (c_k * c_k) + sigma = ctdot / cdot; + + // eta_k = 0 + eta = 0; + } + else + { + var ydot = y.ConjugateDotProduct(y); + + // yDot can only be zero if y is a zero vector + // We'll set yDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // y.DotProduct(t) will be zero and so will c.DotProduct(y) + if (ydot.Real.AlmostEqualNumbersBetween(0, 1) && ydot.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + ydot = 1.0f; + } + + var ytdot = y.ConjugateDotProduct(t); + var cydot = c.ConjugateDotProduct(y); + + var denom = (cdot * ydot) - (cydot * cydot); + + // sigma_k = ((y_k * y_k)(c_k * t_k) - (y_k * t_k)(c_k * y_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + sigma = ((ydot * ctdot) - (ytdot * cydot)) / denom; + + // eta_k = ((c_k * c_k)(y_k * t_k) - (y_k * c_k)(c_k * t_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + eta = ((cdot * ytdot) - (cydot * ctdot)) / denom; + } + + // u_k = sigma_k s_k + eta_k (t_(k-1) - r_k + beta_(k-1) u_(k-1)) + u.Multiply(beta, temp2); + t0.Add(temp2, temp); + + temp.Subtract(residuals, temp3); + temp3.CopyTo(temp); + temp.Multiply(eta, temp); + + s.Multiply(sigma, temp2); + temp.Add(temp2, u); + + // z_k = sigma_k r_k +_ eta_k z_(k-1) - alpha_k u_k + z.Multiply(eta, z); + u.Multiply(-alpha, temp2); + z.Add(temp2, temp3); + temp3.CopyTo(z); + + residuals.Multiply(sigma, temp2); + z.Add(temp2, temp3); + temp3.CopyTo(z); + + // x_(k+1) = x_k + alpha_k p_k + z_k + p.Multiply(alpha, temp2); + xtemp.Add(temp2, temp3); + temp3.CopyTo(xtemp); + + xtemp.Add(z, temp3); + temp3.CopyTo(xtemp); + + // r_(k+1) = t_k - eta_k y_k - sigma_k c_k + // Copy the old residuals to a temp vector because we'll + // need those in the next step + residuals.CopyTo(t0); + + y.Multiply(-eta, temp2); + t.Add(temp2, residuals); + + c.Multiply(-sigma, temp2); + residuals.Add(temp2, temp3); + temp3.CopyTo(residuals); + + // beta_k = alpha_k / sigma_k * (r*_0 * r_(k+1)) / (r*_0 * r_k) + // But first we check if there is a possible NaN. If so just reset beta to zero. + beta = (!sigma.Real.AlmostEqualNumbersBetween(0, 1) || !sigma.Imaginary.AlmostEqualNumbersBetween(0, 1)) ? alpha / sigma * rdash.ConjugateDotProduct(residuals) / rdash.ConjugateDotProduct(t0) : 0; + + // w_k = c_k + beta_k s_k + s.Multiply(beta, temp2); + c.Add(temp2, w); + + // Get the real value + preconditioner.Approximate(xtemp, result); + + // Now check for convergence + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, result, input); + } + + // Next iteration. + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/ILU0Preconditioner.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/ILU0Preconditioner.cs new file mode 100644 index 0000000..28a8a5c --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/ILU0Preconditioner.cs @@ -0,0 +1,223 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +using Numerics; + +/// +/// An incomplete, level 0, LU factorization preconditioner. +/// +/// +/// The ILU(0) algorithm was taken from:
+/// Iterative methods for sparse linear systems
+/// Yousef Saad
+/// Algorithm is described in Chapter 10, section 10.3.2, page 275
+///
+public sealed class ILU0Preconditioner : IPreconditioner +{ + /// + /// The matrix holding the lower (L) and upper (U) matrices. The + /// decomposition matrices are combined to reduce storage. + /// + SparseMatrix _decompositionLU; + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = i; j < _decompositionLU.ColumnCount; j++) + { + result[i, j] = _decompositionLU[i, j]; + } + } + + return result; + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = 0; j <= i; j++) + { + if (i == j) + { + result[i, j] = 1.0f; + } + else + { + result[i, j] = _decompositionLU[i, j]; + } + } + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _decompositionLU = SparseMatrix.OfMatrix(matrix); + + // M == A + // for i = 2, ... , n do + // for k = 1, .... , i - 1 do + // if (i,k) == NZ(Z) then + // compute z(i,k) = z(i,k) / z(k,k); + // for j = k + 1, ...., n do + // if (i,j) == NZ(Z) then + // compute z(i,j) = z(i,j) - z(i,k) * z(k,j) + // end + // end + // end + // end + // end + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var k = 0; k < i; k++) + { + if (_decompositionLU[i, k] != 0.0f) + { + var t = _decompositionLU[i, k] / _decompositionLU[k, k]; + _decompositionLU[i, k] = t; + if (_decompositionLU[k, i] != 0.0f) + { + _decompositionLU[i, i] = _decompositionLU[i, i] - (t * _decompositionLU[k, i]); + } + + for (var j = k + 1; j < _decompositionLU.RowCount; j++) + { + if (j == i) + { + continue; + } + + if (_decompositionLU[i, j] != 0.0f) + { + _decompositionLU[i, j] = _decompositionLU[i, j] - (t * _decompositionLU[k, j]); + } + } + } + } + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_decompositionLU == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _decompositionLU.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Solve: + // Lz = y + // Which gives + // for (int i = 1; i < matrix.RowLength; i++) + // { + // z_i = l_ii^-1 * (y_i - SUM_(j -1; i--) + // { + // x_i = u_ii^-1 * (z_i - SUM_(j > i) u_ij * x_j) + // } + for (var i = _decompositionLU.RowCount - 1; i > -1; i--) + { + _decompositionLU.Row(i, rowValues); + + var sum = Complex32.Zero; + for (var j = _decompositionLU.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/ILUTPPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/ILUTPPreconditioner.cs new file mode 100644 index 0000000..16bb38f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/ILUTPPreconditioner.cs @@ -0,0 +1,871 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +using Numerics; + +/// +/// This class performs an Incomplete LU factorization with drop tolerance +/// and partial pivoting. The drop tolerance indicates which additional entries +/// will be dropped from the factorized LU matrices. +/// +/// +/// The ILUTP-Mem algorithm was taken from:
+/// ILUTP_Mem: a Space-Efficient Incomplete LU Preconditioner +///
+/// Tzu-Yi Chen, Department of Mathematics and Computer Science,
+/// Pomona College, Claremont CA 91711, USA
+/// Published in:
+/// Lecture Notes in Computer Science
+/// Volume 3046 / 2004
+/// pp. 20 - 28
+/// Algorithm is described in Section 2, page 22 +///
+public sealed class ILUTPPreconditioner : IPreconditioner +{ + /// + /// The default fill level. + /// + public const double DefaultFillLevel = 200.0; + + /// + /// The default drop tolerance. + /// + public const double DefaultDropTolerance = 0.0001; + + /// + /// The decomposed upper triangular matrix. + /// + SparseMatrix _upper; + + /// + /// The decomposed lower triangular matrix. + /// + SparseMatrix _lower; + + /// + /// The array containing the pivot values. + /// + int[] _pivots; + + /// + /// The fill level. + /// + double _fillLevel = DefaultFillLevel; + + /// + /// The drop tolerance. + /// + double _dropTolerance = DefaultDropTolerance; + + /// + /// The pivot tolerance. + /// + double _pivotTolerance; + + /// + /// Initializes a new instance of the class with the default settings. + /// + public ILUTPPreconditioner() + { + } + + /// + /// Initializes a new instance of the class with the specified settings. + /// + /// + /// The amount of fill that is allowed in the matrix. The value is a fraction of + /// the number of non-zero entries in the original matrix. Values should be positive. + /// + /// + /// The absolute drop tolerance which indicates below what absolute value an entry + /// will be dropped from the matrix. A drop tolerance of 0.0 means that no values + /// will be dropped. Values should always be positive. + /// + /// + /// The pivot tolerance which indicates at what level pivoting will take place. A + /// value of 0.0 means that no pivoting will take place. + /// + public ILUTPPreconditioner(double fillLevel, double dropTolerance, double pivotTolerance) + { + if (fillLevel < 0) + { + throw new ArgumentOutOfRangeException(nameof(fillLevel)); + } + + if (dropTolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(dropTolerance)); + } + + if (pivotTolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(pivotTolerance)); + } + + _fillLevel = fillLevel; + _dropTolerance = dropTolerance; + _pivotTolerance = pivotTolerance; + } + + /// + /// Gets or sets the amount of fill that is allowed in the matrix. The + /// value is a fraction of the number of non-zero entries in the original + /// matrix. The standard value is 200. + /// + /// + /// + /// Values should always be positive and can be higher than 1.0. A value lower + /// than 1.0 means that the eventual preconditioner matrix will have fewer + /// non-zero entries as the original matrix. A value higher than 1.0 means that + /// the eventual preconditioner can have more non-zero values than the original + /// matrix. + /// + /// + /// Note that any changes to the FillLevel after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double FillLevel + { + get { return _fillLevel; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _fillLevel = value; + } + } + + /// + /// Gets or sets the absolute drop tolerance which indicates below what absolute value + /// an entry will be dropped from the matrix. The standard value is 0.0001. + /// + /// + /// + /// The values should always be positive and can be larger than 1.0. A low value will + /// keep more small numbers in the preconditioner matrix. A high value will remove + /// more small numbers from the preconditioner matrix. + /// + /// + /// Note that any changes to the DropTolerance after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double DropTolerance + { + get { return _dropTolerance; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _dropTolerance = value; + } + } + + /// + /// Gets or sets the pivot tolerance which indicates at what level pivoting will + /// take place. The standard value is 0.0 which means pivoting will never take place. + /// + /// + /// + /// The pivot tolerance is used to calculate if pivoting is necessary. Pivoting + /// will take place if any of the values in a row is bigger than the + /// diagonal value of that row divided by the pivot tolerance, i.e. pivoting + /// will take place if row(i,j) > row(i,i) / PivotTolerance for + /// any j that is not equal to i. + /// + /// + /// Note that any changes to the PivotTolerance after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double PivotTolerance + { + get { return _pivotTolerance; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _pivotTolerance = value; + } + } + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + return _upper.Clone(); + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + return _lower.Clone(); + } + + /// + /// Returns the pivot array. This array is not needed for normal use because + /// the preconditioner will return the solution vector values in the proper order. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// The pivot array. + internal int[] Pivots() + { + var result = new int[_pivots.Length]; + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = _pivots[i]; + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. Note that the + /// method takes a general matrix type. However internally the data is stored + /// as a sparse matrix. Therefore it is not recommended to pass a dense matrix. + /// + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + var sparseMatrix = matrix as SparseMatrix ?? SparseMatrix.OfMatrix(matrix); + + // The creation of the preconditioner follows the following algorithm. + // spaceLeft = lfilNnz * nnz(A) + // for i = 1, .. , n + // { + // w = a(i,*) + // for j = 1, .. , i - 1 + // { + // if (w(j) != 0) + // { + // w(j) = w(j) / a(j,j) + // if (w(j) < dropTol) + // { + // w(j) = 0; + // } + // if (w(j) != 0) + // { + // w = w - w(j) * U(j,*) + // } + // } + // } + // + // for j = i, .. ,n + // { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + // } + // + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + // lfil = spaceRow / 2 // space for this row of L + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + // + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + // u(i,j) = w(j) for j = i, .. , n // only the largest lfil - 1 elements + // w = 0 + // + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + // } + // Create the lower triangular matrix + _lower = new SparseMatrix(sparseMatrix.RowCount); + + // Create the upper triangular matrix and copy the values + _upper = new SparseMatrix(sparseMatrix.RowCount); + + // Create the pivot array + _pivots = new int[sparseMatrix.RowCount]; + for (var i = 0; i < _pivots.Length; i++) + { + _pivots[i] = i; + } + + var workVector = new DenseVector(sparseMatrix.RowCount); + var rowVector = new DenseVector(sparseMatrix.ColumnCount); + var indexSorting = new int[sparseMatrix.RowCount]; + + // spaceLeft = lfilNnz * nnz(A) + var spaceLeft = (int)_fillLevel * sparseMatrix.NonZerosCount; + + // for i = 1, .. , n + for (var i = 0; i < sparseMatrix.RowCount; i++) + { + // w = a(i,*) + sparseMatrix.Row(i, workVector); + + // pivot the row + PivotRow(workVector); + var vectorNorm = workVector.InfinityNorm(); + + // for j = 1, .. , i - 1) + for (var j = 0; j < i; j++) + { + // if (w(j) != 0) + // { + // w(j) = w(j) / a(j,j) + // if (w(j) < dropTol) + // { + // w(j) = 0; + // } + // if (w(j) != 0) + // { + // w = w - w(j) * U(j,*) + // } + if (workVector[j] != 0.0f) + { + // Calculate the multiplication factors that go into the L matrix + workVector[j] = workVector[j] / _upper[j, j]; + if (workVector[j].Magnitude < _dropTolerance) + { + workVector[j] = 0.0f; + } + + // Calculate the addition factor + if (workVector[j] != 0.0f) + { + // vector update all in one go + _upper.Row(j, rowVector); + + // zero out columnVector[k] because we don't need that + // one anymore for k = 0 to k = j + for (var k = 0; k <= j; k++) + { + rowVector[k] = 0.0f; + } + + rowVector.Multiply(workVector[j], rowVector); + workVector.Subtract(rowVector, workVector); + } + } + } + + // for j = i, .. ,n + for (var j = i; j < sparseMatrix.RowCount; j++) + { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + if (workVector[j].Magnitude <= _dropTolerance * vectorNorm) + { + workVector[j] = 0.0f; + } + } + + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + var spaceRow = spaceLeft / (sparseMatrix.RowCount - i + 1); + + // lfil = spaceRow / 2 // space for this row of L + var fillLevel = spaceRow / 2; + FindLargestItems(0, i - 1, indexSorting, workVector); + + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + var lowerNonZeroCount = 0; + var count = 0; + for (var j = 0; j < i; j++) + { + if ((count > fillLevel) || (indexSorting[j] == -1)) + { + break; + } + + _lower[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + lowerNonZeroCount += 1; + } + + FindLargestItems(i + 1, sparseMatrix.RowCount - 1, indexSorting, workVector); + + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + fillLevel = spaceRow - lowerNonZeroCount; + + // u(i,j) = w(j) for j = i + 1, .. , n // only the largest lfil - 1 elements + var upperNonZeroCount = 0; + count = 0; + for (var j = 0; j < sparseMatrix.RowCount - i; j++) + { + if ((count > fillLevel - 1) || (indexSorting[j] == -1)) + { + break; + } + + _upper[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + upperNonZeroCount += 1; + } + + // Simply copy the diagonal element. Next step is to see if we pivot + _upper[i, i] = workVector[i]; + + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + + // Check if we really need to pivot. If (i+1) >=(mCoefficientMatrix.Rows -1) then + // we are working on the last row. That means that there is only one number + // And pivoting is useless. Also the indexSorting array will only contain + // -1 values. + if ((i + 1) < (sparseMatrix.RowCount - 1)) + { + if (workVector[i].Magnitude < _pivotTolerance * workVector[indexSorting[0]].Magnitude) + { + // swap columns of u (which holds the values of A in the + // sections that haven't been partitioned yet. + SwapColumns(_upper, i, indexSorting[0]); + + // Update P + var temp = _pivots[i]; + _pivots[i] = _pivots[indexSorting[0]]; + _pivots[indexSorting[0]] = temp; + } + } + + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + spaceLeft -= lowerNonZeroCount + upperNonZeroCount; + } + + for (var i = 0; i < _lower.RowCount; i++) + { + _lower[i, i] = 1.0f; + } + } + + /// + /// Pivot elements in the according to internal pivot array + /// + /// Row to pivot in + void PivotRow(Vector row) + { + var knownPivots = new Dictionary(); + + // pivot the row + for (var i = 0; i < row.Count; i++) + { + if ((_pivots[i] != i) && (!PivotMapFound(knownPivots, i))) + { + // store the pivots in the hashtable + knownPivots.Add(_pivots[i], i); + + var t = row[i]; + row[i] = row[_pivots[i]]; + row[_pivots[i]] = t; + } + } + } + + /// + /// Was pivoting already performed + /// + /// Pivots already done + /// Current item to pivot + /// true if performed, otherwise false + bool PivotMapFound(Dictionary knownPivots, int currentItem) + { + if (knownPivots.ContainsKey(_pivots[currentItem])) + { + if (knownPivots[_pivots[currentItem]].Equals(currentItem)) + { + return true; + } + } + + if (knownPivots.ContainsKey(currentItem)) + { + if (knownPivots[currentItem].Equals(_pivots[currentItem])) + { + return true; + } + } + + return false; + } + + /// + /// Swap columns in the + /// + /// Source . + /// First column index to swap + /// Second column index to swap + static void SwapColumns(Matrix matrix, int firstColumn, int secondColumn) + { + for (var i = 0; i < matrix.RowCount; i++) + { + var temp = matrix[i, firstColumn]; + matrix[i, firstColumn] = matrix[i, secondColumn]; + matrix[i, secondColumn] = temp; + } + } + + /// + /// Sort vector descending, not changing vector but placing sorted indices to + /// + /// Start sort form + /// Sort till upper bound + /// Array with sorted vector indices + /// Source + static void FindLargestItems(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + // Copy the indices for the values into the array + for (var i = 0; i < upperBound + 1 - lowerBound; i++) + { + sortedIndices[i] = lowerBound + i; + } + + for (var i = upperBound + 1 - lowerBound; i < sortedIndices.Length; i++) + { + sortedIndices[i] = -1; + } + + // Sort the first set of items. + // Sorting starts at index 0 because the index array + // starts at zero + // and ends at index upperBound - lowerBound + ILUTPElementSorter.SortDoubleIndicesDecreasing(0, upperBound - lowerBound, sortedIndices, values); + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_upper == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _upper.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rhs)); + } + + // Solve equation here + // Pivot(vector, result); + // Solve L*Y = B(piv,:) + var rowValues = new DenseVector(_lower.RowCount); + for (var i = 0; i < _lower.RowCount; i++) + { + _lower.Row(i, rowValues); + + var sum = Complex32.Zero; + for (var j = 0; j < i; j++) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = rhs[i] - sum; + } + + // Solve U*X = Y; + for (var i = _upper.RowCount - 1; i > -1; i--) + { + _upper.Row(i, rowValues); + + var sum = Complex32.Zero; + for (var j = _upper.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + + // We have a column pivot so we only need to pivot the + // end result not the incoming right hand side vector + var temp = lhs.Clone(); + + Pivot(temp, lhs); + } + + /// + /// Pivot elements in according to internal pivot array + /// + /// Source . + /// Result after pivoting. + void Pivot(Vector vector, Vector result) + { + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = vector[_pivots[i]]; + } + } +} + +/// +/// An element sort algorithm for the class. +/// +/// +/// This sort algorithm is used to sort the columns in a sparse matrix based on +/// the value of the element on the diagonal of the matrix. +/// +internal static class ILUTPElementSorter +{ + /// + /// Sorts the elements of the vector in decreasing + /// fashion. The vector itself is not affected. + /// + /// The starting index. + /// The stopping index. + /// An array that will contain the sorted indices once the algorithm finishes. + /// The that contains the values that need to be sorted. + public static void SortDoubleIndicesDecreasing(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + // Move all the indices that we're interested in to the beginning of the + // array. Ignore the rest of the indices. + if (lowerBound > 0) + { + for (var i = 0; i < (upperBound - lowerBound + 1); i++) + { + Exchange(sortedIndices, i, i + lowerBound); + } + + upperBound -= lowerBound; + lowerBound = 0; + } + + HeapSortDoublesIndices(lowerBound, upperBound, sortedIndices, values); + } + + /// + /// Sorts the elements of the vector in decreasing + /// fashion using heap sort algorithm. The vector itself is not affected. + /// + /// The starting index. + /// The stopping index. + /// An array that will contain the sorted indices once the algorithm finishes. + /// The that contains the values that need to be sorted. + private static void HeapSortDoublesIndices(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + var start = ((upperBound - lowerBound + 1) / 2) - 1 + lowerBound; + var end = (upperBound - lowerBound + 1) - 1 + lowerBound; + + BuildDoubleIndexHeap(start, upperBound - lowerBound + 1, sortedIndices, values); + + while (end >= lowerBound) + { + Exchange(sortedIndices, end, lowerBound); + SiftDoubleIndices(sortedIndices, values, lowerBound, end); + end -= 1; + } + } + + /// + /// Build heap for double indices + /// + /// Root position + /// Length of + /// Indices of + /// Target + private static void BuildDoubleIndexHeap(int start, int count, int[] sortedIndices, Vector values) + { + while (start >= 0) + { + SiftDoubleIndices(sortedIndices, values, start, count); + start -= 1; + } + } + + /// + /// Sift double indices + /// + /// Indices of + /// Target + /// Root position + /// Length of + private static void SiftDoubleIndices(int[] sortedIndices, Vector values, int begin, int count) + { + var root = begin; + + while (root * 2 < count) + { + var child = root * 2; + if ((child < count - 1) && (values[sortedIndices[child]].Magnitude > values[sortedIndices[child + 1]].Magnitude)) + { + child += 1; + } + + if (values[sortedIndices[root]].Magnitude <= values[sortedIndices[child]].Magnitude) + { + return; + } + + Exchange(sortedIndices, root, child); + root = child; + } + } + + /// + /// Sorts the given integers in a decreasing fashion. + /// + /// The values. + public static void SortIntegersDecreasing(int[] values) + { + HeapSortIntegers(values, values.Length); + } + + /// + /// Sort the given integers in a decreasing fashion using heapsort algorithm + /// + /// Array of values to sort + /// Length of + private static void HeapSortIntegers(int[] values, int count) + { + var start = (count / 2) - 1; + var end = count - 1; + + BuildHeap(values, start, count); + + while (end >= 0) + { + Exchange(values, end, 0); + Sift(values, 0, end); + end -= 1; + } + } + + /// + /// Build heap + /// + /// Target values array + /// Root position + /// Length of + private static void BuildHeap(int[] values, int start, int count) + { + while (start >= 0) + { + Sift(values, start, count); + start -= 1; + } + } + + /// + /// Sift values + /// + /// Target value array + /// Root position + /// Length of + private static void Sift(int[] values, int start, int count) + { + var root = start; + + while (root * 2 < count) + { + var child = root * 2; + if ((child < count - 1) && (values[child] > values[child + 1])) + { + child += 1; + } + + if (values[root] > values[child]) + { + Exchange(values, root, child); + root = child; + } + else + { + return; + } + } + } + + /// + /// Exchange values in array + /// + /// Target values array + /// First value to exchange + /// Second value to exchange + private static void Exchange(int[] values, int first, int second) + { + var t = values[first]; + values[first] = values[second]; + values[second] = t; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/MILU0Preconditioner.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/MILU0Preconditioner.cs new file mode 100644 index 0000000..a58f2c9 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/MILU0Preconditioner.cs @@ -0,0 +1,263 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +using Numerics; + +/// +/// A simple milu(0) preconditioner. +/// +/// +/// Original Fortran code by Yousef Saad (07 January 2004) +/// +public sealed class MILU0Preconditioner : IPreconditioner +{ + // Matrix stored in Modified Sparse Row (MSR) format containing the L and U + // factors together. + + // The diagonal (stored in alu(0:n-1) ) is inverted. Each i-th row of the matrix + // contains the i-th row of L (excluding the diagonal entry = 1) followed by + // the i-th row of U. + private Complex32[] _alu; + + // The row pointers (stored in jlu(0:n) ) and column indices to off-diagonal elements. + private int[] _jlu; + + // Pointer to the diagonal elements in MSR storage (for faster LU solving). + private int[] _diag; + + /// Use modified or standard ILU(0) + public MILU0Preconditioner(bool modified = true) + { + UseModified = modified; + } + + /// + /// Gets or sets a value indicating whether to use modified or standard ILU(0). + /// + public bool UseModified { get; set; } + + /// + /// Gets a value indicating whether the preconditioner is initialized. + /// + public bool IsInitialized { get; private set; } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square or is not an + /// instance of SparseCompressedRowMatrixStorage. + public void Initialize(Matrix matrix) + { + var csr = matrix.Storage as SparseCompressedRowMatrixStorage; + if (csr == null) + { + throw new ArgumentException(Resources.MatrixMustBeSparse, nameof(matrix)); + } + + // Dimension of matrix + int n = csr.RowCount; + if (n != csr.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + // Original matrix compressed sparse row storage. + Complex32[] a = csr.Values; + int[] ja = csr.ColumnIndices; + int[] ia = csr.RowPointers; + + _alu = new Complex32[ia[n] + 1]; + _jlu = new int[ia[n] + 1]; + _diag = new int[n]; + + int code = Compute(n, a, ja, ia, _alu, _jlu, _diag, UseModified); + if (code > -1) + { + throw new NumericalBreakdownException("Zero pivot encountered on row " + code + " during ILU process"); + } + + IsInitialized = true; + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector b. + /// The left hand side vector x. + public void Approximate(Vector input, Vector result) + { + if (_alu == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((result.Count != input.Count) || (result.Count != _diag.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + int n = _diag.Length; + + // Forward solve. + for (int i = 0; i < n; i++) + { + result[i] = input[i]; + for (int k = _jlu[i]; k < _diag[i]; k++) + { + result[i] = result[i] - _alu[k] * result[_jlu[k]]; + } + } + + // Backward solve. + for (int i = n - 1; i >= 0; i--) + { + for (int k = _diag[i]; k < _jlu[i + 1]; k++) + { + result[i] = result[i] - _alu[k] * result[_jlu[k]]; + } + + result[i] = _alu[i] * result[i]; + } + } + + /// + /// MILU0 is a simple milu(0) preconditioner. + /// + /// Order of the matrix. + /// Matrix values in CSR format (input). + /// Column indices (input). + /// Row pointers (input). + /// Matrix values in MSR format (output). + /// Row pointers and column indices (output). + /// Pointer to diagonal elements (output). + /// True if the modified/MILU algorithm should be used (recommended) + /// Returns 0 on success or k > 0 if a zero pivot was encountered at step k. + private int Compute(int n, Complex32[] a, int[] ja, int[] ia, Complex32[] alu, int[] jlu, int[] ju, bool modified) + { + var iw = new int[n]; + int i; + + // Set initial pointer value. + int p = n + 1; + jlu[0] = p; + + // Initialize work vector. + for (i = 0; i < n; i++) + { + iw[i] = -1; + } + + // The main loop. + for (i = 0; i < n; i++) + { + int pold = p; + + // Generating row i of L and U. + int j; + for (j = ia[i]; j < ia[i + 1]; j++) + { + // Copy row i of A, JA, IA into row i of ALU, JLU (LU matrix). + int jcol = ja[j]; + + if (jcol == i) + { + alu[i] = a[j]; + iw[jcol] = i; + ju[i] = p; + } + else + { + alu[p] = a[j]; + jlu[p] = ja[j]; + iw[jcol] = p; + p = p + 1; + } + } + + jlu[i + 1] = p; + + Complex32 s = Complex32.Zero; + + int k; + for (j = pold; j < ju[i]; j++) + { + int jrow = jlu[j]; + Complex32 tl = alu[j] * alu[jrow]; + alu[j] = tl; + + // Perform linear combination. + for (k = ju[jrow]; k < jlu[jrow + 1]; k++) + { + int jw = iw[jlu[k]]; + if (jw != -1) + { + alu[jw] = alu[jw] - tl * alu[k]; + } + else + { + // Accumulate fill-in values. + s = s + tl * alu[k]; + } + } + } + + if (modified) + { + alu[i] = alu[i] - s; + } + + if (alu[i] == Complex32.Zero) + { + return i; + } + + // Invert and store diagonal element. + alu[i] = 1.0f / alu[i]; + + // Reset pointers in work array. + iw[i] = -1; + for (k = pold; k < p; k++) + { + iw[jlu[k]] = -1; + } + } + + return -1; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/MlkBiCgStab.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/MlkBiCgStab.cs new file mode 100644 index 0000000..c41ab17 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/MlkBiCgStab.cs @@ -0,0 +1,542 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +/// +/// A Multiple-Lanczos Bi-Conjugate Gradient stabilized iterative matrix solver. +/// +/// +/// +/// The Multiple-Lanczos Bi-Conjugate Gradient stabilized (ML(k)-BiCGStab) solver is an 'improvement' +/// of the standard BiCgStab solver. +/// +/// +/// The algorithm was taken from:
+/// ML(k)BiCGSTAB: A BiCGSTAB variant based on multiple Lanczos starting vectors +///
+/// Man-Chung Yeung and Tony F. Chan +///
+/// SIAM Journal of Scientific Computing +///
+/// Volume 21, Number 4, pp. 1263 - 1290 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class MlkBiCgStab : IIterativeSolver +{ + /// + /// The default number of starting vectors. + /// + const int DefaultNumberOfStartingVectors = 50; + + /// + /// The collection of starting vectors which are used as the basis for the Krylov sub-space. + /// + IList> _startingVectors; + + /// + /// The number of starting vectors used by the algorithm + /// + int _numberOfStartingVectors = DefaultNumberOfStartingVectors; + + /// + /// Gets or sets the number of starting vectors. + /// + /// + /// Must be larger than 1 and smaller than the number of variables in the matrix that + /// for which this solver will be used. + /// + public int NumberOfStartingVectors + { + [DebuggerStepThrough] + get { return _numberOfStartingVectors; } + + [DebuggerStepThrough] + set + { + if (value <= 1) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfStartingVectors = value; + } + } + + /// + /// Resets the number of starting vectors to the default value. + /// + public void ResetNumberOfStartingVectors() + { + _numberOfStartingVectors = DefaultNumberOfStartingVectors; + } + + /// + /// Gets or sets a series of orthonormal vectors which will be used as basis for the + /// Krylov sub-space. + /// + public IList> StartingVectors + { + [DebuggerStepThrough] + get { return _startingVectors; } + + [DebuggerStepThrough] + set + { + if ((value == null) || (value.Count == 0)) + { + _startingVectors = null; + } + else + { + _startingVectors = value; + } + } + } + + /// + /// Gets the number of starting vectors to create + /// + /// Maximum number + /// Number of variables + /// Number of starting vectors to create + static int NumberOfStartingVectorsToCreate(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + return Math.Min(maximumNumberOfStartingVectors, (numberOfVariables - 1)); + } + + /// + /// Returns an array of starting vectors. + /// + /// The maximum number of starting vectors that should be created. + /// The number of variables. + /// + /// An array with starting vectors. The array will never be larger than the + /// but it may be smaller if + /// the is smaller than + /// the . + /// + static IList> CreateStartingVectors(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + // Get random values and then orthogonalize them with + // modified Gramm - Schmidt + var count = NumberOfStartingVectorsToCreate(maximumNumberOfStartingVectors, numberOfVariables); + + // Get a random set of samples based on the standard normal distribution with + // mean = 0 and sd = 1 + var distribution = new Normal(); + + var matrix = new DenseMatrix(numberOfVariables, count); + for (var i = 0; i < matrix.ColumnCount; i++) + { + var samples = new Numerics.Complex32[matrix.RowCount]; + var samplesRe = distribution.Samples().Take(matrix.RowCount).ToArray(); + var samplesIm = distribution.Samples().Take(matrix.RowCount).ToArray(); + for (int j = 0; j < matrix.RowCount; j++) + { + samples[j] = new Numerics.Complex32((float)samplesRe[j], (float)samplesIm[j]); + } + + // Set the column + matrix.SetColumn(i, samples); + } + + // Compute the orthogonalization. + var gs = matrix.GramSchmidt(); + var orthogonalMatrix = gs.Q; + + // Now transfer this to vectors + var result = new List>(orthogonalMatrix.ColumnCount); + for (var i = 0; i < orthogonalMatrix.ColumnCount; i++) + { + result.Add(orthogonalMatrix.Column(i)); + + // Normalize the result vector + result[i].Multiply(1 / (float)result[i].L2Norm(), result[i]); + } + + return result; + } + + /// + /// Create random vectors array + /// + /// Number of vectors + /// Size of each vector + /// Array of random vectors + static Vector[] CreateVectorArray(int arraySize, int vectorSize) + { + var result = new Vector[arraySize]; + for (var i = 0; i < result.Length; i++) + { + result[i] = new DenseVector(vectorSize); + } + + return result; + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Source A. + /// Residual data. + /// x data. + /// b data. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // Choose an initial guess x_0 + // Take x_0 = 0 + var xtemp = new DenseVector(input.Count); + + // Choose k vectors q_1, q_2, ..., q_k + // Build a new set if: + // a) the stored set doesn't exist (i.e. == null) + // b) Is of an incorrect length (i.e. too long) + // c) The vectors are of an incorrect length (i.e. too long or too short) + var useOld = false; + if (_startingVectors != null) + { + // We don't accept collections with zero starting vectors so ... + if (_startingVectors.Count <= NumberOfStartingVectorsToCreate(_numberOfStartingVectors, input.Count)) + { + // Only check the first vector for sizing. If that matches we assume the + // other vectors match too. If they don't the process will crash + if (_startingVectors[0].Count == input.Count) + { + useOld = true; + } + } + } + + _startingVectors = useOld ? _startingVectors : CreateStartingVectors(_numberOfStartingVectors, input.Count); + + // Store the number of starting vectors. Not really necessary but easier to type :) + var k = _startingVectors.Count; + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary values + var c = new Numerics.Complex32[k]; + + // Define the temporary vectors + var gtemp = new DenseVector(residuals.Count); + + var u = new DenseVector(residuals.Count); + var utemp = new DenseVector(residuals.Count); + var temp = new DenseVector(residuals.Count); + var temp1 = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + + var zd = new DenseVector(residuals.Count); + var zg = new DenseVector(residuals.Count); + var zw = new DenseVector(residuals.Count); + + var d = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // g_0 = r_0 + var g = CreateVectorArray(_startingVectors.Count, residuals.Count); + residuals.CopyTo(g[k - 1]); + + var w = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // FOR (j = 0, 1, 2 ....) + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue) + { + // SOLVE M g~_((j-1)k+k) = g_((j-1)k+k) + preconditioner.Approximate(g[k - 1], gtemp); + + // w_((j-1)k+k) = A g~_((j-1)k+k) + matrix.Multiply(gtemp, w[k - 1]); + + // c_((j-1)k+k) = q^T_1 w_((j-1)k+k) + c[k - 1] = _startingVectors[0].ConjugateDotProduct(w[k - 1]); + if (c[k - 1].Real.AlmostEqualNumbersBetween(0, 1) && c[k - 1].Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // alpha_(jk+1) = q^T_1 r_((j-1)k+k) / c_((j-1)k+k) + var alpha = _startingVectors[0].ConjugateDotProduct(residuals) / c[k - 1]; + + // u_(jk+1) = r_((j-1)k+k) - alpha_(jk+1) w_((j-1)k+k) + w[k - 1].Multiply(-alpha, temp); + residuals.Add(temp, u); + + // SOLVE M u~_(jk+1) = u_(jk+1) + preconditioner.Approximate(u, temp1); + temp1.CopyTo(utemp); + + // rho_(j+1) = -u^t_(jk+1) A u~_(jk+1) / ||A u~_(jk+1)||^2 + matrix.Multiply(temp1, temp); + var rho = temp.ConjugateDotProduct(temp); + + // If rho is zero then temp is a zero vector and we're probably + // about to have zero residuals (i.e. an exact solution). + // So set rho to 1.0 because in the next step it will turn to zero. + if (rho.Real.AlmostEqualNumbersBetween(0, 1) && rho.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + rho = 1.0f; + } + + rho = -u.ConjugateDotProduct(temp) / rho; + + // r_(jk+1) = rho_(j+1) A u~_(jk+1) + u_(jk+1) + u.CopyTo(residuals); + + // Reuse temp + temp.Multiply(rho, temp); + residuals.Add(temp, temp2); + temp2.CopyTo(residuals); + + // x_(jk+1) = x_((j-1)k_k) - rho_(j+1) u~_(jk+1) + alpha_(jk+1) g~_((j-1)k+k) + utemp.Multiply(-rho, temp); + xtemp.Add(temp, temp2); + temp2.CopyTo(xtemp); + + gtemp.Multiply(alpha, gtemp); + xtemp.Add(gtemp, temp2); + temp2.CopyTo(xtemp); + + // Check convergence and stop if we are converged. + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // We're all good now. + // Exit from the while loop. + break; + } + } + + // FOR (i = 1,2, ...., k) + for (var i = 0; i < k; i++) + { + // z_d = u_(jk+1) + u.CopyTo(zd); + + // z_g = r_(jk+i) + residuals.CopyTo(zg); + + // z_w = 0 + zw.Clear(); + + // FOR (s = i, ...., k-1) AND j >= 1 + Numerics.Complex32 beta; + if (iterationNumber >= 1) + { + for (var s = i; s < k - 1; s++) + { + // beta^(jk+i)_((j-1)k+s) = -q^t_(s+1) z_d / c_((j-1)k+s) + beta = -_startingVectors[s + 1].ConjugateDotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_((j-1)k+s) d_((j-1)k+s) + d[s].Multiply(beta, temp); + zd.Add(temp, temp2); + temp2.CopyTo(zd); + + // z_g = z_g + beta^(jk+i)_((j-1)k+s) g_((j-1)k+s) + g[s].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + + // z_w = z_w + beta^(jk+i)_((j-1)k+s) w_((j-1)k+s) + w[s].Multiply(beta, temp); + zw.Add(temp, temp2); + temp2.CopyTo(zw); + } + } + + beta = rho * c[k - 1]; + if (beta.Real.AlmostEqualNumbersBetween(0, 1) && beta.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // beta^(jk+i)_((j-1)k+k) = -(q^T_1 (r_(jk+1) + rho_(j+1) z_w)) / (rho_(j+1) c_((j-1)k+k)) + zw.Multiply(rho, temp2); + residuals.Add(temp2, temp); + beta = -_startingVectors[0].ConjugateDotProduct(temp) / beta; + + // z_g = z_g + beta^(jk+i)_((j-1)k+k) g_((j-1)k+k) + g[k - 1].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + + // z_w = rho_(j+1) (z_w + beta^(jk+i)_((j-1)k+k) w_((j-1)k+k)) + w[k - 1].Multiply(beta, temp); + zw.Add(temp, temp2); + temp2.CopyTo(zw); + zw.Multiply(rho, zw); + + // z_d = r_(jk+i) + z_w + residuals.Add(zw, zd); + + // FOR (s = 1, ... i - 1) + for (var s = 0; s < i - 1; s++) + { + // beta^(jk+i)_(jk+s) = -q^T_s+1 z_d / c_(jk+s) + beta = -_startingVectors[s + 1].ConjugateDotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_(jk+s) * d_(jk+s) + d[s].Multiply(beta, temp); + zd.Add(temp, temp2); + temp2.CopyTo(zd); + + // z_g = z_g + beta^(jk+i)_(jk+s) * g_(jk+s) + g[s].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + } + + // d_(jk+i) = z_d - u_(jk+i) + zd.Subtract(u, d[i]); + + // g_(jk+i) = z_g + z_w + zg.Add(zw, g[i]); + + // IF (i < k - 1) + if (i < k - 1) + { + // c_(jk+1) = q^T_i+1 d_(jk+i) + c[i] = _startingVectors[i + 1].ConjugateDotProduct(d[i]); + if (c[i].Real.AlmostEqualNumbersBetween(0, 1) && c[i].Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // alpha_(jk+i+1) = q^T_(i+1) u_(jk+i) / c_(jk+i) + alpha = _startingVectors[i + 1].ConjugateDotProduct(u) / c[i]; + + // u_(jk+i+1) = u_(jk+i) - alpha_(jk+i+1) d_(jk+i) + d[i].Multiply(-alpha, temp); + u.Add(temp, temp2); + temp2.CopyTo(u); + + // SOLVE M g~_(jk+i) = g_(jk+i) + preconditioner.Approximate(g[i], gtemp); + + // x_(jk+i+1) = x_(jk+i) + rho_(j+1) alpha_(jk+i+1) g~_(jk+i) + gtemp.Multiply(rho * alpha, temp); + xtemp.Add(temp, temp2); + temp2.CopyTo(xtemp); + + // w_(jk+i) = A g~_(jk+i) + matrix.Multiply(gtemp, w[i]); + + // r_(jk+i+1) = r_(jk+i) - rho_(j+1) alpha_(jk+i+1) w_(jk+i) + w[i].Multiply(-rho * alpha, temp); + residuals.Add(temp, temp2); + temp2.CopyTo(residuals); + + // We can check the residuals here if they're close + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, xtemp, input); + } + } + } // END ITERATION OVER i + + iterationNumber++; + } + + // copy the temporary result to the real result vector + xtemp.CopyTo(result); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/TFQMR.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/TFQMR.cs new file mode 100644 index 0000000..de724a8 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Solvers/TFQMR.cs @@ -0,0 +1,277 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers; + +/// +/// A Transpose Free Quasi-Minimal Residual (TFQMR) iterative matrix solver. +/// +/// +/// +/// The TFQMR algorithm was taken from:
+/// Iterative methods for sparse linear systems. +///
+/// Yousef Saad +///
+/// Algorithm is described in Chapter 7, section 7.4.3, page 219 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class TFQMR : IIterativeSolver +{ + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Is even? + /// + /// Number to check + /// true if even, otherwise false + static bool IsEven(int number) + { + return number % 2 == 0; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + var d = new DenseVector(input.Count); + var r = DenseVector.OfVector(input); + + var uodd = new DenseVector(input.Count); + var ueven = new DenseVector(input.Count); + + var v = new DenseVector(input.Count); + var pseudoResiduals = DenseVector.OfVector(input); + + var x = new DenseVector(input.Count); + var yodd = new DenseVector(input.Count); + var yeven = DenseVector.OfVector(input); + + // Temp vectors + var temp = new DenseVector(input.Count); + var temp1 = new DenseVector(input.Count); + var temp2 = new DenseVector(input.Count); + + // Define the scalars + Numerics.Complex32 alpha = 0; + Numerics.Complex32 eta = 0; + float theta = 0; + + // Initialize + var tau = (float)input.L2Norm(); + Numerics.Complex32 rho = tau * tau; + + // Calculate the initial values for v + // M temp = yEven + preconditioner.Approximate(yeven, temp); + + // v = A temp + matrix.Multiply(temp, v); + + // Set uOdd + v.CopyTo(ueven); + + // Start the iteration + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, result, input, pseudoResiduals) == IterationStatus.Continue) + { + // First part of the step, the even bit + if (IsEven(iterationNumber)) + { + // sigma = (v, r) + var sigma = r.ConjugateDotProduct(v); + if (sigma.Real.AlmostEqualNumbersBetween(0, 1) && sigma.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + // FAIL HERE + iterator.Cancel(); + break; + } + + // alpha = rho / sigma + alpha = rho / sigma; + + // yOdd = yEven - alpha * v + v.Multiply(-alpha, temp1); + yeven.Add(temp1, yodd); + + // Solve M temp = yOdd + preconditioner.Approximate(yodd, temp); + + // uOdd = A temp + matrix.Multiply(temp, uodd); + } + + // The intermediate step which is equal for both even and + // odd iteration steps. + // Select the correct vector + var uinternal = IsEven(iterationNumber) ? ueven : uodd; + var yinternal = IsEven(iterationNumber) ? yeven : yodd; + + // pseudoResiduals = pseudoResiduals - alpha * uOdd + uinternal.Multiply(-alpha, temp1); + pseudoResiduals.Add(temp1, temp2); + temp2.CopyTo(pseudoResiduals); + + // d = yOdd + theta * theta * eta / alpha * d + d.Multiply(theta * theta * eta / alpha, temp); + yinternal.Add(temp, d); + + // theta = ||pseudoResiduals||_2 / tau + theta = (float)pseudoResiduals.L2Norm() / tau; + var c = 1 / (float)Math.Sqrt(1 + (theta * theta)); + + // tau = tau * theta * c + tau *= theta * c; + + // eta = c^2 * alpha + eta = c * c * alpha; + + // x = x + eta * d + d.Multiply(eta, temp1); + x.Add(temp1, temp2); + temp2.CopyTo(x); + + // Check convergence and see if we can bail + if (iterator.DetermineStatus(iterationNumber, result, input, pseudoResiduals) != IterationStatus.Continue) + { + // Calculate the real values + preconditioner.Approximate(x, result); + + // Calculate the true residual. Use the temp vector for that + // so that we don't pollute the pseudoResidual vector for no + // good reason. + CalculateTrueResidual(matrix, temp, result, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, result, input, temp) != IterationStatus.Continue) + { + // We're all good now. + return; + } + } + + // The odd step + if (!IsEven(iterationNumber)) + { + if (rho.Real.AlmostEqualNumbersBetween(0, 1) && rho.Imaginary.AlmostEqualNumbersBetween(0, 1)) + { + // FAIL HERE + iterator.Cancel(); + break; + } + + var rhoNew = r.ConjugateDotProduct(pseudoResiduals); + var beta = rhoNew / rho; + + // Update rho for the next loop + rho = rhoNew; + + // yOdd = pseudoResiduals + beta * yOdd + yodd.Multiply(beta, temp1); + pseudoResiduals.Add(temp1, yeven); + + // Solve M temp = yOdd + preconditioner.Approximate(yeven, temp); + + // uOdd = A temp + matrix.Multiply(temp, ueven); + + // v = uEven + beta * (uOdd + beta * v) + v.Multiply(beta, temp1); + uodd.Add(temp1, temp); + + temp.Multiply(beta, temp1); + ueven.Add(temp1, v); + } + + // Calculate the real values + preconditioner.Approximate(x, result); + + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/SparseMatrix.cs b/MathNet.Numerics/LinearAlgebra/Complex32/SparseMatrix.cs new file mode 100644 index 0000000..18355ba --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/SparseMatrix.cs @@ -0,0 +1,1560 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Complex32; + +using Numerics; + +/// +/// A Matrix with sparse storage, intended for very large matrices where most of the cells are zero. +/// The underlying storage scheme is 3-array compressed-sparse-row (CSR) Format. +/// Wikipedia - CSR. +/// +[Serializable] +[DebuggerDisplay("SparseMatrix {RowCount}x{ColumnCount}-Complex32 {NonZerosCount}-NonZero")] +public class SparseMatrix : Matrix +{ + readonly SparseCompressedRowMatrixStorage _storage; + + /// + /// Gets the number of non zero elements in the matrix. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get { return _storage.ValueCount; } + } + + /// + /// Create a new sparse matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public SparseMatrix(SparseCompressedRowMatrixStorage storage) + : base(storage) + { + _storage = storage; + } + + /// + /// Create a new square sparse matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public SparseMatrix(int order) + : this(order, order) + { + } + + /// + /// Create a new sparse matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public SparseMatrix(int rows, int columns) + : this(new SparseCompressedRowMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new sparse matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfMatrix(Matrix matrix) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfArray(Complex32[,] array) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfArray(array)); + } + + /// + /// Create a new sparse matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in row-major order (row by row). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + /// + public static SparseMatrix OfRowMajor(int rows, int columns, IEnumerable rowMajor) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowMajorEnumerable(rows, columns, rowMajor)); + } + + /// + /// Create a new sparse matrix with the given number of rows and columns as a copy of the given array. + /// The array is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + /// + public static SparseMatrix OfColumnMajor(int rows, int columns, IList columnMajor) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnMajorList(rows, columns, columnMajor)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumns(IEnumerable> data) + { + return OfColumnArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumns(int rows, int columns, IEnumerable> data) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnArrays(params Complex32[][] columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnArrays(IEnumerable columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnArrays((columns as Complex32[][]) ?? columns.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnVectors(IEnumerable> columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRows(IEnumerable> data) + { + return OfRowArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRows(int rows, int columns, IEnumerable> data) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowArrays(params Complex32[][] rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowArrays(IEnumerable rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowArrays((rows as Complex32[][]) ?? rows.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowVectors(IEnumerable> rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalVector(Vector diagonal) + { + var m = new SparseMatrix(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = new SparseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalArray(Complex32[] diagonal) + { + var m = new SparseMatrix(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalArray(int rows, int columns, Complex32[] diagonal) + { + var m = new SparseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix and initialize each value to the same provided value. + /// + public static SparseMatrix Create(int rows, int columns, Complex32 value) + { + if (value == Complex32.Zero) + return new SparseMatrix(rows, columns); + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new sparse matrix and initialize each value using the provided init function. + /// + public static SparseMatrix Create(int rows, int columns, Func init) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value to the same provided value. + /// + public static SparseMatrix CreateDiagonal(int rows, int columns, Complex32 value) + { + if (value == Complex32.Zero) + return new SparseMatrix(rows, columns); + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value using the provided init function. + /// + public static SparseMatrix CreateDiagonal(int rows, int columns, Func init) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static SparseMatrix CreateIdentity(int order) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + var result = Build.SameAs(this); + LowerTriangleImpl(result); + return result; + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + LowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + LowerTriangleImpl(result); + } + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void LowerTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row >= columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + var result = Build.SameAs(this); + UpperTriangleImpl(result); + return result; + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + UpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + UpperTriangleImpl(result); + } + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void UpperTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row <= columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + var result = Build.SameAs(this); + StrictlyLowerTriangleImpl(result); + return result; + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + StrictlyLowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyLowerTriangleImpl(result); + } + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyLowerTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row > columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + var result = Build.SameAs(this); + StrictlyUpperTriangleImpl(result); + return result; + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + StrictlyUpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyUpperTriangleImpl(result); + } + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyUpperTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row < columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + CopyTo(result); + DoMultiply(-1, result); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + var rowPointers = _storage.RowPointers; + var values = _storage.Values; + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + var startIndex = rowPointers[i]; + var endIndex = rowPointers[i + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var s = 0d; + for (var j = startIndex; j < endIndex; j++) + { + s += values[j].Magnitude; + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + var aat = (SparseCompressedRowMatrixStorage)(this * ConjugateTranspose()).Storage; + var norm = 0d; + for (var i = 0; i < aat.RowCount; i++) + { + var startIndex = aat.RowPointers[i]; + var endIndex = aat.RowPointers[i + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var j = startIndex; j < endIndex; j++) + { + if (i == aat.ColumnIndices[j]) + { + norm += aat.Values[j].Magnitude; + } + } + } + + return Math.Sqrt(norm); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther == null || sparseResult == null) + { + base.DoAdd(other, result); + return; + } + + if (ReferenceEquals(this, other)) + { + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + LinearAlgebraControl.Provider.ScaleArray(2.0f, sparseResult._storage.Values, sparseResult._storage.Values); + return; + } + + SparseMatrix left; + + if (ReferenceEquals(sparseOther, sparseResult)) + { + left = this; + } + else if (ReferenceEquals(this, sparseResult)) + { + left = sparseOther; + } + else + { + CopyTo(sparseResult); + left = sparseOther; + } + + var leftStorage = left._storage; + for (var i = 0; i < leftStorage.RowCount; i++) + { + var endIndex = leftStorage.RowPointers[i + 1]; + for (var j = leftStorage.RowPointers[i]; j < endIndex; j++) + { + var columnIndex = leftStorage.ColumnIndices[j]; + var resVal = leftStorage.Values[j] + result.At(i, columnIndex); + result.At(i, columnIndex, resVal); + } + } + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract to this matrix. + /// The matrix to store the result of subtraction. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther == null || sparseResult == null) + { + base.DoSubtract(other, result); + return; + } + + if (ReferenceEquals(this, other)) + { + result.Clear(); + return; + } + + var otherStorage = sparseOther._storage; + + if (ReferenceEquals(this, sparseResult)) + { + for (var i = 0; i < otherStorage.RowCount; i++) + { + var endIndex = otherStorage.RowPointers[i + 1]; + for (var j = otherStorage.RowPointers[i]; j < endIndex; j++) + { + var columnIndex = otherStorage.ColumnIndices[j]; + var resVal = sparseResult.At(i, columnIndex) - otherStorage.Values[j]; + result.At(i, columnIndex, resVal); + } + } + } + else + { + if (!ReferenceEquals(sparseOther, sparseResult)) + { + sparseOther.CopyTo(sparseResult); + } + + sparseResult.Negate(sparseResult); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + var columnIndex = columnIndices[j]; + var resVal = sparseResult.At(i, columnIndex) + values[j]; + result.At(i, columnIndex, resVal); + } + } + } + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(Complex32 scalar, Matrix result) + { + if (scalar == 1.0f) + { + CopyTo(result); + return; + } + + if (scalar == 0.0f || NonZerosCount == 0) + { + result.Clear(); + return; + } + + var sparseResult = result as SparseMatrix; + if (sparseResult == null) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + result.At(row, column, values[index] * scalar); + } + } + } + else + { + if (!ReferenceEquals(this, result)) + { + CopyTo(sparseResult); + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, sparseResult._storage.Values, sparseResult._storage.Values); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther != null && sparseResult != null) + { + DoMultiplySparse(sparseOther, sparseResult); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null && sparseResult != null) + { + var diagonal = diagonalOther.Data; + if (other.ColumnCount == other.RowCount) + { + Storage.MapIndexedTo(result.Storage, (i, j, x) => x * diagonal[j], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Storage.Clear(); + Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * diagonal[j], 0, 0, RowCount, 0, 0, ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + + return; + } + + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + // in this case we can directly address the underlying data-array + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var column = 0; column < other.ColumnCount; column++) + { + int otherColumnStartPosition = column * other.RowCount; + var sum = Complex32.Zero; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * denseOther.Data[otherColumnStartPosition + columnIndices[index]]; + } + + result.At(row, column, sum); + } + } + + return; + } + + var columnVector = new DenseVector(other.RowCount); + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var column = 0; column < other.ColumnCount; column++) + { + // Multiply row of matrix A on column of matrix B + other.Column(column, columnVector); + + var sum = Complex32.Zero; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * columnVector[columnIndices[index]]; + } + + result.At(row, column, sum); + } + } + } + + void DoMultiplySparse(SparseMatrix other, SparseMatrix result) + { + result.Clear(); + + var ax = _storage.Values; + var ap = _storage.RowPointers; + var ai = _storage.ColumnIndices; + + var bx = other._storage.Values; + var bp = other._storage.RowPointers; + var bi = other._storage.ColumnIndices; + + int rows = RowCount; + int cols = other.ColumnCount; + + int[] cp = result._storage.RowPointers; + + var marker = new int[cols]; + for (int ib = 0; ib < cols; ib++) + { + marker[ib] = -1; + } + + int count = 0; + for (int i = 0; i < rows; i++) + { + // For each row of A + for (int j = ap[i]; j < ap[i + 1]; j++) + { + // Row number to be added + int a = ai[j]; + for (int k = bp[a]; k < bp[a + 1]; k++) + { + int b = bi[k]; + if (marker[b] != i) + { + marker[b] = i; + count++; + } + } + } + + // Record non-zero count. + cp[i + 1] = count; + } + + var ci = new int[count]; + var cx = new Complex32[count]; + + for (int ib = 0; ib < cols; ib++) + { + marker[ib] = -1; + } + + count = 0; + for (int i = 0; i < rows; i++) + { + int rowStart = cp[i]; + for (int j = ap[i]; j < ap[i + 1]; j++) + { + int a = ai[j]; + Complex32 aEntry = ax[j]; + for (int k = bp[a]; k < bp[a + 1]; k++) + { + int b = bi[k]; + Complex32 bEntry = bx[k]; + if (marker[b] < rowStart) + { + marker[b] = count; + ci[marker[b]] = b; + cx[marker[b]] = aEntry * bEntry; + count++; + } + else + { + cx[marker[b]] += aEntry * bEntry; + } + } + } + } + + result._storage.Values = cx; + result._storage.ColumnIndices = ci; + result._storage.Normalize(); + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var sum = Complex32.Zero; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * rightSide[columnIndices[index]]; + } + + result[row] = sum; + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var otherSparse = other as SparseMatrix; + var resultSparse = result as SparseMatrix; + + if (otherSparse == null || resultSparse == null) + { + base.DoTransposeAndMultiply(other, result); + return; + } + + resultSparse.Clear(); + + var rowPointers = _storage.RowPointers; + var values = _storage.Values; + + var otherStorage = otherSparse._storage; + + for (var j = 0; j < RowCount; j++) + { + var startIndexOther = otherStorage.RowPointers[j]; + var endIndexOther = otherStorage.RowPointers[j + 1]; + + if (startIndexOther == endIndexOther) + { + continue; + } + + for (var i = 0; i < RowCount; i++) + { + // Multiply row of matrix A on row of matrix B + + var startIndexThis = rowPointers[i]; + var endIndexThis = rowPointers[i + 1]; + + if (startIndexThis == endIndexThis) + { + continue; + } + + var sum = Complex32.Zero; + for (var index = startIndexOther; index < endIndexOther; index++) + { + var ind = _storage.FindItem(i, otherStorage.ColumnIndices[index]); + if (ind >= 0) + { + sum += otherStorage.Values[index] * values[ind]; + } + } + + resultSparse._storage.At(i, j, sum + result.At(i, j)); + } + } + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var rightSideValue = rightSide[row]; + for (var index = startIndex; index < endIndex; index++) + { + result[columnIndices[index]] += values[index] * rightSideValue; + } + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + var resVal = values[j] * other.At(i, columnIndices[j]); + if (!resVal.IsZero()) + { + result.At(i, columnIndices[j], resVal); + } + } + } + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + if (!values[j].IsZero()) + { + result.At(i, columnIndices[j], values[j] / divisor.At(i, columnIndices[j])); + } + } + } + } + + public override void KroneckerProduct(Matrix other, Matrix result) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != (RowCount * other.RowCount) || result.ColumnCount != (ColumnCount * other.ColumnCount)) + { + throw DimensionsDontMatch(this, other, result); + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + if (!values[j].IsZero()) + { + result.SetSubMatrix(i * other.RowCount, other.RowCount, columnIndices[j] * other.ColumnCount, other.ColumnCount, values[j] * other); + } + } + } + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public override bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + if (!values[index].Equals(At(column, row))) + { + return false; + } + } + } + + return true; + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public override bool IsHermitian() + { + if (RowCount != ColumnCount) + { + return false; + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + if (!values[index].Equals(At(column, row).Conjugate())) + { + return false; + } + } + } + + return true; + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static SparseMatrix operator +(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.RowCount != rightSide.RowCount || leftSide.ColumnCount != rightSide.ColumnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Add(rightSide); + } + + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static SparseMatrix operator +(SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Clone(); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static SparseMatrix operator -(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.RowCount != rightSide.RowCount || leftSide.ColumnCount != rightSide.ColumnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Subtract(rightSide); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static SparseMatrix operator -(SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Negate(); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator *(SparseMatrix leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator *(Complex32 leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static SparseMatrix operator *(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide.ColumnCount != rightSide.RowCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static SparseVector operator *(SparseMatrix leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static SparseVector operator *(SparseVector leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.LeftMultiply(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator %(SparseMatrix leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseMatrix)leftSide.Remainder(rightSide); + } + + public override string ToTypeString() + { + return string.Format("SparseMatrix {0}x{1}-Complex32 {2:P2} Filled", RowCount, ColumnCount, NonZerosCount / (RowCount * (double)ColumnCount)); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/SparseVector.cs b/MathNet.Numerics/LinearAlgebra/Complex32/SparseVector.cs new file mode 100644 index 0000000..e54deab --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/SparseVector.cs @@ -0,0 +1,958 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace MathNet.Numerics.LinearAlgebra.Complex32; + +using Numerics; + +/// +/// A vector with sparse storage, intended for very large vectors where most of the cells are zero. +/// +/// The sparse vector is not thread safe. +[Serializable] +[DebuggerDisplay("SparseVector {Count}-Complex32 {NonZerosCount}-NonZero")] +public class SparseVector : Vector +{ + readonly SparseVectorStorage _storage; + + /// + /// Gets the number of non zero elements in the vector. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get { return _storage.ValueCount; } + } + + /// + /// Create a new sparse vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public SparseVector(SparseVectorStorage storage) + : base(storage) + { + _storage = storage; + } + + /// + /// Create a new sparse vector with the given length. + /// All cells of the vector will be initialized to zero. + /// Zero-length vectors are not supported. + /// + /// If length is less than one. + public SparseVector(int length) + : this(new SparseVectorStorage(length)) + { + } + + /// + /// Create a new sparse vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfVector(Vector vector) + { + return new SparseVector(SparseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new sparse vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfEnumerable(IEnumerable enumerable) + { + return new SparseVector(SparseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new sparse vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfIndexedEnumerable(int length, IEnumerable> enumerable) + { + return new SparseVector(SparseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided value. + /// + public static SparseVector Create(int length, Complex32 value) + { + return new SparseVector(SparseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided init function. + /// + public static SparseVector Create(int length, Func init) + { + return new SparseVector(SparseVectorStorage.OfInit(length, init)); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// Warning, the new 'sparse vector' with a non-zero scalar added to it will be a 100% filled + /// sparse vector and very inefficient. Would be better to work with a dense vector instead. + /// + /// + /// The scalar to add. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Complex32 scalar, Vector result) + { + if (scalar == Complex32.Zero) + { + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + return; + } + + if (ReferenceEquals(this, result)) + { + //populate a new vector with the scalar + var vnonZeroValues = new Complex32[Count]; + var vnonZeroIndices = new int[Count]; + for (int index = 0; index < Count; index++) + { + vnonZeroIndices[index] = index; + vnonZeroValues[index] = scalar; + } + + //populate the non zero values from this + var indices = _storage.Indices; + var values = _storage.Values; + for (int j = 0; j < _storage.ValueCount; j++) + { + vnonZeroValues[indices[j]] = values[j] + scalar; + } + + //assign this vectors array to the new arrays. + _storage.Values = vnonZeroValues; + _storage.Indices = vnonZeroIndices; + _storage.ValueCount = Count; + } + else + { + for (var index = 0; index < Count; index++) + { + result.At(index, At(index) + scalar); + } + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to add to this one. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Vector other, Vector result) + { + var otherSparse = other as SparseVector; + if (otherSparse == null) + { + base.DoAdd(other, result); + return; + } + + var resultSparse = result as SparseVector; + if (resultSparse == null) + { + base.DoAdd(other, result); + return; + } + + // TODO (ruegg, 2011-10-11): Options to optimize? + + var otherStorage = otherSparse._storage; + if (ReferenceEquals(this, resultSparse)) + { + int i = 0, j = 0; + while (j < otherStorage.ValueCount) + { + if (i >= _storage.ValueCount || _storage.Indices[i] > otherStorage.Indices[j]) + { + var otherValue = otherStorage.Values[j]; + if (!Complex32.Zero.Equals(otherValue)) + { + _storage.InsertAtIndexUnchecked(i++, otherStorage.Indices[j], otherValue); + } + + j++; + } + else if (_storage.Indices[i] == otherStorage.Indices[j]) + { + // TODO: result can be zero, remove? + _storage.Values[i++] += otherStorage.Values[j++]; + } + else + { + i++; + } + } + } + else + { + result.Clear(); + int i = 0, j = 0, last = -1; + while (i < _storage.ValueCount || j < otherStorage.ValueCount) + { + if (j >= otherStorage.ValueCount || i < _storage.ValueCount && _storage.Indices[i] <= otherStorage.Indices[j]) + { + var next = _storage.Indices[i]; + if (next != last) + { + last = next; + result.At(next, _storage.Values[i] + otherSparse.At(next)); + } + + i++; + } + else + { + var next = otherStorage.Indices[j]; + if (next != last) + { + last = next; + result.At(next, At(next) + otherStorage.Values[j]); + } + + j++; + } + } + } + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to subtract. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Complex32 scalar, Vector result) + { + DoAdd(-scalar, result); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to subtract from this one. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Vector other, Vector result) + { + if (ReferenceEquals(this, other)) + { + result.Clear(); + return; + } + + var otherSparse = other as SparseVector; + if (otherSparse == null) + { + base.DoSubtract(other, result); + return; + } + + var resultSparse = result as SparseVector; + if (resultSparse == null) + { + base.DoSubtract(other, result); + return; + } + + // TODO (ruegg, 2011-10-11): Options to optimize? + + var otherStorage = otherSparse._storage; + if (ReferenceEquals(this, resultSparse)) + { + int i = 0, j = 0; + while (j < otherStorage.ValueCount) + { + if (i >= _storage.ValueCount || _storage.Indices[i] > otherStorage.Indices[j]) + { + var otherValue = otherStorage.Values[j]; + if (!Complex32.Zero.Equals(otherValue)) + { + _storage.InsertAtIndexUnchecked(i++, otherStorage.Indices[j], -otherValue); + } + + j++; + } + else if (_storage.Indices[i] == otherStorage.Indices[j]) + { + // TODO: result can be zero, remove? + _storage.Values[i++] -= otherStorage.Values[j++]; + } + else + { + i++; + } + } + } + else + { + result.Clear(); + int i = 0, j = 0, last = -1; + while (i < _storage.ValueCount || j < otherStorage.ValueCount) + { + if (j >= otherStorage.ValueCount || i < _storage.ValueCount && _storage.Indices[i] <= otherStorage.Indices[j]) + { + var next = _storage.Indices[i]; + if (next != last) + { + last = next; + result.At(next, _storage.Values[i] - otherSparse.At(next)); + } + + i++; + } + else + { + var next = otherStorage.Indices[j]; + if (next != last) + { + last = next; + result.At(next, At(next) - otherStorage.Values[j]); + } + + j++; + } + } + } + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult == null) + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], -_storage.Values[index]); + } + + return; + } + + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new Complex32[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ScaleArray(-Complex32.One, sparseResult._storage.Values, sparseResult._storage.Values); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + protected override void DoConjugate(Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult != null) + { + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new Complex32[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ConjugateArray(sparseResult._storage.Values, sparseResult._storage.Values); + return; + } + + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], _storage.Values[index].Conjugate()); + } + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to multiply. + /// + /// + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(Complex32 scalar, Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult == null) + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], scalar * _storage.Values[index]); + } + } + else + { + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new Complex32[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, sparseResult._storage.Values, sparseResult._storage.Values); + } + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override Complex32 DoDotProduct(Vector other) + { + var result = Complex32.Zero; + if (ReferenceEquals(this, other)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i] * _storage.Values[i]; + } + } + else + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i] * other.At(_storage.Indices[i]); + } + } + + return result; + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected override Complex32 DoConjugateDotProduct(Vector other) + { + var result = Complex32.Zero; + if (ReferenceEquals(this, other)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i].Conjugate() * _storage.Values[i]; + } + } + else + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i].Conjugate() * other.At(_storage.Indices[i]); + } + } + + return result; + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static SparseVector operator +(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Add(rightSide); + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static SparseVector operator -(SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static SparseVector operator -(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Subtract(rightSide); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The vector to scale. + /// The complex value. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(SparseVector leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The complex value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(Complex32 leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static Complex32 operator *(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a complex. + /// + /// The vector to divide. + /// The complex value. + /// The result of the division. + /// If is . + public static SparseVector operator /(SparseVector leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Divide(rightSide); + } + + /// + /// Computes the modulus of each element of the vector of the given divisor. + /// + /// The vector whose elements we want to compute the modulus of. + /// The divisor to use, + /// The result of the calculation + /// If is . + public static SparseVector operator %(SparseVector leftSide, Complex32 rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Modulus(rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + if (_storage.ValueCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var min = _storage.Values[index].Magnitude; + for (var i = 1; i < _storage.ValueCount; i++) + { + var test = _storage.Values[i].Magnitude; + if (test < min) + { + index = i; + min = test; + } + } + + return _storage.Indices[index]; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + if (_storage.ValueCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var max = _storage.Values[index].Magnitude; + for (var i = 1; i < _storage.ValueCount; i++) + { + var test = _storage.Values[i].Magnitude; + if (test > max) + { + index = i; + max = test; + } + } + + return _storage.Indices[index]; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override Complex32 Sum() + { + var result = Complex32.Zero; + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i]; + } + + return result; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double result = 0d; + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i].Magnitude; + } + + return result; + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(0, _storage.ValueCount, i => _storage.Values[i].Magnitude, Math.Max, 0f); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = ( ∑|this[i]|^p )^(1/p) + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (_storage.ValueCount == 0) + { + return 0d; + } + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var index = 0; index < _storage.ValueCount; index++) + { + sum += Math.Pow(_storage.Values[index].Magnitude, p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + if (ReferenceEquals(this, other) && ReferenceEquals(this, result)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + _storage.Values[i] *= _storage.Values[i]; + } + } + else + { + base.DoPointwiseMultiply(other, result); + } + } + + #region Parse Functions + + /// + /// Creates a double sparse vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n;n;..', '(n;n;..)', '[n;n;...]', where n is a Complex32. + /// + /// + /// A double sparse vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static SparseVector Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // parsing + var strongTokens = value.Split(new[] { formatProvider.GetTextInfo().ListSeparator }, StringSplitOptions.RemoveEmptyEntries); + var data = new List(); + foreach (string strongToken in strongTokens) + { + var weakTokens = strongToken.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); + string current = string.Empty; + for (int i = 0; i < weakTokens.Length; i++) + { + current += weakTokens[i]; + if (current.EndsWith("+") || current.EndsWith("-") || current.StartsWith("(") && !current.EndsWith(")")) + { + continue; + } + + var ahead = i < weakTokens.Length - 1 ? weakTokens[i + 1] : string.Empty; + if (ahead.StartsWith("+") || ahead.StartsWith("-")) + { + continue; + } + + data.Add(current.ToComplex32(formatProvider)); + current = string.Empty; + } + + if (current != string.Empty) + { + throw new FormatException(); + } + } + + if (data.Count == 0) + { + throw new FormatException(); + } + + return OfEnumerable(data); + } + + /// + /// Converts the string representation of a complex sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out SparseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a complex sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out SparseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + #endregion + + public override string ToTypeString() + { + return string.Format("SparseVector {0}-Complex32 {1:P2} Filled", Count, NonZerosCount / (double)Count); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Complex32/Vector.cs b/MathNet.Numerics/LinearAlgebra/Complex32/Vector.cs new file mode 100644 index 0000000..4506238 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Complex32/Vector.cs @@ -0,0 +1,627 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Complex32; + +using Complex32 = Numerics.Complex32; + +/// +/// Complex32 version of the class. +/// +[Serializable] +public abstract class Vector : Vector +{ + /// + /// Initializes a new instance of the Vector class. + /// + protected Vector(VectorStorage storage) + : base(storage) + { + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero. + /// + public override void CoerceZero(double threshold) + { + MapInplace(x => x.Magnitude < threshold ? Complex32.Zero : x, Zeros.AllowSkip); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + protected override void DoConjugate(Vector result) + { + Map(Complex32.Conjugate, result, Zeros.AllowSkip); + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + Map(Complex32.Negate, result, Zeros.AllowSkip); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to add. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Complex32 scalar, Vector result) + { + Map(x => x + scalar, result, Zeros.Include); + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to add to this one. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Vector other, Vector result) + { + Map2(Complex32.Add, other, result, Zeros.AllowSkip); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to subtract. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Complex32 scalar, Vector result) + { + Map(x => x - scalar, result, Zeros.Include); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to subtract from this one. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Vector other, Vector result) + { + Map2(Complex32.Subtract, other, result, Zeros.AllowSkip); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to multiply. + /// + /// + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(Complex32 scalar, Vector result) + { + Map(x => x * scalar, result, Zeros.AllowSkip); + } + + /// + /// Divides each element of the vector by a scalar and stores the result in the result vector. + /// + /// + /// The scalar to divide with. + /// + /// + /// The vector to store the result of the division. + /// + protected override void DoDivide(Complex32 divisor, Vector result) + { + Map(x => x / divisor, result, divisor.IsZero() ? Zeros.Include : Zeros.AllowSkip); + } + + /// + /// Divides a scalar by each element of the vector and stores the result in the result vector. + /// + /// The scalar to divide. + /// The vector to store the result of the division. + protected override void DoDivideByThis(Complex32 dividend, Vector result) + { + Map(x => dividend / x, result, Zeros.Include); + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + Map2(Complex32.Multiply, other, result, Zeros.AllowSkip); + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + protected override void DoPointwiseDivide(Vector divisor, Vector result) + { + Map2(Complex32.Divide, divisor, result, Zeros.Include); + } + + /// + /// Pointwise raise this vector to an exponent and store the result into the result vector. + /// + /// The exponent to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Complex32 exponent, Vector result) + { + Map(x => x.Power(exponent), result, Zeros.Include); + } + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Vector exponent, Vector result) + { + Map2(Complex32.Pow, exponent, result, Zeros.Include); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected sealed override void DoPointwiseModulus(Vector divisor, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected sealed override void DoPointwiseRemainder(Vector divisor, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected override void DoPointwiseExp(Vector result) + { + Map(Complex32.Exp, result, Zeros.Include); + } + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected override void DoPointwiseLog(Vector result) + { + Map(Complex32.Log, result, Zeros.Include); + } + + protected override void DoPointwiseAbs(Vector result) + { + Map(x => (Complex32)Complex32.Abs(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAcos(Vector result) + { + Map(Complex32.Acos, result, Zeros.Include); + } + protected override void DoPointwiseAsin(Vector result) + { + Map(Complex32.Asin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan(Vector result) + { + Map(Complex32.Atan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan2(Vector other, Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseAtan2(Complex32 scalar, Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseCeiling(Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseCos(Vector result) + { + Map(Complex32.Cos, result, Zeros.Include); + } + protected override void DoPointwiseCosh(Vector result) + { + Map(Complex32.Cosh, result, Zeros.Include); + } + protected override void DoPointwiseFloor(Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseLog10(Vector result) + { + Map(Complex32.Log10, result, Zeros.Include); + } + protected override void DoPointwiseRound(Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseSign(Vector result) + { + throw new NotSupportedException(); + } + protected override void DoPointwiseSin(Vector result) + { + Map(Complex32.Sin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSinh(Vector result) + { + Map(Complex32.Sinh, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSqrt(Vector result) + { + Map(Complex32.Sqrt, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTan(Vector result) + { + Map(Complex32.Tan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTanh(Vector result) + { + Map(Complex32.Tanh, result, Zeros.AllowSkip); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override Complex32 DoDotProduct(Vector other) + { + var dot = Complex32.Zero; + for (var i = 0; i < Count; i++) + { + dot += At(i) * other.At(i); + } + + return dot; + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected override Complex32 DoConjugateDotProduct(Vector other) + { + var dot = Complex32.Zero; + for (var i = 0; i < Count; i++) + { + dot += At(i).Conjugate() * other.At(i); + } + + return dot; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected sealed override void DoModulus(Complex32 divisor, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected sealed override void DoModulusByThis(Complex32 dividend, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected sealed override void DoRemainder(Complex32 divisor, Vector result) + { + throw new NotSupportedException(); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected sealed override void DoRemainderByThis(Complex32 dividend, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMinimum(Complex32 scalar, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMaximum(Complex32 scalar, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseAbsoluteMinimum(Complex32 scalar, Vector result) + { + float absolute = scalar.Magnitude; + Map(x => Math.Min(absolute, x.Magnitude), result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Complex32 scalar, Vector result) + { + float absolute = scalar.Magnitude; + Map(x => Math.Max(absolute, x.Magnitude), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(Vector other, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseMaximum(Vector other, Vector result) + { + throw new NotSupportedException(); + } + + protected override void DoPointwiseAbsoluteMinimum(Vector other, Vector result) + { + Map2((x, y) => Math.Min(x.Magnitude, y.Magnitude), other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Vector other, Vector result) + { + Map2((x, y) => Math.Max(x.Magnitude, y.Magnitude), other, result, Zeros.AllowSkip); + } + + /// + /// Returns the value of the absolute minimum element. + /// + /// The value of the absolute minimum element. + public sealed override Complex32 AbsoluteMinimum() + { + return At(AbsoluteMinimumIndex()).Magnitude; + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = At(index).Magnitude; + for (var i = 1; i < Count; i++) + { + var test = At(i).Magnitude; + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the value of the absolute maximum element. + /// + /// The value of the absolute maximum element. + public override Complex32 AbsoluteMaximum() + { + return At(AbsoluteMaximumIndex()).Magnitude; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = At(index).Magnitude; + for (var i = 1; i < Count; i++) + { + var test = At(i).Magnitude; + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override Complex32 Sum() + { + var sum = Complex32.Zero; + for (var i = 0; i < Count; i++) + { + sum += At(i); + } + + return sum; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double sum = 0d; + for (var i = 0; i < Count; i++) + { + sum += At(i).Magnitude; + } + + return sum; + } + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public override double L2Norm() + { + return DoConjugateDotProduct(this).SquareRoot().Real; + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(0, Count, i => At(i).Magnitude, Math.Max, 0f); + } + + /// + /// Computes the p-Norm. + /// + /// + /// The p value. + /// + /// + /// Scalar ret = ( ∑|At(i)|^p )^(1/p) + /// + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var index = 0; index < Count; index++) + { + sum += Math.Pow(At(index).Magnitude, p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public override int MaximumIndex() + { + throw new NotSupportedException(); + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + throw new NotSupportedException(); + } + + /// + /// Normalizes this vector to a unit vector with respect to the p-norm. + /// + /// + /// The p value. + /// + /// + /// This vector normalized to a unit vector with respect to the p-norm. + /// + public override Vector Normalize(double p) + { + if (p < 0d) + { + throw new ArgumentOutOfRangeException(nameof(p)); + } + + double norm = Norm(p); + var clone = Clone(); + if (norm == 0d) + { + return clone; + } + + clone.Multiply((float)(1d / norm), clone); + + return clone; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/CreateMatrix.cs b/MathNet.Numerics/LinearAlgebra/CreateMatrix.cs new file mode 100644 index 0000000..640ee6d --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/CreateMatrix.cs @@ -0,0 +1,952 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearAlgebra; + +public static class CreateMatrix +{ + /// + /// Create a new matrix straight from an initialized matrix storage instance. + /// If you have an instance of a discrete storage type instead, use their direct methods instead. + /// + public static Matrix WithStorage(MatrixStorage storage) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.OfStorage(storage); + } + + /// + /// Create a new matrix with the same kind of the provided example. + /// + public static Matrix SameAs(Matrix example, int rows, int columns, bool fullyMutable = false) + where T : struct, IEquatable, IFormattable + where TU : struct, IEquatable, IFormattable + { + return Matrix.Build.SameAs(example, rows, columns, fullyMutable); + } + + /// + /// Create a new matrix with the same kind and dimensions of the provided example. + /// + public static Matrix SameAs(Matrix example) + where T : struct, IEquatable, IFormattable + where TU : struct, IEquatable, IFormattable + { + return Matrix.Build.SameAs(example); + } + + /// + /// Create a new matrix with the same kind of the provided example. + /// + public static Matrix SameAs(Vector example, int rows, int columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SameAs(example, rows, columns); + } + + /// + /// Create a new matrix with a type that can represent and is closest to both provided samples. + /// + public static Matrix SameAs(Matrix example, Matrix otherExample, int rows, int columns, bool fullyMutable = false) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SameAs(example, otherExample, rows, columns, fullyMutable); + } + + /// + /// Create a new matrix with a type that can represent and is closest to both provided samples and the dimensions of example. + /// + public static Matrix SameAs(Matrix example, Matrix otherExample) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SameAs(example, otherExample); + } + + /// + /// Create a new dense matrix with values sampled from the provided random distribution. + /// + public static Matrix Random(int rows, int columns, IContinuousDistribution distribution) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Random(rows, columns, distribution); + } + + /// + /// Create a new dense matrix with values sampled from the standard distribution with a system random source. + /// + public static Matrix Random(int rows, int columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Random(rows, columns); + } + + /// + /// Create a new dense matrix with values sampled from the standard distribution with a system random source. + /// + public static Matrix Random(int rows, int columns, int seed) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Random(rows, columns, seed); + } + + /// + /// Create a new positive definite dense matrix where each value is the product + /// of two samples from the provided random distribution. + /// + public static Matrix RandomPositiveDefinite(int order, IContinuousDistribution distribution) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.RandomPositiveDefinite(order, distribution); + } + + /// + /// Create a new positive definite dense matrix where each value is the product + /// of two samples from the standard distribution. + /// + public static Matrix RandomPositiveDefinite(int order) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.RandomPositiveDefinite(order); + } + + /// + /// Create a new positive definite dense matrix where each value is the product + /// of two samples from the provided random distribution. + /// + public static Matrix RandomPositiveDefinite(int order, int seed) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.RandomPositiveDefinite(order, seed); + } + + /// + /// Create a new dense matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public static Matrix Dense(DenseColumnMajorMatrixStorage storage) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Dense(storage); + } + + /// + /// Create a new dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + public static Matrix Dense(int rows, int columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Dense(rows, columns); + } + + /// + /// Create a new dense matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to be in column-major order (column by column) and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + /// + public static Matrix Dense(int rows, int columns, T[] storage) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Dense(rows, columns, storage); + } + + /// + /// Create a new dense matrix and initialize each value to the same provided value. + /// + public static Matrix Dense(int rows, int columns, T value) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Dense(rows, columns, value); + } + + /// + /// Create a new dense matrix and initialize each value using the provided init function. + /// + public static Matrix Dense(int rows, int columns, Func init) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Dense(rows, columns, init); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value to the same provided value. + /// + public static Matrix DenseDiagonal(int rows, int columns, T value) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseDiagonal(rows, columns, value); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value to the same provided value. + /// + public static Matrix DenseDiagonal(int order, T value) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseDiagonal(order, value); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value using the provided init function. + /// + public static Matrix DenseDiagonal(int rows, int columns, Func init) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseDiagonal(rows, columns, init); + } + + /// + /// Create a new diagonal dense identity matrix with a one-diagonal. + /// + public static Matrix DenseIdentity(int rows, int columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseIdentity(rows, columns); + } + + /// + /// Create a new diagonal dense identity matrix with a one-diagonal. + /// + public static Matrix DenseIdentity(int order) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseIdentity(order); + } + + /// + /// Create a new dense matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfMatrix(Matrix matrix) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfMatrix(matrix); + } + + /// + /// Create a new dense matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfArray(T[,] array) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfArray(array); + } + + /// + /// Create a new dense matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfIndexed(int rows, int columns, IEnumerable> enumerable) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfIndexed(rows, columns, enumerable); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfColumnMajor(int rows, int columns, IEnumerable columnMajor) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfColumnMajor(rows, columns, columnMajor); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfColumns(IEnumerable> data) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfColumns(data); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfColumns(int rows, int columns, IEnumerable> data) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfColumns(rows, columns, data); + } + + /// + /// Create a new dense matrix of T as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfColumnArrays(params T[][] columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfColumnArrays(columns); + } + + /// + /// Create a new dense matrix of T as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfColumnArrays(IEnumerable columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfColumnArrays(columns); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfColumnVectors(params Vector[] columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfColumnVectors(columns); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfColumnVectors(IEnumerable> columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfColumnVectors(columns); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfRows(IEnumerable> data) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfRows(data); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfRows(int rows, int columns, IEnumerable> data) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfRows(rows, columns, data); + } + + /// + /// Create a new dense matrix of T as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfRowArrays(params T[][] rows) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfRowArrays(rows); + } + + /// + /// Create a new dense matrix of T as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfRowArrays(IEnumerable rows) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfRowArrays(rows); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfRowVectors(params Vector[] rows) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfRowVectors(rows); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfRowVectors(IEnumerable> rows) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfRowVectors(rows); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfDiagonalVector(Vector diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfDiagonalVector(diagonal); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfDiagonalVector(int rows, int columns, Vector diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfDiagonalVector(rows, columns, diagonal); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfDiagonalArray(T[] diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfDiagonalArray(diagonal); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DenseOfDiagonalArray(int rows, int columns, T[] diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfDiagonalArray(rows, columns, diagonal); + } + + /// + /// Create a new dense matrix from a 2D array of existing matrices. + /// The matrices in the array are not required to be dense already. + /// If the matrices do not align properly, they are placed on the top left + /// corner of their cell with the remaining fields left zero. + /// + public static Matrix DenseOfMatrixArray(Matrix[,] matrices) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DenseOfMatrixArray(matrices); + } + + /// + /// Create a new sparse matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public static Matrix Sparse(SparseCompressedRowMatrixStorage storage) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Sparse(storage); + } + + /// + /// Create a sparse matrix of T with the given number of rows and columns. + /// + /// The number of rows. + /// The number of columns. + public static Matrix Sparse(int rows, int columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Sparse(rows, columns); + } + + /// + /// Create a new sparse matrix and initialize each value to the same provided value. + /// + public static Matrix Sparse(int rows, int columns, T value) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Sparse(rows, columns, value); + } + + /// + /// Create a new sparse matrix and initialize each value using the provided init function. + /// + public static Matrix Sparse(int rows, int columns, Func init) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Sparse(rows, columns, init); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value to the same provided value. + /// + public static Matrix SparseDiagonal(int rows, int columns, T value) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseDiagonal(rows, columns, value); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value to the same provided value. + /// + public static Matrix SparseDiagonal(int order, T value) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseDiagonal(order, value); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value using the provided init function. + /// + public static Matrix SparseDiagonal(int rows, int columns, Func init) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseDiagonal(rows, columns, init); + } + + /// + /// Create a new diagonal dense identity matrix with a one-diagonal. + /// + public static Matrix SparseIdentity(int rows, int columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseIdentity(rows, columns); + } + + /// + /// Create a new diagonal dense identity matrix with a one-diagonal. + /// + public static Matrix SparseIdentity(int order) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseIdentity(order); + } + + /// + /// Create a new sparse matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfMatrix(Matrix matrix) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfMatrix(matrix); + } + + /// + /// Create a new sparse matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfArray(T[,] array) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfArray(array); + } + + /// + /// Create a new sparse matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfIndexed(int rows, int columns, IEnumerable> enumerable) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfIndexed(rows, columns, enumerable); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in row-major order (row by row). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + /// + public static Matrix SparseOfRowMajor(int rows, int columns, IEnumerable rowMajor) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfRowMajor(rows, columns, rowMajor); + } + + /// + /// Create a new sparse matrix with the given number of rows and columns as a copy of the given array. + /// The array is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + /// + public static Matrix SparseOfColumnMajor(int rows, int columns, IList columnMajor) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfColumnMajor(rows, columns, columnMajor); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfColumns(IEnumerable> data) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfColumns(data); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfColumns(int rows, int columns, IEnumerable> data) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfColumns(rows, columns, data); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfColumnArrays(params T[][] columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfColumnArrays(columns); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfColumnArrays(IEnumerable columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfColumnArrays(columns); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfColumnVectors(params Vector[] columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfColumnVectors(columns); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfColumnVectors(IEnumerable> columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfColumnVectors(columns); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfRows(IEnumerable> data) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfRows(data); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfRows(int rows, int columns, IEnumerable> data) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfRows(rows, columns, data); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfRowArrays(params T[][] rows) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfRowArrays(rows); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfRowArrays(IEnumerable rows) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfRowArrays(rows); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfRowVectors(params Vector[] rows) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfRowVectors(rows); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfRowVectors(IEnumerable> rows) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfRowVectors(rows); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfDiagonalVector(Vector diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfDiagonalVector(diagonal); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfDiagonalVector(int rows, int columns, Vector diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfDiagonalVector(rows, columns, diagonal); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfDiagonalArray(T[] diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfDiagonalArray(diagonal); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix SparseOfDiagonalArray(int rows, int columns, T[] diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfDiagonalArray(rows, columns, diagonal); + } + + /// + /// Create a new sparse matrix from a 2D array of existing matrices. + /// The matrices in the array are not required to be sparse already. + /// If the matrices do not align properly, they are placed on the top left + /// corner of their cell with the remaining fields left zero. + /// + public static Matrix SparseOfMatrixArray(Matrix[,] matrices) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.SparseOfMatrixArray(matrices); + } + + /// + /// Create a new diagonal matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public static Matrix Diagonal(DiagonalMatrixStorage storage) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Diagonal(storage); + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + public static Matrix Diagonal(int rows, int columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Diagonal(rows, columns); + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to represent the diagonal values and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + public static Matrix Diagonal(int rows, int columns, T[] storage) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Diagonal(rows, columns, storage); + } + + /// + /// Create a new square diagonal matrix directly binding to a raw array. + /// The array is assumed to represent the diagonal values and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + public static Matrix Diagonal(T[] storage) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Diagonal(storage); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value to the same provided value. + /// + public static Matrix Diagonal(int rows, int columns, T value) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Diagonal(rows, columns, value); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value using the provided init function. + /// + public static Matrix Diagonal(int rows, int columns, Func init) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.Diagonal(rows, columns, init); + } + + /// + /// Create a new diagonal identity matrix with a one-diagonal. + /// + public static Matrix DiagonalIdentity(int rows, int columns) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DiagonalIdentity(rows, columns); + } + + /// + /// Create a new diagonal identity matrix with a one-diagonal. + /// + public static Matrix DiagonalIdentity(int order) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DiagonalIdentity(order); + } + + /// + /// Create a new diagonal matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DiagonalOfDiagonalVector(Vector diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DiagonalOfDiagonalVector(diagonal); + } + + /// + /// Create a new diagonal matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DiagonalOfDiagonalVector(int rows, int columns, Vector diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DiagonalOfDiagonalVector(rows, columns, diagonal); + } + + /// + /// Create a new diagonal matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DiagonalOfDiagonalArray(T[] diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DiagonalOfDiagonalArray(diagonal); + } + + /// + /// Create a new diagonal matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static Matrix DiagonalOfDiagonalArray(int rows, int columns, T[] diagonal) + where T : struct, IEquatable, IFormattable + { + return Matrix.Build.DiagonalOfDiagonalArray(rows, columns, diagonal); + } +} \ No newline at end of file diff --git a/MathNet.Numerics/LinearAlgebra/CreateVector.cs b/MathNet.Numerics/LinearAlgebra/CreateVector.cs new file mode 100644 index 0000000..41efa1e --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/CreateVector.cs @@ -0,0 +1,312 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearAlgebra; + +public static class CreateVector +{ + /// + /// Create a new vector straight from an initialized matrix storage instance. + /// If you have an instance of a discrete storage type instead, use their direct methods instead. + /// + public static Vector WithStorage(VectorStorage storage) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.OfStorage(storage); + } + + /// + /// Create a new vector with the same kind of the provided example. + /// + public static Vector SameAs(Vector example, int length) + where T : struct, IEquatable, IFormattable + where TU : struct, IEquatable, IFormattable + { + return Vector.Build.SameAs(example, length); + } + + /// + /// Create a new vector with the same kind and dimension of the provided example. + /// + public static Vector SameAs(Vector example) + where T : struct, IEquatable, IFormattable + where TU : struct, IEquatable, IFormattable + { + return Vector.Build.SameAs(example); + } + + /// + /// Create a new vector with the same kind of the provided example. + /// + public static Vector SameAs(Matrix example, int length) + where T : struct, IEquatable, IFormattable + where TU : struct, IEquatable, IFormattable + { + return Vector.Build.SameAs(example, length); + } + + /// + /// Create a new vector with a type that can represent and is closest to both provided samples. + /// + public static Vector SameAs(Vector example, Vector otherExample, int length) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.SameAs(example, otherExample, length); + } + + /// + /// Create a new vector with a type that can represent and is closest to both provided samples and the dimensions of example. + /// + public static Vector SameAs(Vector example, Vector otherExample) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.SameAs(example, otherExample); + } + + /// + /// Create a new vector with a type that can represent and is closest to both provided samples. + /// + public static Vector SameAs(Matrix matrix, Vector vector, int length) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.SameAs(matrix, vector, length); + } + + /// + /// Create a new dense vector with values sampled from the provided random distribution. + /// + public static Vector Random(int length, IContinuousDistribution distribution) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Random(length, distribution); + } + + /// + /// Create a new dense vector with values sampled from the standard distribution with a system random source. + /// + public static Vector Random(int length) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Random(length); + } + + /// + /// Create a new dense vector with values sampled from the standard distribution with a system random source. + /// + public static Vector Random(int length, int seed) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Random(length, seed); + } + + /// + /// Create a new dense vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public static Vector Dense(DenseVectorStorage storage) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Dense(storage); + } + + /// + /// Create a dense vector of T with the given size. + /// + /// The size of the vector. + public static Vector Dense(int size) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Dense(size); + } + + /// + /// Create a dense vector of T that is directly bound to the specified array. + /// + public static Vector Dense(T[] array) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Dense(array); + } + + /// + /// Create a new dense vector and initialize each value using the provided value. + /// + public static Vector Dense(int length, T value) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Dense(length, value); + } + + /// + /// Create a new dense vector and initialize each value using the provided init function. + /// + public static Vector Dense(int length, Func init) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Dense(length, init); + } + + /// + /// Create a new dense vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static Vector DenseOfVector(Vector vector) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.DenseOfVector(vector); + } + + /// + /// Create a new dense vector as a copy of the given array. + /// This new vector will be independent from the array. + /// A new memory block will be allocated for storing the vector. + /// + public static Vector DenseOfArray(T[] array) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.DenseOfArray(array); + } + + /// + /// Create a new dense vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static Vector DenseOfEnumerable(IEnumerable enumerable) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.DenseOfEnumerable(enumerable); + } + + /// + /// Create a new dense vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static Vector DenseOfIndexed(int length, IEnumerable> enumerable) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.DenseOfIndexed(length, enumerable); + } + + /// + /// Create a new sparse vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public static Vector Sparse(SparseVectorStorage storage) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Sparse(storage); + } + + /// + /// Create a sparse vector of T with the given size. + /// + /// The size of the vector. + public static Vector Sparse(int size) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Sparse(size); + } + + /// + /// Create a new sparse vector and initialize each value using the provided value. + /// + public static Vector Sparse(int length, T value) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Sparse(length, value); + } + + /// + /// Create a new sparse vector and initialize each value using the provided init function. + /// + public static Vector Sparse(int length, Func init) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.Sparse(length, init); + } + + /// + /// Create a new sparse vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static Vector SparseOfVector(Vector vector) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.SparseOfVector(vector); + } + + /// + /// Create a new sparse vector as a copy of the given array. + /// This new vector will be independent from the array. + /// A new memory block will be allocated for storing the vector. + /// + public static Vector SparseOfArray(T[] array) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.SparseOfArray(array); + } + + /// + /// Create a new sparse vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static Vector SparseOfEnumerable(IEnumerable enumerable) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.SparseOfEnumerable(enumerable); + } + + /// + /// Create a new sparse vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static Vector SparseOfIndexed(int length, IEnumerable> enumerable) + where T : struct, IEquatable, IFormattable + { + return Vector.Build.SparseOfIndexed(length, enumerable); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/DenseMatrix.cs b/MathNet.Numerics/LinearAlgebra/Double/DenseMatrix.cs new file mode 100644 index 0000000..99b8c8d --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/DenseMatrix.cs @@ -0,0 +1,1262 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Double.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double; + +/// +/// A Matrix class with dense storage. The underlying storage is a one dimensional array in column-major order (column by column). +/// +[Serializable] +[DebuggerDisplay("DenseMatrix {RowCount}x{ColumnCount}-Double")] +public class DenseMatrix : Matrix +{ + /// + /// Number of rows. + /// + /// Using this instead of the RowCount property to speed up calculating + /// a matrix index in the data array. + readonly int _rowCount; + + /// + /// Number of columns. + /// + /// Using this instead of the ColumnCount property to speed up calculating + /// a matrix index in the data array. + readonly int _columnCount; + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + readonly double[] _values; + + /// + /// Create a new dense matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DenseMatrix(DenseColumnMajorMatrixStorage storage) + : base(storage) + { + _rowCount = storage.RowCount; + _columnCount = storage.ColumnCount; + _values = storage.Data; + } + + /// + /// Create a new square dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public DenseMatrix(int order) + : this(new DenseColumnMajorMatrixStorage(order, order)) + { + } + + /// + /// Create a new dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DenseMatrix(int rows, int columns) + : this(new DenseColumnMajorMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new dense matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to be in column-major order (column by column) and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + /// + public DenseMatrix(int rows, int columns, double[] storage) + : this(new DenseColumnMajorMatrixStorage(rows, columns, storage)) + { + } + + /// + /// Create a new dense matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfMatrix(Matrix matrix) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new dense matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfArray(double[,] array) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfArray(array)); + } + + /// + /// Create a new dense matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnMajor(int rows, int columns, IEnumerable columnMajor) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnMajorEnumerable(rows, columns, columnMajor)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumns(IEnumerable> data) + { + return OfColumnArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumns(int rows, int columns, IEnumerable> data) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnArrays(params double[][] columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new dense matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnArrays(IEnumerable columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnArrays((columns as double[][]) ?? columns.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnVectors(IEnumerable> columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRows(IEnumerable> data) + { + return OfRowArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRows(int rows, int columns, IEnumerable> data) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowArrays(params double[][] rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new dense matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowArrays(IEnumerable rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowArrays((rows as double[][]) ?? rows.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowVectors(IEnumerable> rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalVector(Vector diagonal) + { + var m = new DenseMatrix(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = new DenseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalArray(double[] diagonal) + { + var m = new DenseMatrix(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalArray(int rows, int columns, double[] diagonal) + { + var m = new DenseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix and initialize each value to the same provided value. + /// + public static DenseMatrix Create(int rows, int columns, double value) + { + if (value == 0d) + return new DenseMatrix(rows, columns); + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new dense matrix and initialize each value using the provided init function. + /// + public static DenseMatrix Create(int rows, int columns, Func init) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value to the same provided value. + /// + public static DenseMatrix CreateDiagonal(int rows, int columns, double value) + { + if (value == 0d) + return new DenseMatrix(rows, columns); + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value using the provided init function. + /// + public static DenseMatrix CreateDiagonal(int rows, int columns, Func init) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static DenseMatrix CreateIdentity(int order) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Create a new dense matrix with values sampled from the provided random distribution. + /// + public static DenseMatrix CreateRandom(int rows, int columns, IContinuousDistribution distribution) + { + return new DenseMatrix(new DenseColumnMajorMatrixStorage(rows, columns, Generate.Random(rows * columns, distribution))); + } + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + public double[] Values + { + get { return _values; } + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.OneNorm, _rowCount, _columnCount, _values); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.InfinityNorm, _rowCount, _columnCount, _values); + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.FrobeniusNorm, _rowCount, _columnCount, _values); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(-1, _values, denseResult._values); + return; + } + + base.DoNegate(result); + } + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected override void DoAdd(double scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoAdd(scalar, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = _values[i] + scalar; + } + }); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of add + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + // dense + dense = dense + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + var denseResult = result.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.AddArrays(_values, denseOther.Data, denseResult.Data); + return; + } + + // dense + diagonal = any + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + Storage.CopyToUnchecked(result.Storage, ExistingData.Clear); + var diagonal = diagonalOther.Data; + for (int i = 0; i < diagonal.Length; i++) + { + result.At(i, i, result.At(i, i) + diagonal[i]); + } + + return; + } + + base.DoAdd(other, result); + } + + /// + /// Subtracts a scalar from each element of the matrix and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(double scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoSubtract(scalar, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = _values[i] - scalar; + } + }); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(Matrix other, Matrix result) + { + // dense + dense = dense + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + var denseResult = result.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.SubtractArrays(_values, denseOther.Data, denseResult.Data); + return; + } + + // dense + diagonal = matrix + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + CopyTo(result); + var diagonal = diagonalOther.Data; + for (int i = 0; i < diagonal.Length; i++) + { + result.At(i, i, result.At(i, i) - diagonal[i]); + } + + return; + } + + base.DoSubtract(other, result); + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(double scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoMultiply(scalar, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(scalar, _values, denseResult._values); + } + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + + if (denseRight == null || denseResult == null) + { + base.DoMultiply(rightSide, result); + } + else + { + LinearAlgebraControl.Provider.MatrixMultiply( + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + denseResult.Values); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiply( + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(ColumnCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.ColumnCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoMultiply(other, result); + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.DontTranspose, + Providers.LinearAlgebra.Transpose.Transpose, + 1.0, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(ColumnCount, other.RowCount); + if (d < other.RowCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.RowCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + + if (denseRight == null || denseResult == null) + { + base.DoTransposeThisAndMultiply(rightSide, result); + } + else + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.Transpose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0, + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + 0.0, + denseResult.Values); + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.Transpose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(RowCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, ColumnCount, RowCount, other.ColumnCount - RowCount); + } + + int index = 0; + for (int i = 0; i < ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + + index += (RowCount - d); + } + + return; + } + + base.DoTransposeThisAndMultiply(other, result); + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(double divisor, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(1.0 / divisor, _values, denseResult._values); + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseMultiply(other, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + var denseDivisor = divisor as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseDivisor == null || denseResult == null) + { + base.DoPointwiseDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseDivideArrays(_values, denseDivisor._values, denseResult._values); + } + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Matrix exponent, Matrix result) + { + var denseExponent = exponent as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseExponent == null || denseResult == null) + { + base.DoPointwisePower(exponent, result); + } + else + { + LinearAlgebraControl.Provider.PointWisePowerArrays(_values, denseExponent._values, denseResult._values); + } + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoModulus(double divisor, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoModulus(divisor, result); + return; + } + + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + CommonParallel.For(0, _values.Length, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = Euclid.Modulus(v[i], divisor); + } + }); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoModulusByThis(double dividend, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoModulusByThis(dividend, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = Euclid.Modulus(dividend, _values[i]); + } + }); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoRemainder(double divisor, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoRemainder(divisor, result); + return; + } + + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + CommonParallel.For(0, _values.Length, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] %= divisor; + } + }); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoRemainderByThis(double dividend, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoRemainderByThis(dividend, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = dividend % _values[i]; + } + }); + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public override double Trace() + { + if (_rowCount != _columnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var sum = 0.0; + for (var i = 0; i < _rowCount; i++) + { + sum += _values[(i * _rowCount) + i]; + } + + return sum; + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static DenseMatrix operator +(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide._rowCount != rightSide._rowCount || leftSide._columnCount != rightSide._columnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Add(rightSide); + } + + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static DenseMatrix operator +(DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Clone(); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static DenseMatrix operator -(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide._rowCount != rightSide._rowCount || leftSide._columnCount != rightSide._columnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Subtract(rightSide); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static DenseMatrix operator -(DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Negate(); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator *(DenseMatrix leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator *(double leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static DenseMatrix operator *(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide._columnCount != rightSide._rowCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static DenseVector operator *(DenseMatrix leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static DenseVector operator *(DenseVector leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.LeftMultiply(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator %(DenseMatrix leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseMatrix)leftSide.Remainder(rightSide); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public override bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = j + 1; i < RowCount; i++) + { + if (_values[(i * ColumnCount) + j] != _values[index + i]) + { + return false; + } + } + } + + return true; + } + + public override Cholesky Cholesky() + { + return DenseCholesky.Create(this); + } + + public override LU LU() + { + return DenseLU.Create(this); + } + + public override QR QR(QRMethod method = QRMethod.Thin) + { + return DenseQR.Create(this, method); + } + + public override GramSchmidt GramSchmidt() + { + return DenseGramSchmidt.Create(this); + } + + public override Svd Svd(bool computeVectors = true) + { + return DenseSvd.Create(this, computeVectors); + } + + public override Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown) + { + return DenseEvd.Create(this, symmetricity); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/DenseVector.cs b/MathNet.Numerics/LinearAlgebra/Double/DenseVector.cs new file mode 100644 index 0000000..25462b3 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/DenseVector.cs @@ -0,0 +1,874 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double; + +/// +/// A vector using dense storage. +/// +[Serializable] +[DebuggerDisplay("DenseVector {" + nameof(Count) + "}-Double")] +public class DenseVector : Vector +{ + /// + /// Number of elements + /// + readonly int _length; + + /// + /// Gets the vector's data. + /// + readonly double[] _values; + + /// + /// Create a new dense vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DenseVector(DenseVectorStorage storage) + : base(storage) + { + _length = storage.Length; + _values = storage.Data; + } + + /// + /// Create a new dense vector with the given length. + /// All cells of the vector will be initialized to zero. + /// Zero-length vectors are not supported. + /// + /// If length is less than one. + public DenseVector(int length) + : this(new DenseVectorStorage(length)) + { + } + + /// + /// Create a new dense vector directly binding to a raw array. + /// The array is used directly without copying. + /// Very efficient, but changes to the array and the vector will affect each other. + /// + public DenseVector(double[] storage) + : this(new DenseVectorStorage(storage.Length, storage)) + { + } + + /// + /// Create a new dense vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfVector(Vector vector) + { + return new DenseVector(DenseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new dense vector as a copy of the given array. + /// This new vector will be independent from the array. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfArray(double[] array) + { + return new DenseVector(DenseVectorStorage.OfVector(new DenseVectorStorage(array.Length, array))); + } + + /// + /// Create a new dense vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfEnumerable(IEnumerable enumerable) + { + return new DenseVector(DenseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new dense vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfIndexedEnumerable(int length, IEnumerable> enumerable) + { + return new DenseVector(DenseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new dense vector and initialize each value using the provided value. + /// + public static DenseVector Create(int length, double value) + { + if (value == 0d) + return new DenseVector(length); + return new DenseVector(DenseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new dense vector and initialize each value using the provided init function. + /// + public static DenseVector Create(int length, Func init) + { + return new DenseVector(DenseVectorStorage.OfInit(length, init)); + } + + /// + /// Create a new dense vector with values sampled from the provided random distribution. + /// + public static DenseVector CreateRandom(int length, IContinuousDistribution distribution) + { + var samples = Generate.Random(length, distribution); + return new DenseVector(new DenseVectorStorage(length, samples)); + } + + /// + /// Gets the vector's data. + /// + /// The vector's data. + public double[] Values + { + get { return _values; } + } + + /// + /// Returns a reference to the internal data structure. + /// + /// The DenseVector whose internal data we are + /// returning. + /// + /// A reference to the internal date of the given vector. + /// + public static explicit operator double[](DenseVector vector) + { + if (vector == null) + { + throw new ArgumentNullException(nameof(vector)); + } + + return vector.Values; + } + + /// + /// Returns a vector bound directly to a reference of the provided array. + /// + /// The array to bind to the DenseVector object. + /// + /// A DenseVector whose values are bound to the given array. + /// + public static implicit operator DenseVector(double[] array) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + return new DenseVector(array); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to add. + /// The vector to store the result of the addition. + protected override void DoAdd(double scalar, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoAdd(scalar, result); + } + else + { + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] + scalar; + } + }); + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + protected override void DoAdd(Vector other, Vector result) + { + var otherDense = other as DenseVector; + var resultDense = result as DenseVector; + + if (otherDense == null || resultDense == null) + { + base.DoAdd(other, result); + } + else + { + LinearAlgebraControl.Provider.AddArrays(_values, otherDense._values, resultDense._values); + } + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static DenseVector operator +(DenseVector leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rightSide)); + } + + return (DenseVector)leftSide.Add(rightSide); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The vector to store the result of the subtraction. + protected override void DoSubtract(double scalar, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoSubtract(scalar, result); + } + else + { + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] - scalar; + } + }); + } + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + protected override void DoSubtract(Vector other, Vector result) + { + var otherDense = other as DenseVector; + var resultDense = result as DenseVector; + + if (otherDense == null || resultDense == null) + { + base.DoSubtract(other, result); + } + else + { + LinearAlgebraControl.Provider.SubtractArrays(_values, otherDense._values, resultDense._values); + } + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static DenseVector operator -(DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static DenseVector operator -(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Subtract(rightSide); + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + var denseResult = result as DenseVector; + if (denseResult == null) + { + base.DoNegate(result); + return; + } + + LinearAlgebraControl.Provider.ScaleArray(-1.0d, _values, denseResult.Values); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to multiply. + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(double scalar, Vector result) + { + var denseResult = result as DenseVector; + if (denseResult == null) + { + base.DoMultiply(scalar, result); + return; + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, _values, denseResult.Values); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override double DoDotProduct(Vector other) + { + var denseVector = other as DenseVector; + return denseVector == null + ? base.DoDotProduct(other) + : LinearAlgebraControl.Provider.DotProduct(_values, denseVector.Values); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The vector to scale. + /// The scalar value. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(DenseVector leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The scalar value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(double leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static double operator *(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a scalar. + /// + /// The vector to divide. + /// The scalar value. + /// The result of the division. + /// If is . + public static DenseVector operator /(DenseVector leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Divide(rightSide); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The divisor to use. + /// A vector to store the results in. + protected override void DoModulus(double divisor, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoModulus(divisor, result); + } + else + { + CommonParallel.For(0, _length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = Euclid.Modulus(_values[i], divisor); + } + }); + } + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The divisor to use. + /// A vector to store the results in. + protected override void DoRemainder(double divisor, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoRemainder(divisor, result); + } + else + { + CommonParallel.For(0, _length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] % divisor; + } + }); + } + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// of each element of the vector of the given divisor. + /// + /// The vector whose elements we want to compute the remainder of. + /// The divisor to use, + /// If is . + public static DenseVector operator %(DenseVector leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Remainder(rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = Math.Abs(_values[index]); + for (var i = 1; i < _length; i++) + { + var test = Math.Abs(_values[i]); + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = Math.Abs(_values[index]); + for (var i = 1; i < _length; i++) + { + var test = Math.Abs(_values[i]); + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public override int MaximumIndex() + { + var index = 0; + var max = _values[0]; + for (var i = 1; i < _length; i++) + { + if (max < _values[i]) + { + index = i; + max = _values[i]; + } + } + + return index; + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + var index = 0; + var min = _values[0]; + for (var i = 1; i < _length; i++) + { + if (min > _values[i]) + { + index = i; + min = _values[i]; + } + } + + return index; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override double Sum() + { + var sum = 0.0; + for (var index = 0; index < _length; index++) + { + sum += _values[index]; + } + + return sum; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + var sum = 0d; + for (var index = 0; index < _length; index++) + { + sum += Math.Abs(_values[index]); + } + + return sum; + } + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public override double L2Norm() + { + // TODO: native provider + return _values.Aggregate(0d, SpecialFunctions.Hypotenuse); + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(_values, (i, v) => Math.Abs(v), Math.Max, 0d); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = ( ∑|this[i]|^p )^(1/p) + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + var sum = 0d; + for (var index = 0; index < _length; index++) + { + sum += Math.Pow(Math.Abs(_values[index]), p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + var denseOther = other as DenseVector; + var denseResult = result as DenseVector; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseMultiply(other, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + /// + protected override void DoPointwiseDivide(Vector divisor, Vector result) + { + var denseOther = divisor as DenseVector; + var denseResult = result as DenseVector; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseDivideArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Vector exponent, Vector result) + { + var denseExponent = exponent as DenseVector; + var denseResult = result as DenseVector; + + if (denseExponent == null || denseResult == null) + { + base.DoPointwisePower(exponent, result); + } + else + { + LinearAlgebraControl.Provider.PointWisePowerArrays(_values, denseExponent._values, denseResult._values); + } + } + + #region Parse Functions + + /// + /// Creates a double dense vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n,n,..', '(n,n,..)', '[n,n,...]', where n is a double. + /// + /// + /// A double dense vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static DenseVector Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // parsing + var tokens = value.Split(new[] { formatProvider.GetTextInfo().ListSeparator, " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); + var data = tokens.Select(t => double.Parse(t, NumberStyles.Any, formatProvider)).ToArray(); + if (data.Length == 0) + throw new FormatException(); + return new DenseVector(data); + } + + /// + /// Converts the string representation of a real dense vector to double-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out DenseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a real dense vector to double-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out DenseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + + #endregion +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/DiagonalMatrix.cs b/MathNet.Numerics/LinearAlgebra/Double/DiagonalMatrix.cs new file mode 100644 index 0000000..cd16fa6 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/DiagonalMatrix.cs @@ -0,0 +1,966 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double; + +/// +/// A matrix type for diagonal matrices. +/// +/// +/// Diagonal matrices can be non-square matrices but the diagonal always starts +/// at element 0,0. A diagonal matrix will throw an exception if non diagonal +/// entries are set. The exception to this is when the off diagonal elements are +/// 0.0 or NaN; these settings will cause no change to the diagonal matrix. +/// +[Serializable] +[DebuggerDisplay("DiagonalMatrix {RowCount}x{ColumnCount}-Double")] +public class DiagonalMatrix : Matrix +{ + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + readonly double[] _data; + + /// + /// Create a new diagonal matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DiagonalMatrix(DiagonalMatrixStorage storage) + : base(storage) + { + _data = storage.Data; + } + + /// + /// Create a new square diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public DiagonalMatrix(int order) + : this(new DiagonalMatrixStorage(order, order)) + { + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DiagonalMatrix(int rows, int columns) + : this(new DiagonalMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All diagonal cells of the matrix will be initialized to the provided value, all non-diagonal ones to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DiagonalMatrix(int rows, int columns, double diagonalValue) + : this(rows, columns) + { + for (var i = 0; i < _data.Length; i++) + { + _data[i] = diagonalValue; + } + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to contain the diagonal elements only and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + public DiagonalMatrix(int rows, int columns, double[] diagonalStorage) + : this(new DiagonalMatrixStorage(rows, columns, diagonalStorage)) + { + } + + /// + /// Create a new diagonal matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// The matrix to copy from must be diagonal as well. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfMatrix(Matrix matrix) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new diagonal matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// The array to copy from must be diagonal as well. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfArray(double[,] array) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfArray(array)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value from the provided indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfIndexedDiagonal(int rows, int columns, IEnumerable> diagonal) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfIndexedEnumerable(rows, columns, diagonal)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value from the provided enumerable. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfDiagonal(int rows, int columns, IEnumerable diagonal) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfEnumerable(rows, columns, diagonal)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value using the provided init function. + /// + public static DiagonalMatrix Create(int rows, int columns, Func init) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static DiagonalMatrix CreateIdentity(int order) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfValue(order, order, One)); + } + + /// + /// Create a new diagonal matrix with diagonal values sampled from the provided random distribution. + /// + public static DiagonalMatrix CreateRandom(int rows, int columns, IContinuousDistribution distribution) + { + return new DiagonalMatrix(new DiagonalMatrixStorage(rows, columns, Generate.Random(Math.Min(rows, columns), distribution))); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(-1, _data, diagResult._data); + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, -_data[i]); + } + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + // diagonal + diagonal = diagonal + var diagOther = other as DiagonalMatrix; + var diagResult = result as DiagonalMatrix; + if (diagOther != null && diagResult != null) + { + LinearAlgebraControl.Provider.AddArrays(_data, diagOther._data, diagResult._data); + return; + } + + other.CopyTo(result); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, result.At(i, i) + _data[i]); + } + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + // diagonal - diagonal = diagonal + var diagOther = other as DiagonalMatrix; + var diagResult = result as DiagonalMatrix; + if (diagOther != null && diagResult != null) + { + LinearAlgebraControl.Provider.SubtractArrays(_data, diagOther._data, diagResult._data); + return; + } + + other.Negate(result); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, result.At(i, i) + _data[i]); + } + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + /// If the result matrix's dimensions are not the same as this matrix. + protected override void DoMultiply(double scalar, Matrix result) + { + if (scalar == 0.0) + { + result.Clear(); + return; + } + + if (scalar == 1.0) + { + CopyTo(result); + return; + } + + var diagResult = result as DiagonalMatrix; + if (diagResult == null) + { + base.DoMultiply(scalar, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(scalar, _data, diagResult._data); + } + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubVector(ColumnCount, RowCount - ColumnCount); + } + + if (d == ColumnCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i] * rightSide.At(i)); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new double[diagonalResult._data.Length]; + var otherDataCopy = new double[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.RowCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.RowCount, RowCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + if (ColumnCount == RowCount) + { + other.Storage.MapIndexedTo(result.Storage, (i, j, x) => x * _data[i], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Clear(); + other.Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * _data[i], 0, 0, other.RowCount, 0, 0, other.ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new double[diagonalResult._data.Length]; + var otherDataCopy = new double[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.ColumnCount, RowCount - denseOther.ColumnCount, 0, denseOther.RowCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < denseOther.RowCount; i++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new double[diagonalResult._data.Length]; + var otherDataCopy = new double[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.RowCount, ColumnCount); + if (d < ColumnCount) + { + result.ClearSubMatrix(denseOther.RowCount, ColumnCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + if (ColumnCount == RowCount) + { + other.Storage.MapIndexedTo(result.Storage, (i, j, x) => x * _data[i], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Clear(); + other.Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * _data[i], 0, 0, other.RowCount, 0, 0, other.ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < ColumnCount) + { + result.ClearSubVector(RowCount, ColumnCount - RowCount); + } + + if (d == RowCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i] * rightSide.At(i)); + } + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(double divisor, Matrix result) + { + if (divisor == 1.0) + { + CopyTo(result); + return; + } + + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(1.0 / divisor, _data, diagResult._data); + return; + } + + result.Clear(); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i] / divisor); + } + } + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to add. + /// The matrix to store the result of the division. + protected override void DoDivideByThis(double dividend, Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + var resultData = diagResult._data; + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + resultData[i] = dividend / _data[i]; + } + }); + return; + } + + result.Clear(); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, dividend / _data[i]); + } + } + + /// + /// Computes the determinant of this matrix. + /// + /// The determinant of this matrix. + public override double Determinant() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + return _data.Aggregate(1.0, (current, t) => current * t); + } + + /// + /// Returns the elements of the diagonal in a . + /// + /// The elements of the diagonal. + /// For non-square matrices, the method returns Min(Rows, Columns) elements where + /// i == j (i is the row index, and j is the column index). + public override Vector Diagonal() + { + return new DenseVector(_data).Clone(); + } + + /// + /// Copies the values of the given array to the diagonal. + /// + /// The array to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(double[] source) + { + if (source.Length != _data.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(source)); + } + + Buffer.BlockCopy(source, 0, _data, 0, source.Length * Constants.SizeOfDouble); + } + + /// + /// Copies the values of the given to the diagonal. + /// + /// The vector to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(Vector source) + { + var denseSource = source as DenseVector; + if (denseSource == null) + { + base.SetDiagonal(source); + return; + } + + if (_data.Length != denseSource.Values.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(source)); + } + + Buffer.BlockCopy(denseSource.Values, 0, _data, 0, denseSource.Values.Length * Constants.SizeOfDouble); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + return _data.Aggregate(0d, (current, t) => Math.Max(current, Math.Abs(t))); + } + + /// Calculates the induced L2 norm of the matrix. + /// The largest singular value of the matrix. + public override double L2Norm() + { + return _data.Aggregate(0d, (current, t) => Math.Max(current, Math.Abs(t))); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + return L1Norm(); + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + return Math.Sqrt(_data.Sum(t => t * t)); + } + + /// Calculates the condition number of this matrix. + /// The condition number of the matrix. + public override double ConditionNumber() + { + var maxSv = double.NegativeInfinity; + var minSv = double.PositiveInfinity; + foreach (var t in _data) + { + maxSv = Math.Max(maxSv, Math.Abs(t)); + minSv = Math.Min(minSv, Math.Abs(t)); + } + + return maxSv / minSv; + } + + /// Computes the inverse of this matrix. + /// If is not a square matrix. + /// If is singular. + /// The inverse of this matrix. + public override Matrix Inverse() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var inverse = (DiagonalMatrix)Clone(); + for (var i = 0; i < _data.Length; i++) + { + if (_data[i] != 0.0) + { + inverse._data[i] = 1.0 / _data[i]; + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixNotSingular); + } + } + + return inverse; + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + return Clone(); + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i]); + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + return Clone(); + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i]); + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + } + + /// + /// Creates a matrix that contains the values from the requested sub-matrix. + /// + /// The row to start copying from. + /// The number of rows to copy. Must be positive. + /// The column to start copying from. + /// The number of columns to copy. Must be positive. + /// The requested sub-matrix. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + /// If or + /// is not positive. + public override Matrix SubMatrix(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + var target = rowIndex == columnIndex + ? (Matrix)new DiagonalMatrix(rowCount, columnCount) + : new SparseMatrix(rowCount, columnCount); + + Storage.CopySubMatrixTo(target.Storage, rowIndex, 0, rowCount, columnIndex, 0, columnCount, ExistingData.AssumeZeros); + return target; + } + + /// + /// Permute the columns of a matrix according to a permutation. + /// + /// The column permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteColumns(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Permute the rows of a matrix according to a permutation. + /// + /// The row permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteRows(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public sealed override bool IsSymmetric() + { + return true; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoModulus(double divisor, Matrix result) + { + var diagonalResult = result as DiagonalMatrix; + if (diagonalResult == null) + { + base.DoModulus(divisor, result); + return; + } + + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + var r = diagonalResult._data; + for (var i = a; i < b; i++) + { + r[i] = Euclid.Modulus(_data[i], divisor); + } + }); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoModulusByThis(double dividend, Matrix result) + { + var diagonalResult = result as DiagonalMatrix; + if (diagonalResult == null) + { + base.DoModulusByThis(dividend, result); + return; + } + + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + var r = diagonalResult._data; + for (var i = a; i < b; i++) + { + r[i] = Euclid.Modulus(dividend, _data[i]); + } + }); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoRemainder(double divisor, Matrix result) + { + var diagonalResult = result as DiagonalMatrix; + if (diagonalResult == null) + { + base.DoRemainder(divisor, result); + return; + } + + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + var r = diagonalResult._data; + for (var i = a; i < b; i++) + { + r[i] = _data[i] % divisor; + } + }); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoRemainderByThis(double dividend, Matrix result) + { + var diagonalResult = result as DiagonalMatrix; + if (diagonalResult == null) + { + base.DoRemainderByThis(dividend, result); + return; + } + + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + var r = diagonalResult._data; + for (var i = a; i < b; i++) + { + r[i] = dividend % _data[i]; + } + }); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/Cholesky.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/Cholesky.cs new file mode 100644 index 0000000..8fadda8 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/Cholesky.cs @@ -0,0 +1,86 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +using System; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal abstract class Cholesky : Cholesky +{ + protected Cholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Gets the determinant of the matrix for which the Cholesky matrix was computed. + /// + public override double Determinant + { + get + { + var det = 1.0; + for (var j = 0; j < Factor.RowCount; j++) + { + var d = Factor.At(j, j); + det *= d * d; + } + + return det; + } + } + + /// + /// Gets the log determinant of the matrix for which the Cholesky matrix was computed. + /// + public override double DeterminantLn + { + get + { + var det = 0.0; + for (var j = 0; j < Factor.RowCount; j++) + { + det += 2 * Math.Log(Factor.At(j, j)); + } + + return det; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseCholesky.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseCholesky.cs new file mode 100644 index 0000000..138f045 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseCholesky.cs @@ -0,0 +1,187 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization for dense matrices. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal sealed class DenseCholesky : Cholesky +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public static DenseCholesky Create(DenseMatrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var factor = (DenseMatrix)matrix.Clone(); + LinearAlgebraControl.Provider.CholeskyFactor(factor.Values, factor.RowCount); + return new DenseCholesky(factor); + } + + DenseCholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + Buffer.BlockCopy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length * Constants.SizeOfDouble); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)Factor; + LinearAlgebraControl.Provider.CholeskySolveFactored(dfactor.Values, dfactor.RowCount, dresult.Values, dresult.ColumnCount); + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + Buffer.BlockCopy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length * Constants.SizeOfDouble); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)Factor; + LinearAlgebraControl.Provider.CholeskySolveFactored(dfactor.Values, dfactor.RowCount, dresult.Values, 1); + } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + /// If does not have the same dimensions as the existing factor. + public override void Factorize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + if (matrix.RowCount != Factor.RowCount || matrix.ColumnCount != Factor.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix, Factor); + } + + var dmatrix = matrix as DenseMatrix; + if (dmatrix == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dfactor = (DenseMatrix)Factor; + + // Overwrite the existing Factor matrix with the input. + Buffer.BlockCopy(dmatrix.Values, 0, dfactor.Values, 0, dmatrix.Values.Length * Constants.SizeOfDouble); + + // Perform factorization (while overwriting). + LinearAlgebraControl.Provider.CholeskyFactor(dfactor.Values, dfactor.RowCount); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseEvd.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseEvd.cs new file mode 100644 index 0000000..c6d9296 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseEvd.cs @@ -0,0 +1,1238 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal sealed class DenseEvd : Evd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If it is known whether the matrix is symmetric or not the routine can skip checking it itself. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public static DenseEvd Create(DenseMatrix matrix, Symmetricity symmetricity) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matrices for eigenvalues and eigenvectors + var eigenVectors = new DenseMatrix(order); + var blockDiagonal = new DenseMatrix(order); + var eigenValues = new LinearAlgebra.Complex.DenseVector(order); + + bool isSymmetric; + switch (symmetricity) + { + case Symmetricity.Symmetric: + case Symmetricity.Hermitian: + isSymmetric = true; + break; + case Symmetricity.Asymmetric: + isSymmetric = false; + break; + default: + isSymmetric = matrix.IsSymmetric(); + break; + } + + LinearAlgebraControl.Provider.EigenDecomp(isSymmetric, order, matrix.Values, eigenVectors.Values, eigenValues.Values, blockDiagonal.Values); + + return new DenseEvd(eigenVectors, eigenValues, blockDiagonal, isSymmetric); + } + + DenseEvd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Symmetric Householder reduction to tridiagonal form. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tred2 by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + internal static void SymmetricTridiagonalize(double[] a, double[] d, double[] e, int order) + { + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0; + var h = 0.0; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(d[k]); + } + + if (scale == 0.0) + { + e[i] = d[i - 1]; + for (var j = 0; j < i; j++) + { + d[j] = a[(j * order) + i - 1]; + a[(j * order) + i] = 0.0; + a[(i * order) + j] = 0.0; + } + } + else + { + // Generate Householder vector. + for (var k = 0; k < i; k++) + { + d[k] /= scale; + h += d[k] * d[k]; + } + + var f = d[i - 1]; + var g = Math.Sqrt(h); + if (f > 0) + { + g = -g; + } + + e[i] = scale * g; + h = h - (f * g); + d[i - 1] = f - g; + + for (var j = 0; j < i; j++) + { + e[j] = 0.0; + } + + // Apply similarity transformation to remaining columns. + for (var j = 0; j < i; j++) + { + f = d[j]; + a[(i * order) + j] = f; + g = e[j] + (a[(j * order) + j] * f); + + for (var k = j + 1; k <= i - 1; k++) + { + g += a[(j * order) + k] * d[k]; + e[k] += a[(j * order) + k] * f; + } + + e[j] = g; + } + + f = 0.0; + + for (var j = 0; j < i; j++) + { + e[j] /= h; + f += e[j] * d[j]; + } + + var hh = f / (h + h); + + for (var j = 0; j < i; j++) + { + e[j] -= hh * d[j]; + } + + for (var j = 0; j < i; j++) + { + f = d[j]; + g = e[j]; + + for (var k = j; k <= i - 1; k++) + { + a[(j * order) + k] -= (f * e[k]) + (g * d[k]); + } + + d[j] = a[(j * order) + i - 1]; + a[(j * order) + i] = 0.0; + } + } + + d[i] = h; + } + + // Accumulate transformations. + for (var i = 0; i < order - 1; i++) + { + a[(i * order) + order - 1] = a[(i * order) + i]; + a[(i * order) + i] = 1.0; + var h = d[i + 1]; + if (h != 0.0) + { + for (var k = 0; k <= i; k++) + { + d[k] = a[((i + 1) * order) + k] / h; + } + + for (var j = 0; j <= i; j++) + { + var g = 0.0; + for (var k = 0; k <= i; k++) + { + g += a[((i + 1) * order) + k] * a[(j * order) + k]; + } + + for (var k = 0; k <= i; k++) + { + a[(j * order) + k] -= g * d[k]; + } + } + } + + for (var k = 0; k <= i; k++) + { + a[((i + 1) * order) + k] = 0.0; + } + } + + for (var j = 0; j < order; j++) + { + d[j] = a[(j * order) + order - 1]; + a[(j * order) + order - 1] = 0.0; + } + + a[(order * order) - 1] = 1.0; + e[0] = 0.0; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + internal static void SymmetricDiagonalize(double[] a, double[] d, double[] e, int order) + { + const int maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0; + + var f = 0.0; + var tst1 = 0.0; + var eps = Precision.DoublePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0 * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0; + var s2 = 0.0; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = a[((i + 1) * order) + k]; + a[((i + 1) * order) + k] = (s * a[(i * order) + k]) + (c * h); + a[(i * order) + k] = (c * a[(i * order) + k]) - (s * h); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + } while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = a[(i * order) + j]; + a[(i * order) + j] = a[(k * order) + j]; + a[(k * order) + j] = p; + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + internal static void NonsymmetricReduceToHessenberg(double[] a, double[] matrixH, int order) + { + var ort = new double[order]; + var high = order - 1; + for (var m = 1; m <= high - 1; m++) + { + var mm1 = m - 1; + var mm1O = mm1 * order; + // Scale column. + var scale = 0.0; + for (var i = m; i <= high; i++) + { + scale += Math.Abs(matrixH[mm1O + i]); + } + + if (scale != 0.0) + { + // Compute Householder transformation. + var h = 0.0; + for (var i = high; i >= m; i--) + { + ort[i] = matrixH[mm1O + i] / scale; + h += ort[i] * ort[i]; + } + + var g = Math.Sqrt(h); + if (ort[m] > 0) + { + g = -g; + } + + h = h - (ort[m] * g); + ort[m] = ort[m] - g; + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var jO = j * order; + var f = 0.0; + for (var i = order - 1; i >= m; i--) + { + f += ort[i] * matrixH[jO + i]; + } + + f = f / h; + + for (var i = m; i <= high; i++) + { + matrixH[jO + i] -= f * ort[i]; + } + } + + for (var i = 0; i <= high; i++) + { + var f = 0.0; + for (var j = high; j >= m; j--) + { + f += ort[j] * matrixH[j * order + i]; + } + + f = f / h; + + for (var j = m; j <= high; j++) + { + matrixH[j * order + i] -= f * ort[j]; + } + } + + ort[m] = scale * ort[m]; + matrixH[mm1O + m] = scale * g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + a[(j * order) + i] = i == j ? 1.0 : 0.0; + } + } + + for (var m = high - 1; m >= 1; m--) + { + var mm1 = m - 1; + var mm1O = mm1 * order; + var mm1Om = mm1O + m; + if (matrixH[mm1Om] != 0.0) + { + for (var i = m + 1; i <= high; i++) + { + ort[i] = matrixH[mm1O + i]; + } + + for (var j = m; j <= high; j++) + { + var g = 0.0; + var jO = j * order; + for (var i = m; i <= high; i++) + { + g += ort[i] * a[jO + i]; + } + + // Double division avoids possible underflow + g = (g / ort[m]) / matrixH[mm1Om]; + + for (var i = m; i <= high; i++) + { + a[jO + i] += g * ort[i]; + } + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + internal static void NonsymmetricReduceHessenberToRealSchur(double[] a, double[] matrixH, double[] d, double[] e, int order) + { + // Initialize + var n = order - 1; + var eps = Math.Pow(2.0, -52.0); + var exshift = 0.0; + double p = 0, q = 0, r = 0, s = 0, z = 0; + double w, x, y; + + // Store roots isolated by balanc and compute matrix norm + var norm = 0.0; + for (var i = 0; i < order; i++) + { + for (var j = Math.Max(i - 1, 0); j < order; j++) + { + norm = norm + Math.Abs(matrixH[j * order + i]); + } + } + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + var lm1 = l - 1; + var lm1O = lm1 * order; + s = Math.Abs(matrixH[lm1O + lm1]) + Math.Abs(matrixH[l * order + l]); + + if (s == 0.0) + { + s = norm; + } + + if (Math.Abs(matrixH[lm1O + l]) < eps * s) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + var index = n * order + n; + matrixH[index] += exshift; + d[n] = matrixH[index]; + e[n] = 0.0; + n--; + iter = 0; + + // Two roots found + } + else if (l == n - 1) + { + var nO = n * order; + var nm1 = n - 1; + var nm1O = nm1 * order; + var nOn = nO + n; + + w = matrixH[nm1O + n] * matrixH[nO + nm1]; + p = (matrixH[nm1O + nm1] - matrixH[nOn]) / 2.0; + q = (p * p) + w; + z = Math.Sqrt(Math.Abs(q)); + + matrixH[nOn] += exshift; + matrixH[nm1O + nm1] += exshift; + x = matrixH[nOn]; + + // Real pair + if (q >= 0) + { + if (p >= 0) + { + z = p + z; + } + else + { + z = p - z; + } + + d[nm1] = x + z; + + d[n] = d[nm1]; + if (z != 0.0) + { + d[n] = x - (w / z); + } + + e[n - 1] = 0.0; + e[n] = 0.0; + x = matrixH[nm1O + n]; + s = Math.Abs(x) + Math.Abs(z); + p = x / s; + q = z / s; + r = Math.Sqrt((p * p) + (q * q)); + p = p / r; + q = q / r; + + // Row modification + for (var j = n - 1; j < order; j++) + { + var jO = j * order; + var jOn = jO + n; + z = matrixH[jO + nm1]; + matrixH[jO + nm1] = (q * z) + (p * matrixH[jOn]); + matrixH[jOn] = (q * matrixH[jOn]) - (p * z); + } + + // Column modification + for (var i = 0; i <= n; i++) + { + var nOi = nO + i; + z = matrixH[nm1O + i]; + matrixH[nm1O + i] = (q * z) + (p * matrixH[nOi]); + matrixH[nOi] = (q * matrixH[nOi]) - (p * z); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + var nOi = nO + i; + z = a[nm1O + i]; + a[nm1O + i] = (q * z) + (p * a[nOi]); + a[nOi] = (q * a[nOi]) - (p * z); + } + + // Complex pair + } + else + { + d[n - 1] = x + p; + d[n] = x + p; + e[n - 1] = z; + e[n] = -z; + } + + n = n - 2; + iter = 0; + + // No convergence yet + } + else + { + var nO = n * order; + var nm1 = n - 1; + var nm1O = nm1 * order; + var nOn = nO + n; + + // Form shift + x = matrixH[nOn]; + y = 0.0; + w = 0.0; + if (l < n) + { + y = matrixH[nm1O + nm1]; + w = matrixH[nm1O + n] * matrixH[nO + nm1]; + } + + // Wilkinson's original ad hoc shift + if (iter == 10) + { + exshift += x; + for (var i = 0; i <= n; i++) + { + matrixH[i * order + i] -= x; + } + + s = Math.Abs(matrixH[nm1O + n]) + Math.Abs(matrixH[(n - 2) * order + nm1]); + x = y = 0.75 * s; + w = (-0.4375) * s * s; + } + + // MATLAB's new ad hoc shift + if (iter == 30) + { + s = (y - x) / 2.0; + s = (s * s) + w; + if (s > 0) + { + s = Math.Sqrt(s); + if (y < x) + { + s = -s; + } + + s = x - (w / (((y - x) / 2.0) + s)); + for (var i = 0; i <= n; i++) + { + matrixH[i * order + i] -= s; + } + + exshift += s; + x = y = w = 0.964; + } + } + + iter = iter + 1; + if (iter >= 30 * order) + { + throw new NonConvergenceException(); + } + + // Look for two consecutive small sub-diagonal elements + var m = n - 2; + while (m >= l) + { + var mp1 = m + 1; + var mm1 = m - 1; + var mO = m * order; + var mp1O = mp1 * order; + var mm1O = mm1 * order; + + z = matrixH[mO + m]; + r = x - z; + s = y - z; + p = (((r * s) - w) / matrixH[mO + mp1]) + matrixH[mp1O + m]; + q = matrixH[mp1O + mp1] - z - r - s; + r = matrixH[mp1O + (m + 2)]; + s = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + p = p / s; + q = q / s; + r = r / s; + + if (m == l) + { + break; + } + + if (Math.Abs(matrixH[mm1O + m]) * (Math.Abs(q) + Math.Abs(r)) < eps * (Math.Abs(p) * (Math.Abs(matrixH[mm1O + mm1]) + Math.Abs(z) + Math.Abs(matrixH[mp1O + mp1])))) + { + break; + } + + m--; + } + + var mp2 = m + 2; + for (var i = mp2; i <= n; i++) + { + matrixH[(i - 2) * order + i] = 0.0; + if (i > mp2) + { + matrixH[(i - 3) * order + i] = 0.0; + } + } + + // Double QR step involving rows l:n and columns m:n + for (var k = m; k <= n - 1; k++) + { + var notlast = k != n - 1; + var kO = k * order; + var km1 = k - 1; + var kp1 = k + 1; + var kp2 = k + 2; + var kp1O = kp1 * order; + var kp2O = kp2 * order; + var km1O = km1 * order; + if (k != m) + { + p = matrixH[km1O + k]; + q = matrixH[km1O + kp1]; + r = notlast ? matrixH[km1O + kp2] : 0.0; + x = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + if (x == 0.0) + { + continue; + } + + p = p / x; + q = q / x; + r = r / x; + } + + s = Math.Sqrt((p * p) + (q * q) + (r * r)); + + if (p < 0) + { + s = -s; + } + + if (s != 0.0) + { + if (k != m) + { + matrixH[km1O + k] = (-s) * x; + } + else if (l != m) + { + matrixH[km1O + k] = -matrixH[km1O + k]; + } + + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // Row modification + for (var j = k; j < order; j++) + { + var jO = j * order; + var jOk = jO + k; + var jOkp1 = jO + kp1; + var jOkp2 = jO + kp2; + p = matrixH[jOk] + (q * matrixH[jOkp1]); + if (notlast) + { + p = p + (r * matrixH[jOkp2]); + matrixH[jOkp2] -= (p * z); + } + + matrixH[jOk] -= (p * x); + matrixH[jOkp1] -= (p * y); + } + + // Column modification + for (var i = 0; i <= Math.Min(n, k + 3); i++) + { + p = (x * matrixH[kO + i]) + (y * matrixH[kp1O + i]); + + if (notlast) + { + p = p + (z * matrixH[kp2O + i]); + matrixH[kp2O + i] -= (p * r); + } + + matrixH[kO + i] -= p; + matrixH[kp1O + i] -= (p * q); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + p = (x * a[kO + i]) + (y * a[kp1O + i]); + + if (notlast) + { + p = p + (z * a[kp2O + i]); + a[kp2O + i] -= p * r; + } + + a[kO + i] -= p; + a[kp1O + i] -= p * q; + } + } // (s != 0) + } // k loop + } // check convergence + } // while (n >= low) + + // Backsubstitute to find vectors of upper triangular form + if (norm == 0.0) + { + return; + } + + for (n = order - 1; n >= 0; n--) + { + var nO = n * order; + var nm1 = n - 1; + var nm1O = nm1 * order; + + p = d[n]; + q = e[n]; + + // Real vector + double t; + if (q == 0.0) + { + var l = n; + matrixH[nO + n] = 1.0; + for (var i = n - 1; i >= 0; i--) + { + var ip1 = i + 1; + var iO = i * order; + var ip1O = ip1 * order; + + w = matrixH[iO + i] - p; + r = 0.0; + for (var j = l; j <= n; j++) + { + r = r + (matrixH[j * order + i] * matrixH[nO + j]); + } + + if (e[i] < 0.0) + { + z = w; + s = r; + } + else + { + l = i; + if (e[i] == 0.0) + { + if (w != 0.0) + { + matrixH[nO + i] = (-r) / w; + } + else + { + matrixH[nO + i] = (-r) / (eps * norm); + } + + // Solve real equations + } + else + { + x = matrixH[ip1O + i]; + y = matrixH[iO + ip1]; + q = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]); + t = ((x * s) - (z * r)) / q; + matrixH[nO + i] = t; + if (Math.Abs(x) > Math.Abs(z)) + { + matrixH[nO + ip1] = (-r - (w * t)) / x; + } + else + { + matrixH[nO + ip1] = (-s - (y * t)) / z; + } + } + + // Overflow control + t = Math.Abs(matrixH[nO + i]); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[nO + j] /= t; + } + } + } + } + + // Complex vector + } + else if (q < 0) + { + var l = n - 1; + + // Last vector component imaginary so matrix is triangular + if (Math.Abs(matrixH[nm1O + n]) > Math.Abs(matrixH[nO + nm1])) + { + matrixH[nm1O + nm1] = q / matrixH[nm1O + n]; + matrixH[nO + nm1] = (-(matrixH[nO + n] - p)) / matrixH[nm1O + n]; + } + else + { + var res = Cdiv(0.0, -matrixH[nO + nm1], matrixH[nm1O + nm1] - p, q); + matrixH[nm1O + nm1] = res.Real; + matrixH[nO + nm1] = res.Imaginary; + } + + matrixH[nm1O + n] = 0.0; + matrixH[nO + n] = 1.0; + for (var i = n - 2; i >= 0; i--) + { + var ip1 = i + 1; + var iO = i * order; + var ip1O = ip1 * order; + var ra = 0.0; + var sa = 0.0; + for (var j = l; j <= n; j++) + { + var jO = j * order; + var jOi = jO + i; + ra = ra + (matrixH[jOi] * matrixH[nm1O + j]); + sa = sa + (matrixH[jOi] * matrixH[nO + j]); + } + + w = matrixH[iO + i] - p; + + if (e[i] < 0.0) + { + z = w; + r = ra; + s = sa; + } + else + { + l = i; + if (e[i] == 0.0) + { + var res = Cdiv(-ra, -sa, w, q); + matrixH[nm1O + i] = res.Real; + matrixH[nO + i] = res.Imaginary; + } + else + { + // Solve complex equations + x = matrixH[ip1O + i]; + y = matrixH[iO + ip1]; + + var vr = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]) - (q * q); + var vi = (d[i] - p) * 2.0 * q; + if ((vr == 0.0) && (vi == 0.0)) + { + vr = eps * norm * (Math.Abs(w) + Math.Abs(q) + Math.Abs(x) + Math.Abs(y) + Math.Abs(z)); + } + + var res = Cdiv((x * r) - (z * ra) + (q * sa), (x * s) - (z * sa) - (q * ra), vr, vi); + matrixH[nm1O + i] = res.Real; + matrixH[nO + i] = res.Imaginary; + if (Math.Abs(x) > (Math.Abs(z) + Math.Abs(q))) + { + matrixH[nm1O + ip1] = (-ra - (w * matrixH[nm1O + i]) + (q * matrixH[nO + i])) / x; + matrixH[nO + ip1] = (-sa - (w * matrixH[nO + i]) - (q * matrixH[nm1O + i])) / x; + } + else + { + res = Cdiv(-r - (y * matrixH[nm1O + i]), -s - (y * matrixH[nO + i]), z, q); + matrixH[nm1O + ip1] = res.Real; + matrixH[nO + ip1] = res.Imaginary; + } + } + + // Overflow control + t = Math.Max(Math.Abs(matrixH[nm1O + i]), Math.Abs(matrixH[nO + i])); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[nm1O + j] /= t; + matrixH[nO + j] /= t; + } + } + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j >= 0; j--) + { + var jO = j * order; + for (var i = 0; i < order; i++) + { + z = 0.0; + for (var k = 0; k <= j; k++) + { + z = z + (a[k * order + i] * matrixH[jO + k]); + } + + a[jO + i] = z; + } + } + } + + /// + /// Complex scalar division X/Y. + /// + /// Real part of X + /// Imaginary part of X + /// Real part of Y + /// Imaginary part of Y + /// Division result as a number. + static Complex Cdiv(double xreal, double ximag, double yreal, double yimag) + { + if (Math.Abs(yimag) < Math.Abs(yreal)) + { + return new Complex((xreal + (ximag * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal))), (ximag - (xreal * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal)))); + } + + return new Complex((ximag + (xreal * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag))), (-xreal + (ximag * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag)))); + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (EigenValues.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (EigenValues.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = EigenValues.Count; + var tmp = new double[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + double value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(j * order) + i] * input.At(i, k); + } + + value /= EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + double value = 0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(i * order) + j] * tmp[i]; + } + + result.At(j, k, value); + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (EigenValues.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (EigenValues.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VT * b; + var order = EigenValues.Count; + var tmp = new double[order]; + double value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(j * order) + i] * input[i]; + } + + value /= EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(i * order) + j] * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseGramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseGramSchmidt.cs new file mode 100644 index 0000000..3108ef2 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseGramSchmidt.cs @@ -0,0 +1,198 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal sealed class DenseGramSchmidt : GramSchmidt +{ + /// + /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public static DenseGramSchmidt Create(Matrix matrix) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var q = (DenseMatrix)matrix.Clone(); + var r = new DenseMatrix(matrix.ColumnCount, matrix.ColumnCount); + Factorize(q.Values, q.RowCount, q.ColumnCount, r.Values); + + return new DenseGramSchmidt(q, r); + } + + DenseGramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Factorize matrix using the modified Gram-Schmidt method. + /// + /// Initial matrix. On exit is replaced by Q. + /// Number of rows in Q. + /// Number of columns in Q. + /// On exit is filled by R. + private static void Factorize(double[] q, int rowsQ, int columnsQ, double[] r) + { + for (var k = 0; k < columnsQ; k++) + { + var norm = 0.0; + for (var i = 0; i < rowsQ; i++) + { + norm += q[(k * rowsQ) + i] * q[(k * rowsQ) + i]; + } + + norm = Math.Sqrt(norm); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r[(k * columnsQ) + k] = norm; + for (var i = 0; i < rowsQ; i++) + { + q[(k * rowsQ) + i] /= norm; + } + + for (var j = k + 1; j < columnsQ; j++) + { + var k1 = k; + var j1 = j; + + var dot = 0.0; + for (var index = 0; index < rowsQ; index++) + { + dot += q[(k1 * rowsQ) + index] * q[(j1 * rowsQ) + index]; + } + + r[(j * columnsQ) + k] = dot; + for (var i = 0; i < rowsQ; i++) + { + var value = q[(j * rowsQ) + i] - (q[(k * rowsQ) + i] * dot); + q[(j * rowsQ) + i] = value; + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (Q.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, null, dinput.Values, input.ColumnCount, dresult.Values, QRMethod.Thin); + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (Q.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(Q, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, null, dinput.Values, 1, dresult.Values, QRMethod.Thin); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseLU.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseLU.cs new file mode 100644 index 0000000..8dde134 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseLU.cs @@ -0,0 +1,194 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal sealed class DenseLU : LU +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public static DenseLU Create(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var pivots = new int[matrix.RowCount]; + + // Create a new matrix for the LU factors, then perform factorization (while overwriting). + var factors = (DenseMatrix)matrix.Clone(); + LinearAlgebraControl.Provider.LUFactor(factors.Values, factors.RowCount, pivots); + + return new DenseLU(factors, pivots); + } + + DenseLU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do LU factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do LU factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + Buffer.BlockCopy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length * Constants.SizeOfDouble); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + LinearAlgebraControl.Provider.LUSolveFactored(input.ColumnCount, dfactors.Values, dfactors.RowCount, Pivots, dresult.Values); + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do LU factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do LU factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + Buffer.BlockCopy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length * Constants.SizeOfDouble); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + LinearAlgebraControl.Provider.LUSolveFactored(1, dfactors.Values, dfactors.RowCount, Pivots, dresult.Values); + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var result = (DenseMatrix)Factors.Clone(); + LinearAlgebraControl.Provider.LUInverseFactored(result.Values, result.RowCount, Pivots); + return result; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseQR.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseQR.cs new file mode 100644 index 0000000..489af7f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseQR.cs @@ -0,0 +1,169 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// +internal sealed class DenseQR : QR +{ + /// + /// Gets or sets Tau vector. Contains additional information on Q - used for native solver. + /// + double[] Tau { get; set; } + + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// The type of QR factorization to perform. + /// If is null. + /// If row count is less then column count + public static DenseQR Create(DenseMatrix matrix, QRMethod method = QRMethod.Full) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var tau = new double[Math.Min(matrix.RowCount, matrix.ColumnCount)]; + Matrix q; + Matrix r; + + if (method == QRMethod.Full) + { + r = matrix.Clone(); + q = new DenseMatrix(matrix.RowCount); + LinearAlgebraControl.Provider.QRFactor(((DenseMatrix)r).Values, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)q).Values, tau); + } + else + { + q = matrix.Clone(); + r = new DenseMatrix(matrix.ColumnCount); + LinearAlgebraControl.Provider.ThinQRFactor(((DenseMatrix)q).Values, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)r).Values, tau); + } + + return new DenseQR(q, r, method, tau); + } + + DenseQR(Matrix q, Matrix rFull, QRMethod method, double[] tau) + : base(q, rFull, method) + { + Tau = tau; + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (FullR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do QR factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do QR factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, Tau, dinput.Values, input.ColumnCount, dresult.Values, Method); + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (FullR.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(FullR, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do QR factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do QR factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, Tau, dinput.Values, 1, dresult.Values, Method); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseSvd.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseSvd.cs new file mode 100644 index 0000000..53d534b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/DenseSvd.cs @@ -0,0 +1,161 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD) for . +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal sealed class DenseSvd : Svd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// If SVD algorithm failed to converge with matrix . + public static DenseSvd Create(DenseMatrix matrix, bool computeVectors) + { + var nm = Math.Min(matrix.RowCount, matrix.ColumnCount); + var s = new DenseVector(nm); + var u = new DenseMatrix(matrix.RowCount); + var vt = new DenseMatrix(matrix.ColumnCount); + LinearAlgebraControl.Provider.SingularValueDecomposition(computeVectors, ((DenseMatrix)matrix.Clone()).Values, matrix.RowCount, matrix.ColumnCount, s.Values, u.Values, vt.Values); + + return new DenseSvd(s, u, vt, computeVectors); + } + + DenseSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (U.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.SvdSolveFactored(U.RowCount, VT.ColumnCount, ((DenseVector)S).Values, ((DenseMatrix)U).Values, ((DenseMatrix)VT).Values, dinput.Values, input.ColumnCount, dresult.Values); + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (U.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VT.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(VT, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.SvdSolveFactored(U.RowCount, VT.ColumnCount, ((DenseVector)S).Values, ((DenseMatrix)U).Values, ((DenseMatrix)VT).Values, dinput.Values, 1, dresult.Values); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/Evd.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/Evd.cs new file mode 100644 index 0000000..92533da --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/Evd.cs @@ -0,0 +1,126 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// Matrix V is encoded in the property EigenVectors in the way that: +/// - column corresponding to real eigenvalue represents real eigenvector, +/// - columns corresponding to the pair of complex conjugate eigenvalues +/// lambda[i] and lambda[i+1] encode real and imaginary parts of eigenvectors. +/// +internal abstract class Evd : Evd +{ + protected Evd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Gets the absolute value of determinant of the square matrix for which the EVD was computed. + /// + public override double Determinant + { + get + { + var det = Complex.One; + for (var i = 0; i < EigenValues.Count; i++) + { + det *= EigenValues[i]; + + if (EigenValues[i].AlmostEqual(Complex.Zero)) + { + return 0; + } + } + + return det.Magnitude; + } + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public override int Rank + { + get + { + var rank = 0; + for (var i = 0; i < EigenValues.Count; i++) + { + if (EigenValues[i].AlmostEqual(Complex.Zero)) + { + continue; + } + + rank++; + } + + return rank; + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < EigenValues.Count; i++) + { + if (EigenValues[i].AlmostEqual(Complex.Zero)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs new file mode 100644 index 0000000..f93cc54 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs @@ -0,0 +1,96 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal abstract class GramSchmidt : GramSchmidt +{ + protected GramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public override double Determinant + { + get + { + if (FullR.RowCount != FullR.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = 1.0; + for (var i = 0; i < FullR.ColumnCount; i++) + { + det *= FullR.At(i, i); + if (Math.Abs(FullR.At(i, i)).AlmostEqual(0.0)) + { + return 0; + } + } + + return Convert.ToSingle(Math.Abs(det)); + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < FullR.ColumnCount; i++) + { + if (Math.Abs(FullR.At(i, i)).AlmostEqual(0.0)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/LU.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/LU.cs new file mode 100644 index 0000000..b4409f1 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/LU.cs @@ -0,0 +1,74 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// In the Math.Net implementation we also store a set of pivot elements for increased +/// numerical stability. The pivot elements encode a permutation matrix P such that P*A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal abstract class LU : LU +{ + protected LU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Gets the determinant of the matrix for which the LU factorization was computed. + /// + public override double Determinant + { + get + { + var det = 1.0; + for (var j = 0; j < Factors.RowCount; j++) + { + if (Pivots[j] != j) + { + det *= -Factors.At(j, j); + } + else + { + det *= Factors.At(j, j); + } + } + + return det; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/QR.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/QR.cs new file mode 100644 index 0000000..9e2fef6 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/QR.cs @@ -0,0 +1,101 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A (m x n) may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// If a factorization is performed, the resulting Q matrix is an m x m matrix +/// and the R matrix is an m x n matrix. If a factorization is performed, the +/// resulting Q matrix is an m x n matrix and the R matrix is an n x n matrix. +/// +internal abstract class QR : QR +{ + protected QR(Matrix q, Matrix rFull, QRMethod method) + : base(q, rFull, method) + { + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public override double Determinant + { + get + { + if (FullR.RowCount != FullR.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = 1.0; + for (var i = 0; i < FullR.ColumnCount; i++) + { + det *= FullR.At(i, i); + if (Math.Abs(FullR.At(i, i)).AlmostEqual(0.0)) + { + return 0; + } + } + + return Math.Abs(det); + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < FullR.ColumnCount; i++) + { + if (Math.Abs(FullR.At(i, i)).AlmostEqual(0.0)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/Svd.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/Svd.cs new file mode 100644 index 0000000..0af0a83 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/Svd.cs @@ -0,0 +1,122 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD). +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal abstract class Svd : Svd +{ + protected Svd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public override int Rank + { + get + { + double tolerance = Precision.EpsilonOf(S.Maximum()) * Math.Max(U.RowCount, VT.RowCount); + return S.Count(t => Math.Abs(t) > tolerance); + } + } + + /// + /// Gets the two norm of the . + /// + /// The 2-norm of the . + public override double L2Norm + { + get + { + return Math.Abs(S[0]); + } + } + + /// + /// Gets the condition number max(S) / min(S) + /// + /// The condition number. + public override double ConditionNumber + { + get + { + var tmp = Math.Min(U.RowCount, VT.ColumnCount) - 1; + return Math.Abs(S[0]) / Math.Abs(S[tmp]); + } + } + + /// + /// Gets the determinant of the square matrix for which the SVD was computed. + /// + public override double Determinant + { + get + { + if (U.RowCount != VT.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = 1.0; + foreach (var value in S) + { + det *= value; + if (Math.Abs(value).AlmostEqual(0.0)) + { + return 0; + } + } + + return Math.Abs(det); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserCholesky.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserCholesky.cs new file mode 100644 index 0000000..b8ff8a8 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserCholesky.cs @@ -0,0 +1,273 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization for user matrices. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal sealed class UserCholesky : Cholesky +{ + /// + /// Computes the Cholesky factorization in-place. + /// + /// On entry, the matrix to factor. On exit, the Cholesky factor matrix + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + static void DoCholesky(Matrix factor) + { + if (factor.RowCount != factor.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var tmpColumn = new double[factor.RowCount]; + + // Main loop - along the diagonal + for (var ij = 0; ij < factor.RowCount; ij++) + { + // "Pivot" element + var tmpVal = factor.At(ij, ij); + + if (tmpVal > 0.0) + { + tmpVal = Math.Sqrt(tmpVal); + factor.At(ij, ij, tmpVal); + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < factor.RowCount; i++) + { + factor.At(i, ij, factor.At(i, ij) / tmpVal); + tmpColumn[i] = factor.At(i, ij); + } + + // Remaining columns, below the diagonal + DoCholeskyStep(factor, factor.RowCount, ij + 1, factor.RowCount, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (var i = ij + 1; i < factor.RowCount; i++) + { + factor.At(ij, i, 0.0); + } + } + } + + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public static UserCholesky Create(Matrix matrix) + { + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var factor = matrix.Clone(); + DoCholesky(factor); + return new UserCholesky(factor); + } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + /// If does not have the same dimensions as the existing factor. + public override void Factorize(Matrix matrix) + { + if (matrix.RowCount != Factor.RowCount || matrix.ColumnCount != Factor.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix, Factor); + } + + matrix.CopyTo(Factor); + DoCholesky(Factor); + } + + UserCholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(Matrix data, int rowDim, int firstCol, int colLimit, double[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data.At(i, j, data.At(i, j) - (multipliers[i] * tmpVal)); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + input.CopyTo(result); + var order = Factor.RowCount; + + for (var c = 0; c < result.ColumnCount; c++) + { + // Solve L*Y = B; + double sum; + for (var i = 0; i < order; i++) + { + sum = result.At(i, c); + for (var k = i - 1; k >= 0; k--) + { + sum -= Factor.At(i, k) * result.At(k, c); + } + + result.At(i, c, sum / Factor.At(i, i)); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result.At(i, c); + for (var k = i + 1; k < order; k++) + { + sum -= Factor.At(k, i) * result.At(k, c); + } + + result.At(i, c, sum / Factor.At(i, i)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + input.CopyTo(result); + var order = Factor.RowCount; + + // Solve L*Y = B; + double sum; + for (var i = 0; i < order; i++) + { + sum = result[i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= Factor.At(i, k) * result[k]; + } + + result[i] = sum / Factor.At(i, i); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result[i]; + for (var k = i + 1; k < order; k++) + { + sum -= Factor.At(k, i) * result[k]; + } + + result[i] = sum / Factor.At(i, i); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs new file mode 100644 index 0000000..87d680a --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs @@ -0,0 +1,1212 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal sealed class UserEvd : Evd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If it is known whether the matrix is symmetric or not the routine can skip checking it itself. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public static UserEvd Create(Matrix matrix, Symmetricity symmetricity) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matrices for eigenvalues and eigenvectors + var eigenVectors = Matrix.Build.SameAs(matrix, order, order, fullyMutable: true); + var blockDiagonal = Matrix.Build.SameAs(matrix, order, order); + var eigenValues = new LinearAlgebra.Complex.DenseVector(order); + + bool isSymmetric; + switch (symmetricity) + { + case Symmetricity.Symmetric: + case Symmetricity.Hermitian: + isSymmetric = true; + break; + case Symmetricity.Asymmetric: + isSymmetric = false; + break; + default: + isSymmetric = matrix.IsSymmetric(); + break; + } + + var d = new double[order]; + var e = new double[order]; + + if (isSymmetric) + { + matrix.CopyTo(eigenVectors); + d = eigenVectors.Row(order - 1).ToArray(); + + SymmetricTridiagonalize(eigenVectors, d, e, order); + SymmetricDiagonalize(eigenVectors, d, e, order); + } + else + { + var matrixH = matrix.ToArray(); + + NonsymmetricReduceToHessenberg(eigenVectors, matrixH, order); + NonsymmetricReduceHessenberToRealSchur(eigenVectors, matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + blockDiagonal.At(i, i, d[i]); + + if (e[i] > 0) + { + blockDiagonal.At(i, i + 1, e[i]); + } + else if (e[i] < 0) + { + blockDiagonal.At(i, i - 1, e[i]); + } + } + + for (var i = 0; i < order; i++) + { + eigenValues[i] = new Complex(d[i], e[i]); + } + + return new UserEvd(eigenVectors, eigenValues, blockDiagonal, isSymmetric); + } + + UserEvd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Symmetric Householder reduction to tridiagonal form. + /// + /// The eigen vectors to work on. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tred2 by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void SymmetricTridiagonalize(Matrix eigenVectors, double[] d, double[] e, int order) + { + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0; + var h = 0.0; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(d[k]); + } + + if (scale == 0.0) + { + e[i] = d[i - 1]; + for (var j = 0; j < i; j++) + { + d[j] = eigenVectors.At(i - 1, j); + eigenVectors.At(i, j, 0.0); + eigenVectors.At(j, i, 0.0); + } + } + else + { + // Generate Householder vector. + for (var k = 0; k < i; k++) + { + d[k] /= scale; + h += d[k] * d[k]; + } + + var f = d[i - 1]; + var g = Math.Sqrt(h); + if (f > 0) + { + g = -g; + } + + e[i] = scale * g; + h = h - (f * g); + d[i - 1] = f - g; + + for (var j = 0; j < i; j++) + { + e[j] = 0.0; + } + + // Apply similarity transformation to remaining columns. + for (var j = 0; j < i; j++) + { + f = d[j]; + eigenVectors.At(j, i, f); + g = e[j] + (eigenVectors.At(j, j) * f); + + for (var k = j + 1; k <= i - 1; k++) + { + g += eigenVectors.At(k, j) * d[k]; + e[k] += eigenVectors.At(k, j) * f; + } + + e[j] = g; + } + + f = 0.0; + + for (var j = 0; j < i; j++) + { + e[j] /= h; + f += e[j] * d[j]; + } + + var hh = f / (h + h); + + for (var j = 0; j < i; j++) + { + e[j] -= hh * d[j]; + } + + for (var j = 0; j < i; j++) + { + f = d[j]; + g = e[j]; + + for (var k = j; k <= i - 1; k++) + { + eigenVectors.At(k, j, eigenVectors.At(k, j) - (f * e[k]) - (g * d[k])); + } + + d[j] = eigenVectors.At(i - 1, j); + eigenVectors.At(i, j, 0.0); + } + } + + d[i] = h; + } + + // Accumulate transformations. + for (var i = 0; i < order - 1; i++) + { + eigenVectors.At(order - 1, i, eigenVectors.At(i, i)); + eigenVectors.At(i, i, 1.0); + var h = d[i + 1]; + if (h != 0.0) + { + for (var k = 0; k <= i; k++) + { + d[k] = eigenVectors.At(k, i + 1) / h; + } + + for (var j = 0; j <= i; j++) + { + var g = 0.0; + for (var k = 0; k <= i; k++) + { + g += eigenVectors.At(k, i + 1) * eigenVectors.At(k, j); + } + + for (var k = 0; k <= i; k++) + { + eigenVectors.At(k, j, eigenVectors.At(k, j) - g * d[k]); + } + } + } + + for (var k = 0; k <= i; k++) + { + eigenVectors.At(k, i + 1, 0.0); + } + } + + for (var j = 0; j < order; j++) + { + d[j] = eigenVectors.At(order - 1, j); + eigenVectors.At(order - 1, j, 0.0); + } + + eigenVectors.At(order - 1, order - 1, 1.0); + e[0] = 0.0; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// The eigen vectors to work on. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + static void SymmetricDiagonalize(Matrix eigenVectors, double[] d, double[] e, int order) + { + const int maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0; + + var f = 0.0; + var tst1 = 0.0; + var eps = Precision.DoublePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0 * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0; + var s2 = 0.0; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = eigenVectors.At(k, i + 1); + eigenVectors.At(k, i + 1, (s * eigenVectors.At(k, i)) + (c * h)); + eigenVectors.At(k, i, (c * eigenVectors.At(k, i)) - (s * h)); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + } while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = eigenVectors.At(j, i); + eigenVectors.At(j, i, eigenVectors.At(j, k)); + eigenVectors.At(j, k, p); + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// The eigen vectors to work on. + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + static void NonsymmetricReduceToHessenberg(Matrix eigenVectors, double[,] matrixH, int order) + { + var ort = new double[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0; + for (var i = m; i < order; i++) + { + scale = scale + Math.Abs(matrixH[i, m - 1]); + } + + if (scale != 0.0) + { + // Compute Householder transformation. + var h = 0.0; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i] * ort[i]; + } + + var g = Math.Sqrt(h); + if (ort[m] > 0) + { + g = -g; + } + + h = h - (ort[m] * g); + ort[m] = ort[m] - g; + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = 0.0; + for (var i = order - 1; i >= m; i--) + { + f += ort[i] * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = 0.0; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[i, j] -= f * ort[j]; + } + } + + ort[m] = scale * ort[m]; + matrixH[m, m - 1] = scale * g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + eigenVectors.At(i, j, i == j ? 1.0 : 0.0); + } + } + + for (var m = order - 2; m >= 1; m--) + { + if (matrixH[m, m - 1] != 0.0) + { + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j < order; j++) + { + var g = 0.0; + for (var i = m; i < order; i++) + { + g += ort[i] * eigenVectors.At(i, j); + } + + // Double division avoids possible underflow + g = (g / ort[m]) / matrixH[m, m - 1]; + for (var i = m; i < order; i++) + { + eigenVectors.At(i, j, eigenVectors.At(i, j) + g * ort[i]); + } + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// The eigen vectors to work on. + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void NonsymmetricReduceHessenberToRealSchur(Matrix eigenVectors, double[,] matrixH, double[] d, double[] e, int order) + { + // Initialize + var n = order - 1; + var eps = Precision.DoublePrecision; + var exshift = 0.0; + double p = 0, q = 0, r = 0, s = 0, z = 0, w, x, y; + + // Store roots isolated by balanc and compute matrix norm + var norm = 0.0; + for (var i = 0; i < order; i++) + { + for (var j = Math.Max(i - 1, 0); j < order; j++) + { + norm = norm + Math.Abs(matrixH[i, j]); + } + } + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + s = Math.Abs(matrixH[l - 1, l - 1]) + Math.Abs(matrixH[l, l]); + + if (s == 0.0) + { + s = norm; + } + + if (Math.Abs(matrixH[l, l - 1]) < eps * s) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] = matrixH[n, n] + exshift; + d[n] = matrixH[n, n]; + e[n] = 0.0; + n--; + iter = 0; + + // Two roots found + } + else if (l == n - 1) + { + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + p = (matrixH[n - 1, n - 1] - matrixH[n, n]) / 2.0; + q = (p * p) + w; + z = Math.Sqrt(Math.Abs(q)); + matrixH[n, n] = matrixH[n, n] + exshift; + matrixH[n - 1, n - 1] = matrixH[n - 1, n - 1] + exshift; + x = matrixH[n, n]; + + // Real pair + if (q >= 0) + { + if (p >= 0) + { + z = p + z; + } + else + { + z = p - z; + } + + d[n - 1] = x + z; + + d[n] = d[n - 1]; + if (z != 0.0) + { + d[n] = x - (w / z); + } + + e[n - 1] = 0.0; + e[n] = 0.0; + x = matrixH[n, n - 1]; + s = Math.Abs(x) + Math.Abs(z); + p = x / s; + q = z / s; + r = Math.Sqrt((p * p) + (q * q)); + p = p / r; + q = q / r; + + // Row modification + for (var j = n - 1; j < order; j++) + { + z = matrixH[n - 1, j]; + matrixH[n - 1, j] = (q * z) + (p * matrixH[n, j]); + matrixH[n, j] = (q * matrixH[n, j]) - (p * z); + } + + // Column modification + for (var i = 0; i <= n; i++) + { + z = matrixH[i, n - 1]; + matrixH[i, n - 1] = (q * z) + (p * matrixH[i, n]); + matrixH[i, n] = (q * matrixH[i, n]) - (p * z); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + z = eigenVectors.At(i, n - 1); + eigenVectors.At(i, n - 1, (q * z) + (p * eigenVectors.At(i, n))); + eigenVectors.At(i, n, (q * eigenVectors.At(i, n)) - (p * z)); + } + + // Complex pair + } + else + { + d[n - 1] = x + p; + d[n] = x + p; + e[n - 1] = z; + e[n] = -z; + } + + n = n - 2; + iter = 0; + + // No convergence yet + } + else + { + // Form shift + x = matrixH[n, n]; + y = 0.0; + w = 0.0; + if (l < n) + { + y = matrixH[n - 1, n - 1]; + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + } + + // Wilkinson's original ad hoc shift + if (iter == 10) + { + exshift += x; + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= x; + } + + s = Math.Abs(matrixH[n, n - 1]) + Math.Abs(matrixH[n - 1, n - 2]); + x = y = 0.75 * s; + w = (-0.4375) * s * s; + } + + // MATLAB's new ad hoc shift + if (iter == 30) + { + s = (y - x) / 2.0; + s = (s * s) + w; + if (s > 0) + { + s = Math.Sqrt(s); + if (y < x) + { + s = -s; + } + + s = x - (w / (((y - x) / 2.0) + s)); + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + x = y = w = 0.964; + } + } + + iter = iter + 1; // (Could check iteration count here.) + + // Look for two consecutive small sub-diagonal elements + var m = n - 2; + while (m >= l) + { + z = matrixH[m, m]; + r = x - z; + s = y - z; + p = (((r * s) - w) / matrixH[m + 1, m]) + matrixH[m, m + 1]; + q = matrixH[m + 1, m + 1] - z - r - s; + r = matrixH[m + 2, m + 1]; + s = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + p = p / s; + q = q / s; + r = r / s; + + if (m == l) + { + break; + } + + if (Math.Abs(matrixH[m, m - 1]) * (Math.Abs(q) + Math.Abs(r)) < eps * (Math.Abs(p) * (Math.Abs(matrixH[m - 1, m - 1]) + Math.Abs(z) + Math.Abs(matrixH[m + 1, m + 1])))) + { + break; + } + + m--; + } + + for (var i = m + 2; i <= n; i++) + { + matrixH[i, i - 2] = 0.0; + if (i > m + 2) + { + matrixH[i, i - 3] = 0.0; + } + } + + // Double QR step involving rows l:n and columns m:n + for (var k = m; k <= n - 1; k++) + { + bool notlast = k != n - 1; + + if (k != m) + { + p = matrixH[k, k - 1]; + q = matrixH[k + 1, k - 1]; + r = notlast ? matrixH[k + 2, k - 1] : 0.0; + x = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + if (x != 0.0) + { + p = p / x; + q = q / x; + r = r / x; + } + } + + if (x == 0.0) + { + break; + } + + s = Math.Sqrt((p * p) + (q * q) + (r * r)); + if (p < 0) + { + s = -s; + } + + if (s != 0.0) + { + if (k != m) + { + matrixH[k, k - 1] = (-s) * x; + } + else if (l != m) + { + matrixH[k, k - 1] = -matrixH[k, k - 1]; + } + + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // Row modification + for (var j = k; j < order; j++) + { + p = matrixH[k, j] + (q * matrixH[k + 1, j]); + + if (notlast) + { + p = p + (r * matrixH[k + 2, j]); + matrixH[k + 2, j] = matrixH[k + 2, j] - (p * z); + } + + matrixH[k, j] = matrixH[k, j] - (p * x); + matrixH[k + 1, j] = matrixH[k + 1, j] - (p * y); + } + + // Column modification + for (var i = 0; i <= Math.Min(n, k + 3); i++) + { + p = (x * matrixH[i, k]) + (y * matrixH[i, k + 1]); + + if (notlast) + { + p = p + (z * matrixH[i, k + 2]); + matrixH[i, k + 2] = matrixH[i, k + 2] - (p * r); + } + + matrixH[i, k] = matrixH[i, k] - p; + matrixH[i, k + 1] = matrixH[i, k + 1] - (p * q); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + p = (x * eigenVectors.At(i, k)) + (y * eigenVectors.At(i, k + 1)); + + if (notlast) + { + p = p + (z * eigenVectors.At(i, k + 2)); + eigenVectors.At(i, k + 2, eigenVectors.At(i, k + 2) - (p * r)); + } + + eigenVectors.At(i, k, eigenVectors.At(i, k) - p); + eigenVectors.At(i, k + 1, eigenVectors.At(i, k + 1) - (p * q)); + } + } // (s != 0) + } // k loop + } // check convergence + } // while (n >= low) + + // Backsubstitute to find vectors of upper triangular form + if (norm == 0.0) + { + return; + } + + for (n = order - 1; n >= 0; n--) + { + double t; + + p = d[n]; + q = e[n]; + + // Real vector + if (q == 0.0) + { + var l = n; + matrixH[n, n] = 1.0; + for (var i = n - 1; i >= 0; i--) + { + w = matrixH[i, i] - p; + r = 0.0; + for (var j = l; j <= n; j++) + { + r = r + (matrixH[i, j] * matrixH[j, n]); + } + + if (e[i] < 0.0) + { + z = w; + s = r; + } + else + { + l = i; + if (e[i] == 0.0) + { + if (w != 0.0) + { + matrixH[i, n] = (-r) / w; + } + else + { + matrixH[i, n] = (-r) / (eps * norm); + } + + // Solve real equations + } + else + { + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + q = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]); + t = ((x * s) - (z * r)) / q; + matrixH[i, n] = t; + if (Math.Abs(x) > Math.Abs(z)) + { + matrixH[i + 1, n] = (-r - (w * t)) / x; + } + else + { + matrixH[i + 1, n] = (-s - (y * t)) / z; + } + } + + // Overflow control + t = Math.Abs(matrixH[i, n]); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + + // Complex vector + } + else if (q < 0) + { + var l = n - 1; + + // Last vector component imaginary so matrix is triangular + if (Math.Abs(matrixH[n, n - 1]) > Math.Abs(matrixH[n - 1, n])) + { + matrixH[n - 1, n - 1] = q / matrixH[n, n - 1]; + matrixH[n - 1, n] = (-(matrixH[n, n] - p)) / matrixH[n, n - 1]; + } + else + { + var res = Cdiv(0.0, -matrixH[n - 1, n], matrixH[n - 1, n - 1] - p, q); + matrixH[n - 1, n - 1] = res.Real; + matrixH[n - 1, n] = res.Imaginary; + } + + matrixH[n, n - 1] = 0.0; + matrixH[n, n] = 1.0; + for (var i = n - 2; i >= 0; i--) + { + double ra = 0.0; + double sa = 0.0; + for (var j = l; j <= n; j++) + { + ra = ra + (matrixH[i, j] * matrixH[j, n - 1]); + sa = sa + (matrixH[i, j] * matrixH[j, n]); + } + + w = matrixH[i, i] - p; + + if (e[i] < 0.0) + { + z = w; + r = ra; + s = sa; + } + else + { + l = i; + if (e[i] == 0.0) + { + var res = Cdiv(-ra, -sa, w, q); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + } + else + { + // Solve complex equations + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + + double vr = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]) - (q * q); + double vi = (d[i] - p) * 2.0 * q; + if ((vr == 0.0) && (vi == 0.0)) + { + vr = eps * norm * (Math.Abs(w) + Math.Abs(q) + Math.Abs(x) + Math.Abs(y) + Math.Abs(z)); + } + + var res = Cdiv((x * r) - (z * ra) + (q * sa), (x * s) - (z * sa) - (q * ra), vr, vi); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + if (Math.Abs(x) > (Math.Abs(z) + Math.Abs(q))) + { + matrixH[i + 1, n - 1] = (-ra - (w * matrixH[i, n - 1]) + (q * matrixH[i, n])) / x; + matrixH[i + 1, n] = (-sa - (w * matrixH[i, n]) - (q * matrixH[i, n - 1])) / x; + } + else + { + res = Cdiv(-r - (y * matrixH[i, n - 1]), -s - (y * matrixH[i, n]), z, q); + matrixH[i + 1, n - 1] = res.Real; + matrixH[i + 1, n] = res.Imaginary; + } + } + + // Overflow control + t = Math.Max(Math.Abs(matrixH[i, n - 1]), Math.Abs(matrixH[i, n])); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n - 1] = matrixH[j, n - 1] / t; + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j >= 0; j--) + { + for (var i = 0; i < order; i++) + { + z = 0.0; + for (var k = 0; k <= j; k++) + { + z = z + (eigenVectors.At(i, k) * matrixH[k, j]); + } + + eigenVectors.At(i, j, z); + } + } + } + + /// + /// Complex scalar division X/Y. + /// + /// Real part of X + /// Imaginary part of X + /// Real part of Y + /// Imaginary part of Y + /// Division result as a number. + static Complex Cdiv(double xreal, double ximag, double yreal, double yimag) + { + if (Math.Abs(yimag) < Math.Abs(yreal)) + { + return new Complex((xreal + (ximag * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal))), (ximag - (xreal * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal)))); + } + + return new Complex((ximag + (xreal * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag))), (-xreal + (ximag * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag)))); + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (EigenValues.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (EigenValues.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = EigenValues.Count; + var tmp = new double[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + double value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(i, j) * input.At(i, k); + } + + value /= EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + double value = 0; + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(j, i) * tmp[i]; + } + + result.At(j, k, value); + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (EigenValues.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (EigenValues.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VT * b; + var order = EigenValues.Count; + var tmp = new double[order]; + double value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(i, j) * input[i]; + } + + value /= EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (int i = 0; i < order; i++) + { + value += EigenVectors.At(j, i) * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserGramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserGramSchmidt.cs new file mode 100644 index 0000000..427efaf --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserGramSchmidt.cs @@ -0,0 +1,226 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal sealed class UserGramSchmidt : GramSchmidt +{ + /// + /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public static UserGramSchmidt Create(Matrix matrix) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var q = matrix.Clone(); + var r = Matrix.Build.SameAs(matrix, matrix.ColumnCount, matrix.ColumnCount, fullyMutable: true); + + for (var k = 0; k < q.ColumnCount; k++) + { + var norm = q.Column(k).L2Norm(); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r.At(k, k, norm); + for (var i = 0; i < q.RowCount; i++) + { + q.At(i, k, q.At(i, k) / norm); + } + + for (var j = k + 1; j < q.ColumnCount; j++) + { + var dot = q.Column(k).DotProduct(q.Column(j)); + r.At(k, j, dot); + for (var i = 0; i < q.RowCount; i++) + { + var value = q.At(i, j) - (q.At(i, k) * dot); + q.At(i, j, value); + } + } + } + + return new UserGramSchmidt(q, r); + } + + UserGramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (Q.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new double[Q.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < Q.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < Q.ColumnCount; i++) + { + double s = 0; + for (var k = 0; k < Q.RowCount; k++) + { + s += Q.At(k, i) * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = Q.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / FullR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * FullR.At(i, k))); + } + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (Q.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(Q, result); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new double[Q.RowCount]; + for (var k = 0; k < Q.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < Q.ColumnCount; i++) + { + double s = 0; + for (var k = 0; k < Q.RowCount; k++) + { + s += Q.At(k, i) * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = Q.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= FullR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * FullR.At(i, k); + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserLU.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserLU.cs new file mode 100644 index 0000000..b4fa78e --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserLU.cs @@ -0,0 +1,306 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal sealed class UserLU : LU +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public static UserLU Create(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var order = matrix.RowCount; + var factors = matrix.Clone(); + var pivots = new int[order]; + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + pivots[i] = i; + } + + var vectorLUcolj = new double[order]; + for (var j = 0; j < order; j++) + { + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vectorLUcolj[i] = factors.At(i, j); + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + var kmax = Math.Min(i, j); + var s = 0.0; + for (var k = 0; k < kmax; k++) + { + s += factors.At(i, k) * vectorLUcolj[k]; + } + + vectorLUcolj[i] -= s; + factors.At(i, j, vectorLUcolj[i]); + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (Math.Abs(vectorLUcolj[i]) > Math.Abs(vectorLUcolj[p])) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var temp = factors.At(p, k); + factors.At(p, k, factors.At(j, k)); + factors.At(j, k, temp); + } + + pivots[j] = p; + } + + // Compute multipliers. + if (j < order & factors.At(j, j) != 0.0) + { + for (var i = j + 1; i < order; i++) + { + factors.At(i, j, (factors.At(i, j) / factors.At(j, j))); + } + } + } + + return new UserLU(factors, pivots); + } + + UserLU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(p, j); + result.At(p, j, result.At(i, j)); + result.At(i, j, temp); + } + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + for (var j = 0; j < result.ColumnCount; j++) + { + result.At(k, j, (result.At(k, j) / Factors.At(k, k))); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + var temp = result[p]; + result[p] = result[i]; + result[i] = temp; + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + result[k] /= Factors.At(k, k); + for (var i = 0; i < k; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var order = Factors.RowCount; + var inverse = Matrix.Build.SameAs(Factors, order, order); + for (var i = 0; i < order; i++) + { + inverse.At(i, i, 1.0); + } + + return Solve(inverse); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserQR.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserQR.cs new file mode 100644 index 0000000..ea991f4 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserQR.cs @@ -0,0 +1,350 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// +internal sealed class UserQR : QR +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// The QR factorization method to use. + /// If is null. + public static UserQR Create(Matrix matrix, QRMethod method = QRMethod.Full) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + Matrix q; + Matrix r; + + var minmn = Math.Min(matrix.RowCount, matrix.ColumnCount); + var u = new double[minmn][]; + + if (method == QRMethod.Full) + { + r = matrix.Clone(); + q = Matrix.Build.SameAs(matrix, matrix.RowCount, matrix.RowCount, fullyMutable: true); + + for (var i = 0; i < matrix.RowCount; i++) + { + q.At(i, i, 1.0); + } + + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(r, i, i); + ComputeQR(u[i], r, i, matrix.RowCount, i + 1, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], q, i, matrix.RowCount, i, matrix.RowCount, Control.MaxDegreeOfParallelism); + } + } + else + { + q = matrix.Clone(); + + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(q, i, i); + ComputeQR(u[i], q, i, matrix.RowCount, i + 1, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + + r = q.SubMatrix(0, matrix.ColumnCount, 0, matrix.ColumnCount); + q.Clear(); + + for (var i = 0; i < matrix.ColumnCount; i++) + { + q.At(i, i, 1.0); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], q, i, matrix.RowCount, i, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + } + + return new UserQR(q, r, method); + } + + UserQR(Matrix q, Matrix rFull, QRMethod method) + : base(q, rFull, method) + { + } + + /// + /// Generate column from initial matrix to work array + /// + /// Initial matrix + /// The first row + /// Column index + /// Generated vector + static double[] GenerateColumn(Matrix a, int row, int column) + { + var ru = a.RowCount - row; + var u = new double[ru]; + + for (var i = row; i < a.RowCount; i++) + { + u[i - row] = a.At(i, row); + a.At(i, row, 0.0); + } + + var norm = u.Sum(t => t * t); + norm = Math.Sqrt(norm); + + if (row == a.RowCount - 1 || norm == 0) + { + a.At(row, column, -u[0]); + u[0] = Constants.Sqrt2; + return u; + } + + var scale = 1.0 / norm; + if (u[0] < 0.0) + { + scale *= -1.0; + } + + a.At(row, column, -1.0 / scale); + + for (var i = 0; i < ru; i++) + { + u[i] *= scale; + } + + u[0] += 1.0; + var s = Math.Sqrt(1.0 / u[0]); + + for (var i = 0; i < ru; i++) + { + u[i] *= s; + } + + return u; + } + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Q or R matrices + /// The first row + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(double[] u, Matrix a, int rowStart, int rowDim, int columnStart, int columnDim, int availableCores) + { + if (rowDim < rowStart || columnDim < columnStart) + { + return; + } + + var tmpColCount = columnDim - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(u, a, rowStart, rowDim, columnStart, tmpSplit, tmpCores), + () => ComputeQR(u, a, rowStart, rowDim, tmpSplit, columnDim, tmpCores)); + } + else + { + for (var j = columnStart; j < columnDim; j++) + { + var scale = 0.0; + for (var i = rowStart; i < rowDim; i++) + { + scale += u[i - rowStart] * a.At(i, j); + } + + for (var i = rowStart; i < rowDim; i++) + { + a.At(i, j, a.At(i, j) - (u[i - rowStart] * scale)); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (FullR.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (FullR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new double[FullR.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < FullR.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < FullR.RowCount; i++) + { + double s = 0; + for (var k = 0; k < FullR.RowCount; k++) + { + s += Q.At(k, i) * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = FullR.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / FullR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * FullR.At(i, k))); + } + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + for (var j = 0; j < inputCopy.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (FullR.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (FullR.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(FullR, result); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new double[FullR.RowCount]; + for (var k = 0; k < FullR.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < FullR.RowCount; i++) + { + double s = 0; + for (var k = 0; k < FullR.RowCount; k++) + { + s += Q.At(k, i) * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = FullR.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= FullR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * FullR.At(i, k); + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserSvd.cs b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserSvd.cs new file mode 100644 index 0000000..93c9902 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Factorization/UserSvd.cs @@ -0,0 +1,903 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD) for . +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal sealed class UserSvd : Svd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// + public static UserSvd Create(Matrix matrix, bool computeVectors) + { + var nm = Math.Min(matrix.RowCount + 1, matrix.ColumnCount); + var matrixCopy = matrix.Clone(); + + var s = Vector.Build.SameAs(matrixCopy, nm); + var u = Matrix.Build.SameAs(matrixCopy, matrixCopy.RowCount, matrixCopy.RowCount, fullyMutable: true); + var vt = Matrix.Build.SameAs(matrixCopy, matrixCopy.ColumnCount, matrixCopy.ColumnCount, fullyMutable: true); + + const int maxiter = 1000; + var e = new double[matrixCopy.ColumnCount]; + var work = new double[matrixCopy.RowCount]; + + int i, j; + int l, lp1; + double t; + + var ncu = matrixCopy.RowCount; + + // Reduce matrixCopy to bidiagonal form, storing the diagonal elements + // In s and the super-diagonal elements in e. + var nct = Math.Min(matrixCopy.RowCount - 1, matrixCopy.ColumnCount); + var nrt = Math.Max(0, Math.Min(matrixCopy.ColumnCount - 2, matrixCopy.RowCount)); + var lu = Math.Max(nct, nrt); + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and place the l-th diagonal in VectorS[l]. + var xnorm = Dnrm2Column(matrixCopy, matrixCopy.RowCount, l, l); + s[l] = xnorm; + if (s[l] != 0.0) + { + if (matrixCopy.At(l, l) != 0.0) + { + s[l] = Dsign(s[l], matrixCopy.At(l, l)); + } + + DscalColumn(matrixCopy, matrixCopy.RowCount, l, l, 1.0 / s[l]); + matrixCopy.At(l, l, (1.0 + matrixCopy.At(l, l))); + } + + s[l] = -s[l]; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (l < nct) + { + if (s[l] != 0.0) + { + // Apply the transformation. + t = -Ddot(matrixCopy, matrixCopy.RowCount, l, j, l) / matrixCopy.At(l, l); + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (t * matrixCopy.At(ii, l))); + } + } + } + + // Place the l-th row of matrixCopy into e for the + // Subsequent calculation of the row transformation. + e[j] = matrixCopy.At(l, j); + } + + if (computeVectors && l < nct) + { + // Place the transformation in u for subsequent back multiplication. + for (i = l; i < matrixCopy.RowCount; i++) + { + u.At(i, l, matrixCopy.At(i, l)); + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = Dnrm2Vector(e, lp1); + e[l] = enorm; + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = Dsign(e[l], e[lp1]); + } + + DscalVector(e, lp1, 1.0 / e[l]); + e[lp1] = 1.0 + e[lp1]; + } + + e[l] = -e[l]; + if (lp1 < matrixCopy.RowCount && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < matrixCopy.RowCount; i++) + { + work[i] = 0.0; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + work[ii] += e[j] * matrixCopy.At(ii, j); + } + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + var ww = -e[j] / e[lp1]; + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (ww * work[ii])); + } + } + } + + if (computeVectors) + { + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < matrixCopy.ColumnCount; i++) + { + vt.At(i, l, e[i]); + } + } + } + + // Set up the final bidiagonal matrixCopy or order m. + var m = Math.Min(matrixCopy.ColumnCount, matrixCopy.RowCount + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < matrixCopy.ColumnCount) + { + s[nctp1 - 1] = matrixCopy.At((nctp1 - 1), (nctp1 - 1)); + } + + if (matrixCopy.RowCount < m) + { + s[m - 1] = 0.0; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = matrixCopy.At((nrtp1 - 1), (m - 1)); + } + + e[m - 1] = 0.0; + + // If required, generate u. + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + u.At(i, j, 0.0); + } + + u.At(j, j, 1.0); + } + + for (l = nct - 1; l >= 0; l--) + { + if (s[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = -Ddot(u, matrixCopy.RowCount, l, j, l) / u.At(l, l); + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + u.At(ii, j, u.At(ii, j) + (t * u.At(ii, l))); + } + } + + DscalColumn(u, matrixCopy.RowCount, l, l, -1.0); + u.At(l, l, 1.0 + u.At(l, l)); + for (i = 0; i < l; i++) + { + u.At(i, l, 0.0); + } + } + else + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + u.At(i, l, 0.0); + } + + u.At(l, l, 1.0); + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = matrixCopy.ColumnCount - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + t = -Ddot(vt, matrixCopy.ColumnCount, l, j, lp1) / vt.At(lp1, l); + for (var ii = l; ii < matrixCopy.ColumnCount; ii++) + { + vt.At(ii, j, vt.At(ii, j) + (t * vt.At(ii, l))); + } + } + } + } + + for (i = 0; i < matrixCopy.ColumnCount; i++) + { + vt.At(i, l, 0.0); + } + + vt.At(l, l, 1.0); + } + } + + // Transform s and e so that they are double . + for (i = 0; i < m; i++) + { + double r; + if (s[i] != 0.0) + { + t = s[i]; + r = s[i] / t; + s[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + DscalColumn(u, matrixCopy.RowCount, i, 0, r); + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] != 0.0) + { + t = e[i]; + r = t / e[i]; + e[i] = t; + s[i + 1] = s[i + 1] * r; + if (computeVectors) + { + DscalColumn(vt, matrixCopy.ColumnCount, i + 1, 0, r); + } + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays. On + // completion the variables case and l are set as follows. + // Case = 1 if VectorS[m] and e[l-1] are negligible and l < m + // Case = 2 if VectorS[l] is negligible and l < m + // Case = 3 if e[l-1] is negligible, l < m, and VectorS[l, ..., VectorS[m] are not negligible (qr step). + // Case = 4 if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = Math.Abs(s[l]) + Math.Abs(s[l + 1]); + ztest = test + Math.Abs(e[l]); + if (ztest.AlmostEqualRelative(test, 15)) + { + e[l] = 0.0; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + Math.Abs(e[ls]); + } + + if (ls != l + 1) + { + test = test + Math.Abs(e[ls - 1]); + } + + ztest = test + Math.Abs(s[ls]); + if (ztest.AlmostEqualRelative(test, 15)) + { + s[ls] = 0.0; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + double f; + double sn; + double cs; + switch (kase) + { + // Deflate negligible VectorS[m]. + case 1: + f = e[m - 2]; + e[m - 2] = 0.0; + double t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = s[k]; + Drotg(ref t1, ref f, out cs, out sn); + s[k] = t1; + if (k != l) + { + f = -sn * e[k - 1]; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + Drot(vt, matrixCopy.ColumnCount, k, m - 1, cs, sn); + } + } + + break; + + // Split at negligible VectorS[l]. + case 2: + f = e[l - 1]; + e[l - 1] = 0.0; + for (k = l; k < m; k++) + { + t1 = s[k]; + Drotg(ref t1, ref f, out cs, out sn); + s[k] = t1; + f = -sn * e[k]; + e[k] = cs * e[k]; + if (computeVectors) + { + Drot(u, matrixCopy.RowCount, k, l - 1, cs, sn); + } + } + + break; + + // Perform one qr step. + case 3: + // Calculate the shift. + var scale = 0.0; + scale = Math.Max(scale, Math.Abs(s[m - 1])); + scale = Math.Max(scale, Math.Abs(s[m - 2])); + scale = Math.Max(scale, Math.Abs(e[m - 2])); + scale = Math.Max(scale, Math.Abs(s[l])); + scale = Math.Max(scale, Math.Abs(e[l])); + var sm = s[m - 1] / scale; + var smm1 = s[m - 2] / scale; + var emm1 = e[m - 2] / scale; + var sl = s[l] / scale; + var el = e[l] / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0; + if (b != 0.0 || c != 0.0) + { + shift = Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros. + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * s[k]) + (sn * e[k]); + e[k] = (cs * e[k]) - (sn * s[k]); + g = sn * s[k + 1]; + s[k + 1] = cs * s[k + 1]; + if (computeVectors) + { + Drot(vt, matrixCopy.ColumnCount, k, k + 1, cs, sn); + } + + Drotg(ref f, ref g, out cs, out sn); + s[k] = f; + f = (cs * e[k]) + (sn * s[k + 1]); + s[k + 1] = (-sn * e[k]) + (cs * s[k + 1]); + g = sn * e[k + 1]; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < matrixCopy.RowCount) + { + Drot(u, matrixCopy.RowCount, k, k + 1, cs, sn); + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence. + case 4: + // Make the singular value positive + if (s[l] < 0.0) + { + s[l] = -s[l]; + if (computeVectors) + { + DscalColumn(vt, matrixCopy.ColumnCount, l, 0, -1.0); + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (s[l] >= s[l + 1]) + { + break; + } + + t = s[l]; + s[l] = s[l + 1]; + s[l + 1] = t; + if (computeVectors && l < matrixCopy.ColumnCount) + { + Dswap(vt, matrixCopy.ColumnCount, l, l + 1); + } + + if (computeVectors && l < matrixCopy.RowCount) + { + Dswap(u, matrixCopy.RowCount, l, l + 1); + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + vt = vt.Transpose(); + } + + // Adjust the size of s if rows < columns. We are using ported copy of linpack's svd code and it uses + // a singular vector of length mRows+1 when mRows < mColumns. The last element is not used and needs to be removed. + // we should port lapack's svd routine to remove this problem. + if (matrixCopy.RowCount < matrixCopy.ColumnCount) + { + nm--; + var tmp = Vector.Build.SameAs(matrixCopy, nm); + for (i = 0; i < nm; i++) + { + tmp[i] = s[i]; + } + + s = tmp; + } + + return new UserSvd(s, u, vt, computeVectors); + } + + UserSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Calculates absolute value of multiplied on signum function of + /// + /// Double value z1 + /// Double value z2 + /// Result multiplication of signum function and absolute value + static double Dsign(double z1, double z2) + { + return Math.Abs(z1) * (z2 / Math.Abs(z2)); + } + + /// + /// Swap column and + /// + /// Source matrix + /// The number of rows in + /// Column A index to swap + /// Column B index to swap + static void Dswap(Matrix a, int rowCount, int columnA, int columnB) + { + for (var i = 0; i < rowCount; i++) + { + var z = a.At(i, columnA); + a.At(i, columnA, a.At(i, columnB)); + a.At(i, columnB, z); + } + } + + /// + /// Scale column by starting from row + /// + /// Source matrix + /// The number of rows in + /// Column to scale + /// Row to scale from + /// Scale value + static void DscalColumn(Matrix a, int rowCount, int column, int rowStart, double z) + { + for (var i = rowStart; i < rowCount; i++) + { + a.At(i, column, a.At(i, column) * z); + } + } + + /// + /// Scale vector by starting from index + /// + /// Source vector + /// Row to scale from + /// Scale value + static void DscalVector(double[] a, int start, double z) + { + for (var i = start; i < a.Length; i++) + { + a[i] = a[i] * z; + } + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + static void Drotg(ref double da, ref double db, out double c, out double s) + { + double r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0) + { + c = 1.0; + s = 0.0; + r = 0.0; + z = 0.0; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0) + { + z = 1.0 / c; + } + } + + da = r; + db = z; + } + + /// + /// Calculate Norm 2 of the column in matrix starting from row + /// + /// Source matrix + /// The number of rows in + /// Column index + /// Start row index + /// Norm2 (Euclidean norm) of the column + static double Dnrm2Column(Matrix a, int rowCount, int column, int rowStart) + { + double s = 0; + for (var i = rowStart; i < rowCount; i++) + { + s += a.At(i, column) * a.At(i, column); + } + + return Math.Sqrt(s); + } + + /// + /// Calculate Norm 2 of the vector starting from index + /// + /// Source vector + /// Start index + /// Norm2 (Euclidean norm) of the vector + static double Dnrm2Vector(double[] a, int rowStart) + { + double s = 0; + for (var i = rowStart; i < a.Length; i++) + { + s += a[i] * a[i]; + } + + return Math.Sqrt(s); + } + + /// + /// Calculate dot product of and + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Starting row index + /// Dot product value + static double Ddot(Matrix a, int rowCount, int columnA, int columnB, int rowStart) + { + var z = 0.0; + for (var i = rowStart; i < rowCount; i++) + { + z += a.At(i, columnB) * a.At(i, columnA); + } + + return z; + } + + /// + /// Performs rotation of points in the plane. Given two vectors x and y , + /// each vector element of these vectors is replaced as follows: x(i) = c*x(i) + s*y(i); y(i) = c*y(i) - s*x(i) + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Scalar "c" value + /// Scalar "s" value + static void Drot(Matrix a, int rowCount, int columnA, int columnB, double c, double s) + { + for (var i = 0; i < rowCount; i++) + { + var z = (c * a.At(i, columnA)) + (s * a.At(i, columnB)); + var tmp = (c * a.At(i, columnB)) - (s * a.At(i, columnA)); + a.At(i, columnB, tmp); + a.At(i, columnA, z); + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (U.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var mn = Math.Min(U.RowCount, VT.ColumnCount); + var bn = input.ColumnCount; + + var tmp = new double[VT.ColumnCount]; + + for (var k = 0; k < bn; k++) + { + for (var j = 0; j < VT.ColumnCount; j++) + { + double value = 0; + if (j < mn) + { + for (var i = 0; i < U.RowCount; i++) + { + value += U.At(i, j) * input.At(i, k); + } + + value /= S[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < VT.ColumnCount; j++) + { + double value = 0; + for (var i = 0; i < VT.ColumnCount; i++) + { + value += VT.At(i, j) * tmp[i]; + } + + result.At(j, k, value); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (U.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VT.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(VT, result); + } + + var mn = Math.Min(U.RowCount, VT.ColumnCount); + var tmp = new double[VT.ColumnCount]; + double value; + for (var j = 0; j < VT.ColumnCount; j++) + { + value = 0; + if (j < mn) + { + for (var i = 0; i < U.RowCount; i++) + { + value += U.At(i, j) * input[i]; + } + + value /= S[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < VT.ColumnCount; j++) + { + value = 0; + for (int i = 0; i < VT.ColumnCount; i++) + { + value += VT.At(i, j) * tmp[i]; + } + + result[j] = value; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Matrix.cs b/MathNet.Numerics/LinearAlgebra/Double/Matrix.cs new file mode 100644 index 0000000..28ac4d4 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Matrix.cs @@ -0,0 +1,814 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Double.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double; + +/// +/// double version of the class. +/// +[Serializable] +public abstract class Matrix : Matrix +{ + /// + /// Initializes a new instance of the Matrix class. + /// + protected Matrix(MatrixStorage storage) + : base(storage) + { + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero. + /// + public override void CoerceZero(double threshold) + { + MapInplace(x => Math.Abs(x) < threshold ? 0d : x, Zeros.AllowSkip); + } + + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public sealed override Matrix ConjugateTranspose() + { + return Transpose(); + } + + /// + /// Puts the conjugate transpose of this matrix into the result matrix. + /// + public sealed override void ConjugateTranspose(Matrix result) + { + Transpose(result); + } + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected sealed override void DoConjugate(Matrix result) + { + if (ReferenceEquals(this, result)) + { + return; + } + + CopyTo(result); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + Map(x => -x, result, Zeros.AllowSkip); + } + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected override void DoAdd(double scalar, Matrix result) + { + Map(x => x + scalar, result, Zeros.Include); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + Map2((x, y) => x + y, other, result, Zeros.AllowSkip); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(double scalar, Matrix result) + { + Map(x => x - scalar, result, Zeros.Include); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract to this matrix. + /// The matrix to store the result of subtraction. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + Map2((x, y) => x - y, other, result, Zeros.AllowSkip); + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(double scalar, Matrix result) + { + Map(x => x * scalar, result, Zeros.AllowSkip); + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + for (var i = 0; i < RowCount; i++) + { + var s = 0.0; + for (var j = 0; j < ColumnCount; j++) + { + s += At(i, j) * rightSide[j]; + } + + result[i] = s; + } + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(double divisor, Matrix result) + { + Map(x => x / divisor, result, divisor == 0.0 ? Zeros.Include : Zeros.AllowSkip); + } + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to divide by each element of the matrix. + /// The matrix to store the result of the division. + protected override void DoDivideByThis(double dividend, Matrix result) + { + Map(x => dividend / x, result, Zeros.Include); + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + for (var i = 0; i < RowCount; i++) + { + for (var j = 0; j < other.ColumnCount; j++) + { + var s = 0.0; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.RowCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + var s = 0.0; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(j, k); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected sealed override void DoConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.ColumnCount; j++) + { + for (var i = 0; i < ColumnCount; i++) + { + var s = 0.0; + for (var k = 0; k < RowCount; k++) + { + s += At(k, i) * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected sealed override void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + DoTransposeThisAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + for (var j = 0; j < ColumnCount; j++) + { + var s = 0.0; + for (var i = 0; i < RowCount; i++) + { + s += At(i, j) * rightSide[i]; + } + + result[j] = s; + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected sealed override void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + DoTransposeThisAndMultiply(rightSide, result); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoModulus(double divisor, Matrix result) + { + Map(x => Euclid.Modulus(x, divisor), result, Zeros.Include); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoModulusByThis(double dividend, Matrix result) + { + Map(x => Euclid.Modulus(dividend, x), result, Zeros.Include); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoRemainder(double divisor, Matrix result) + { + Map(x => Euclid.Remainder(x, divisor), result, Zeros.Include); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoRemainderByThis(double dividend, Matrix result) + { + Map(x => Euclid.Remainder(dividend, x), result, Zeros.Include); + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + Map2((x, y) => x * y, other, result, Zeros.AllowSkip); + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + Map2((x, y) => x / y, divisor, result, Zeros.Include); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The matrix to store the result of the pointwise power. + protected override void DoPointwisePower(double exponent, Matrix result) + { + Map(x => Math.Pow(x, exponent), result, exponent > 0.0 ? Zeros.AllowSkip : Zeros.Include); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Matrix exponent, Matrix result) + { + Map2(Math.Pow, result, Zeros.Include); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected override void DoPointwiseModulus(Matrix divisor, Matrix result) + { + Map2(Euclid.Modulus, divisor, result, Zeros.Include); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected override void DoPointwiseRemainder(Matrix divisor, Matrix result) + { + Map2(Euclid.Remainder, divisor, result, Zeros.Include); + } + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected override void DoPointwiseExp(Matrix result) + { + Map(Math.Exp, result, Zeros.Include); + } + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected override void DoPointwiseLog(Matrix result) + { + Map(Math.Log, result, Zeros.Include); + } + + protected override void DoPointwiseAbs(Matrix result) + { + Map(Math.Abs, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAcos(Matrix result) + { + Map(Math.Acos, result, Zeros.Include); + } + protected override void DoPointwiseAsin(Matrix result) + { + Map(Math.Asin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan(Matrix result) + { + Map(Math.Atan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan2(Matrix other, Matrix result) + { + Map2(Math.Atan2, other, result, Zeros.Include); + } + protected override void DoPointwiseCeiling(Matrix result) + { + Map(Math.Ceiling, result, Zeros.AllowSkip); + } + protected override void DoPointwiseCos(Matrix result) + { + Map(Math.Cos, result, Zeros.Include); + } + protected override void DoPointwiseCosh(Matrix result) + { + Map(Math.Cosh, result, Zeros.Include); + } + protected override void DoPointwiseFloor(Matrix result) + { + Map(Math.Floor, result, Zeros.AllowSkip); + } + protected override void DoPointwiseLog10(Matrix result) + { + Map(Math.Log10, result, Zeros.Include); + } + protected override void DoPointwiseRound(Matrix result) + { + Map(Math.Round, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSign(Matrix result) + { + Map(x => (double)Math.Sign(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSin(Matrix result) + { + Map(Math.Sin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSinh(Matrix result) + { + Map(Math.Sinh, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSqrt(Matrix result) + { + Map(Math.Sqrt, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTan(Matrix result) + { + Map(Math.Tan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTanh(Matrix result) + { + Map(Math.Tanh, result, Zeros.AllowSkip); + } + + /// + /// Computes the Moore-Penrose Pseudo-Inverse of this matrix. + /// + public override Matrix PseudoInverse() + { + var svd = Svd(true); + var w = svd.W; + var s = svd.S; + double tolerance = Math.Max(RowCount, ColumnCount) * svd.L2Norm * Precision.DoublePrecision; + + for (int i = 0; i < s.Count; i++) + { + s[i] = s[i] < tolerance ? 0 : 1 / s[i]; + } + + w.SetDiagonal(s); + return (svd.U * w * svd.VT).Transpose(); + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public override double Trace() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var sum = 0.0; + for (var i = 0; i < RowCount; i++) + { + sum += At(i, i); + } + + return sum; + } + + protected override void DoPointwiseMinimum(double scalar, Matrix result) + { + Map(x => Math.Min(scalar, x), result, scalar >= 0d ? Zeros.AllowSkip : Zeros.Include); + } + + protected override void DoPointwiseMaximum(double scalar, Matrix result) + { + Map(x => Math.Max(scalar, x), result, scalar <= 0d ? Zeros.AllowSkip : Zeros.Include); + } + + protected override void DoPointwiseAbsoluteMinimum(double scalar, Matrix result) + { + double absolute = Math.Abs(scalar); + Map(x => Math.Min(absolute, Math.Abs(x)), result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(double scalar, Matrix result) + { + double absolute = Math.Abs(scalar); + Map(x => Math.Max(absolute, Math.Abs(x)), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(Matrix other, Matrix result) + { + Map2(Math.Min, other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseMaximum(Matrix other, Matrix result) + { + Map2(Math.Max, other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMinimum(Matrix other, Matrix result) + { + Map2((x, y) => Math.Min(Math.Abs(x), Math.Abs(y)), other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Matrix other, Matrix result) + { + Map2((x, y) => Math.Max(Math.Abs(x), Math.Abs(y)), other, result, Zeros.AllowSkip); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + var norm = 0d; + for (var j = 0; j < ColumnCount; j++) + { + var s = 0d; + for (var i = 0; i < RowCount; i++) + { + s += Math.Abs(At(i, j)); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + var s = 0d; + for (var j = 0; j < ColumnCount; j++) + { + s += Math.Abs(At(i, j)); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + var transpose = Transpose(); + var aat = this * transpose; + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + norm += aat.At(i, i); + } + + return Math.Sqrt(norm); + } + + /// + /// Calculates the p-norms of all row vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public override Vector RowNorms(double norm) + { + if (norm <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(norm), Resources.ArgumentMustBePositive); + } + + var ret = new double[RowCount]; + if (norm == 2.0) + { + Storage.FoldByRowUnchecked(ret, (s, x) => s + x * x, (x, c) => Math.Sqrt(x), ret, Zeros.AllowSkip); + } + else if (norm == 1.0) + { + Storage.FoldByRowUnchecked(ret, (s, x) => s + Math.Abs(x), (x, c) => x, ret, Zeros.AllowSkip); + } + else if (double.IsPositiveInfinity(norm)) + { + Storage.FoldByRowUnchecked(ret, (s, x) => Math.Max(s, Math.Abs(x)), (x, c) => x, ret, Zeros.AllowSkip); + } + else + { + double invnorm = 1.0 / norm; + Storage.FoldByRowUnchecked(ret, (s, x) => s + Math.Pow(Math.Abs(x), norm), (x, c) => Math.Pow(x, invnorm), ret, Zeros.AllowSkip); + } + + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the p-norms of all column vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public override Vector ColumnNorms(double norm) + { + if (norm <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(norm), Resources.ArgumentMustBePositive); + } + + var ret = new double[ColumnCount]; + if (norm == 2.0) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x * x, (x, c) => Math.Sqrt(x), ret, Zeros.AllowSkip); + } + else if (norm == 1.0) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => s + Math.Abs(x), (x, c) => x, ret, Zeros.AllowSkip); + } + else if (double.IsPositiveInfinity(norm)) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => Math.Max(s, Math.Abs(x)), (x, c) => x, ret, Zeros.AllowSkip); + } + else + { + double invnorm = 1.0 / norm; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + Math.Pow(Math.Abs(x), norm), (x, c) => Math.Pow(x, invnorm), ret, Zeros.AllowSkip); + } + + return Vector.Build.Dense(ret); + } + + /// + /// Normalizes all row vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public sealed override Matrix NormalizeRows(double norm) + { + var norminv = ((DenseVectorStorage)RowNorms(norm).Storage).Data; + for (int i = 0; i < norminv.Length; i++) + { + norminv[i] = norminv[i] == 0d ? 1d : 1d / norminv[i]; + } + + var result = Build.SameAs(this, RowCount, ColumnCount); + Storage.MapIndexedTo(result.Storage, (i, j, x) => norminv[i] * x, Zeros.AllowSkip, ExistingData.AssumeZeros); + return result; + } + + /// + /// Normalizes all column vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public sealed override Matrix NormalizeColumns(double norm) + { + var norminv = ((DenseVectorStorage)ColumnNorms(norm).Storage).Data; + for (int i = 0; i < norminv.Length; i++) + { + norminv[i] = norminv[i] == 0d ? 1d : 1d / norminv[i]; + } + + var result = Build.SameAs(this, RowCount, ColumnCount); + Storage.MapIndexedTo(result.Storage, (i, j, x) => norminv[j] * x, Zeros.AllowSkip, ExistingData.AssumeZeros); + return result; + } + + /// + /// Calculates the value sum of each row vector. + /// + public override Vector RowSums() + { + var ret = new double[RowCount]; + Storage.FoldByRowUnchecked(ret, (s, x) => s + x, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the absolute value sum of each row vector. + /// + public override Vector RowAbsoluteSums() + { + var ret = new double[RowCount]; + Storage.FoldByRowUnchecked(ret, (s, x) => s + Math.Abs(x), (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the value sum of each column vector. + /// + public override Vector ColumnSums() + { + var ret = new double[ColumnCount]; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the absolute value sum of each column vector. + /// + public override Vector ColumnAbsoluteSums() + { + var ret = new double[ColumnCount]; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + Math.Abs(x), (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public sealed override bool IsHermitian() + { + return IsSymmetric(); + } + + public override Cholesky Cholesky() + { + return UserCholesky.Create(this); + } + + public override LU LU() + { + return UserLU.Create(this); + } + + public override QR QR(QRMethod method = QRMethod.Thin) + { + return UserQR.Create(this, method); + } + + public override GramSchmidt GramSchmidt() + { + return UserGramSchmidt.Create(this); + } + + public override Svd Svd(bool computeVectors = true) + { + return UserSvd.Create(this, computeVectors); + } + + public override Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown) + { + return UserEvd.Create(this, symmetricity); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/BiCgStab.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/BiCgStab.cs new file mode 100644 index 0000000..cb1e9d6 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/BiCgStab.cs @@ -0,0 +1,273 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// A Bi-Conjugate Gradient stabilized iterative matrix solver. +/// +/// +/// +/// The Bi-Conjugate Gradient Stabilized (BiCGStab) solver is an 'improvement' +/// of the standard Conjugate Gradient (CG) solver. Unlike the CG solver the +/// BiCGStab can be used on non-symmetric matrices.
+/// Note that much of the success of the solver depends on the selection of the +/// proper preconditioner. +///
+/// +/// The Bi-CGSTAB algorithm was taken from:
+/// Templates for the solution of linear systems: Building blocks +/// for iterative methods +///
+/// Richard Barrett, Michael Berry, Tony F. Chan, James Demmel, +/// June M. Donato, Jack Dongarra, Victor Eijkhout, Roldan Pozo, +/// Charles Romine and Henk van der Vorst +///
+/// Url: http://www.netlib.org/templates/Templates.html +///
+/// Algorithm is described in Chapter 2, section 2.3.8, page 27 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class BiCgStab : IIterativeSolver +{ + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + + // Do not use residual = residual.Negate() because it creates another object + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient , A. + /// The solution , b. + /// The result , x. + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // Compute r_0 = b - Ax_0 for some initial guess x_0 + // In this case we take x_0 = vector + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, result, input); + + // Choose r~ (for example, r~ = r_0) + var tempResiduals = residuals.Clone(); + + // create seven temporary vectors needed to hold temporary + // coefficients. All vectors are mangled in each iteration. + // These are defined here to prevent stressing the garbage collector + var vecP = new DenseVector(residuals.Count); + var vecPdash = new DenseVector(residuals.Count); + var nu = new DenseVector(residuals.Count); + var vecS = new DenseVector(residuals.Count); + var vecSdash = new DenseVector(residuals.Count); + var temp = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + + // create some temporary double variables that are needed + // to hold values in between iterations + double currentRho = 0; + double alpha = 0; + double omega = 0; + + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, result, input, residuals) == IterationStatus.Continue) + { + // rho_(i-1) = r~^T r_(i-1) // dotproduct r~ and r_(i-1) + var oldRho = currentRho; + currentRho = tempResiduals.DotProduct(residuals); + + // if (rho_(i-1) == 0) // METHOD FAILS + // If rho is only 1 ULP from zero then we fail. + if (currentRho.AlmostEqualNumbersBetween(0, 1)) + { + // Rho-type breakdown + throw new NumericalBreakdownException(); + } + + if (iterationNumber != 0) + { + // beta_(i-1) = (rho_(i-1)/rho_(i-2))(alpha_(i-1)/omega(i-1)) + var beta = (currentRho / oldRho) * (alpha / omega); + + // p_i = r_(i-1) + beta_(i-1)(p_(i-1) - omega_(i-1) * nu_(i-1)) + nu.Multiply(-omega, temp); + vecP.Add(temp, temp2); + temp2.CopyTo(vecP); + + vecP.Multiply(beta, vecP); + vecP.Add(residuals, temp2); + temp2.CopyTo(vecP); + } + else + { + // p_i = r_(i-1) + residuals.CopyTo(vecP); + } + + // SOLVE Mp~ = p_i // M = preconditioner + preconditioner.Approximate(vecP, vecPdash); + + // nu_i = Ap~ + matrix.Multiply(vecPdash, nu); + + // alpha_i = rho_(i-1)/ (r~^T nu_i) = rho / dotproduct(r~ and nu_i) + alpha = currentRho * 1 / tempResiduals.DotProduct(nu); + + // s = r_(i-1) - alpha_i nu_i + nu.Multiply(-alpha, temp); + residuals.Add(temp, vecS); + + // Check if we're converged. If so then stop. Otherwise continue; + // Calculate the temporary result. + // Be careful not to change any of the temp vectors, except for + // temp. Others will be used in the calculation later on. + // x_i = x_(i-1) + alpha_i * p^_i + s^_i + vecPdash.Multiply(alpha, temp); + temp.Add(vecSdash, temp2); + temp2.CopyTo(temp); + temp.Add(result, temp2); + temp2.CopyTo(temp); + + // Check convergence and stop if we are converged. + if (iterator.DetermineStatus(iterationNumber, temp, input, vecS) != IterationStatus.Continue) + { + temp.CopyTo(result); + + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, result, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // We're all good now. + return; + } + + // Continue the calculation + iterationNumber++; + continue; + } + + // SOLVE Ms~ = s + preconditioner.Approximate(vecS, vecSdash); + + // temp = As~ + matrix.Multiply(vecSdash, temp); + + // omega_i = temp^T s / temp^T temp + omega = temp.DotProduct(vecS) / temp.DotProduct(temp); + + // x_i = x_(i-1) + alpha_i p^ + omega_i s^ + temp.Multiply(-omega, residuals); + residuals.Add(vecS, temp2); + temp2.CopyTo(residuals); + + vecSdash.Multiply(omega, temp); + result.Add(temp, temp2); + temp2.CopyTo(result); + + vecPdash.Multiply(alpha, temp); + result.Add(temp, temp2); + temp2.CopyTo(result); + + // for continuation it is necessary that omega_i != 0.0 + // If omega is only 1 ULP from zero then we fail. + if (omega.AlmostEqualNumbersBetween(0, 1)) + { + // Omega-type breakdown + throw new NumericalBreakdownException(); + } + + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + // The residual calculation based on omega_i * s can be off by a factor 10. So here + // we calculate the real residual (which can be expensive) but we only do it if we're + // sufficiently close to the finish. + CalculateTrueResidual(matrix, residuals, result, input); + } + + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/CompositeSolver.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/CompositeSolver.cs new file mode 100644 index 0000000..016a850 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/CompositeSolver.cs @@ -0,0 +1,152 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// A composite matrix solver. The actual solver is made by a sequence of +/// matrix solvers. +/// +/// +/// +/// Solver based on:
+/// Faster PDE-based simulations using robust composite linear solvers
+/// S. Bhowmicka, P. Raghavan a,*, L. McInnes b, B. Norris
+/// Future Generation Computer Systems, Vol 20, 2004, pp 373387
+///
+/// +/// Note that if an iterator is passed to this solver it will be used for all the sub-solvers. +/// +///
+public sealed class CompositeSolver : IIterativeSolver +{ + /// + /// The collection of solvers that will be used + /// + readonly List, IPreconditioner>> _solvers; + + public CompositeSolver(IEnumerable> solvers) + { + _solvers = solvers.Select(setup => new Tuple, IPreconditioner>(setup.CreateSolver(), setup.CreatePreconditioner() ?? new UnitPreconditioner())).ToList(); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + // Create a copy of the solution and result vectors so we can use them + // later on + var internalInput = input.Clone(); + var internalResult = result.Clone(); + + foreach (var solver in _solvers) + { + // Store a reference to the solver so we can stop it. + + IterationStatus status; + try + { + // Reset the iterator and pass it to the solver + iterator.Reset(); + + // Start the solver + solver.Item1.Solve(matrix, internalInput, internalResult, iterator, solver.Item2 ?? preconditioner); + status = iterator.Status; + } + catch (Exception) + { + // The solver broke down. + // Log a message about this + // Switch to the next preconditioner. + // Reset the solution vector to the previous solution + input.CopyTo(internalInput); + continue; + } + + // There was no fatal breakdown so check the status + if (status == IterationStatus.Converged) + { + // We're done + internalResult.CopyTo(result); + break; + } + + // We're not done + // Either: + // - calculation finished without convergence + if (status == IterationStatus.StoppedWithoutConvergence) + { + // Copy the internal result to the result vector and + // continue with the calculation. + internalResult.CopyTo(result); + } + else + { + // - calculation failed --> restart with the original vector + // - calculation diverged --> restart with the original vector + // - Some unknown status occurred --> To be safe restart. + input.CopyTo(internalInput); + } + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/DiagonalPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/DiagonalPreconditioner.cs new file mode 100644 index 0000000..bd39361 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/DiagonalPreconditioner.cs @@ -0,0 +1,106 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// A diagonal preconditioner. The preconditioner uses the inverse +/// of the matrix diagonal as preconditioning values. +/// +public sealed class DiagonalPreconditioner : IPreconditioner +{ + /// + /// The inverse of the matrix diagonal. + /// + double[] _inverseDiagonals; + + /// + /// Returns the decomposed matrix diagonal. + /// + /// The matrix diagonal. + internal DiagonalMatrix DiagonalEntries() + { + var result = new DiagonalMatrix(_inverseDiagonals.Length); + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + result[i, i] = 1 / _inverseDiagonals[i]; + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _inverseDiagonals = new double[matrix.RowCount]; + for (var i = 0; i < matrix.RowCount; i++) + { + _inverseDiagonals[i] = 1 / matrix[i, i]; + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_inverseDiagonals == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _inverseDiagonals.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rhs)); + } + + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + lhs[i] = rhs[i] * _inverseDiagonals[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/GpBiCg.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/GpBiCg.cs new file mode 100644 index 0000000..e650176 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/GpBiCg.cs @@ -0,0 +1,383 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// A Generalized Product Bi-Conjugate Gradient iterative matrix solver. +/// +/// +/// +/// The Generalized Product Bi-Conjugate Gradient (GPBiCG) solver is an +/// alternative version of the Bi-Conjugate Gradient stabilized (CG) solver. +/// Unlike the CG solver the GPBiCG solver can be used on +/// non-symmetric matrices.
+/// Note that much of the success of the solver depends on the selection of the +/// proper preconditioner. +///
+/// +/// The GPBiCG algorithm was taken from:
+/// GPBiCG(m,l): A hybrid of BiCGSTAB and GPBiCG methods with +/// efficiency and robustness +///
+/// S. Fujino +///
+/// Applied Numerical Mathematics, Volume 41, 2002, pp 107 - 117 +///
+///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class GpBiCg : IIterativeSolver +{ + /// + /// Indicates the number of BiCGStab steps should be taken + /// before switching. + /// + int _numberOfBiCgStabSteps = 1; + + /// + /// Indicates the number of GPBiCG steps should be taken + /// before switching. + /// + int _numberOfGpbiCgSteps = 4; + + /// + /// Gets or sets the number of steps taken with the BiCgStab algorithm + /// before switching over to the GPBiCG algorithm. + /// + public int NumberOfBiCgStabSteps + { + get + { + return _numberOfBiCgStabSteps; + } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfBiCgStabSteps = value; + } + } + + /// + /// Gets or sets the number of steps taken with the GPBiCG algorithm + /// before switching over to the BiCgStab algorithm. + /// + public int NumberOfGpBiCgSteps + { + get + { + return _numberOfGpbiCgSteps; + } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfGpbiCgSteps = value; + } + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Decide if to do steps with BiCgStab + /// + /// Number of iteration + /// true if yes, otherwise false + bool ShouldRunBiCgStabSteps(int iterationNumber) + { + // Run the first steps as BiCGStab + // The number of steps past a whole iteration set + var difference = iterationNumber % (_numberOfBiCgStabSteps + _numberOfGpbiCgSteps); + + // Do steps with BiCGStab if: + // - The difference is zero or more (i.e. we have done zero or more complete cycles) + // - The difference is less than the number of BiCGStab steps that should be taken + return (difference >= 0) && (difference < _numberOfBiCgStabSteps); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // x_0 is initial guess + // Take x_0 = 0 + var xtemp = new DenseVector(input.Count); + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary scalars + double beta = 0; + + // Define the temporary vectors + // rDash_0 = r_0 + var rdash = DenseVector.OfVector(residuals); + + // t_-1 = 0 + var t = new DenseVector(residuals.Count); + var t0 = new DenseVector(residuals.Count); + + // w_-1 = 0 + var w = new DenseVector(residuals.Count); + + // Define the remaining temporary vectors + var c = new DenseVector(residuals.Count); + var p = new DenseVector(residuals.Count); + var s = new DenseVector(residuals.Count); + var u = new DenseVector(residuals.Count); + var y = new DenseVector(residuals.Count); + var z = new DenseVector(residuals.Count); + + var temp = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + var temp3 = new DenseVector(residuals.Count); + + // for (k = 0, 1, .... ) + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue) + { + // p_k = r_k + beta_(k-1) * (p_(k-1) - u_(k-1)) + p.Subtract(u, temp); + + temp.Multiply(beta, temp2); + residuals.Add(temp2, p); + + // Solve M b_k = p_k + preconditioner.Approximate(p, temp); + + // s_k = A b_k + matrix.Multiply(temp, s); + + // alpha_k = (r*_0 * r_k) / (r*_0 * s_k) + var alpha = rdash.DotProduct(residuals) / rdash.DotProduct(s); + + // y_k = t_(k-1) - r_k - alpha_k * w_(k-1) + alpha_k s_k + s.Subtract(w, temp); + t.Subtract(residuals, y); + + temp.Multiply(alpha, temp2); + y.Add(temp2, temp3); + temp3.CopyTo(y); + + // Store the old value of t in t0 + t.CopyTo(t0); + + // t_k = r_k - alpha_k s_k + s.Multiply(-alpha, temp2); + residuals.Add(temp2, t); + + // Solve M d_k = t_k + preconditioner.Approximate(t, temp); + + // c_k = A d_k + matrix.Multiply(temp, c); + var cdot = c.DotProduct(c); + + // cDot can only be zero if c is a zero vector + // We'll set cDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // c.DotProduct(t) will be zero and so will c.DotProduct(y) + if (cdot.AlmostEqualNumbersBetween(0, 1)) + { + cdot = 1.0; + } + + // Even if we don't want to do any BiCGStab steps we'll still have + // to do at least one at the start to initialize the + // system, but we'll only have to take special measures + // if we don't do any so ... + var ctdot = c.DotProduct(t); + double eta; + double sigma; + if (((_numberOfBiCgStabSteps == 0) && (iterationNumber == 0)) || ShouldRunBiCgStabSteps(iterationNumber)) + { + // sigma_k = (c_k * t_k) / (c_k * c_k) + sigma = ctdot / cdot; + + // eta_k = 0 + eta = 0; + } + else + { + var ydot = y.DotProduct(y); + + // yDot can only be zero if y is a zero vector + // We'll set yDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // y.DotProduct(t) will be zero and so will c.DotProduct(y) + if (ydot.AlmostEqualNumbersBetween(0, 1)) + { + ydot = 1.0; + } + + var ytdot = y.DotProduct(t); + var cydot = c.DotProduct(y); + + var denom = (cdot * ydot) - (cydot * cydot); + + // sigma_k = ((y_k * y_k)(c_k * t_k) - (y_k * t_k)(c_k * y_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + sigma = ((ydot * ctdot) - (ytdot * cydot)) / denom; + + // eta_k = ((c_k * c_k)(y_k * t_k) - (y_k * c_k)(c_k * t_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + eta = ((cdot * ytdot) - (cydot * ctdot)) / denom; + } + + // u_k = sigma_k s_k + eta_k (t_(k-1) - r_k + beta_(k-1) u_(k-1)) + u.Multiply(beta, temp2); + t0.Add(temp2, temp); + + temp.Subtract(residuals, temp3); + temp3.CopyTo(temp); + temp.Multiply(eta, temp); + + s.Multiply(sigma, temp2); + temp.Add(temp2, u); + + // z_k = sigma_k r_k +_ eta_k z_(k-1) - alpha_k u_k + z.Multiply(eta, z); + u.Multiply(-alpha, temp2); + z.Add(temp2, temp3); + temp3.CopyTo(z); + + residuals.Multiply(sigma, temp2); + z.Add(temp2, temp3); + temp3.CopyTo(z); + + // x_(k+1) = x_k + alpha_k p_k + z_k + p.Multiply(alpha, temp2); + xtemp.Add(temp2, temp3); + temp3.CopyTo(xtemp); + + xtemp.Add(z, temp3); + temp3.CopyTo(xtemp); + + // r_(k+1) = t_k - eta_k y_k - sigma_k c_k + // Copy the old residuals to a temp vector because we'll + // need those in the next step + residuals.CopyTo(t0); + + y.Multiply(-eta, temp2); + t.Add(temp2, residuals); + + c.Multiply(-sigma, temp2); + residuals.Add(temp2, temp3); + temp3.CopyTo(residuals); + + // beta_k = alpha_k / sigma_k * (r*_0 * r_(k+1)) / (r*_0 * r_k) + // But first we check if there is a possible NaN. If so just reset beta to zero. + beta = (!sigma.AlmostEqualNumbersBetween(0, 1)) ? alpha / sigma * rdash.DotProduct(residuals) / rdash.DotProduct(t0) : 0; + + // w_k = c_k + beta_k s_k + s.Multiply(beta, temp2); + c.Add(temp2, w); + + // Get the real value + preconditioner.Approximate(xtemp, result); + + // Now check for convergence + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, result, input); + } + + // Next iteration. + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/ILU0Preconditioner.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/ILU0Preconditioner.cs new file mode 100644 index 0000000..eb3d1fa --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/ILU0Preconditioner.cs @@ -0,0 +1,221 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// An incomplete, level 0, LU factorization preconditioner. +/// +/// +/// The ILU(0) algorithm was taken from:
+/// Iterative methods for sparse linear systems
+/// Yousef Saad
+/// Algorithm is described in Chapter 10, section 10.3.2, page 275
+///
+public sealed class ILU0Preconditioner : IPreconditioner +{ + /// + /// The matrix holding the lower (L) and upper (U) matrices. The + /// decomposition matrices are combined to reduce storage. + /// + SparseMatrix _decompositionLU; + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = i; j < _decompositionLU.ColumnCount; j++) + { + result[i, j] = _decompositionLU[i, j]; + } + } + + return result; + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = 0; j <= i; j++) + { + if (i == j) + { + result[i, j] = 1.0; + } + else + { + result[i, j] = _decompositionLU[i, j]; + } + } + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _decompositionLU = SparseMatrix.OfMatrix(matrix); + + // M == A + // for i = 2, ... , n do + // for k = 1, .... , i - 1 do + // if (i,k) == NZ(Z) then + // compute z(i,k) = z(i,k) / z(k,k); + // for j = k + 1, ...., n do + // if (i,j) == NZ(Z) then + // compute z(i,j) = z(i,j) - z(i,k) * z(k,j) + // end + // end + // end + // end + // end + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var k = 0; k < i; k++) + { + if (_decompositionLU[i, k] != 0.0) + { + var t = _decompositionLU[i, k] / _decompositionLU[k, k]; + _decompositionLU[i, k] = t; + if (_decompositionLU[k, i] != 0.0) + { + _decompositionLU[i, i] = _decompositionLU[i, i] - (t * _decompositionLU[k, i]); + } + + for (var j = k + 1; j < _decompositionLU.RowCount; j++) + { + if (j == i) + { + continue; + } + + if (_decompositionLU[i, j] != 0.0) + { + _decompositionLU[i, j] = _decompositionLU[i, j] - (t * _decompositionLU[k, j]); + } + } + } + } + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_decompositionLU == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _decompositionLU.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Solve: + // Lz = y + // Which gives + // for (int i = 1; i < matrix.RowLength; i++) + // { + // z_i = l_ii^-1 * (y_i - SUM_(j -1; i--) + // { + // x_i = u_ii^-1 * (z_i - SUM_(j > i) u_ij * x_j) + // } + for (var i = _decompositionLU.RowCount - 1; i > -1; i--) + { + _decompositionLU.Row(i, rowValues); + + var sum = 0.0; + for (var j = _decompositionLU.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/ILUTPPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/ILUTPPreconditioner.cs new file mode 100644 index 0000000..af4aa92 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/ILUTPPreconditioner.cs @@ -0,0 +1,869 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// This class performs an Incomplete LU factorization with drop tolerance +/// and partial pivoting. The drop tolerance indicates which additional entries +/// will be dropped from the factorized LU matrices. +/// +/// +/// The ILUTP-Mem algorithm was taken from:
+/// ILUTP_Mem: a Space-Efficient Incomplete LU Preconditioner +///
+/// Tzu-Yi Chen, Department of Mathematics and Computer Science,
+/// Pomona College, Claremont CA 91711, USA
+/// Published in:
+/// Lecture Notes in Computer Science
+/// Volume 3046 / 2004
+/// pp. 20 - 28
+/// Algorithm is described in Section 2, page 22 +///
+public sealed class ILUTPPreconditioner : IPreconditioner +{ + /// + /// The default fill level. + /// + public const double DefaultFillLevel = 200.0; + + /// + /// The default drop tolerance. + /// + public const double DefaultDropTolerance = 0.0001; + + /// + /// The decomposed upper triangular matrix. + /// + SparseMatrix _upper; + + /// + /// The decomposed lower triangular matrix. + /// + SparseMatrix _lower; + + /// + /// The array containing the pivot values. + /// + int[] _pivots; + + /// + /// The fill level. + /// + double _fillLevel = DefaultFillLevel; + + /// + /// The drop tolerance. + /// + double _dropTolerance = DefaultDropTolerance; + + /// + /// The pivot tolerance. + /// + double _pivotTolerance; + + /// + /// Initializes a new instance of the class with the default settings. + /// + public ILUTPPreconditioner() + { + } + + /// + /// Initializes a new instance of the class with the specified settings. + /// + /// + /// The amount of fill that is allowed in the matrix. The value is a fraction of + /// the number of non-zero entries in the original matrix. Values should be positive. + /// + /// + /// The absolute drop tolerance which indicates below what absolute value an entry + /// will be dropped from the matrix. A drop tolerance of 0.0 means that no values + /// will be dropped. Values should always be positive. + /// + /// + /// The pivot tolerance which indicates at what level pivoting will take place. A + /// value of 0.0 means that no pivoting will take place. + /// + public ILUTPPreconditioner(double fillLevel, double dropTolerance, double pivotTolerance) + { + if (fillLevel < 0) + { + throw new ArgumentOutOfRangeException(nameof(fillLevel)); + } + + if (dropTolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(dropTolerance)); + } + + if (pivotTolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(pivotTolerance)); + } + + _fillLevel = fillLevel; + _dropTolerance = dropTolerance; + _pivotTolerance = pivotTolerance; + } + + /// + /// Gets or sets the amount of fill that is allowed in the matrix. The + /// value is a fraction of the number of non-zero entries in the original + /// matrix. The standard value is 200. + /// + /// + /// + /// Values should always be positive and can be higher than 1.0. A value lower + /// than 1.0 means that the eventual preconditioner matrix will have fewer + /// non-zero entries as the original matrix. A value higher than 1.0 means that + /// the eventual preconditioner can have more non-zero values than the original + /// matrix. + /// + /// + /// Note that any changes to the FillLevel after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double FillLevel + { + get { return _fillLevel; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _fillLevel = value; + } + } + + /// + /// Gets or sets the absolute drop tolerance which indicates below what absolute value + /// an entry will be dropped from the matrix. The standard value is 0.0001. + /// + /// + /// + /// The values should always be positive and can be larger than 1.0. A low value will + /// keep more small numbers in the preconditioner matrix. A high value will remove + /// more small numbers from the preconditioner matrix. + /// + /// + /// Note that any changes to the DropTolerance after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double DropTolerance + { + get { return _dropTolerance; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _dropTolerance = value; + } + } + + /// + /// Gets or sets the pivot tolerance which indicates at what level pivoting will + /// take place. The standard value is 0.0 which means pivoting will never take place. + /// + /// + /// + /// The pivot tolerance is used to calculate if pivoting is necessary. Pivoting + /// will take place if any of the values in a row is bigger than the + /// diagonal value of that row divided by the pivot tolerance, i.e. pivoting + /// will take place if row(i,j) > row(i,i) / PivotTolerance for + /// any j that is not equal to i. + /// + /// + /// Note that any changes to the PivotTolerance after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double PivotTolerance + { + get { return _pivotTolerance; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _pivotTolerance = value; + } + } + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + return _upper.Clone(); + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + return _lower.Clone(); + } + + /// + /// Returns the pivot array. This array is not needed for normal use because + /// the preconditioner will return the solution vector values in the proper order. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// The pivot array. + internal int[] Pivots() + { + var result = new int[_pivots.Length]; + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = _pivots[i]; + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. Note that the + /// method takes a general matrix type. However internally the data is stored + /// as a sparse matrix. Therefore it is not recommended to pass a dense matrix. + /// + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + var sparseMatrix = matrix as SparseMatrix ?? SparseMatrix.OfMatrix(matrix); + + // The creation of the preconditioner follows the following algorithm. + // spaceLeft = lfilNnz * nnz(A) + // for i = 1, .. , n + // { + // w = a(i,*) + // for j = 1, .. , i - 1 + // { + // if (w(j) != 0) + // { + // w(j) = w(j) / a(j,j) + // if (w(j) < dropTol) + // { + // w(j) = 0; + // } + // if (w(j) != 0) + // { + // w = w - w(j) * U(j,*) + // } + // } + // } + // + // for j = i, .. ,n + // { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + // } + // + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + // lfil = spaceRow / 2 // space for this row of L + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + // + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + // u(i,j) = w(j) for j = i, .. , n // only the largest lfil - 1 elements + // w = 0 + // + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + // } + // Create the lower triangular matrix + _lower = new SparseMatrix(sparseMatrix.RowCount); + + // Create the upper triangular matrix and copy the values + _upper = new SparseMatrix(sparseMatrix.RowCount); + + // Create the pivot array + _pivots = new int[sparseMatrix.RowCount]; + for (var i = 0; i < _pivots.Length; i++) + { + _pivots[i] = i; + } + + var workVector = new DenseVector(sparseMatrix.RowCount); + var rowVector = new DenseVector(sparseMatrix.ColumnCount); + var indexSorting = new int[sparseMatrix.RowCount]; + + // spaceLeft = lfilNnz * nnz(A) + var spaceLeft = (int)_fillLevel * sparseMatrix.NonZerosCount; + + // for i = 1, .. , n + for (var i = 0; i < sparseMatrix.RowCount; i++) + { + // w = a(i,*) + sparseMatrix.Row(i, workVector); + + // pivot the row + PivotRow(workVector); + var vectorNorm = workVector.InfinityNorm(); + + // for j = 1, .. , i - 1) + for (var j = 0; j < i; j++) + { + // if (w(j) != 0) + // { + // w(j) = w(j) / a(j,j) + // if (w(j) < dropTol) + // { + // w(j) = 0; + // } + // if (w(j) != 0) + // { + // w = w - w(j) * U(j,*) + // } + if (workVector[j] != 0.0) + { + // Calculate the multiplication factors that go into the L matrix + workVector[j] = workVector[j] / _upper[j, j]; + if (Math.Abs(workVector[j]) < _dropTolerance) + { + workVector[j] = 0.0; + } + + // Calculate the addition factor + if (workVector[j] != 0.0) + { + // vector update all in one go + _upper.Row(j, rowVector); + + // zero out columnVector[k] because we don't need that + // one anymore for k = 0 to k = j + for (var k = 0; k <= j; k++) + { + rowVector[k] = 0.0; + } + + rowVector.Multiply(workVector[j], rowVector); + workVector.Subtract(rowVector, workVector); + } + } + } + + // for j = i, .. ,n + for (var j = i; j < sparseMatrix.RowCount; j++) + { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + if (Math.Abs(workVector[j]) <= _dropTolerance * vectorNorm) + { + workVector[j] = 0.0; + } + } + + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + var spaceRow = spaceLeft / (sparseMatrix.RowCount - i + 1); + + // lfil = spaceRow / 2 // space for this row of L + var fillLevel = spaceRow / 2; + FindLargestItems(0, i - 1, indexSorting, workVector); + + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + var lowerNonZeroCount = 0; + var count = 0; + for (var j = 0; j < i; j++) + { + if ((count > fillLevel) || (indexSorting[j] == -1)) + { + break; + } + + _lower[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + lowerNonZeroCount += 1; + } + + FindLargestItems(i + 1, sparseMatrix.RowCount - 1, indexSorting, workVector); + + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + fillLevel = spaceRow - lowerNonZeroCount; + + // u(i,j) = w(j) for j = i + 1, .. , n // only the largest lfil - 1 elements + var upperNonZeroCount = 0; + count = 0; + for (var j = 0; j < sparseMatrix.RowCount - i; j++) + { + if ((count > fillLevel - 1) || (indexSorting[j] == -1)) + { + break; + } + + _upper[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + upperNonZeroCount += 1; + } + + // Simply copy the diagonal element. Next step is to see if we pivot + _upper[i, i] = workVector[i]; + + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + + // Check if we really need to pivot. If (i+1) >=(mCoefficientMatrix.Rows -1) then + // we are working on the last row. That means that there is only one number + // And pivoting is useless. Also the indexSorting array will only contain + // -1 values. + if ((i + 1) < (sparseMatrix.RowCount - 1)) + { + if (Math.Abs(workVector[i]) < _pivotTolerance * Math.Abs(workVector[indexSorting[0]])) + { + // swap columns of u (which holds the values of A in the + // sections that haven't been partitioned yet. + SwapColumns(_upper, i, indexSorting[0]); + + // Update P + var temp = _pivots[i]; + _pivots[i] = _pivots[indexSorting[0]]; + _pivots[indexSorting[0]] = temp; + } + } + + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + spaceLeft -= lowerNonZeroCount + upperNonZeroCount; + } + + for (var i = 0; i < _lower.RowCount; i++) + { + _lower[i, i] = 1.0; + } + } + + /// + /// Pivot elements in the according to internal pivot array + /// + /// Row to pivot in + void PivotRow(Vector row) + { + var knownPivots = new Dictionary(); + + // pivot the row + for (var i = 0; i < row.Count; i++) + { + if ((_pivots[i] != i) && (!PivotMapFound(knownPivots, i))) + { + // store the pivots in the hashtable + knownPivots.Add(_pivots[i], i); + + var t = row[i]; + row[i] = row[_pivots[i]]; + row[_pivots[i]] = t; + } + } + } + + /// + /// Was pivoting already performed + /// + /// Pivots already done + /// Current item to pivot + /// true if performed, otherwise false + bool PivotMapFound(Dictionary knownPivots, int currentItem) + { + if (knownPivots.ContainsKey(_pivots[currentItem])) + { + if (knownPivots[_pivots[currentItem]].Equals(currentItem)) + { + return true; + } + } + + if (knownPivots.ContainsKey(currentItem)) + { + if (knownPivots[currentItem].Equals(_pivots[currentItem])) + { + return true; + } + } + + return false; + } + + /// + /// Swap columns in the + /// + /// Source . + /// First column index to swap + /// Second column index to swap + static void SwapColumns(Matrix matrix, int firstColumn, int secondColumn) + { + for (var i = 0; i < matrix.RowCount; i++) + { + var temp = matrix[i, firstColumn]; + matrix[i, firstColumn] = matrix[i, secondColumn]; + matrix[i, secondColumn] = temp; + } + } + + /// + /// Sort vector descending, not changing vector but placing sorted indices to + /// + /// Start sort form + /// Sort till upper bound + /// Array with sorted vector indices + /// Source + static void FindLargestItems(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + // Copy the indices for the values into the array + for (var i = 0; i < upperBound + 1 - lowerBound; i++) + { + sortedIndices[i] = lowerBound + i; + } + + for (var i = upperBound + 1 - lowerBound; i < sortedIndices.Length; i++) + { + sortedIndices[i] = -1; + } + + // Sort the first set of items. + // Sorting starts at index 0 because the index array + // starts at zero + // and ends at index upperBound - lowerBound + ILUTPElementSorter.SortDoubleIndicesDecreasing(0, upperBound - lowerBound, sortedIndices, values); + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_upper == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _upper.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rhs)); + } + + // Solve equation here + // Pivot(vector, result); + // Solve L*Y = B(piv,:) + var rowValues = new DenseVector(_lower.RowCount); + for (var i = 0; i < _lower.RowCount; i++) + { + _lower.Row(i, rowValues); + + var sum = 0.0; + for (var j = 0; j < i; j++) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = rhs[i] - sum; + } + + // Solve U*X = Y; + for (var i = _upper.RowCount - 1; i > -1; i--) + { + _upper.Row(i, rowValues); + + var sum = 0.0; + for (var j = _upper.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + + // We have a column pivot so we only need to pivot the + // end result not the incoming right hand side vector + var temp = lhs.Clone(); + + Pivot(temp, lhs); + } + + /// + /// Pivot elements in according to internal pivot array + /// + /// Source . + /// Result after pivoting. + void Pivot(Vector vector, Vector result) + { + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = vector[_pivots[i]]; + } + } +} + +/// +/// An element sort algorithm for the class. +/// +/// +/// This sort algorithm is used to sort the columns in a sparse matrix based on +/// the value of the element on the diagonal of the matrix. +/// +internal static class ILUTPElementSorter +{ + /// + /// Sorts the elements of the vector in decreasing + /// fashion. The vector itself is not affected. + /// + /// The starting index. + /// The stopping index. + /// An array that will contain the sorted indices once the algorithm finishes. + /// The that contains the values that need to be sorted. + public static void SortDoubleIndicesDecreasing(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + // Move all the indices that we're interested in to the beginning of the + // array. Ignore the rest of the indices. + if (lowerBound > 0) + { + for (var i = 0; i < (upperBound - lowerBound + 1); i++) + { + Exchange(sortedIndices, i, i + lowerBound); + } + + upperBound -= lowerBound; + lowerBound = 0; + } + + HeapSortDoublesIndices(lowerBound, upperBound, sortedIndices, values); + } + + /// + /// Sorts the elements of the vector in decreasing + /// fashion using heap sort algorithm. The vector itself is not affected. + /// + /// The starting index. + /// The stopping index. + /// An array that will contain the sorted indices once the algorithm finishes. + /// The that contains the values that need to be sorted. + private static void HeapSortDoublesIndices(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + var start = ((upperBound - lowerBound + 1) / 2) - 1 + lowerBound; + var end = (upperBound - lowerBound + 1) - 1 + lowerBound; + + BuildDoubleIndexHeap(start, upperBound - lowerBound + 1, sortedIndices, values); + + while (end >= lowerBound) + { + Exchange(sortedIndices, end, lowerBound); + SiftDoubleIndices(sortedIndices, values, lowerBound, end); + end -= 1; + } + } + + /// + /// Build heap for double indices + /// + /// Root position + /// Length of + /// Indices of + /// Target + private static void BuildDoubleIndexHeap(int start, int count, int[] sortedIndices, Vector values) + { + while (start >= 0) + { + SiftDoubleIndices(sortedIndices, values, start, count); + start -= 1; + } + } + + /// + /// Sift double indices + /// + /// Indices of + /// Target + /// Root position + /// Length of + private static void SiftDoubleIndices(int[] sortedIndices, Vector values, int begin, int count) + { + var root = begin; + + while (root * 2 < count) + { + var child = root * 2; + if ((child < count - 1) && (values[sortedIndices[child]] > values[sortedIndices[child + 1]])) + { + child += 1; + } + + if (values[sortedIndices[root]] <= values[sortedIndices[child]]) + { + return; + } + + Exchange(sortedIndices, root, child); + root = child; + } + } + + /// + /// Sorts the given integers in a decreasing fashion. + /// + /// The values. + public static void SortIntegersDecreasing(int[] values) + { + HeapSortIntegers(values, values.Length); + } + + /// + /// Sort the given integers in a decreasing fashion using heapsort algorithm + /// + /// Array of values to sort + /// Length of + private static void HeapSortIntegers(int[] values, int count) + { + var start = (count / 2) - 1; + var end = count - 1; + + BuildHeap(values, start, count); + + while (end >= 0) + { + Exchange(values, end, 0); + Sift(values, 0, end); + end -= 1; + } + } + + /// + /// Build heap + /// + /// Target values array + /// Root position + /// Length of + private static void BuildHeap(int[] values, int start, int count) + { + while (start >= 0) + { + Sift(values, start, count); + start -= 1; + } + } + + /// + /// Sift values + /// + /// Target value array + /// Root position + /// Length of + private static void Sift(int[] values, int start, int count) + { + var root = start; + + while (root * 2 < count) + { + var child = root * 2; + if ((child < count - 1) && (values[child] > values[child + 1])) + { + child += 1; + } + + if (values[root] > values[child]) + { + Exchange(values, root, child); + root = child; + } + else + { + return; + } + } + } + + /// + /// Exchange values in array + /// + /// Target values array + /// First value to exchange + /// Second value to exchange + private static void Exchange(int[] values, int first, int second) + { + var t = values[first]; + values[first] = values[second]; + values[second] = t; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/MILU0Preconditioner.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/MILU0Preconditioner.cs new file mode 100644 index 0000000..aa3edca --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/MILU0Preconditioner.cs @@ -0,0 +1,261 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// A simple milu(0) preconditioner. +/// +/// +/// Original Fortran code by Yousef Saad (07 January 2004) +/// +public sealed class MILU0Preconditioner : IPreconditioner +{ + // Matrix stored in Modified Sparse Row (MSR) format containing the L and U + // factors together. + + // The diagonal (stored in alu(0:n-1) ) is inverted. Each i-th row of the matrix + // contains the i-th row of L (excluding the diagonal entry = 1) followed by + // the i-th row of U. + private double[] _alu; + + // The row pointers (stored in jlu(0:n) ) and column indices to off-diagonal elements. + private int[] _jlu; + + // Pointer to the diagonal elements in MSR storage (for faster LU solving). + private int[] _diag; + + /// Use modified or standard ILU(0) + public MILU0Preconditioner(bool modified = true) + { + UseModified = modified; + } + + /// + /// Gets or sets a value indicating whether to use modified or standard ILU(0). + /// + public bool UseModified { get; set; } + + /// + /// Gets a value indicating whether the preconditioner is initialized. + /// + public bool IsInitialized { get; private set; } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square or is not an + /// instance of SparseCompressedRowMatrixStorage. + public void Initialize(Matrix matrix) + { + var csr = matrix.Storage as SparseCompressedRowMatrixStorage; + if (csr == null) + { + throw new ArgumentException(Resources.MatrixMustBeSparse, nameof(matrix)); + } + + // Dimension of matrix + int n = csr.RowCount; + if (n != csr.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + // Original matrix compressed sparse row storage. + double[] a = csr.Values; + int[] ja = csr.ColumnIndices; + int[] ia = csr.RowPointers; + + _alu = new double[ia[n] + 1]; + _jlu = new int[ia[n] + 1]; + _diag = new int[n]; + + int code = Compute(n, a, ja, ia, _alu, _jlu, _diag, UseModified); + if (code > -1) + { + throw new NumericalBreakdownException("Zero pivot encountered on row " + code + " during ILU process"); + } + + IsInitialized = true; + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector b. + /// The left hand side vector x. + public void Approximate(Vector input, Vector result) + { + if (_alu == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((result.Count != input.Count) || (result.Count != _diag.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + int n = _diag.Length; + + // Forward solve. + for (int i = 0; i < n; i++) + { + result[i] = input[i]; + for (int k = _jlu[i]; k < _diag[i]; k++) + { + result[i] = result[i] - _alu[k] * result[_jlu[k]]; + } + } + + // Backward solve. + for (int i = n - 1; i >= 0; i--) + { + for (int k = _diag[i]; k < _jlu[i + 1]; k++) + { + result[i] = result[i] - _alu[k] * result[_jlu[k]]; + } + + result[i] = _alu[i] * result[i]; + } + } + + /// + /// MILU0 is a simple milu(0) preconditioner. + /// + /// Order of the matrix. + /// Matrix values in CSR format (input). + /// Column indices (input). + /// Row pointers (input). + /// Matrix values in MSR format (output). + /// Row pointers and column indices (output). + /// Pointer to diagonal elements (output). + /// True if the modified/MILU algorithm should be used (recommended) + /// Returns 0 on success or k > 0 if a zero pivot was encountered at step k. + private int Compute(int n, double[] a, int[] ja, int[] ia, double[] alu, int[] jlu, int[] ju, bool modified) + { + var iw = new int[n]; + int i; + + // Set initial pointer value. + int p = n + 1; + jlu[0] = p; + + // Initialize work vector. + for (i = 0; i < n; i++) + { + iw[i] = -1; + } + + // The main loop. + for (i = 0; i < n; i++) + { + int pold = p; + + // Generating row i of L and U. + int j; + for (j = ia[i]; j < ia[i + 1]; j++) + { + // Copy row i of A, JA, IA into row i of ALU, JLU (LU matrix). + int jcol = ja[j]; + + if (jcol == i) + { + alu[i] = a[j]; + iw[jcol] = i; + ju[i] = p; + } + else + { + alu[p] = a[j]; + jlu[p] = ja[j]; + iw[jcol] = p; + p = p + 1; + } + } + + jlu[i + 1] = p; + + double s = 0.0; + + int k; + for (j = pold; j < ju[i]; j++) + { + int jrow = jlu[j]; + double tl = alu[j] * alu[jrow]; + alu[j] = tl; + + // Perform linear combination. + for (k = ju[jrow]; k < jlu[jrow + 1]; k++) + { + int jw = iw[jlu[k]]; + if (jw != -1) + { + alu[jw] = alu[jw] - tl * alu[k]; + } + else + { + // Accumulate fill-in values. + s = s + tl * alu[k]; + } + } + } + + if (modified) + { + alu[i] = alu[i] - s; + } + + if (alu[i] == 0.0) + { + return i; + } + + // Invert and store diagonal element. + alu[i] = 1.0 / alu[i]; + + // Reset pointers in work array. + iw[i] = -1; + for (k = pold; k < p; k++) + { + iw[jlu[k]] = -1; + } + } + + return -1; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/MlkBiCgStab.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/MlkBiCgStab.cs new file mode 100644 index 0000000..d108a69 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/MlkBiCgStab.cs @@ -0,0 +1,542 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// A Multiple-Lanczos Bi-Conjugate Gradient stabilized iterative matrix solver. +/// +/// +/// +/// The Multiple-Lanczos Bi-Conjugate Gradient stabilized (ML(k)-BiCGStab) solver is an 'improvement' +/// of the standard BiCgStab solver. +/// +/// +/// The algorithm was taken from:
+/// ML(k)BiCGSTAB: A BiCGSTAB variant based on multiple Lanczos starting vectors +///
+/// Man-Chung Yeung and Tony F. Chan +///
+/// SIAM Journal of Scientific Computing +///
+/// Volume 21, Number 4, pp. 1263 - 1290 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class MlkBiCgStab : IIterativeSolver +{ + /// + /// The default number of starting vectors. + /// + const int DefaultNumberOfStartingVectors = 50; + + /// + /// The collection of starting vectors which are used as the basis for the Krylov sub-space. + /// + IList> _startingVectors; + + /// + /// The number of starting vectors used by the algorithm + /// + int _numberOfStartingVectors = DefaultNumberOfStartingVectors; + + /// + /// Gets or sets the number of starting vectors. + /// + /// + /// Must be larger than 1 and smaller than the number of variables in the matrix that + /// for which this solver will be used. + /// + public int NumberOfStartingVectors + { + [DebuggerStepThrough] + get + { + return _numberOfStartingVectors; + } + + [DebuggerStepThrough] + set + { + if (value <= 1) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfStartingVectors = value; + } + } + + /// + /// Resets the number of starting vectors to the default value. + /// + public void ResetNumberOfStartingVectors() + { + _numberOfStartingVectors = DefaultNumberOfStartingVectors; + } + + /// + /// Gets or sets a series of orthonormal vectors which will be used as basis for the + /// Krylov sub-space. + /// + public IList> StartingVectors + { + [DebuggerStepThrough] + get + { + return _startingVectors; + } + + [DebuggerStepThrough] + set + { + if ((value == null) || (value.Count == 0)) + { + _startingVectors = null; + } + else + { + _startingVectors = value; + } + } + } + + /// + /// Gets the number of starting vectors to create + /// + /// Maximum number + /// Number of variables + /// Number of starting vectors to create + static int NumberOfStartingVectorsToCreate(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + return Math.Min(maximumNumberOfStartingVectors, (numberOfVariables - 1)); + } + + /// + /// Returns an array of starting vectors. + /// + /// The maximum number of starting vectors that should be created. + /// The number of variables. + /// + /// An array with starting vectors. The array will never be larger than the + /// but it may be smaller if + /// the is smaller than + /// the . + /// + static IList> CreateStartingVectors(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + // Get random values and then orthogonalize them with + // modified Gramm - Schmidt + var count = NumberOfStartingVectorsToCreate(maximumNumberOfStartingVectors, numberOfVariables); + + // Get a random set of samples based on the standard normal distribution with + // mean = 0 and sd = 1 + var distribution = new Normal(); + + var matrix = new DenseMatrix(numberOfVariables, count); + for (var i = 0; i < matrix.ColumnCount; i++) + { + var samples = distribution.Samples().Take(matrix.RowCount).ToArray(); + + // Set the column + matrix.SetColumn(i, samples); + } + + // Compute the orthogonalization. + var gs = matrix.GramSchmidt(); + var orthogonalMatrix = gs.Q; + + // Now transfer this to vectors + var result = new List>(orthogonalMatrix.ColumnCount); + for (var i = 0; i < orthogonalMatrix.ColumnCount; i++) + { + result.Add(orthogonalMatrix.Column(i)); + + // Normalize the result vector + result[i].Multiply(1 / result[i].L2Norm(), result[i]); + } + + return result; + } + + /// + /// Create random vectors array + /// + /// Number of vectors + /// Size of each vector + /// Array of random vectors + static Vector[] CreateVectorArray(int arraySize, int vectorSize) + { + var result = new Vector[arraySize]; + for (var i = 0; i < result.Length; i++) + { + result[i] = new DenseVector(vectorSize); + } + + return result; + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Source A. + /// Residual data. + /// x data. + /// b data. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // Choose an initial guess x_0 + // Take x_0 = 0 + var xtemp = new DenseVector(input.Count); + + // Choose k vectors q_1, q_2, ..., q_k + // Build a new set if: + // a) the stored set doesn't exist (i.e. == null) + // b) Is of an incorrect length (i.e. too long) + // c) The vectors are of an incorrect length (i.e. too long or too short) + var useOld = false; + if (_startingVectors != null) + { + // We don't accept collections with zero starting vectors so ... + if (_startingVectors.Count <= NumberOfStartingVectorsToCreate(_numberOfStartingVectors, input.Count)) + { + // Only check the first vector for sizing. If that matches we assume the + // other vectors match too. If they don't the process will crash + if (_startingVectors[0].Count == input.Count) + { + useOld = true; + } + } + } + + _startingVectors = useOld ? _startingVectors : CreateStartingVectors(_numberOfStartingVectors, input.Count); + + // Store the number of starting vectors. Not really necessary but easier to type :) + var k = _startingVectors.Count; + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary scalars + var c = new double[k]; + + // Define the temporary vectors + var gtemp = new DenseVector(residuals.Count); + + var u = new DenseVector(residuals.Count); + var utemp = new DenseVector(residuals.Count); + var temp = new DenseVector(residuals.Count); + var temp1 = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + + var zd = new DenseVector(residuals.Count); + var zg = new DenseVector(residuals.Count); + var zw = new DenseVector(residuals.Count); + + var d = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // g_0 = r_0 + var g = CreateVectorArray(_startingVectors.Count, residuals.Count); + residuals.CopyTo(g[k - 1]); + + var w = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // FOR (j = 0, 1, 2 ....) + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue) + { + // SOLVE M g~_((j-1)k+k) = g_((j-1)k+k) + preconditioner.Approximate(g[k - 1], gtemp); + + // w_((j-1)k+k) = A g~_((j-1)k+k) + matrix.Multiply(gtemp, w[k - 1]); + + // c_((j-1)k+k) = q^T_1 w_((j-1)k+k) + c[k - 1] = _startingVectors[0].DotProduct(w[k - 1]); + if (c[k - 1].AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // alpha_(jk+1) = q^T_1 r_((j-1)k+k) / c_((j-1)k+k) + var alpha = _startingVectors[0].DotProduct(residuals) / c[k - 1]; + + // u_(jk+1) = r_((j-1)k+k) - alpha_(jk+1) w_((j-1)k+k) + w[k - 1].Multiply(-alpha, temp); + residuals.Add(temp, u); + + // SOLVE M u~_(jk+1) = u_(jk+1) + preconditioner.Approximate(u, temp1); + temp1.CopyTo(utemp); + + // rho_(j+1) = -u^t_(jk+1) A u~_(jk+1) / ||A u~_(jk+1)||^2 + matrix.Multiply(temp1, temp); + var rho = temp.DotProduct(temp); + + // If rho is zero then temp is a zero vector and we're probably + // about to have zero residuals (i.e. an exact solution). + // So set rho to 1.0 because in the next step it will turn to zero. + if (rho.AlmostEqualNumbersBetween(0, 1)) + { + rho = 1.0; + } + + rho = -u.DotProduct(temp) / rho; + + // r_(jk+1) = rho_(j+1) A u~_(jk+1) + u_(jk+1) + u.CopyTo(residuals); + + // Reuse temp + temp.Multiply(rho, temp); + residuals.Add(temp, temp2); + temp2.CopyTo(residuals); + + // x_(jk+1) = x_((j-1)k_k) - rho_(j+1) u~_(jk+1) + alpha_(jk+1) g~_((j-1)k+k) + utemp.Multiply(-rho, temp); + xtemp.Add(temp, temp2); + temp2.CopyTo(xtemp); + + gtemp.Multiply(alpha, gtemp); + xtemp.Add(gtemp, temp2); + temp2.CopyTo(xtemp); + + // Check convergence and stop if we are converged. + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // We're all good now. + // Exit from the while loop. + break; + } + } + + // FOR (i = 1,2, ...., k) + for (var i = 0; i < k; i++) + { + // z_d = u_(jk+1) + u.CopyTo(zd); + + // z_g = r_(jk+i) + residuals.CopyTo(zg); + + // z_w = 0 + zw.Clear(); + + // FOR (s = i, ...., k-1) AND j >= 1 + double beta; + if (iterationNumber >= 1) + { + for (var s = i; s < k - 1; s++) + { + // beta^(jk+i)_((j-1)k+s) = -q^t_(s+1) z_d / c_((j-1)k+s) + beta = -_startingVectors[s + 1].DotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_((j-1)k+s) d_((j-1)k+s) + d[s].Multiply(beta, temp); + zd.Add(temp, temp2); + temp2.CopyTo(zd); + + // z_g = z_g + beta^(jk+i)_((j-1)k+s) g_((j-1)k+s) + g[s].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + + // z_w = z_w + beta^(jk+i)_((j-1)k+s) w_((j-1)k+s) + w[s].Multiply(beta, temp); + zw.Add(temp, temp2); + temp2.CopyTo(zw); + } + } + + beta = rho * c[k - 1]; + if (beta.AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // beta^(jk+i)_((j-1)k+k) = -(q^T_1 (r_(jk+1) + rho_(j+1) z_w)) / (rho_(j+1) c_((j-1)k+k)) + zw.Multiply(rho, temp2); + residuals.Add(temp2, temp); + beta = -_startingVectors[0].DotProduct(temp) / beta; + + // z_g = z_g + beta^(jk+i)_((j-1)k+k) g_((j-1)k+k) + g[k - 1].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + + // z_w = rho_(j+1) (z_w + beta^(jk+i)_((j-1)k+k) w_((j-1)k+k)) + w[k - 1].Multiply(beta, temp); + zw.Add(temp, temp2); + temp2.CopyTo(zw); + zw.Multiply(rho, zw); + + // z_d = r_(jk+i) + z_w + residuals.Add(zw, zd); + + // FOR (s = 1, ... i - 1) + for (var s = 0; s < i - 1; s++) + { + // beta^(jk+i)_(jk+s) = -q^T_s+1 z_d / c_(jk+s) + beta = -_startingVectors[s + 1].DotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_(jk+s) * d_(jk+s) + d[s].Multiply(beta, temp); + zd.Add(temp, temp2); + temp2.CopyTo(zd); + + // z_g = z_g + beta^(jk+i)_(jk+s) * g_(jk+s) + g[s].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + } + + // d_(jk+i) = z_d - u_(jk+i) + zd.Subtract(u, d[i]); + + // g_(jk+i) = z_g + z_w + zg.Add(zw, g[i]); + + // IF (i < k - 1) + if (i < k - 1) + { + // c_(jk+1) = q^T_i+1 d_(jk+i) + c[i] = _startingVectors[i + 1].DotProduct(d[i]); + if (c[i].AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // alpha_(jk+i+1) = q^T_(i+1) u_(jk+i) / c_(jk+i) + alpha = _startingVectors[i + 1].DotProduct(u) / c[i]; + + // u_(jk+i+1) = u_(jk+i) - alpha_(jk+i+1) d_(jk+i) + d[i].Multiply(-alpha, temp); + u.Add(temp, temp2); + temp2.CopyTo(u); + + // SOLVE M g~_(jk+i) = g_(jk+i) + preconditioner.Approximate(g[i], gtemp); + + // x_(jk+i+1) = x_(jk+i) + rho_(j+1) alpha_(jk+i+1) g~_(jk+i) + gtemp.Multiply(rho * alpha, temp); + xtemp.Add(temp, temp2); + temp2.CopyTo(xtemp); + + // w_(jk+i) = A g~_(jk+i) + matrix.Multiply(gtemp, w[i]); + + // r_(jk+i+1) = r_(jk+i) - rho_(j+1) alpha_(jk+i+1) w_(jk+i) + w[i].Multiply(-rho * alpha, temp); + residuals.Add(temp, temp2); + temp2.CopyTo(residuals); + + // We can check the residuals here if they're close + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, xtemp, input); + } + } + } // END ITERATION OVER i + + iterationNumber++; + } + + // copy the temporary result to the real result vector + xtemp.CopyTo(result); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Solvers/TFQMR.cs b/MathNet.Numerics/LinearAlgebra/Double/Solvers/TFQMR.cs new file mode 100644 index 0000000..f9b9db5 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Solvers/TFQMR.cs @@ -0,0 +1,277 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers; + +/// +/// A Transpose Free Quasi-Minimal Residual (TFQMR) iterative matrix solver. +/// +/// +/// +/// The TFQMR algorithm was taken from:
+/// Iterative methods for sparse linear systems. +///
+/// Yousef Saad +///
+/// Algorithm is described in Chapter 7, section 7.4.3, page 219 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class TFQMR : IIterativeSolver +{ + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Is even? + /// + /// Number to check + /// true if even, otherwise false + static bool IsEven(int number) + { + return number % 2 == 0; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + var d = new DenseVector(input.Count); + var r = DenseVector.OfVector(input); + + var uodd = new DenseVector(input.Count); + var ueven = new DenseVector(input.Count); + + var v = new DenseVector(input.Count); + var pseudoResiduals = DenseVector.OfVector(input); + + var x = new DenseVector(input.Count); + var yodd = new DenseVector(input.Count); + var yeven = DenseVector.OfVector(input); + + // Temp vectors + var temp = new DenseVector(input.Count); + var temp1 = new DenseVector(input.Count); + var temp2 = new DenseVector(input.Count); + + // Define the scalars + double alpha = 0; + double eta = 0; + double theta = 0; + + // Initialize + var tau = input.L2Norm(); + var rho = tau * tau; + + // Calculate the initial values for v + // M temp = yEven + preconditioner.Approximate(yeven, temp); + + // v = A temp + matrix.Multiply(temp, v); + + // Set uOdd + v.CopyTo(ueven); + + // Start the iteration + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, result, input, pseudoResiduals) == IterationStatus.Continue) + { + // First part of the step, the even bit + if (IsEven(iterationNumber)) + { + // sigma = (v, r) + var sigma = v.DotProduct(r); + if (sigma.AlmostEqualNumbersBetween(0, 1)) + { + // FAIL HERE + iterator.Cancel(); + break; + } + + // alpha = rho / sigma + alpha = rho / sigma; + + // yOdd = yEven - alpha * v + v.Multiply(-alpha, temp1); + yeven.Add(temp1, yodd); + + // Solve M temp = yOdd + preconditioner.Approximate(yodd, temp); + + // uOdd = A temp + matrix.Multiply(temp, uodd); + } + + // The intermediate step which is equal for both even and + // odd iteration steps. + // Select the correct vector + var uinternal = IsEven(iterationNumber) ? ueven : uodd; + var yinternal = IsEven(iterationNumber) ? yeven : yodd; + + // pseudoResiduals = pseudoResiduals - alpha * uOdd + uinternal.Multiply(-alpha, temp1); + pseudoResiduals.Add(temp1, temp2); + temp2.CopyTo(pseudoResiduals); + + // d = yOdd + theta * theta * eta / alpha * d + d.Multiply(theta * theta * eta / alpha, temp); + yinternal.Add(temp, d); + + // theta = ||pseudoResiduals||_2 / tau + theta = pseudoResiduals.L2Norm() / tau; + var c = 1 / Math.Sqrt(1 + (theta * theta)); + + // tau = tau * theta * c + tau *= theta * c; + + // eta = c^2 * alpha + eta = c * c * alpha; + + // x = x + eta * d + d.Multiply(eta, temp1); + x.Add(temp1, temp2); + temp2.CopyTo(x); + + // Check convergence and see if we can bail + if (iterator.DetermineStatus(iterationNumber, result, input, pseudoResiduals) != IterationStatus.Continue) + { + // Calculate the real values + preconditioner.Approximate(x, result); + + // Calculate the true residual. Use the temp vector for that + // so that we don't pollute the pseudoResidual vector for no + // good reason. + CalculateTrueResidual(matrix, temp, result, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, result, input, temp) != IterationStatus.Continue) + { + // We're all good now. + return; + } + } + + // The odd step + if (!IsEven(iterationNumber)) + { + if (rho.AlmostEqualNumbersBetween(0, 1)) + { + // FAIL HERE + iterator.Cancel(); + break; + } + + var rhoNew = pseudoResiduals.DotProduct(r); + var beta = rhoNew / rho; + + // Update rho for the next loop + rho = rhoNew; + + // yOdd = pseudoResiduals + beta * yOdd + yodd.Multiply(beta, temp1); + pseudoResiduals.Add(temp1, yeven); + + // Solve M temp = yOdd + preconditioner.Approximate(yeven, temp); + + // uOdd = A temp + matrix.Multiply(temp, ueven); + + // v = uEven + beta * (uOdd + beta * v) + v.Multiply(beta, temp1); + uodd.Add(temp1, temp); + + temp.Multiply(beta, temp1); + ueven.Add(temp1, v); + } + + // Calculate the real values + preconditioner.Approximate(x, result); + + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/SparseMatrix.cs b/MathNet.Numerics/LinearAlgebra/Double/SparseMatrix.cs new file mode 100644 index 0000000..c95f930 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/SparseMatrix.cs @@ -0,0 +1,1576 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double; + +/// +/// A Matrix with sparse storage, intended for very large matrices where most of the cells are zero. +/// The underlying storage scheme is 3-array compressed-sparse-row (CSR) Format. +/// Wikipedia - CSR. +/// +[Serializable] +[DebuggerDisplay("SparseMatrix {RowCount}x{ColumnCount}-Double {NonZerosCount}-NonZero")] +public class SparseMatrix : Matrix +{ + readonly SparseCompressedRowMatrixStorage _storage; + + /// + /// Gets the number of non zero elements in the matrix. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get { return _storage.ValueCount; } + } + + /// + /// Create a new sparse matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public SparseMatrix(SparseCompressedRowMatrixStorage storage) + : base(storage) + { + _storage = storage; + } + + /// + /// Create a new square sparse matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public SparseMatrix(int order) + : this(order, order) + { + } + + /// + /// Create a new sparse matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public SparseMatrix(int rows, int columns) + : this(new SparseCompressedRowMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new sparse matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfMatrix(Matrix matrix) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfArray(double[,] array) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfArray(array)); + } + + /// + /// Create a new sparse matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in row-major order (row by row). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + /// + public static SparseMatrix OfRowMajor(int rows, int columns, IEnumerable rowMajor) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowMajorEnumerable(rows, columns, rowMajor)); + } + + /// + /// Create a new sparse matrix with the given number of rows and columns as a copy of the given array. + /// The array is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + /// + public static SparseMatrix OfColumnMajor(int rows, int columns, IList columnMajor) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnMajorList(rows, columns, columnMajor)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumns(IEnumerable> data) + { + return OfColumnArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumns(int rows, int columns, IEnumerable> data) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnArrays(params double[][] columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnArrays(IEnumerable columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnArrays((columns as double[][]) ?? columns.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnVectors(IEnumerable> columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRows(IEnumerable> data) + { + return OfRowArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRows(int rows, int columns, IEnumerable> data) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowArrays(params double[][] rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowArrays(IEnumerable rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowArrays((rows as double[][]) ?? rows.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowVectors(IEnumerable> rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalVector(Vector diagonal) + { + var m = new SparseMatrix(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = new SparseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalArray(double[] diagonal) + { + var m = new SparseMatrix(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalArray(int rows, int columns, double[] diagonal) + { + var m = new SparseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix and initialize each value to the same provided value. + /// + public static SparseMatrix Create(int rows, int columns, double value) + { + if (value == 0d) + return new SparseMatrix(rows, columns); + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new sparse matrix and initialize each value using the provided init function. + /// + public static SparseMatrix Create(int rows, int columns, Func init) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value to the same provided value. + /// + public static SparseMatrix CreateDiagonal(int rows, int columns, double value) + { + if (value == 0d) + return new SparseMatrix(rows, columns); + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value using the provided init function. + /// + public static SparseMatrix CreateDiagonal(int rows, int columns, Func init) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static SparseMatrix CreateIdentity(int order) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + var result = Build.SameAs(this); + LowerTriangleImpl(result); + return result; + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + LowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + LowerTriangleImpl(result); + } + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void LowerTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row >= columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + var result = Build.SameAs(this); + UpperTriangleImpl(result); + return result; + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + UpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + UpperTriangleImpl(result); + } + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void UpperTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row <= columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + var result = Build.SameAs(this); + StrictlyLowerTriangleImpl(result); + return result; + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + StrictlyLowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyLowerTriangleImpl(result); + } + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyLowerTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row > columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + var result = Build.SameAs(this); + StrictlyUpperTriangleImpl(result); + return result; + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + StrictlyUpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyUpperTriangleImpl(result); + } + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyUpperTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row < columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + CopyTo(result); + DoMultiply(-1, result); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + var rowPointers = _storage.RowPointers; + var values = _storage.Values; + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + var startIndex = rowPointers[i]; + var endIndex = rowPointers[i + 1]; + + if (startIndex == endIndex) + { + // Begin and end are equal. There are no values in the row, Move to the next row + continue; + } + + var s = 0d; + for (var j = startIndex; j < endIndex; j++) + { + s += Math.Abs(values[j]); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + var aat = (SparseCompressedRowMatrixStorage)(this * Transpose()).Storage; + var norm = 0d; + for (var i = 0; i < aat.RowCount; i++) + { + var startIndex = aat.RowPointers[i]; + var endIndex = aat.RowPointers[i + 1]; + + if (startIndex == endIndex) + { + // Begin and end are equal. There are no values in the row, Move to the next row + continue; + } + + for (var j = startIndex; j < endIndex; j++) + { + if (i == aat.ColumnIndices[j]) + { + norm += Math.Abs(aat.Values[j]); + } + } + } + + return Math.Sqrt(norm); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther == null || sparseResult == null) + { + base.DoAdd(other, result); + return; + } + + if (ReferenceEquals(this, other)) + { + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + LinearAlgebraControl.Provider.ScaleArray(2.0, sparseResult._storage.Values, sparseResult._storage.Values); + return; + } + + SparseMatrix left; + + if (ReferenceEquals(sparseOther, sparseResult)) + { + left = this; + } + else if (ReferenceEquals(this, sparseResult)) + { + left = sparseOther; + } + else + { + CopyTo(sparseResult); + left = sparseOther; + } + + var leftStorage = left._storage; + for (var i = 0; i < leftStorage.RowCount; i++) + { + var endIndex = leftStorage.RowPointers[i + 1]; + for (var j = leftStorage.RowPointers[i]; j < endIndex; j++) + { + var columnIndex = leftStorage.ColumnIndices[j]; + var resVal = leftStorage.Values[j] + result.At(i, columnIndex); + result.At(i, columnIndex, resVal); + } + } + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract to this matrix. + /// The matrix to store the result of subtraction. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + + if (sparseOther == null || sparseResult == null) + { + base.DoSubtract(other, result); + return; + } + + if (ReferenceEquals(this, other)) + { + result.Clear(); + return; + } + + var otherStorage = sparseOther._storage; + + if (ReferenceEquals(this, sparseResult)) + { + for (var i = 0; i < otherStorage.RowCount; i++) + { + var endIndex = otherStorage.RowPointers[i + 1]; + for (var j = otherStorage.RowPointers[i]; j < endIndex; j++) + { + var columnIndex = otherStorage.ColumnIndices[j]; + var resVal = sparseResult.At(i, columnIndex) - otherStorage.Values[j]; + result.At(i, columnIndex, resVal); + } + } + } + else + { + if (!ReferenceEquals(sparseOther, sparseResult)) + { + sparseOther.CopyTo(sparseResult); + } + + sparseResult.Negate(sparseResult); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + var columnIndex = columnIndices[j]; + var resVal = sparseResult.At(i, columnIndex) + values[j]; + result.At(i, columnIndex, resVal); + } + } + } + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(double scalar, Matrix result) + { + if (scalar == 1.0) + { + CopyTo(result); + return; + } + + if (scalar == 0.0 || NonZerosCount == 0) + { + result.Clear(); + return; + } + + var sparseResult = result as SparseMatrix; + if (sparseResult == null) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + result.At(row, column, values[index] * scalar); + } + } + } + else + { + if (!ReferenceEquals(this, result)) + { + CopyTo(sparseResult); + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, sparseResult._storage.Values, sparseResult._storage.Values); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther != null && sparseResult != null) + { + DoMultiplySparse(sparseOther, sparseResult); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null && sparseResult != null) + { + var diagonal = diagonalOther.Data; + if (other.ColumnCount == other.RowCount) + { + Storage.MapIndexedTo(result.Storage, (i, j, x) => x * diagonal[j], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Storage.Clear(); + Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * diagonal[j], 0, 0, RowCount, 0, 0, ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + + return; + } + + result.Clear(); + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + // in this case we can directly address the underlying data-array + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var column = 0; column < other.ColumnCount; column++) + { + int otherColumnStartPosition = column * other.RowCount; + var sum = 0d; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * denseOther.Data[otherColumnStartPosition + columnIndices[index]]; + } + + result.At(row, column, sum); + } + } + + return; + } + + var columnVector = new DenseVector(other.RowCount); + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var column = 0; column < other.ColumnCount; column++) + { + // Multiply row of matrix A on column of matrix B + other.Column(column, columnVector); + + var sum = 0d; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * columnVector[columnIndices[index]]; + } + + result.At(row, column, sum); + } + } + } + + void DoMultiplySparse(SparseMatrix other, SparseMatrix result) + { + result.Clear(); + + var ax = _storage.Values; + var ap = _storage.RowPointers; + var ai = _storage.ColumnIndices; + + var bx = other._storage.Values; + var bp = other._storage.RowPointers; + var bi = other._storage.ColumnIndices; + + int rows = RowCount; + int cols = other.ColumnCount; + + int[] cp = result._storage.RowPointers; + + var marker = new int[cols]; + for (int ib = 0; ib < cols; ib++) + { + marker[ib] = -1; + } + + int count = 0; + for (int i = 0; i < rows; i++) + { + // For each row of A + for (int j = ap[i]; j < ap[i + 1]; j++) + { + // Row number to be added + int a = ai[j]; + for (int k = bp[a]; k < bp[a + 1]; k++) + { + int b = bi[k]; + if (marker[b] != i) + { + marker[b] = i; + count++; + } + } + } + + // Record non-zero count. + cp[i + 1] = count; + } + + var ci = new int[count]; + var cx = new double[count]; + + for (int ib = 0; ib < cols; ib++) + { + marker[ib] = -1; + } + + count = 0; + for (int i = 0; i < rows; i++) + { + int rowStart = cp[i]; + for (int j = ap[i]; j < ap[i + 1]; j++) + { + int a = ai[j]; + double aEntry = ax[j]; + for (int k = bp[a]; k < bp[a + 1]; k++) + { + int b = bi[k]; + double bEntry = bx[k]; + if (marker[b] < rowStart) + { + marker[b] = count; + ci[marker[b]] = b; + cx[marker[b]] = aEntry * bEntry; + count++; + } + else + { + cx[marker[b]] += aEntry * bEntry; + } + } + } + } + + result._storage.Values = cx; + result._storage.ColumnIndices = ci; + result._storage.Normalize(); + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var sum = 0d; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * rightSide[columnIndices[index]]; + } + + result[row] = sum; + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var otherSparse = other as SparseMatrix; + var resultSparse = result as SparseMatrix; + + if (otherSparse == null || resultSparse == null) + { + base.DoTransposeAndMultiply(other, result); + return; + } + + resultSparse.Clear(); + + var rowPointers = _storage.RowPointers; + var values = _storage.Values; + + var otherStorage = otherSparse._storage; + + for (var j = 0; j < RowCount; j++) + { + var startIndexOther = otherStorage.RowPointers[j]; + var endIndexOther = otherStorage.RowPointers[j + 1]; + + if (startIndexOther == endIndexOther) + { + continue; + } + + for (var i = 0; i < RowCount; i++) + { + var startIndexThis = rowPointers[i]; + var endIndexThis = rowPointers[i + 1]; + + if (startIndexThis == endIndexThis) + { + continue; + } + + var sum = 0d; + for (var index = startIndexOther; index < endIndexOther; index++) + { + var ind = _storage.FindItem(i, otherStorage.ColumnIndices[index]); + if (ind >= 0) + { + sum += otherStorage.Values[index] * values[ind]; + } + } + + resultSparse._storage.At(i, j, sum + result.At(i, j)); + } + } + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var rightSideValue = rightSide[row]; + for (var index = startIndex; index < endIndex; index++) + { + result[columnIndices[index]] += values[index] * rightSideValue; + } + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + var resVal = values[j] * other.At(i, columnIndices[j]); + if (resVal != 0d) + { + result.At(i, columnIndices[j], resVal); + } + } + } + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + if (values[j] != 0d) + { + result.At(i, columnIndices[j], values[j] / divisor.At(i, columnIndices[j])); + } + } + } + } + + public override void KroneckerProduct(Matrix other, Matrix result) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != (RowCount * other.RowCount) || result.ColumnCount != (ColumnCount * other.ColumnCount)) + { + throw DimensionsDontMatch(this, other, result); + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + if (values[j] != 0d) + { + result.SetSubMatrix(i * other.RowCount, other.RowCount, columnIndices[j] * other.ColumnCount, other.ColumnCount, values[j] * other); + } + } + } + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoModulus(double divisor, Matrix result) + { + var sparseResult = result as SparseMatrix; + if (sparseResult == null) + { + base.DoModulus(divisor, result); + return; + } + + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + var resultStorage = sparseResult._storage; + for (var index = 0; index < resultStorage.Values.Length; index++) + { + resultStorage.Values[index] = Euclid.Modulus(resultStorage.Values[index], divisor); + } + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoRemainder(double divisor, Matrix result) + { + var sparseResult = result as SparseMatrix; + if (sparseResult == null) + { + base.DoRemainder(divisor, result); + return; + } + + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + var resultStorage = sparseResult._storage; + for (var index = 0; index < resultStorage.Values.Length; index++) + { + resultStorage.Values[index] %= divisor; + } + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public override bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + if (!values[index].Equals(At(column, row))) + { + return false; + } + } + } + + return true; + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static SparseMatrix operator +(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.RowCount != rightSide.RowCount || leftSide.ColumnCount != rightSide.ColumnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Add(rightSide); + } + + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static SparseMatrix operator +(SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Clone(); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static SparseMatrix operator -(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.RowCount != rightSide.RowCount || leftSide.ColumnCount != rightSide.ColumnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Subtract(rightSide); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static SparseMatrix operator -(SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Negate(); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator *(SparseMatrix leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator *(double leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static SparseMatrix operator *(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide.ColumnCount != rightSide.RowCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static SparseVector operator *(SparseMatrix leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static SparseVector operator *(SparseVector leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.LeftMultiply(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator %(SparseMatrix leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseMatrix)leftSide.Remainder(rightSide); + } + + public override string ToTypeString() + { + return string.Format("SparseMatrix {0}x{1}-Double {2:P2} Filled", RowCount, ColumnCount, NonZerosCount / (RowCount * (double)ColumnCount)); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/SparseVector.cs b/MathNet.Numerics/LinearAlgebra/Double/SparseVector.cs new file mode 100644 index 0000000..894d9e0 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/SparseVector.cs @@ -0,0 +1,974 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Double; + +/// +/// A vector with sparse storage, intended for very large vectors where most of the cells are zero. +/// +/// The sparse vector is not thread safe. +[Serializable] +[DebuggerDisplay("SparseVector {Count}-Double {NonZerosCount}-NonZero")] +public class SparseVector : Vector +{ + readonly SparseVectorStorage _storage; + + /// + /// Gets the number of non zero elements in the vector. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get { return _storage.ValueCount; } + } + + /// + /// Create a new sparse vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public SparseVector(SparseVectorStorage storage) + : base(storage) + { + _storage = storage; + } + + /// + /// Create a new sparse vector with the given length. + /// All cells of the vector will be initialized to zero. + /// Zero-length vectors are not supported. + /// + /// If length is less than one. + public SparseVector(int length) + : this(new SparseVectorStorage(length)) + { + } + + /// + /// Create a new sparse vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfVector(Vector vector) + { + return new SparseVector(SparseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new sparse vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfEnumerable(IEnumerable enumerable) + { + return new SparseVector(SparseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new sparse vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfIndexedEnumerable(int length, IEnumerable> enumerable) + { + return new SparseVector(SparseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided value. + /// + public static SparseVector Create(int length, double value) + { + return new SparseVector(SparseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided init function. + /// + public static SparseVector Create(int length, Func init) + { + return new SparseVector(SparseVectorStorage.OfInit(length, init)); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// Warning, the new 'sparse vector' with a non-zero scalar added to it will be a 100% filled + /// sparse vector and very inefficient. Would be better to work with a dense vector instead. + /// + /// + /// The scalar to add. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(double scalar, Vector result) + { + if (scalar == 0.0) + { + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + return; + } + + if (ReferenceEquals(this, result)) + { + // populate a new vector with the scalar + var vnonZeroValues = new double[Count]; + var vnonZeroIndices = new int[Count]; + for (int index = 0; index < Count; index++) + { + vnonZeroIndices[index] = index; + vnonZeroValues[index] = scalar; + } + + // populate the non zero values from this + var indices = _storage.Indices; + var values = _storage.Values; + for (int j = 0; j < _storage.ValueCount; j++) + { + vnonZeroValues[indices[j]] = values[j] + scalar; + } + + // assign this vectors array to the new arrays. + _storage.Values = vnonZeroValues; + _storage.Indices = vnonZeroIndices; + _storage.ValueCount = Count; + } + else + { + for (var index = 0; index < Count; index++) + { + result.At(index, At(index) + scalar); + } + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to add to this one. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Vector other, Vector result) + { + var otherSparse = other as SparseVector; + if (otherSparse == null) + { + base.DoAdd(other, result); + return; + } + + var resultSparse = result as SparseVector; + if (resultSparse == null) + { + base.DoAdd(other, result); + return; + } + + // TODO (ruegg, 2011-10-11): Options to optimize? + + var otherStorage = otherSparse._storage; + if (ReferenceEquals(this, resultSparse)) + { + int i = 0, j = 0; + while (j < otherStorage.ValueCount) + { + if (i >= _storage.ValueCount || _storage.Indices[i] > otherStorage.Indices[j]) + { + var otherValue = otherStorage.Values[j]; + if (otherValue != 0.0) + { + _storage.InsertAtIndexUnchecked(i++, otherStorage.Indices[j], otherValue); + } + + j++; + } + else if (_storage.Indices[i] == otherStorage.Indices[j]) + { + // TODO: result can be zero, remove? + _storage.Values[i++] += otherStorage.Values[j++]; + } + else + { + i++; + } + } + } + else + { + result.Clear(); + int i = 0, j = 0, last = -1; + while (i < _storage.ValueCount || j < otherStorage.ValueCount) + { + if (j >= otherStorage.ValueCount || i < _storage.ValueCount && _storage.Indices[i] <= otherStorage.Indices[j]) + { + var next = _storage.Indices[i]; + if (next != last) + { + last = next; + result.At(next, _storage.Values[i] + otherSparse.At(next)); + } + + i++; + } + else + { + var next = otherStorage.Indices[j]; + if (next != last) + { + last = next; + result.At(next, At(next) + otherStorage.Values[j]); + } + + j++; + } + } + } + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to subtract. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(double scalar, Vector result) + { + DoAdd(-scalar, result); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to subtract from this one. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Vector other, Vector result) + { + if (ReferenceEquals(this, other)) + { + result.Clear(); + return; + } + + var otherSparse = other as SparseVector; + if (otherSparse == null) + { + base.DoSubtract(other, result); + return; + } + + var resultSparse = result as SparseVector; + if (resultSparse == null) + { + base.DoSubtract(other, result); + return; + } + + // TODO (ruegg, 2011-10-11): Options to optimize? + + var otherStorage = otherSparse._storage; + if (ReferenceEquals(this, resultSparse)) + { + int i = 0, j = 0; + while (j < otherStorage.ValueCount) + { + if (i >= _storage.ValueCount || _storage.Indices[i] > otherStorage.Indices[j]) + { + var otherValue = otherStorage.Values[j]; + if (otherValue != 0.0) + { + _storage.InsertAtIndexUnchecked(i++, otherStorage.Indices[j], -otherValue); + } + + j++; + } + else if (_storage.Indices[i] == otherStorage.Indices[j]) + { + // TODO: result can be zero, remove? + _storage.Values[i++] -= otherStorage.Values[j++]; + } + else + { + i++; + } + } + } + else + { + result.Clear(); + int i = 0, j = 0, last = -1; + while (i < _storage.ValueCount || j < otherStorage.ValueCount) + { + if (j >= otherStorage.ValueCount || i < _storage.ValueCount && _storage.Indices[i] <= otherStorage.Indices[j]) + { + var next = _storage.Indices[i]; + if (next != last) + { + last = next; + result.At(next, _storage.Values[i] - otherSparse.At(next)); + } + + i++; + } + else + { + var next = otherStorage.Indices[j]; + if (next != last) + { + last = next; + result.At(next, At(next) - otherStorage.Values[j]); + } + + j++; + } + } + } + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult == null) + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], -_storage.Values[index]); + } + + return; + } + + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new double[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ScaleArray(-1.0d, sparseResult._storage.Values, sparseResult._storage.Values); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to multiply. + /// + /// + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(double scalar, Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult == null) + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], scalar * _storage.Values[index]); + } + } + else + { + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new double[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, sparseResult._storage.Values, sparseResult._storage.Values); + } + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override double DoDotProduct(Vector other) + { + var result = 0d; + if (ReferenceEquals(this, other)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i] * _storage.Values[i]; + } + } + else + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i] * other.At(_storage.Indices[i]); + } + } + + return result; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoModulus(double divisor, Vector result) + { + if (ReferenceEquals(this, result)) + { + for (var index = 0; index < _storage.ValueCount; index++) + { + _storage.Values[index] = Euclid.Modulus(_storage.Values[index], divisor); + } + } + else + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], Euclid.Modulus(_storage.Values[index], divisor)); + } + } + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoRemainder(double divisor, Vector result) + { + if (ReferenceEquals(this, result)) + { + for (var index = 0; index < _storage.ValueCount; index++) + { + _storage.Values[index] %= divisor; + } + } + else + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], _storage.Values[index] % divisor); + } + } + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static SparseVector operator +(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Add(rightSide); + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static SparseVector operator -(SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static SparseVector operator -(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Subtract(rightSide); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The vector to scale. + /// The scalar value. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(SparseVector leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The scalar value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(double leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static double operator *(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a scalar. + /// + /// The vector to divide. + /// The scalar value. + /// The result of the division. + /// If is . + public static SparseVector operator /(SparseVector leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Divide(rightSide); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// of each element of the vector of the given divisor. + /// + /// The vector whose elements we want to compute the modulus of. + /// The divisor to use, + /// The result of the calculation + /// If is . + public static SparseVector operator %(SparseVector leftSide, double rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Remainder(rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + if (_storage.ValueCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var min = Math.Abs(_storage.Values[index]); + for (var i = 1; i < _storage.ValueCount; i++) + { + var test = Math.Abs(_storage.Values[i]); + if (test < min) + { + index = i; + min = test; + } + } + + return _storage.Indices[index]; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + if (_storage.ValueCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var max = Math.Abs(_storage.Values[index]); + for (var i = 1; i < _storage.ValueCount; i++) + { + var test = Math.Abs(_storage.Values[i]); + if (test > max) + { + index = i; + max = test; + } + } + + return _storage.Indices[index]; + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public override int MaximumIndex() + { + if (_storage.ValueCount == 0) + { + return 0; + } + + var index = 0; + var max = _storage.Values[0]; + for (var i = 1; i < _storage.ValueCount; i++) + { + if (max < _storage.Values[i]) + { + index = i; + max = _storage.Values[i]; + } + } + + return _storage.Indices[index]; + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + if (_storage.ValueCount == 0) + { + return 0; + } + + var index = 0; + var min = _storage.Values[0]; + for (var i = 1; i < _storage.ValueCount; i++) + { + if (min > _storage.Values[i]) + { + index = i; + min = _storage.Values[i]; + } + } + + return _storage.Indices[index]; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override double Sum() + { + double result = 0; + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i]; + } + + return result; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + var result = 0d; + for (var i = 0; i < _storage.ValueCount; i++) + { + result += Math.Abs(_storage.Values[i]); + } + + return result; + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(0, _storage.ValueCount, i => Math.Abs(_storage.Values[i]), Math.Max, 0d); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = ( ∑|this[i]|^p )^(1/p) + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (_storage.ValueCount == 0) + { + return 0d; + } + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + var sum = 0d; + for (var index = 0; index < _storage.ValueCount; index++) + { + sum += Math.Pow(Math.Abs(_storage.Values[index]), p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + if (ReferenceEquals(this, other) && ReferenceEquals(this, result)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + _storage.Values[i] *= _storage.Values[i]; + } + } + else + { + base.DoPointwiseMultiply(other, result); + } + } + + #region Parse Functions + + /// + /// Creates a double sparse vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n,n,..', '(n,n,..)', '[n,n,...]', where n is a double. + /// + /// + /// A double sparse vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static SparseVector Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // parsing + var tokens = value.Split(new[] { formatProvider.GetTextInfo().ListSeparator, " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); + var data = tokens.Select(t => double.Parse(t, NumberStyles.Any, formatProvider)).ToList(); + if (data.Count == 0) + throw new FormatException(); + return new SparseVector(SparseVectorStorage.OfEnumerable(data)); + } + + /// + /// Converts the string representation of a real sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out SparseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a real sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out SparseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + + #endregion + + public override string ToTypeString() + { + return string.Format("SparseVector {0}-Double {1:P2} Filled", Count, NonZerosCount / (double)Count); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Double/Vector.cs b/MathNet.Numerics/LinearAlgebra/Double/Vector.cs new file mode 100644 index 0000000..27c4c45 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Double/Vector.cs @@ -0,0 +1,648 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Double; + +/// +/// double version of the class. +/// +[Serializable] +public abstract class Vector : Vector +{ + /// + /// Initializes a new instance of the Vector class. + /// + protected Vector(VectorStorage storage) + : base(storage) + { + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero. + /// + public override void CoerceZero(double threshold) + { + MapInplace(x => Math.Abs(x) < threshold ? 0d : x, Zeros.AllowSkip); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + protected sealed override void DoConjugate(Vector result) + { + if (ReferenceEquals(this, result)) + { + return; + } + + CopyTo(result); + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + Map(x => -x, result, Zeros.AllowSkip); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to add. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(double scalar, Vector result) + { + Map(x => x + scalar, result, Zeros.Include); + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to add to this one. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Vector other, Vector result) + { + Map2((x, y) => x + y, other, result, Zeros.AllowSkip); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to subtract. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(double scalar, Vector result) + { + Map(x => x - scalar, result, Zeros.Include); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to subtract from this one. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Vector other, Vector result) + { + Map2((x, y) => x - y, other, result, Zeros.AllowSkip); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to multiply. + /// + /// + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(double scalar, Vector result) + { + Map(x => x * scalar, result, Zeros.AllowSkip); + } + + /// + /// Divides each element of the vector by a scalar and stores the result in the result vector. + /// + /// + /// The scalar to divide with. + /// + /// + /// The vector to store the result of the division. + /// + protected override void DoDivide(double divisor, Vector result) + { + Map(x => x / divisor, result, divisor == 0.0 ? Zeros.Include : Zeros.AllowSkip); + } + + /// + /// Divides a scalar by each element of the vector and stores the result in the result vector. + /// + /// The scalar to divide. + /// The vector to store the result of the division. + protected override void DoDivideByThis(double dividend, Vector result) + { + Map(x => dividend / x, result, Zeros.Include); + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + Map2((x, y) => x * y, other, result, Zeros.AllowSkip); + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + protected override void DoPointwiseDivide(Vector divisor, Vector result) + { + Map2((x, y) => x / y, divisor, result, Zeros.Include); + } + + /// + /// Pointwise raise this vector to an exponent and store the result into the result vector. + /// + /// The exponent to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(double exponent, Vector result) + { + Map(x => Math.Pow(x, exponent), result, exponent > 0.0 ? Zeros.AllowSkip : Zeros.Include); + } + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Vector exponent, Vector result) + { + Map2(Math.Pow, exponent, result, Zeros.Include); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected override void DoPointwiseModulus(Vector divisor, Vector result) + { + Map2(Euclid.Modulus, divisor, result, Zeros.Include); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected override void DoPointwiseRemainder(Vector divisor, Vector result) + { + Map2(Euclid.Remainder, divisor, result, Zeros.Include); + } + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected override void DoPointwiseExp(Vector result) + { + Map(Math.Exp, result, Zeros.Include); + } + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected override void DoPointwiseLog(Vector result) + { + Map(Math.Log, result, Zeros.Include); + } + + protected override void DoPointwiseAbs(Vector result) + { + Map(Math.Abs, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAcos(Vector result) + { + Map(Math.Acos, result, Zeros.Include); + } + protected override void DoPointwiseAsin(Vector result) + { + Map(Math.Asin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan(Vector result) + { + Map(Math.Atan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan2(Vector other, Vector result) + { + Map2(Math.Atan2, other, result, Zeros.Include); + } + protected override void DoPointwiseAtan2(double scalar, Vector result) + { + Map(x => Math.Atan2(x, scalar), result, Zeros.Include); + } + protected override void DoPointwiseCeiling(Vector result) + { + Map(Math.Ceiling, result, Zeros.AllowSkip); + } + protected override void DoPointwiseCos(Vector result) + { + Map(Math.Cos, result, Zeros.Include); + } + protected override void DoPointwiseCosh(Vector result) + { + Map(Math.Cosh, result, Zeros.Include); + } + protected override void DoPointwiseFloor(Vector result) + { + Map(Math.Floor, result, Zeros.AllowSkip); + } + protected override void DoPointwiseLog10(Vector result) + { + Map(Math.Log10, result, Zeros.Include); + } + protected override void DoPointwiseRound(Vector result) + { + Map(Math.Round, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSign(Vector result) + { + Map(x => (double)Math.Sign(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSin(Vector result) + { + Map(Math.Sin, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSinh(Vector result) + { + Map(Math.Sinh, result, Zeros.AllowSkip); + } + protected override void DoPointwiseSqrt(Vector result) + { + Map(Math.Sqrt, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTan(Vector result) + { + Map(Math.Tan, result, Zeros.AllowSkip); + } + protected override void DoPointwiseTanh(Vector result) + { + Map(Math.Tanh, result, Zeros.AllowSkip); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override double DoDotProduct(Vector other) + { + var dot = 0.0; + for (var i = 0; i < Count; i++) + { + dot += At(i) * other.At(i); + } + + return dot; + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected sealed override double DoConjugateDotProduct(Vector other) + { + return DoDotProduct(other); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoModulus(double divisor, Vector result) + { + Map(x => Euclid.Modulus(x, divisor), result, Zeros.Include); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoModulusByThis(double dividend, Vector result) + { + Map(x => Euclid.Modulus(dividend, x), result, Zeros.Include); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoRemainder(double divisor, Vector result) + { + Map(x => Euclid.Remainder(x, divisor), result, Zeros.Include); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoRemainderByThis(double dividend, Vector result) + { + Map(x => Euclid.Remainder(dividend, x), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(double scalar, Vector result) + { + Map(x => Math.Min(scalar, x), result, scalar >= 0d ? Zeros.AllowSkip : Zeros.Include); + } + + protected override void DoPointwiseMaximum(double scalar, Vector result) + { + Map(x => Math.Max(scalar, x), result, scalar <= 0d ? Zeros.AllowSkip : Zeros.Include); + } + + protected override void DoPointwiseAbsoluteMinimum(double scalar, Vector result) + { + double absolute = Math.Abs(scalar); + Map(x => Math.Min(absolute, Math.Abs(x)), result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(double scalar, Vector result) + { + double absolute = Math.Abs(scalar); + Map(x => Math.Max(absolute, Math.Abs(x)), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(Vector other, Vector result) + { + Map2(Math.Min, other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseMaximum(Vector other, Vector result) + { + Map2(Math.Max, other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMinimum(Vector other, Vector result) + { + Map2((x, y) => Math.Min(Math.Abs(x), Math.Abs(y)), other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Vector other, Vector result) + { + Map2((x, y) => Math.Max(Math.Abs(x), Math.Abs(y)), other, result, Zeros.AllowSkip); + } + + /// + /// Returns the value of the absolute minimum element. + /// + /// The value of the absolute minimum element. + public override double AbsoluteMinimum() + { + return Math.Abs(At(AbsoluteMinimumIndex())); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = Math.Abs(At(index)); + for (var i = 1; i < Count; i++) + { + var test = Math.Abs(At(i)); + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the value of the absolute maximum element. + /// + /// The value of the absolute maximum element. + public override double AbsoluteMaximum() + { + return Math.Abs(At(AbsoluteMaximumIndex())); + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = Math.Abs(At(index)); + for (var i = 1; i < Count; i++) + { + var test = Math.Abs(At(i)); + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override double Sum() + { + var sum = 0.0; + for (var i = 0; i < Count; i++) + { + sum += At(i); + } + + return sum; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + var sum = 0.0; + for (var i = 0; i < Count; i++) + { + sum += Math.Abs(At(i)); + } + + return sum; + } + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public override double L2Norm() + { + return Math.Sqrt(DoDotProduct(this)); + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(0, Count, i => Math.Abs(At(i)), Math.Max, 0d); + } + + /// + /// Computes the p-Norm. + /// + /// + /// The p value. + /// + /// + /// Scalar ret = ( ∑|At(i)|^p )^(1/p) + /// + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + var sum = 0d; + for (var index = 0; index < Count; index++) + { + sum += Math.Pow(Math.Abs(At(index)), p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public override int MaximumIndex() + { + var index = 0; + var max = At(index); + for (var i = 1; i < Count; i++) + { + var test = At(i); + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + var index = 0; + var min = At(index); + for (var i = 1; i < Count; i++) + { + var test = At(i); + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Normalizes this vector to a unit vector with respect to the p-norm. + /// + /// + /// The p value. + /// + /// + /// This vector normalized to a unit vector with respect to the p-norm. + /// + public override Vector Normalize(double p) + { + if (p < 0d) + { + throw new ArgumentOutOfRangeException(nameof(p)); + } + + double norm = Norm(p); + var clone = Clone(); + if (norm == 0d) + { + return clone; + } + + clone.Multiply(1d / norm, clone); + + return clone; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Factorization/Cholesky.cs b/MathNet.Numerics/LinearAlgebra/Factorization/Cholesky.cs new file mode 100644 index 0000000..cb68818 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Factorization/Cholesky.cs @@ -0,0 +1,110 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Factorization; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +/// Supported data types are double, single, , and . +public abstract class Cholesky : ISolver + where T : struct, IEquatable, IFormattable +{ + protected Cholesky(Matrix factor) + { + Factor = factor; + } + + /// + /// Gets the lower triangular form of the Cholesky matrix. + /// + public Matrix Factor { get; private set; } + + /// + /// Gets the determinant of the matrix for which the Cholesky matrix was computed. + /// + public abstract T Determinant { get; } + + /// + /// Gets the log determinant of the matrix for which the Cholesky matrix was computed. + /// + public abstract T DeterminantLn { get; } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + public abstract void Factorize(Matrix matrix); + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public virtual Matrix Solve(Matrix input) + { + var x = Matrix.Build.SameAs(input, input.RowCount, input.ColumnCount, fullyMutable: true); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public abstract void Solve(Matrix input, Matrix result); + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public virtual Vector Solve(Vector input) + { + var x = Vector.Build.SameAs(input, input.Count); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public abstract void Solve(Vector input, Vector result); +} diff --git a/MathNet.Numerics/LinearAlgebra/Factorization/Evd.cs b/MathNet.Numerics/LinearAlgebra/Factorization/Evd.cs new file mode 100644 index 0000000..c2430b8 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Factorization/Evd.cs @@ -0,0 +1,139 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Factorization; + +using Numerics; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +/// Supported data types are double, single, , and . +public abstract class Evd : ISolver +where T : struct, IEquatable, IFormattable +{ + protected Evd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + { + EigenVectors = eigenVectors; + EigenValues = eigenValues; + D = blockDiagonal; + IsSymmetric = isSymmetric; + } + + /// + /// Gets or sets a value indicating whether matrix is symmetric or not + /// + public bool IsSymmetric { get; private set; } + + /// + /// Gets the absolute value of determinant of the square matrix for which the EVD was computed. + /// + public abstract T Determinant { get; } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public abstract int Rank { get; } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public abstract bool IsFullRank { get; } + + /// + /// Gets or sets the eigen values (λ) of matrix in ascending value. + /// + public Vector EigenValues { get; private set; } + + /// + /// Gets or sets eigenvectors. + /// + public Matrix EigenVectors { get; private set; } + + /// + /// Gets or sets the block diagonal eigenvalue matrix. + /// + public Matrix D { get; private set; } + + /// + /// Solves a system of linear equations, AX = B, with A EVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public virtual Matrix Solve(Matrix input) + { + var x = Matrix.Build.SameAs(EigenVectors, EigenVectors.ColumnCount, input.ColumnCount, fullyMutable: true); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, AX = B, with A EVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public abstract void Solve(Matrix input, Matrix result); + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public virtual Vector Solve(Vector input) + { + var x = Vector.Build.SameAs(EigenVectors, EigenVectors.ColumnCount); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public abstract void Solve(Vector input, Vector result); +} diff --git a/MathNet.Numerics/LinearAlgebra/Factorization/GramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Factorization/GramSchmidt.cs new file mode 100644 index 0000000..9523b6b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Factorization/GramSchmidt.cs @@ -0,0 +1,49 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +/// Supported data types are double, single, , and . +public abstract class GramSchmidt : QR +where T : struct, IEquatable, IFormattable +{ + protected GramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull, QRMethod.Full) + { + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Factorization/ISolver.cs b/MathNet.Numerics/LinearAlgebra/Factorization/ISolver.cs new file mode 100644 index 0000000..5aa49b5 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Factorization/ISolver.cs @@ -0,0 +1,67 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Factorization; + +/// +/// Classes that solves a system of linear equations, AX = B. +/// +/// Supported data types are double, single, , and . +public interface ISolver where T : struct, IEquatable, IFormattable +{ + /// + /// Solves a system of linear equations, AX = B. + /// + /// The right hand side Matrix, B. + /// The left hand side Matrix, X. + Matrix Solve(Matrix input); + + /// + /// Solves a system of linear equations, AX = B. + /// + /// The right hand side Matrix, B. + /// The left hand side Matrix, X. + void Solve(Matrix input, Matrix result); + + /// + /// Solves a system of linear equations, Ax = b + /// + /// The right hand side vector, b. + /// The left hand side Vector, x. + Vector Solve(Vector input); + + /// + /// Solves a system of linear equations, Ax = b. + /// + /// The right hand side vector, b. + /// The left hand side Matrix>, x. + void Solve(Vector input, Vector result); +} diff --git a/MathNet.Numerics/LinearAlgebra/Factorization/LU.cs b/MathNet.Numerics/LinearAlgebra/Factorization/LU.cs new file mode 100644 index 0000000..83e89ee --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Factorization/LU.cs @@ -0,0 +1,150 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Factorization; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// In the Math.Net implementation we also store a set of pivot elements for increased +/// numerical stability. The pivot elements encode a permutation matrix P such that P*A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +/// Supported data types are double, single, , and . +public abstract class LU : ISolver + where T : struct, IEquatable, IFormattable +{ + static readonly T One = BuilderInstance.Matrix.One; + + readonly Lazy> _lazyL; + readonly Lazy> _lazyU; + readonly Lazy _lazyP; + + protected readonly Matrix Factors; + protected readonly int[] Pivots; + + protected LU(Matrix factors, int[] pivots) + { + Factors = factors; + Pivots = pivots; + + _lazyL = new Lazy>(ComputeL); + _lazyU = new Lazy>(Factors.UpperTriangle); + _lazyP = new Lazy(() => Permutation.FromInversions(Pivots)); + } + + Matrix ComputeL() + { + var result = Factors.LowerTriangle(); + for (var i = 0; i < result.RowCount; i++) + { + result.At(i, i, One); + } + + return result; + } + + /// + /// Gets the lower triangular factor. + /// + public Matrix L + { + get { return _lazyL.Value; } + } + + /// + /// Gets the upper triangular factor. + /// + public Matrix U + { + get { return _lazyU.Value; } + } + + /// + /// Gets the permutation applied to LU factorization. + /// + public Permutation P + { + get { return _lazyP.Value; } + } + + /// + /// Gets the determinant of the matrix for which the LU factorization was computed. + /// + public abstract T Determinant { get; } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public virtual Matrix Solve(Matrix input) + { + var x = Matrix.Build.SameAs(input, input.RowCount, input.ColumnCount, fullyMutable: true); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public abstract void Solve(Matrix input, Matrix result); + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public virtual Vector Solve(Vector input) + { + var x = Vector.Build.SameAs(input, input.Count); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public abstract void Solve(Vector input, Vector result); + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public abstract Matrix Inverse(); +} diff --git a/MathNet.Numerics/LinearAlgebra/Factorization/QR.cs b/MathNet.Numerics/LinearAlgebra/Factorization/QR.cs new file mode 100644 index 0000000..d6a5fe7 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Factorization/QR.cs @@ -0,0 +1,141 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Factorization; + +/// +/// The type of QR factorization go perform. +/// +public enum QRMethod +{ + /// + /// Compute the full QR factorization of a matrix. + /// + Full = 0, + + /// + /// Compute the thin QR factorization of a matrix. + /// + Thin = 1 +} + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A (m x n) may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// If a factorization is performed, the resulting Q matrix is an m x m matrix +/// and the R matrix is an m x n matrix. If a factorization is performed, the +/// resulting Q matrix is an m x n matrix and the R matrix is an n x n matrix. +/// +/// Supported data types are double, single, , and . +public abstract class QR : ISolver + where T : struct, IEquatable, IFormattable +{ + readonly Lazy> _lazyR; + + protected readonly Matrix FullR; + protected readonly QRMethod Method; + + protected QR(Matrix q, Matrix rFull, QRMethod method) + { + Q = q; + FullR = rFull; + Method = method; + + _lazyR = new Lazy>(FullR.UpperTriangle); + } + + /// + /// Gets or sets orthogonal Q matrix + /// + public Matrix Q { get; private set; } + + /// + /// Gets the upper triangular factor R. + /// + public Matrix R + { + get { return _lazyR.Value; } + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public abstract T Determinant { get; } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public abstract bool IsFullRank { get; } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public virtual Matrix Solve(Matrix input) + { + var x = Matrix.Build.SameAs(input, FullR.ColumnCount, input.ColumnCount, fullyMutable: true); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public abstract void Solve(Matrix input, Matrix result); + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public virtual Vector Solve(Vector input) + { + var x = Vector.Build.SameAs(input, FullR.ColumnCount); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public abstract void Solve(Vector input, Vector result); +} diff --git a/MathNet.Numerics/LinearAlgebra/Factorization/Svd.cs b/MathNet.Numerics/LinearAlgebra/Factorization/Svd.cs new file mode 100644 index 0000000..2511d64 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Factorization/Svd.cs @@ -0,0 +1,183 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Factorization; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD). +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +/// Supported data types are double, single, , and . +public abstract class Svd : ISolver + where T : struct, IEquatable, IFormattable +{ + readonly Lazy> _lazyW; + + /// Indicating whether U and VT matrices have been computed during SVD factorization. + protected readonly bool VectorsComputed; + + protected Svd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + { + S = s; + U = u; + VT = vt; + + VectorsComputed = vectorsComputed; + + _lazyW = new Lazy>(ComputeW); + } + + Matrix ComputeW() + { + var rows = U.RowCount; + var columns = VT.ColumnCount; + var result = Matrix.Build.SameAs(U, rows, columns); + for (var i = 0; i < rows; i++) + { + for (var j = 0; j < columns; j++) + { + if (i == j) + { + result.At(i, i, S[i]); + } + } + } + + return result; + } + + /// + /// Gets the singular values (Σ) of matrix in ascending value. + /// + public Vector S { get; private set; } + + /// + /// Gets the left singular vectors (U - m-by-m unitary matrix) + /// + public Matrix U { get; private set; } + + /// + /// Gets the transpose right singular vectors (transpose of V, an n-by-n unitary matrix) + /// + public Matrix VT { get; private set; } + + /// + /// Returns the singular values as a diagonal . + /// + /// The singular values as a diagonal . + public Matrix W + { + get { return _lazyW.Value; } + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public abstract int Rank { get; } + + /// + /// Gets the two norm of the . + /// + /// The 2-norm of the . + public abstract double L2Norm { get; } + + /// + /// Gets the condition number max(S) / min(S) + /// + /// The condition number. + public abstract T ConditionNumber { get; } + + /// + /// Gets the determinant of the square matrix for which the SVD was computed. + /// + public abstract T Determinant { get; } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public virtual Matrix Solve(Matrix input) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + var x = Matrix.Build.SameAs(U, VT.ColumnCount, input.ColumnCount, fullyMutable: true); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public abstract void Solve(Matrix input, Matrix result); + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public virtual Vector Solve(Vector input) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + var x = Vector.Build.SameAs(U, VT.ColumnCount); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public abstract void Solve(Vector input, Vector result); +} diff --git a/MathNet.Numerics/LinearAlgebra/Matrix.Arithmetic.cs b/MathNet.Numerics/LinearAlgebra/Matrix.Arithmetic.cs new file mode 100644 index 0000000..fbfa243 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Matrix.Arithmetic.cs @@ -0,0 +1,2423 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra; + +/// +/// Defines the base class for Matrix classes. +/// +public abstract partial class Matrix +{ + /// + /// The value of 1.0. + /// + public static readonly T One = BuilderInstance.Matrix.One; + + /// + /// The value of 0.0. + /// + public static readonly T Zero = BuilderInstance.Matrix.Zero; + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected abstract void DoNegate(Matrix result); + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected abstract void DoConjugate(Matrix result); + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected abstract void DoAdd(T scalar, Matrix result); + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the two matrices don't have the same dimensions. + protected abstract void DoAdd(Matrix other, Matrix result); + + /// + /// Subtracts a scalar from each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected abstract void DoSubtract(T scalar, Matrix result); + + /// + /// Subtracts each element of the matrix from a scalar and stores the result in the result matrix. + /// + /// The scalar to subtract from. + /// The matrix to store the result of the subtraction. + protected void DoSubtractFrom(T scalar, Matrix result) + { + DoNegate(result); + result.DoAdd(scalar, result); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + protected abstract void DoSubtract(Matrix other, Matrix result); + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected abstract void DoMultiply(T scalar, Matrix result); + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected abstract void DoMultiply(Vector rightSide, Vector result); + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected abstract void DoMultiply(Matrix other, Matrix result); + + /// + /// Multiplies this matrix with the transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected abstract void DoTransposeAndMultiply(Matrix other, Matrix result); + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected abstract void DoConjugateTransposeAndMultiply(Matrix other, Matrix result); + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected abstract void DoTransposeThisAndMultiply(Vector rightSide, Vector result); + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected abstract void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result); + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected abstract void DoTransposeThisAndMultiply(Matrix other, Matrix result); + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected abstract void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result); + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar denominator to use. + /// The matrix to store the result of the division. + protected abstract void DoDivide(T divisor, Matrix result); + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar numerator to use. + /// The matrix to store the result of the division. + protected abstract void DoDivideByThis(T dividend, Matrix result); + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected abstract void DoModulus(T divisor, Matrix result); + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected abstract void DoModulusByThis(T dividend, Matrix result); + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected abstract void DoRemainder(T divisor, Matrix result); + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected abstract void DoRemainderByThis(T dividend, Matrix result); + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected abstract void DoPointwiseMultiply(Matrix other, Matrix result); + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use. + /// The matrix to store the result of the pointwise division. + protected abstract void DoPointwiseDivide(Matrix divisor, Matrix result); + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The matrix to store the result of the pointwise power. + protected abstract void DoPointwisePower(T exponent, Matrix result); + + /// + /// Pointwise raise this matrix to an exponent matrix and store the result into the result matrix. + /// + /// The exponent matrix to raise this matrix values to. + /// The matrix to store the result of the pointwise power. + protected abstract void DoPointwisePower(Matrix exponent, Matrix result); + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected abstract void DoPointwiseModulus(Matrix divisor, Matrix result); + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected abstract void DoPointwiseRemainder(Matrix divisor, Matrix result); + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected abstract void DoPointwiseExp(Matrix result); + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected abstract void DoPointwiseLog(Matrix result); + + protected abstract void DoPointwiseAbs(Matrix result); + protected abstract void DoPointwiseAcos(Matrix result); + protected abstract void DoPointwiseAsin(Matrix result); + protected abstract void DoPointwiseAtan(Matrix result); + protected abstract void DoPointwiseCeiling(Matrix result); + protected abstract void DoPointwiseCos(Matrix result); + protected abstract void DoPointwiseCosh(Matrix result); + protected abstract void DoPointwiseFloor(Matrix result); + protected abstract void DoPointwiseLog10(Matrix result); + protected abstract void DoPointwiseRound(Matrix result); + protected abstract void DoPointwiseSign(Matrix result); + protected abstract void DoPointwiseSin(Matrix result); + protected abstract void DoPointwiseSinh(Matrix result); + protected abstract void DoPointwiseSqrt(Matrix result); + protected abstract void DoPointwiseTan(Matrix result); + protected abstract void DoPointwiseTanh(Matrix result); + protected abstract void DoPointwiseAtan2(Matrix other, Matrix result); + protected abstract void DoPointwiseMinimum(T scalar, Matrix result); + protected abstract void DoPointwiseMinimum(Matrix other, Matrix result); + protected abstract void DoPointwiseMaximum(T scalar, Matrix result); + protected abstract void DoPointwiseMaximum(Matrix other, Matrix result); + protected abstract void DoPointwiseAbsoluteMinimum(T scalar, Matrix result); + protected abstract void DoPointwiseAbsoluteMinimum(Matrix other, Matrix result); + protected abstract void DoPointwiseAbsoluteMaximum(T scalar, Matrix result); + protected abstract void DoPointwiseAbsoluteMaximum(Matrix other, Matrix result); + + /// + /// Adds a scalar to each element of the matrix. + /// + /// The scalar to add. + /// The result of the addition. + /// If the two matrices don't have the same dimensions. + public Matrix Add(T scalar) + { + if (scalar.Equals(Zero)) + { + return Clone(); + } + + var result = Build.SameAs(this); + DoAdd(scalar, result); + return result; + } + + /// + /// Adds a scalar to each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + /// If the two matrices don't have the same dimensions. + public void Add(T scalar, Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (scalar.Equals(Zero)) + { + CopyTo(result); + return; + } + + DoAdd(scalar, result); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The result of the addition. + /// If the two matrices don't have the same dimensions. + public Matrix Add(Matrix other) + { + if (other.RowCount != RowCount || other.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, other); + } + + var result = Build.SameAs(this, other, RowCount, ColumnCount); + DoAdd(other, result); + return result; + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the two matrices don't have the same dimensions. + public void Add(Matrix other, Matrix result) + { + if (other.RowCount != RowCount || other.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, other, "other"); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + DoAdd(other, result); + } + + /// + /// Subtracts a scalar from each element of the matrix. + /// + /// The scalar to subtract. + /// A new matrix containing the subtraction of this matrix and the scalar. + public Matrix Subtract(T scalar) + { + if (scalar.Equals(Zero)) + { + return Clone(); + } + + var result = Build.SameAs(this); + DoSubtract(scalar, result); + return result; + } + + /// + /// Subtracts a scalar from each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + /// If this matrix and are not the same size. + public void Subtract(T scalar, Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (scalar.Equals(Zero)) + { + CopyTo(result); + return; + } + + DoSubtract(scalar, result); + } + + /// + /// Subtracts each element of the matrix from a scalar. + /// + /// The scalar to subtract from. + /// A new matrix containing the subtraction of the scalar and this matrix. + public Matrix SubtractFrom(T scalar) + { + var result = Build.SameAs(this); + DoSubtractFrom(scalar, result); + return result; + } + + /// + /// Subtracts each element of the matrix from a scalar and stores the result in the result matrix. + /// + /// The scalar to subtract from. + /// The matrix to store the result of the subtraction. + /// If this matrix and are not the same size. + public void SubtractFrom(T scalar, Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + DoSubtractFrom(scalar, result); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The result of the subtraction. + /// If the two matrices don't have the same dimensions. + public Matrix Subtract(Matrix other) + { + if (other.RowCount != RowCount || other.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, other); + } + + var result = Build.SameAs(this, other, RowCount, ColumnCount); + DoSubtract(other, result); + return result; + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + /// If the two matrices don't have the same dimensions. + public void Subtract(Matrix other, Matrix result) + { + if (other.RowCount != RowCount || other.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, other, "other"); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + DoSubtract(other, result); + } + + /// + /// Multiplies each element of this matrix with a scalar. + /// + /// The scalar to multiply with. + /// The result of the multiplication. + public Matrix Multiply(T scalar) + { + if (scalar.Equals(One)) + { + return Clone(); + } + + if (scalar.Equals(Zero)) + { + return Build.SameAs(this); + } + + var result = Build.SameAs(this); + DoMultiply(scalar, result); + return result; + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + /// If the result matrix's dimensions are not the same as this matrix. + public void Multiply(T scalar, Matrix result) + { + if (result.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, nameof(result)); + } + + if (result.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, nameof(result)); + } + + if (scalar.Equals(One)) + { + CopyTo(result); + return; + } + + if (scalar.Equals(Zero)) + { + result.Clear(); + return; + } + + DoMultiply(scalar, result); + } + + /// + /// Divides each element of this matrix with a scalar. + /// + /// The scalar to divide with. + /// The result of the division. + public Matrix Divide(T scalar) + { + if (scalar.Equals(One)) + { + return Clone(); + } + + if (scalar.Equals(Zero)) + { + throw new DivideByZeroException(); + } + + var result = Build.SameAs(this); + DoDivide(scalar, result); + return result; + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + /// If the result matrix's dimensions are not the same as this matrix. + public void Divide(T scalar, Matrix result) + { + if (result.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, nameof(result)); + } + + if (result.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, nameof(result)); + } + + if (scalar.Equals(One)) + { + CopyTo(result); + return; + } + + if (scalar.Equals(Zero)) + { + throw new DivideByZeroException(); + } + + DoDivide(scalar, result); + } + + /// + /// Divides a scalar by each element of the matrix. + /// + /// The scalar to divide. + /// The result of the division. + public Matrix DivideByThis(T scalar) + { + var result = Build.SameAs(this); + DoDivideByThis(scalar, result); + return result; + } + + /// + /// Divides a scalar by each element of the matrix and places results into the result matrix. + /// + /// The scalar to divide. + /// The matrix to store the result of the division. + /// If the result matrix's dimensions are not the same as this matrix. + public void DivideByThis(T scalar, Matrix result) + { + if (result.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, nameof(result)); + } + + if (result.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, nameof(result)); + } + + DoDivideByThis(scalar, result); + } + + /// + /// Multiplies this matrix by a vector and returns the result. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If this.ColumnCount != rightSide.Count. + public Vector Multiply(Vector rightSide) + { + if (ColumnCount != rightSide.Count) + { + throw DimensionsDontMatch(this, rightSide, "rightSide"); + } + + var ret = Vector.Build.SameAs(this, rightSide, RowCount); + DoMultiply(rightSide, ret); + return ret; + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If result.Count != this.RowCount. + /// If this.ColumnCount != .Count. + public void Multiply(Vector rightSide, Vector result) + { + if (ColumnCount != rightSide.Count) + { + throw DimensionsDontMatch(this, rightSide, "rightSide"); + } + + if (RowCount != result.Count) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(rightSide, result)) + { + var tmp = Vector.Build.SameAs(result); + DoMultiply(rightSide, tmp); + tmp.CopyTo(result); + } + else + { + DoMultiply(rightSide, result); + } + } + + /// + /// Left multiply a matrix with a vector ( = vector * matrix ). + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If this.RowCount != .Count. + public Vector LeftMultiply(Vector leftSide) + { + if (RowCount != leftSide.Count) + { + throw DimensionsDontMatch(this, leftSide, "leftSide"); + } + + var ret = Vector.Build.SameAs(this, leftSide, ColumnCount); + DoLeftMultiply(leftSide, ret); + return ret; + } + + /// + /// Left multiply a matrix with a vector ( = vector * matrix ) and place the result in the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If result.Count != this.ColumnCount. + /// If this.RowCount != .Count. + public void LeftMultiply(Vector leftSide, Vector result) + { + if (RowCount != leftSide.Count) + { + throw DimensionsDontMatch(this, leftSide, "leftSide"); + } + + if (ColumnCount != result.Count) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(leftSide, result)) + { + var tmp = Vector.Build.SameAs(result); + DoLeftMultiply(leftSide, tmp); + tmp.CopyTo(result); + } + else + { + DoLeftMultiply(leftSide, result); + } + } + + /// + /// Left multiply a matrix with a vector ( = vector * matrix ) and place the result in the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected void DoLeftMultiply(Vector leftSide, Vector result) + { + DoTransposeThisAndMultiply(leftSide, result); + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If this.Columns != other.Rows. + /// If the result matrix's dimensions are not the this.Rows x other.Columns. + public void Multiply(Matrix other, Matrix result) + { + if (ColumnCount != other.RowCount || result.RowCount != RowCount || result.ColumnCount != other.ColumnCount) + { + throw DimensionsDontMatch(this, other, result); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Build.SameAs(result); + DoMultiply(other, tmp); + tmp.CopyTo(result); + } + else + { + DoMultiply(other, result); + } + } + + /// + /// Multiplies this matrix with another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Columns != other.Rows. + /// The result of the multiplication. + public Matrix Multiply(Matrix other) + { + if (ColumnCount != other.RowCount) + { + throw DimensionsDontMatch(this, other); + } + + var result = Build.SameAs(this, other, RowCount, other.ColumnCount); + DoMultiply(other, result); + return result; + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If this.Columns != other.ColumnCount. + /// If the result matrix's dimensions are not the this.RowCount x other.RowCount. + public void TransposeAndMultiply(Matrix other, Matrix result) + { + if (ColumnCount != other.ColumnCount || result.RowCount != RowCount || result.ColumnCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, result); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Build.SameAs(result); + DoTransposeAndMultiply(other, tmp); + tmp.CopyTo(result); + } + else + { + DoTransposeAndMultiply(other, result); + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Columns != other.ColumnCount. + /// The result of the multiplication. + public Matrix TransposeAndMultiply(Matrix other) + { + if (ColumnCount != other.ColumnCount) + { + throw DimensionsDontMatch(this, other); + } + + var result = Build.SameAs(this, other, RowCount, other.RowCount); + DoTransposeAndMultiply(other, result); + return result; + } + + /// + /// Multiplies the transpose of this matrix by a vector and returns the result. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If this.RowCount != rightSide.Count. + public Vector TransposeThisAndMultiply(Vector rightSide) + { + if (RowCount != rightSide.Count) + { + throw DimensionsDontMatch(this, rightSide, "rightSide"); + } + + var result = Vector.Build.SameAs(this, rightSide, ColumnCount); + DoTransposeThisAndMultiply(rightSide, result); + return result; + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If result.Count != this.ColumnCount. + /// If this.RowCount != .Count. + public void TransposeThisAndMultiply(Vector rightSide, Vector result) + { + if (RowCount != rightSide.Count) + { + throw DimensionsDontMatch(this, rightSide, "rightSide"); + } + + if (ColumnCount != result.Count) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(rightSide, result)) + { + var tmp = Vector.Build.SameAs(result); + DoTransposeThisAndMultiply(rightSide, tmp); + tmp.CopyTo(result); + } + else + { + DoTransposeThisAndMultiply(rightSide, result); + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If this.Rows != other.RowCount. + /// If the result matrix's dimensions are not the this.ColumnCount x other.ColumnCount. + public void TransposeThisAndMultiply(Matrix other, Matrix result) + { + if (RowCount != other.RowCount || result.RowCount != ColumnCount || result.ColumnCount != other.ColumnCount) + { + throw DimensionsDontMatch(this, other, result); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Build.SameAs(result); + DoTransposeThisAndMultiply(other, tmp); + tmp.CopyTo(result); + } + else + { + DoTransposeThisAndMultiply(other, result); + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Rows != other.RowCount. + /// The result of the multiplication. + public Matrix TransposeThisAndMultiply(Matrix other) + { + if (RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other); + } + + var result = Build.SameAs(this, other, ColumnCount, other.ColumnCount); + DoTransposeThisAndMultiply(other, result); + return result; + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If this.Columns != other.ColumnCount. + /// If the result matrix's dimensions are not the this.RowCount x other.RowCount. + public void ConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + if (ColumnCount != other.ColumnCount || result.RowCount != RowCount || result.ColumnCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, result); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Build.SameAs(result); + DoConjugateTransposeAndMultiply(other, tmp); + tmp.CopyTo(result); + } + else + { + DoConjugateTransposeAndMultiply(other, result); + } + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Columns != other.ColumnCount. + /// The result of the multiplication. + public Matrix ConjugateTransposeAndMultiply(Matrix other) + { + if (ColumnCount != other.ColumnCount) + { + throw DimensionsDontMatch(this, other); + } + + var result = Build.SameAs(this, other, RowCount, other.RowCount); + DoConjugateTransposeAndMultiply(other, result); + return result; + } + + /// + /// Multiplies the conjugate transpose of this matrix by a vector and returns the result. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If this.RowCount != rightSide.Count. + public Vector ConjugateTransposeThisAndMultiply(Vector rightSide) + { + if (RowCount != rightSide.Count) + { + throw DimensionsDontMatch(this, rightSide, "rightSide"); + } + + var result = Vector.Build.SameAs(this, rightSide, ColumnCount); + DoConjugateTransposeThisAndMultiply(rightSide, result); + return result; + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If result.Count != this.ColumnCount. + /// If this.RowCount != .Count. + public void ConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + if (RowCount != rightSide.Count) + { + throw DimensionsDontMatch(this, rightSide, "rightSide"); + } + + if (ColumnCount != result.Count) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(rightSide, result)) + { + var tmp = Vector.Build.SameAs(result); + DoConjugateTransposeThisAndMultiply(rightSide, tmp); + tmp.CopyTo(result); + } + else + { + DoConjugateTransposeThisAndMultiply(rightSide, result); + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If this.Rows != other.RowCount. + /// If the result matrix's dimensions are not the this.ColumnCount x other.ColumnCount. + public void ConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + if (RowCount != other.RowCount || result.RowCount != ColumnCount || result.ColumnCount != other.ColumnCount) + { + throw DimensionsDontMatch(this, other, result); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Build.SameAs(result); + DoConjugateTransposeThisAndMultiply(other, tmp); + tmp.CopyTo(result); + } + else + { + DoConjugateTransposeThisAndMultiply(other, result); + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Rows != other.RowCount. + /// The result of the multiplication. + public Matrix ConjugateTransposeThisAndMultiply(Matrix other) + { + if (RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other); + } + + var result = Build.SameAs(this, other, ColumnCount, other.ColumnCount); + DoConjugateTransposeThisAndMultiply(other, result); + return result; + } + + private static Matrix IntPower(int exponent, Matrix x, Matrix y, Matrix work) + { + // We try to be smart about not allocating more matrices than needed + // and to minimize the number of multiplications (not optimal on either though) + + // TODO: For large or non-integer exponents we could diagonalize the matrix with + // a similarity transform (eigenvalue decomposition) + + // return y*x + if (exponent == 1) + { + // return x + if (y == null) + { + return x; + } + + if (work == null) + work = y.Multiply(x); + else + y.Multiply(x, work); + return work; + } + + // return y*x^2 + if (exponent == 2) + { + if (work == null) + work = x.Multiply(x); + else + x.Multiply(x, work); + + // return x^2 + if (y == null) + { + return work; + } + + y.Multiply(work, x); + return x; + } + + // recursive n <-- n/2, y <-- y, x <-- x^2 + if (exponent.IsEven()) + { + // we store the new x in work, keep the y as is and reuse the old x as new work matrix. + if (work == null) + work = x.Multiply(x); + else + x.Multiply(x, work); + return IntPower(exponent / 2, work, y, x); + } + + // recursive n <-- (n-1)/2, y <-- x, x <-- x^2 + if (y == null) + { + // we store the new x in work, directly use the old x as y. no work matrix. + if (work == null) + work = x.Multiply(x); + else + x.Multiply(x, work); + return IntPower((exponent - 1) / 2, work, x, null); + } + + // recursive n <-- (n-1)/2, y <-- y*x, x <-- x^2 + // we store the new y in work, the new x in y, and reuse the old x as work + if (work == null) + work = y.Multiply(x); + else + y.Multiply(x, work); + x.Multiply(x, y); + return IntPower((exponent - 1) / 2, y, work, x); + } + + /// + /// Raises this square matrix to a positive integer exponent and places the results into the result matrix. + /// + /// The positive integer exponent to raise the matrix to. + /// The result of the power. + public void Power(int exponent, Matrix result) + { + if (RowCount != ColumnCount || result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result); + } + + if (exponent < 0) + { + throw new ArgumentException(Resources.ArgumentNotNegative); + } + + if (exponent == 0) + { + Build.DiagonalIdentity(RowCount, ColumnCount).CopyTo(result); + return; + } + + if (exponent == 1) + { + CopyTo(result); + return; + } + + if (exponent == 2) + { + Multiply(this, result); + return; + } + + var res = IntPower(exponent, Clone(), null, result); + if (!ReferenceEquals(res, result)) + { + res.CopyTo(result); + } + } + + /// + /// Multiplies this square matrix with another matrix and returns the result. + /// + /// The positive integer exponent to raise the matrix to. + public Matrix Power(int exponent) + { + if (RowCount != ColumnCount) + throw new ArgumentException(Resources.ArgumentMatrixSquare); + if (exponent < 0) + throw new ArgumentException(Resources.ArgumentNotNegative); + + if (exponent == 0) + return Build.DiagonalIdentity(RowCount, ColumnCount); + if (exponent == 1) + return this; + if (exponent == 2) + return Multiply(this); + + return IntPower(exponent, Clone(), null, null); + } + + /// + /// Negate each element of this matrix. + /// + /// A matrix containing the negated values. + public Matrix Negate() + { + var result = Build.SameAs(this); + DoNegate(result); + return result; + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + /// if the result matrix's dimensions are not the same as this matrix. + public void Negate(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result); + } + + DoNegate(result); + } + + /// + /// Complex conjugate each element of this matrix. + /// + /// A matrix containing the conjugated values. + public Matrix Conjugate() + { + var result = Build.SameAs(this); + DoConjugate(result); + return result; + } + + /// + /// Complex conjugate each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + /// if the result matrix's dimensions are not the same as this matrix. + public void Conjugate(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result); + } + + DoConjugate(result); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the matrix. + /// + /// The scalar denominator to use. + /// A matrix containing the results. + public Matrix Modulus(T divisor) + { + var result = Build.SameAs(this); + DoModulus(divisor, result); + return result; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + public void Modulus(T divisor, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoModulus(divisor, result); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the matrix. + /// + /// The scalar numerator to use. + /// A matrix containing the results. + public Matrix ModulusByThis(T dividend) + { + var result = Build.SameAs(this); + DoModulusByThis(dividend, result); + return result; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the matrix. + /// + /// The scalar numerator to use. + /// Matrix to store the results in. + public void ModulusByThis(T dividend, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoModulusByThis(dividend, result); + } + + /// + /// Computes the remainder (matrix % divisor), where the result has the sign of the dividend, + /// for each element of the matrix. + /// + /// The scalar denominator to use. + /// A matrix containing the results. + public Matrix Remainder(T divisor) + { + var result = Build.SameAs(this); + DoRemainder(divisor, result); + return result; + } + + /// + /// Computes the remainder (matrix % divisor), where the result has the sign of the dividend, + /// for each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + public void Remainder(T divisor, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoRemainder(divisor, result); + } + + /// + /// Computes the remainder (dividend % matrix), where the result has the sign of the dividend, + /// for each element of the matrix. + /// + /// The scalar numerator to use. + /// A matrix containing the results. + public Matrix RemainderByThis(T dividend) + { + var result = Build.SameAs(this); + DoRemainderByThis(dividend, result); + return result; + } + + /// + /// Computes the remainder (dividend % matrix), where the result has the sign of the dividend, + /// for each element of the matrix. + /// + /// The scalar numerator to use. + /// Matrix to store the results in. + public void RemainderByThis(T dividend, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoRemainderByThis(dividend, result); + } + + /// + /// Pointwise multiplies this matrix with another matrix. + /// + /// The matrix to pointwise multiply with this one. + /// If this matrix and are not the same size. + /// A new matrix that is the pointwise multiplication of this matrix and . + public Matrix PointwiseMultiply(Matrix other) + { + if (ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, "other"); + } + + var result = Build.SameAs(this, other); + DoPointwiseMultiply(other, result); + return result; + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + /// If this matrix and are not the same size. + /// If this matrix and are not the same size. + public void PointwiseMultiply(Matrix other, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, result); + } + + DoPointwiseMultiply(other, result); + } + + /// + /// Pointwise divide this matrix by another matrix. + /// + /// The pointwise denominator matrix to use. + /// If this matrix and are not the same size. + /// A new matrix that is the pointwise division of this matrix and . + public Matrix PointwiseDivide(Matrix divisor) + { + if (ColumnCount != divisor.ColumnCount || RowCount != divisor.RowCount) + { + throw DimensionsDontMatch(this, divisor); + } + + var result = Build.SameAs(this, divisor); + DoPointwiseDivide(divisor, result); + return result; + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use. + /// The matrix to store the result of the pointwise division. + /// If this matrix and are not the same size. + /// If this matrix and are not the same size. + public void PointwiseDivide(Matrix divisor, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != divisor.ColumnCount || RowCount != divisor.RowCount) + { + throw DimensionsDontMatch(this, divisor, result); + } + + DoPointwiseDivide(divisor, result); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + public Matrix PointwisePower(T exponent) + { + var result = Build.SameAs(this); + DoPointwisePower(exponent, result); + return result; + } + + /// + /// Pointwise raise this matrix to an exponent. + /// + /// The exponent to raise this matrix values to. + /// The matrix to store the result into. + /// If this matrix and are not the same size. + public void PointwisePower(T exponent, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoPointwisePower(exponent, result); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + public Matrix PointwisePower(Matrix exponent) + { + if (ColumnCount != exponent.ColumnCount || RowCount != exponent.RowCount) + { + throw DimensionsDontMatch(this, exponent); + } + + var result = Build.SameAs(this); + DoPointwisePower(exponent, result); + return result; + } + + /// + /// Pointwise raise this matrix to an exponent. + /// + /// The exponent to raise this matrix values to. + /// The matrix to store the result into. + /// If this matrix and are not the same size. + public void PointwisePower(Matrix exponent, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != exponent.ColumnCount || RowCount != exponent.RowCount) + { + throw DimensionsDontMatch(this, exponent, result); + } + + DoPointwisePower(exponent, result); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this matrix by another matrix. + /// + /// The pointwise denominator matrix to use. + /// If this matrix and are not the same size. + public Matrix PointwiseModulus(Matrix divisor) + { + if (ColumnCount != divisor.ColumnCount || RowCount != divisor.RowCount) + { + throw DimensionsDontMatch(this, divisor); + } + + var result = Build.SameAs(this, divisor); + DoPointwiseModulus(divisor, result); + return result; + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this matrix by another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use. + /// The matrix to store the result of the pointwise modulus. + /// If this matrix and are not the same size. + /// If this matrix and are not the same size. + public void PointwiseModulus(Matrix divisor, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != divisor.ColumnCount || RowCount != divisor.RowCount) + { + throw DimensionsDontMatch(this, divisor, result); + } + + DoPointwiseModulus(divisor, result); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this matrix by another matrix. + /// + /// The pointwise denominator matrix to use. + /// If this matrix and are not the same size. + public Matrix PointwiseRemainder(Matrix divisor) + { + if (ColumnCount != divisor.ColumnCount || RowCount != divisor.RowCount) + { + throw DimensionsDontMatch(this, divisor); + } + + var result = Build.SameAs(this, divisor); + DoPointwiseRemainder(divisor, result); + return result; + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this matrix by another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use. + /// The matrix to store the result of the pointwise remainder. + /// If this matrix and are not the same size. + /// If this matrix and are not the same size. + public void PointwiseRemainder(Matrix divisor, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != divisor.ColumnCount || RowCount != divisor.RowCount) + { + throw DimensionsDontMatch(this, divisor, result); + } + + DoPointwiseRemainder(divisor, result); + } + + /// + /// Helper function to apply a unary function to a matrix. The function + /// f modifies the matrix given to it in place. Before its + /// called, a copy of the 'this' matrix is first created, then passed to + /// f. The copy is then returned as the result + /// + /// Function which takes a matrix, modifies it in place and returns void + /// New instance of matrix which is the result + protected Matrix PointwiseUnary(Action> f) + { + var result = Build.SameAs(this); + f(result); + return result; + } + + /// + /// Helper function to apply a unary function which modifies a matrix + /// in place. + /// + /// Function which takes a matrix, modifies it in place and returns void + /// The matrix to be passed to f and where the result is to be stored + /// If this vector and are not the same size. + protected void PointwiseUnary(Action> f, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + f(result); + } + + /// + /// Helper function to apply a binary function which takes two matrices + /// and modifies the latter in place. A copy of the "this" matrix is + /// first made and then passed to f together with the other matrix. The + /// copy is then returned as the result + /// + /// Function which takes two matrices, modifies the second in place and returns void + /// The other matrix to be passed to the function as argument. It is not modified + /// The resulting matrix + /// If this matrix and are not the same dimension. + protected Matrix PointwiseBinary(Action, Matrix> f, Matrix other) + { + if (ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other); + } + + var result = Build.SameAs(this, other); + f(other, result); + return result; + } + + /// + /// Helper function to apply a binary function which takes two matrices + /// and modifies the second one in place + /// + /// Function which takes two matrices, modifies the second in place and returns void + /// The other matrix to be passed to the function as argument. It is not modified + /// The matrix to store the result. + /// The resulting matrix + /// If this matrix and are not the same dimension. + protected void PointwiseBinary(Action, Matrix> f, Matrix other, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, result); + } + + f(other, result); + } + + /// + /// Pointwise applies the exponent function to each value. + /// + public Matrix PointwiseExp() + { + return PointwiseUnary(DoPointwiseExp); + } + + /// + /// Pointwise applies the exponent function to each value. + /// + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseExp(Matrix result) + { + PointwiseUnary(DoPointwiseExp, result); + } + + /// + /// Pointwise applies the natural logarithm function to each value. + /// + public Matrix PointwiseLog() + { + return PointwiseUnary(DoPointwiseLog); + } + + /// + /// Pointwise applies the natural logarithm function to each value. + /// + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseLog(Matrix result) + { + PointwiseUnary(DoPointwiseLog, result); + } + + /// + /// Pointwise applies the abs function to each value + /// + public Matrix PointwiseAbs() + { + return PointwiseUnary(DoPointwiseAbs); + } + + /// + /// Pointwise applies the abs function to each value + /// + /// The vector to store the result + public void PointwiseAbs(Matrix result) + { + PointwiseUnary(DoPointwiseAbs, result); + } + + /// + /// Pointwise applies the acos function to each value + /// + public Matrix PointwiseAcos() + { + return PointwiseUnary(DoPointwiseAcos); + } + + /// + /// Pointwise applies the acos function to each value + /// + /// The vector to store the result + public void PointwiseAcos(Matrix result) + { + PointwiseUnary(DoPointwiseAcos, result); + } + + /// + /// Pointwise applies the asin function to each value + /// + public Matrix PointwiseAsin() + { + return PointwiseUnary(DoPointwiseAsin); + } + + /// + /// Pointwise applies the asin function to each value + /// + /// The vector to store the result + public void PointwiseAsin(Matrix result) + { + PointwiseUnary(DoPointwiseAsin, result); + } + + /// + /// Pointwise applies the atan function to each value + /// + public Matrix PointwiseAtan() + { + return PointwiseUnary(DoPointwiseAtan); + } + + /// + /// Pointwise applies the atan function to each value + /// + /// The vector to store the result + public void PointwiseAtan(Matrix result) + { + PointwiseUnary(DoPointwiseAtan, result); + } + + /// + /// Pointwise applies the atan2 function to each value of the current + /// matrix and a given other matrix being the 'x' of atan2 and the + /// 'this' matrix being the 'y' + /// + /// + /// + public Matrix PointwiseAtan2(Matrix other) + { + return PointwiseBinary(DoPointwiseAtan2, other); + } + + /// + /// Pointwise applies the atan2 function to each value of the current + /// matrix and a given other matrix being the 'x' of atan2 and the + /// 'this' matrix being the 'y' + /// + /// The other matrix 'y' + /// The matrix with the result and 'x' + /// + public void PointwiseAtan2(Matrix other, Matrix result) + { + PointwiseBinary(DoPointwiseAtan2, other, result); + } + + /// + /// Pointwise applies the ceiling function to each value + /// + public Matrix PointwiseCeiling() + { + return PointwiseUnary(DoPointwiseCeiling); + } + + /// + /// Pointwise applies the ceiling function to each value + /// + /// The vector to store the result + public void PointwiseCeiling(Matrix result) + { + PointwiseUnary(DoPointwiseCeiling, result); + } + + /// + /// Pointwise applies the cos function to each value + /// + public Matrix PointwiseCos() + { + return PointwiseUnary(DoPointwiseCos); + } + + /// + /// Pointwise applies the cos function to each value + /// + /// The vector to store the result + public void PointwiseCos(Matrix result) + { + PointwiseUnary(DoPointwiseCos, result); + } + + /// + /// Pointwise applies the cosh function to each value + /// + public Matrix PointwiseCosh() + { + return PointwiseUnary(DoPointwiseCosh); + } + + /// + /// Pointwise applies the cosh function to each value + /// + /// The vector to store the result + public void PointwiseCosh(Matrix result) + { + PointwiseUnary(DoPointwiseCosh, result); + } + + /// + /// Pointwise applies the floor function to each value + /// + public Matrix PointwiseFloor() + { + return PointwiseUnary(DoPointwiseFloor); + } + + /// + /// Pointwise applies the floor function to each value + /// + /// The vector to store the result + public void PointwiseFloor(Matrix result) + { + PointwiseUnary(DoPointwiseFloor, result); + } + + /// + /// Pointwise applies the log10 function to each value + /// + public Matrix PointwiseLog10() + { + return PointwiseUnary(DoPointwiseLog10); + } + + /// + /// Pointwise applies the log10 function to each value + /// + /// The vector to store the result + public void PointwiseLog10(Matrix result) + { + PointwiseUnary(DoPointwiseLog10, result); + } + + /// + /// Pointwise applies the round function to each value + /// + public Matrix PointwiseRound() + { + return PointwiseUnary(DoPointwiseRound); + } + + /// + /// Pointwise applies the round function to each value + /// + /// The vector to store the result + public void PointwiseRound(Matrix result) + { + PointwiseUnary(DoPointwiseRound, result); + } + + /// + /// Pointwise applies the sign function to each value + /// + public Matrix PointwiseSign() + { + return PointwiseUnary(DoPointwiseSign); + } + + /// + /// Pointwise applies the sign function to each value + /// + /// The vector to store the result + public void PointwiseSign(Matrix result) + { + PointwiseUnary(DoPointwiseSign, result); + } + + /// + /// Pointwise applies the sin function to each value + /// + public Matrix PointwiseSin() + { + return PointwiseUnary(DoPointwiseSin); + } + + /// + /// Pointwise applies the sin function to each value + /// + /// The vector to store the result + public void PointwiseSin(Matrix result) + { + PointwiseUnary(DoPointwiseSin, result); + } + + /// + /// Pointwise applies the sinh function to each value + /// + public Matrix PointwiseSinh() + { + return PointwiseUnary(DoPointwiseSinh); + } + + /// + /// Pointwise applies the sinh function to each value + /// + /// The vector to store the result + public void PointwiseSinh(Matrix result) + { + PointwiseUnary(DoPointwiseSinh, result); + } + + /// + /// Pointwise applies the sqrt function to each value + /// + public Matrix PointwiseSqrt() + { + return PointwiseUnary(DoPointwiseSqrt); + } + + /// + /// Pointwise applies the sqrt function to each value + /// + /// The vector to store the result + public void PointwiseSqrt(Matrix result) + { + PointwiseUnary(DoPointwiseSqrt, result); + } + + /// + /// Pointwise applies the tan function to each value + /// + public Matrix PointwiseTan() + { + return PointwiseUnary(DoPointwiseTan); + } + + /// + /// Pointwise applies the tan function to each value + /// + /// The vector to store the result + public void PointwiseTan(Matrix result) + { + PointwiseUnary(DoPointwiseTan, result); + } + + /// + /// Pointwise applies the tanh function to each value + /// + public Matrix PointwiseTanh() + { + return PointwiseUnary(DoPointwiseTanh); + } + + /// + /// Pointwise applies the tanh function to each value + /// + /// The vector to store the result + public void PointwiseTanh(Matrix result) + { + PointwiseUnary(DoPointwiseTanh, result); + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public abstract T Trace(); + + /// + /// Calculates the rank of the matrix. + /// + /// effective numerical rank, obtained from SVD + public virtual int Rank() + { + return Svd(false).Rank; + } + + /// + /// Calculates the nullity of the matrix. + /// + /// effective numerical nullity, obtained from SVD + public int Nullity() + { + return ColumnCount - Rank(); + } + + /// Calculates the condition number of this matrix. + /// The condition number of the matrix. + /// The condition number is calculated using singular value decomposition. + public virtual T ConditionNumber() + { + return Svd(false).ConditionNumber; + } + + /// Computes the determinant of this matrix. + /// The determinant of this matrix. + public virtual T Determinant() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + return LU().Determinant; + } + + /// + /// Computes an orthonormal basis for the null space of this matrix, + /// also known as the kernel of the corresponding matrix transformation. + /// + public virtual Vector[] Kernel() + { + var svd = Svd(true); + return svd.VT.EnumerateRows(svd.Rank, ColumnCount - svd.Rank).ToArray(); + } + + /// + /// Computes an orthonormal basis for the column space of this matrix, + /// also known as the range or image of the corresponding matrix transformation. + /// + public virtual Vector[] Range() + { + var svd = Svd(true); + return svd.U.EnumerateColumns(0, svd.Rank).ToArray(); + } + + /// Computes the inverse of this matrix. + /// The inverse of this matrix. + public virtual Matrix Inverse() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + return LU().Inverse(); + } + + /// Computes the Moore-Penrose Pseudo-Inverse of this matrix. + public abstract Matrix PseudoInverse(); + + /// + /// Computes the Kronecker product of this matrix with the given matrix. The new matrix is M-by-N + /// with M = this.Rows * lower.Rows and N = this.Columns * lower.Columns. + /// + /// The other matrix. + /// The Kronecker product of the two matrices. + public Matrix KroneckerProduct(Matrix other) + { + var result = Build.SameAs(this, other, RowCount * other.RowCount, ColumnCount * other.ColumnCount); + KroneckerProduct(other, result); + return result; + } + + /// + /// Computes the Kronecker product of this matrix with the given matrix. The new matrix is M-by-N + /// with M = this.Rows * lower.Rows and N = this.Columns * lower.Columns. + /// + /// The other matrix. + /// The Kronecker product of the two matrices. + /// If the result matrix's dimensions are not (this.Rows * lower.rows) x (this.Columns * lower.Columns). + public virtual void KroneckerProduct(Matrix other, Matrix result) + { + if (result.RowCount != (RowCount * other.RowCount) || result.ColumnCount != (ColumnCount * other.ColumnCount)) + { + throw DimensionsDontMatch(this, other, result); + } + + for (var j = 0; j < ColumnCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + result.SetSubMatrix(i * other.RowCount, other.RowCount, j * other.ColumnCount, other.ColumnCount, At(i, j) * other); + } + } + } + + /// + /// Pointwise applies the minimum with a scalar to each value. + /// + /// The scalar value to compare to. + public Matrix PointwiseMinimum(T scalar) + { + var result = Build.SameAs(this); + DoPointwiseMinimum(scalar, result); + return result; + } + + /// + /// Pointwise applies the minimum with a scalar to each value. + /// + /// The scalar value to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseMinimum(T scalar, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoPointwiseMinimum(scalar, result); + } + + /// + /// Pointwise applies the maximum with a scalar to each value. + /// + /// The scalar value to compare to. + public Matrix PointwiseMaximum(T scalar) + { + var result = Build.SameAs(this); + DoPointwiseMaximum(scalar, result); + return result; + } + + /// + /// Pointwise applies the maximum with a scalar to each value. + /// + /// The scalar value to compare to. + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseMaximum(T scalar, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoPointwiseMaximum(scalar, result); + } + + /// + /// Pointwise applies the absolute minimum with a scalar to each value. + /// + /// The scalar value to compare to. + public Matrix PointwiseAbsoluteMinimum(T scalar) + { + var result = Build.SameAs(this); + DoPointwiseAbsoluteMinimum(scalar, result); + return result; + } + + /// + /// Pointwise applies the absolute minimum with a scalar to each value. + /// + /// The scalar value to compare to. + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseAbsoluteMinimum(T scalar, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoPointwiseAbsoluteMinimum(scalar, result); + } + + /// + /// Pointwise applies the absolute maximum with a scalar to each value. + /// + /// The scalar value to compare to. + public Matrix PointwiseAbsoluteMaximum(T scalar) + { + var result = Build.SameAs(this); + DoPointwiseAbsoluteMaximum(scalar, result); + return result; + } + + /// + /// Pointwise applies the absolute maximum with a scalar to each value. + /// + /// The scalar value to compare to. + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseAbsoluteMaximum(T scalar, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw DimensionsDontMatch(this, result); + } + + DoPointwiseAbsoluteMaximum(scalar, result); + } + + /// + /// Pointwise applies the minimum with the values of another matrix to each value. + /// + /// The matrix with the values to compare to. + public Matrix PointwiseMinimum(Matrix other) + { + var result = Build.SameAs(this); + DoPointwiseMinimum(other, result); + return result; + } + + /// + /// Pointwise applies the minimum with the values of another matrix to each value. + /// + /// The matrix with the values to compare to. + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseMinimum(Matrix other, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, result); + } + + DoPointwiseMinimum(other, result); + } + + /// + /// Pointwise applies the maximum with the values of another matrix to each value. + /// + /// The matrix with the values to compare to. + public Matrix PointwiseMaximum(Matrix other) + { + var result = Build.SameAs(this); + DoPointwiseMaximum(other, result); + return result; + } + + /// + /// Pointwise applies the maximum with the values of another matrix to each value. + /// + /// The matrix with the values to compare to. + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseMaximum(Matrix other, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, result); + } + + DoPointwiseMaximum(other, result); + } + + /// + /// Pointwise applies the absolute minimum with the values of another matrix to each value. + /// + /// The matrix with the values to compare to. + public Matrix PointwiseAbsoluteMinimum(Matrix other) + { + var result = Build.SameAs(this); + DoPointwiseAbsoluteMinimum(other, result); + return result; + } + + /// + /// Pointwise applies the absolute minimum with the values of another matrix to each value. + /// + /// The matrix with the values to compare to. + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseAbsoluteMinimum(Matrix other, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, result); + } + + DoPointwiseAbsoluteMinimum(other, result); + } + + /// + /// Pointwise applies the absolute maximum with the values of another matrix to each value. + /// + /// The matrix with the values to compare to. + public Matrix PointwiseAbsoluteMaximum(Matrix other) + { + var result = Build.SameAs(this); + DoPointwiseAbsoluteMaximum(other, result); + return result; + } + + /// + /// Pointwise applies the absolute maximum with the values of another matrix to each value. + /// + /// The matrix with the values to compare to. + /// The matrix to store the result. + /// If this matrix and are not the same size. + public void PointwiseAbsoluteMaximum(Matrix other, Matrix result) + { + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount || ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw DimensionsDontMatch(this, other, result); + } + + DoPointwiseAbsoluteMaximum(other, result); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public abstract double L1Norm(); + + /// Calculates the induced L2 norm of the matrix. + /// The largest singular value of the matrix. + /// + /// For sparse matrices, the L2 norm is computed using a dense implementation of singular value decomposition. + /// In a later release, it will be replaced with a sparse implementation. + /// + public virtual double L2Norm() + { + return Svd(false).L2Norm; + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public abstract double InfinityNorm(); + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public abstract double FrobeniusNorm(); + + /// + /// Calculates the p-norms of all row vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public abstract Vector RowNorms(double norm); + + /// + /// Calculates the p-norms of all column vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public abstract Vector ColumnNorms(double norm); + + /// + /// Normalizes all row vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public abstract Matrix NormalizeRows(double norm); + + /// + /// Normalizes all column vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public abstract Matrix NormalizeColumns(double norm); + + /// + /// Calculates the value sum of each row vector. + /// + public abstract Vector RowSums(); + + /// + /// Calculates the value sum of each column vector. + /// + public abstract Vector ColumnSums(); + + /// + /// Calculates the absolute value sum of each row vector. + /// + public abstract Vector RowAbsoluteSums(); + + /// + /// Calculates the absolute value sum of each column vector. + /// + public abstract Vector ColumnAbsoluteSums(); + + #region Exceptions - possibly move elsewhere? + + internal static Exception DimensionsDontMatch(Matrix left, Matrix right, Matrix result, string paramName = null) + where TException : Exception + { + var message = string.Format(Resources.ArgumentMatrixDimensions3, left.RowCount + "x" + left.ColumnCount, right.RowCount + "x" + right.ColumnCount, result.RowCount + "x" + result.ColumnCount); + return CreateException(message, paramName); + } + + internal static Exception DimensionsDontMatch(Matrix left, Matrix right, string paramName = null) + where TException : Exception + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, left.RowCount + "x" + left.ColumnCount, right.RowCount + "x" + right.ColumnCount); + return CreateException(message, paramName); + } + + internal static Exception DimensionsDontMatch(Matrix matrix) + where TException : Exception + { + var message = string.Format(Resources.ArgumentMatrixDimensions1, matrix.RowCount + "x" + matrix.ColumnCount); + return CreateException(message); + } + + internal static Exception DimensionsDontMatch(Matrix left, Vector right, Vector result, string paramName = null) + where TException : Exception + { + return DimensionsDontMatch(left, right.ToColumnMatrix(), result.ToColumnMatrix(), paramName); + } + + internal static Exception DimensionsDontMatch(Matrix left, Vector right, string paramName = null) + where TException : Exception + { + return DimensionsDontMatch(left, right.ToColumnMatrix(), paramName); + } + + internal static Exception DimensionsDontMatch(Vector left, Matrix right, string paramName = null) + where TException : Exception + { + return DimensionsDontMatch(left.ToColumnMatrix(), right, paramName); + } + + internal static Exception DimensionsDontMatch(Vector left, Vector right, string paramName = null) + where TException : Exception + { + return DimensionsDontMatch(left.ToColumnMatrix(), right.ToColumnMatrix(), paramName); + } + + static Exception CreateException(string message, string paramName = null) + where TException : Exception + { + if (typeof(TException) == typeof(ArgumentException)) + { + return new ArgumentException(message, paramName); + } + + if (typeof(TException) == typeof(ArgumentOutOfRangeException)) + { + return new ArgumentOutOfRangeException(paramName, message); + } + + return new Exception(message); + } + + #endregion +} diff --git a/MathNet.Numerics/LinearAlgebra/Matrix.BCL.cs b/MathNet.Numerics/LinearAlgebra/Matrix.BCL.cs new file mode 100644 index 0000000..719aa6f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Matrix.BCL.cs @@ -0,0 +1,418 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace MathNet.Numerics.LinearAlgebra; + +[DebuggerDisplay("Matrix {RowCount}x{ColumnCount}")] + +public abstract partial class Matrix +{ + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// An object to compare with this object. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(Matrix other) + { + return other != null && Storage.Equals(other.Storage); + } + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) + { + var other = obj as Matrix; + return other != null && Storage.Equals(other.Storage); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + return Storage.GetHashCode(); + } + +#if !NETSTANDARD1_3 + + /// + /// Creates a new object that is a copy of the current instance. + /// + /// + /// A new object that is a copy of this instance. + /// + object ICloneable.Clone() + { + return Clone(); + } + +#endif + + /// + /// Returns a string that describes the type, dimensions and shape of this matrix. + /// + public virtual string ToTypeString() + { + return string.Format("{0} {1}x{2}-{3}", GetType().Name, RowCount, ColumnCount, typeof(T).Name); + } + + /// + /// Returns a string 2D array that summarizes the content of this matrix. + /// + public string[,] ToMatrixStringArray(int upperRows, int lowerRows, int leftColumns, int rightColumns, + string horizontalEllipsis, string verticalEllipsis, string diagonalEllipsis, Func formatValue) + { + upperRows = Math.Max(upperRows, 1); + lowerRows = Math.Max(lowerRows, 0); + leftColumns = Math.Max(leftColumns, 1); + rightColumns = Math.Max(rightColumns, 0); + + int upper = RowCount <= upperRows ? RowCount : upperRows; + int lower = RowCount <= upperRows ? 0 : RowCount <= upperRows + lowerRows ? RowCount - upperRows : lowerRows; + bool rowEllipsis = RowCount > upper + lower; + int rows = rowEllipsis ? upper + lower + 1 : upper + lower; + + int left = ColumnCount <= leftColumns ? ColumnCount : leftColumns; + int right = ColumnCount <= leftColumns ? 0 : ColumnCount <= leftColumns + rightColumns ? ColumnCount - leftColumns : rightColumns; + bool colEllipsis = ColumnCount > left + right; + int cols = colEllipsis ? left + right + 1 : left + right; + + var array = new string[rows, cols]; + for (int i = 0; i < upper; i++) + { + for (int j = 0; j < left; j++) + { + array[i, j] = formatValue(At(i, j)); + } + + int colOffset = left; + if (colEllipsis) + { + array[i, left] = horizontalEllipsis; + colOffset++; + } + + for (int j = 0; j < right; j++) + { + array[i, colOffset + j] = formatValue(At(i, ColumnCount - right + j)); + } + } + + int rowOffset = upper; + if (rowEllipsis) + { + for (int j = 0; j < left; j++) + { + array[upper, j] = verticalEllipsis; + } + + int colOffset = left; + if (colEllipsis) + { + array[upper, left] = diagonalEllipsis; + colOffset++; + } + + for (int j = 0; j < right; j++) + { + array[upper, colOffset + j] = verticalEllipsis; + } + + rowOffset++; + } + + for (int i = 0; i < lower; i++) + { + for (int j = 0; j < left; j++) + { + array[rowOffset + i, j] = formatValue(At(RowCount - lower + i, j)); + } + + int colOffset = left; + if (colEllipsis) + { + array[rowOffset + i, left] = horizontalEllipsis; + colOffset++; + } + + for (int j = 0; j < right; j++) + { + array[rowOffset + i, colOffset + j] = formatValue(At(RowCount - lower + i, ColumnCount - right + j)); + } + } + + return array; + } + + /// + /// Returns a string 2D array that summarizes the content of this matrix. + /// + public string[,] ToMatrixStringArray(int upperRows, int lowerRows, int minLeftColumns, int rightColumns, int maxWidth, int padding, + string horizontalEllipsis, string verticalEllipsis, string diagonalEllipsis, Func formatValue) + { + upperRows = Math.Max(upperRows, 1); + lowerRows = Math.Max(lowerRows, 0); + minLeftColumns = Math.Max(minLeftColumns, 1); + maxWidth = Math.Max(maxWidth, 12); + + int upper = RowCount <= upperRows ? RowCount : upperRows; + int lower = RowCount <= upperRows ? 0 : RowCount <= upperRows + lowerRows ? RowCount - upperRows : lowerRows; + bool rowEllipsis = RowCount > upper + lower; + int rows = rowEllipsis ? upper + lower + 1 : upper + lower; + + int left = ColumnCount <= minLeftColumns ? ColumnCount : minLeftColumns; + int right = ColumnCount <= minLeftColumns ? 0 : ColumnCount <= minLeftColumns + rightColumns ? ColumnCount - minLeftColumns : rightColumns; + + var columnsLeft = new List>(); + for (int j = 0; j < left; j++) + { + columnsLeft.Add(FormatColumn(j, rows, upper, lower, rowEllipsis, verticalEllipsis, formatValue)); + } + + var columnsRight = new List>(); + for (int j = 0; j < right; j++) + { + columnsRight.Add(FormatColumn(ColumnCount - right + j, rows, upper, lower, rowEllipsis, verticalEllipsis, formatValue)); + } + + int chars = columnsLeft.Sum(t => t.Item1 + padding) + columnsRight.Sum(t => t.Item1 + padding); + for (int j = left; j < ColumnCount - right; j++) + { + var candidate = FormatColumn(j, rows, upper, lower, rowEllipsis, verticalEllipsis, formatValue); + chars += candidate.Item1 + padding; + if (chars > maxWidth) + { + break; + } + + columnsLeft.Add(candidate); + } + + int cols = columnsLeft.Count + columnsRight.Count; + bool colEllipsis = ColumnCount > cols; + if (colEllipsis) + { + cols++; + } + + var array = new string[rows, cols]; + int colIndex = 0; + foreach (var column in columnsLeft) + { + for (int i = 0; i < column.Item2.Length; i++) + { + array[i, colIndex] = column.Item2[i]; + } + + colIndex++; + } + + if (colEllipsis) + { + int rowIndex = 0; + for (var row = 0; row < upper; row++) + { + array[rowIndex++, colIndex] = horizontalEllipsis; + } + + if (rowEllipsis) + { + array[rowIndex++, colIndex] = diagonalEllipsis; + } + + for (var row = RowCount - lower; row < RowCount; row++) + { + array[rowIndex++, colIndex] = horizontalEllipsis; + } + + colIndex++; + } + + foreach (var column in columnsRight) + { + for (int i = 0; i < column.Item2.Length; i++) + { + array[i, colIndex] = column.Item2[i]; + } + + colIndex++; + } + + return array; + } + + Tuple FormatColumn(int column, int height, int upper, int lower, bool withEllipsis, string ellipsis, Func formatValue) + { + var c = new string[height]; + int index = 0; + for (var row = 0; row < upper; row++) + { + c[index++] = formatValue(At(row, column)); + } + + if (withEllipsis) + { + c[index++] = ""; + } + + for (var row = RowCount - lower; row < RowCount; row++) + { + c[index++] = formatValue(At(row, column)); + } + + int w = c.Max(x => x.Length); + if (withEllipsis) + { + c[upper] = ellipsis; + } + + return new Tuple(w, c); + } + + static string FormatStringArrayToString(string[,] array, string columnSeparator, string rowSeparator) + { + var rows = array.GetLength(0); + var cols = array.GetLength(1); + + var widths = new int[cols]; + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + widths[j] = Math.Max(widths[j], array[i, j].Length); + } + } + + var sb = new StringBuilder(); + for (int i = 0; i < rows; i++) + { + sb.Append(array[i, 0].PadLeft(widths[0])); + for (int j = 1; j < cols; j++) + { + sb.Append(columnSeparator); + sb.Append(array[i, j].PadLeft(widths[j])); + } + + sb.Append(rowSeparator); + } + + return sb.ToString(); + } + + public string ToMatrixString(int upperRows, int lowerRows, int leftColumns, int rightColumns, + string horizontalEllipsis, string verticalEllipsis, string diagonalEllipsis, + string columnSeparator, string rowSeparator, Func formatValue) + { + return FormatStringArrayToString( + ToMatrixStringArray(upperRows, lowerRows, leftColumns, rightColumns, horizontalEllipsis, verticalEllipsis, diagonalEllipsis, formatValue), + columnSeparator, rowSeparator); + } + + public string ToMatrixString(int upperRows, int lowerRows, int minLeftColumns, int rightColumns, int maxWidth, + string horizontalEllipsis, string verticalEllipsis, string diagonalEllipsis, + string columnSeparator, string rowSeparator, Func formatValue) + { + return FormatStringArrayToString( + ToMatrixStringArray(upperRows, lowerRows, minLeftColumns, rightColumns, maxWidth, columnSeparator.Length, horizontalEllipsis, verticalEllipsis, diagonalEllipsis, formatValue), + columnSeparator, rowSeparator); + } + + /// + /// Returns a string that summarizes the content of this matrix. + /// + public string ToMatrixString(int maxRows, int maxColumns, string format = null, IFormatProvider provider = null) + { + if (format == null) + { + format = "G6"; + } + + int bottom = maxRows > 4 ? 2 : 0; + int right = maxColumns > 4 ? 2 : 0; + return ToMatrixString(maxRows - bottom, bottom, maxColumns - right, right, "..", "..", "..", " ", Environment.NewLine, x => x.ToString(format, provider)); + } + + /// + /// Returns a string that summarizes the content of this matrix. + /// + public string ToMatrixString(string format = null, IFormatProvider provider = null) + { + if (format == null) + { + format = "G6"; + } + + return ToMatrixString(8, 4, 5, 2, 76, "..", "..", "..", " ", Environment.NewLine, x => x.ToString(format, provider)); + } + + /// + /// Returns a string that summarizes this matrix. + /// + public string ToString(int maxRows, int maxColumns, string format = null, IFormatProvider formatProvider = null) + { + return string.Concat(ToTypeString(), Environment.NewLine, ToMatrixString(maxRows, maxColumns, format, formatProvider)); + } + + /// + /// Returns a string that summarizes this matrix. + /// The maximum number of cells can be configured in the class. + /// + public sealed override string ToString() + { + return string.Concat(ToTypeString(), Environment.NewLine, ToMatrixString()); + } + + /// + /// Returns a string that summarizes this matrix. + /// The maximum number of cells can be configured in the class. + /// The format string is ignored. + /// + public string ToString(string format = null, IFormatProvider formatProvider = null) + { + return string.Concat(ToTypeString(), Environment.NewLine, ToMatrixString(format, formatProvider)); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Matrix.Operators.cs b/MathNet.Numerics/LinearAlgebra/Matrix.Operators.cs new file mode 100644 index 0000000..119d718 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Matrix.Operators.cs @@ -0,0 +1,478 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Runtime.CompilerServices; + +namespace MathNet.Numerics.LinearAlgebra; + +/// +/// Defines the base class for Matrix classes. +/// +public abstract partial class Matrix +{ + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static Matrix operator +(Matrix rightSide) + { + return rightSide.Clone(); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static Matrix operator -(Matrix rightSide) + { + return rightSide.Negate(); + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static Matrix operator +(Matrix leftSide, Matrix rightSide) + { + return leftSide.Add(rightSide); + } + + /// + /// Adds a scalar to each element of the matrix. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of the provided matrix. + /// The left matrix to add. + /// The scalar value to add. + /// The result of the addition. + /// If is . + public static Matrix operator +(Matrix leftSide, T rightSide) + { + return leftSide.Add(rightSide); + } + + /// + /// Adds a scalar to each element of the matrix. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of the provided matrix. + /// The scalar value to add. + /// The right matrix to add. + /// The result of the addition. + /// If is . + public static Matrix operator +(T leftSide, Matrix rightSide) + { + return rightSide.Add(leftSide); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the subtraction. + /// If and don't have the same dimensions. + /// If or is . + public static Matrix operator -(Matrix leftSide, Matrix rightSide) + { + return leftSide.Subtract(rightSide); + } + + /// + /// Subtracts a scalar from each element of a matrix. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of the provided matrix. + /// The left matrix to subtract. + /// The scalar value to subtract. + /// The result of the subtraction. + /// If and don't have the same dimensions. + /// If or is . + public static Matrix operator -(Matrix leftSide, T rightSide) + { + return leftSide.Subtract(rightSide); + } + + /// + /// Subtracts each element of a matrix from a scalar. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of the provided matrix. + /// The scalar value to subtract. + /// The right matrix to subtract. + /// The result of the subtraction. + /// If and don't have the same dimensions. + /// If or is . + public static Matrix operator -(T leftSide, Matrix rightSide) + { + return rightSide.SubtractFrom(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static Matrix operator *(Matrix leftSide, T rightSide) + { + return leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static Matrix operator *(T leftSide, Matrix rightSide) + { + return rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static Matrix operator *(Matrix leftSide, Matrix rightSide) + { + return leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static Vector operator *(Matrix leftSide, Vector rightSide) + { + return leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static Vector operator *(Vector leftSide, Matrix rightSide) + { + return rightSide.LeftMultiply(leftSide); + } + + /// + /// Divides a scalar with a matrix. + /// + /// The scalar to divide. + /// The matrix. + /// The result of the division. + /// If is . + public static Matrix operator /(T dividend, Matrix divisor) + { + return divisor.DivideByThis(dividend); + } + + /// + /// Divides a matrix with a scalar. + /// + /// The matrix to divide. + /// The scalar value. + /// The result of the division. + /// If is . + public static Matrix operator /(Matrix dividend, T divisor) + { + return dividend.Divide(divisor); + } + + /// + /// Computes the pointwise remainder (% operator), where the result has the sign of the dividend, + /// of each element of the matrix of the given divisor. + /// + /// The matrix whose elements we want to compute the modulus of. + /// The divisor to use. + /// The result of the calculation + /// If is . + public static Matrix operator %(Matrix dividend, T divisor) + { + return dividend.Remainder(divisor); + } + + /// + /// Computes the pointwise remainder (% operator), where the result has the sign of the dividend, + /// of the given dividend of each element of the matrix. + /// + /// The dividend we want to compute the modulus of. + /// The matrix whose elements we want to use as divisor. + /// The result of the calculation + /// If is . + public static Matrix operator %(T dividend, Matrix divisor) + { + return divisor.RemainderByThis(dividend); + } + + /// + /// Computes the pointwise remainder (% operator), where the result has the sign of the dividend, + /// of each element of two matrices. + /// + /// The matrix whose elements we want to compute the remainder of. + /// The divisor to use. + /// If and are not the same size. + /// If is . + public static Matrix operator %(Matrix dividend, Matrix divisor) + { + return dividend.PointwiseRemainder(divisor); + } + + [SpecialName] + public static Matrix op_DotMultiply(Matrix x, Matrix y) + { + return x.PointwiseMultiply(y); + } + + [SpecialName] + public static Matrix op_DotDivide(Matrix dividend, Matrix divisor) + { + return dividend.PointwiseDivide(divisor); + } + + [SpecialName] + public static Matrix op_DotPercent(Matrix dividend, Matrix divisor) + { + return dividend.PointwiseRemainder(divisor); + } + + [SpecialName] + public static Matrix op_DotHat(Matrix matrix, Matrix exponent) + { + return matrix.PointwisePower(exponent); + } + + [SpecialName] + public static Matrix op_DotHat(Matrix matrix, T exponent) + { + return matrix.PointwisePower(exponent); + } + + /// + /// Computes the sqrt of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Sqrt(Matrix x) + { + return x.PointwiseSqrt(); + } + + /// + /// Computes the exponential of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Exp(Matrix x) + { + return x.PointwiseExp(); + } + + /// + /// Computes the log of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Log(Matrix x) + { + return x.PointwiseLog(); + } + + /// + /// Computes the log10 of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Log10(Matrix x) + { + return x.PointwiseLog10(); + } + + /// + /// Computes the sin of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Sin(Matrix x) + { + return x.PointwiseSin(); + } + + /// + /// Computes the cos of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Cos(Matrix x) + { + return x.PointwiseCos(); + } + + /// + /// Computes the tan of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Tan(Matrix x) + { + return x.PointwiseTan(); + } + + /// + /// Computes the asin of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Asin(Matrix x) + { + return x.PointwiseAsin(); + } + + /// + /// Computes the acos of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Acos(Matrix x) + { + return x.PointwiseAcos(); + } + + /// + /// Computes the atan of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Atan(Matrix x) + { + return x.PointwiseAtan(); + } + + /// + /// Computes the sinh of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Sinh(Matrix x) + { + return x.PointwiseSinh(); + } + + /// + /// Computes the cosh of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Cosh(Matrix x) + { + return x.PointwiseCosh(); + } + + /// + /// Computes the tanh of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Tanh(Matrix x) + { + return x.PointwiseTanh(); + } + + /// + /// Computes the absolute value of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Abs(Matrix x) + { + return x.PointwiseAbs(); + } + + /// + /// Computes the floor of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Floor(Matrix x) + { + return x.PointwiseFloor(); + } + + /// + /// Computes the ceiling of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Ceiling(Matrix x) + { + return x.PointwiseCeiling(); + } + + /// + /// Computes the rounded value of a matrix pointwise + /// + /// The input matrix + /// + public static Matrix Round(Matrix x) + { + return x.PointwiseRound(); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Matrix.Solve.cs b/MathNet.Numerics/LinearAlgebra/Matrix.Solve.cs new file mode 100644 index 0000000..5f438de --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Matrix.Solve.cs @@ -0,0 +1,353 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Solvers; + +using System; + +namespace MathNet.Numerics.LinearAlgebra; + +/// +/// Defines the base class for Matrix classes. +/// +public abstract partial class Matrix +{ + + // Factorizations + + /// + /// Computes the Cholesky decomposition for a matrix. + /// + /// The Cholesky decomposition object. + public abstract Cholesky Cholesky(); + + /// + /// Computes the LU decomposition for a matrix. + /// + /// The LU decomposition object. + public abstract LU LU(); + + /// + /// Computes the QR decomposition for a matrix. + /// + /// The type of QR factorization to perform. + /// The QR decomposition object. + public abstract QR QR(QRMethod method = QRMethod.Thin); + + /// + /// Computes the QR decomposition for a matrix using Modified Gram-Schmidt Orthogonalization. + /// + /// The QR decomposition object. + public abstract GramSchmidt GramSchmidt(); + + /// + /// Computes the SVD decomposition for a matrix. + /// + /// Compute the singular U and VT vectors or not. + /// The SVD decomposition object. + public abstract Svd Svd(bool computeVectors = true); + + /// + /// Computes the EVD decomposition for a matrix. + /// + /// The EVD decomposition object. + public abstract Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown); + + // Direct Solvers: Full + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public void Solve(Vector input, Vector result) + { + if (ColumnCount == RowCount) + { + LU().Solve(input, result); + return; + } + + QR().Solve(input, result); + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public void Solve(Matrix input, Matrix result) + { + if (ColumnCount == RowCount) + { + LU().Solve(input, result); + return; + } + + QR().Solve(input, result); + } + + // Direct Solvers: Simple + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public Matrix Solve(Matrix input) + { + var x = Build.SameAs(this, ColumnCount, input.ColumnCount, fullyMutable: true); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public Vector Solve(Vector input) + { + var x = Vector.Build.SameAs(this, ColumnCount); + Solve(input, x); + return x; + } + + // Iterative Solvers: Full + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix (this matrix), b is the solution vector and x is the unknown vector. + /// + /// The solution vector b. + /// The result vector x. + /// The iterative solver to use. + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public IterationStatus TrySolveIterative(Vector input, Vector result, IIterativeSolver solver, Iterator iterator = null, IPreconditioner preconditioner = null) + { + if (iterator == null) + { + iterator = new Iterator(Build.IterativeSolverStopCriteria()); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + solver.Solve(this, input, result, iterator, preconditioner); + + return iterator.Status; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix (this matrix), B is the solution matrix and X is the unknown matrix. + /// + /// The solution matrix B. + /// The result matrix X + /// The iterative solver to use. + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public IterationStatus TrySolveIterative(Matrix input, Matrix result, IIterativeSolver solver, Iterator iterator = null, IPreconditioner preconditioner = null) + { + if (RowCount != input.RowCount || input.RowCount != result.RowCount || input.ColumnCount != result.ColumnCount) + { + throw DimensionsDontMatch(this, input, result); + } + + if (iterator == null) + { + iterator = new Iterator(Build.IterativeSolverStopCriteria()); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + for (var column = 0; column < input.ColumnCount; column++) + { + var solution = Vector.Build.Dense(RowCount); + + solver.Solve(this, input.Column(column), solution, iterator, preconditioner); + + foreach (var element in solution.EnumerateIndexed(Zeros.AllowSkip)) + { + result.At(element.Item1, column, element.Item2); + } + } + + return iterator.Status; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix (this matrix), b is the solution vector and x is the unknown vector. + /// + /// The solution vector b. + /// The result vector x. + /// The iterative solver to use. + /// Criteria to control when to stop iterating. + /// The preconditioner to use for approximations. + public IterationStatus TrySolveIterative(Vector input, Vector result, IIterativeSolver solver, IPreconditioner preconditioner, params IIterationStopCriterion[] stopCriteria) + { + var iterator = new Iterator(stopCriteria.Length == 0 ? Build.IterativeSolverStopCriteria() : stopCriteria); + return TrySolveIterative(input, result, solver, iterator, preconditioner); + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix (this matrix), B is the solution matrix and X is the unknown matrix. + /// + /// The solution matrix B. + /// The result matrix X + /// The iterative solver to use. + /// Criteria to control when to stop iterating. + /// The preconditioner to use for approximations. + public IterationStatus TrySolveIterative(Matrix input, Matrix result, IIterativeSolver solver, IPreconditioner preconditioner, params IIterationStopCriterion[] stopCriteria) + { + var iterator = new Iterator(stopCriteria.Length == 0 ? Build.IterativeSolverStopCriteria() : stopCriteria); + return TrySolveIterative(input, result, solver, iterator, preconditioner); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix (this matrix), b is the solution vector and x is the unknown vector. + /// + /// The solution vector b. + /// The result vector x. + /// The iterative solver to use. + /// Criteria to control when to stop iterating. + public IterationStatus TrySolveIterative(Vector input, Vector result, IIterativeSolver solver, params IIterationStopCriterion[] stopCriteria) + { + var iterator = new Iterator(stopCriteria.Length == 0 ? Build.IterativeSolverStopCriteria() : stopCriteria); + return TrySolveIterative(input, result, solver, iterator); + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix (this matrix), B is the solution matrix and X is the unknown matrix. + /// + /// The solution matrix B. + /// The result matrix X + /// The iterative solver to use. + /// Criteria to control when to stop iterating. + public IterationStatus TrySolveIterative(Matrix input, Matrix result, IIterativeSolver solver, params IIterationStopCriterion[] stopCriteria) + { + var iterator = new Iterator(stopCriteria.Length == 0 ? Build.IterativeSolverStopCriteria() : stopCriteria); + return TrySolveIterative(input, result, solver, iterator); + } + + // Iterative Solvers: Simple + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix (this matrix), b is the solution vector and x is the unknown vector. + /// + /// The solution vector b. + /// The iterative solver to use. + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + /// The result vector x. + public Vector SolveIterative(Vector input, IIterativeSolver solver, Iterator iterator = null, IPreconditioner preconditioner = null) + { + var result = Vector.Build.Dense(RowCount); + TrySolveIterative(input, result, solver, iterator, preconditioner); + return result; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix (this matrix), B is the solution matrix and X is the unknown matrix. + /// + /// The solution matrix B. + /// The iterative solver to use. + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + /// The result matrix X. + public Matrix SolveIterative(Matrix input, IIterativeSolver solver, Iterator iterator = null, IPreconditioner preconditioner = null) + { + var result = Build.Dense(input.RowCount, input.ColumnCount); + TrySolveIterative(input, result, solver, iterator, preconditioner); + return result; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix (this matrix), b is the solution vector and x is the unknown vector. + /// + /// The solution vector b. + /// The iterative solver to use. + /// Criteria to control when to stop iterating. + /// The preconditioner to use for approximations. + /// The result vector x. + public Vector SolveIterative(Vector input, IIterativeSolver solver, IPreconditioner preconditioner, params IIterationStopCriterion[] stopCriteria) + { + var result = Vector.Build.Dense(RowCount); + TrySolveIterative(input, result, solver, preconditioner, stopCriteria); + return result; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix (this matrix), B is the solution matrix and X is the unknown matrix. + /// + /// The solution matrix B. + /// The iterative solver to use. + /// Criteria to control when to stop iterating. + /// The preconditioner to use for approximations. + /// The result matrix X. + public Matrix SolveIterative(Matrix input, IIterativeSolver solver, IPreconditioner preconditioner, params IIterationStopCriterion[] stopCriteria) + { + var result = Build.Dense(input.RowCount, input.ColumnCount); + TrySolveIterative(input, result, solver, preconditioner, stopCriteria); + return result; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix (this matrix), b is the solution vector and x is the unknown vector. + /// + /// The solution vector b. + /// The iterative solver to use. + /// Criteria to control when to stop iterating. + /// The result vector x. + public Vector SolveIterative(Vector input, IIterativeSolver solver, params IIterationStopCriterion[] stopCriteria) + { + var result = Vector.Build.Dense(RowCount); + TrySolveIterative(input, result, solver, stopCriteria); + return result; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix (this matrix), B is the solution matrix and X is the unknown matrix. + /// + /// The solution matrix B. + /// The iterative solver to use. + /// Criteria to control when to stop iterating. + /// The result matrix X. + public Matrix SolveIterative(Matrix input, IIterativeSolver solver, params IIterationStopCriterion[] stopCriteria) + { + var result = Build.Dense(input.RowCount, input.ColumnCount); + TrySolveIterative(input, result, solver, stopCriteria); + return result; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Matrix.cs b/MathNet.Numerics/LinearAlgebra/Matrix.cs new file mode 100644 index 0000000..3b38f2e --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Matrix.cs @@ -0,0 +1,1888 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime; +using System.Runtime.CompilerServices; + +namespace MathNet.Numerics.LinearAlgebra; + +/// +/// Defines the base class for Matrix classes. +/// +/// Supported data types are double, single, , and . +[Serializable] +public abstract partial class Matrix : + IFormattable, IEquatable> +#if !NETSTANDARD1_3 + , ICloneable +#endif + where T : struct, IEquatable, IFormattable +{ + /// + /// Initializes a new instance of the Matrix class. + /// + protected Matrix(MatrixStorage storage) + { + Storage = storage; + RowCount = storage.RowCount; + ColumnCount = storage.ColumnCount; + } + + public static readonly MatrixBuilder Build = BuilderInstance.Matrix; + + /// + /// Gets the raw matrix data storage. + /// + public MatrixStorage Storage { get; private set; } + + /// + /// Gets the number of columns. + /// + /// The number of columns. + public int ColumnCount { get; private set; } + + /// + /// Gets the number of rows. + /// + /// The number of rows. + public int RowCount { get; private set; } + + /// + /// Gets or sets the value at the given row and column, with range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// The value to get or set. + /// This method is ranged checked. and + /// to get and set values without range checking. + public T this[int row, int column] + { +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + get { return Storage[row, column]; } + +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + set { Storage[row, column] = value; } + } + + /// + /// Retrieves the requested element without range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The requested element. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + public T At(int row, int column) + { + return Storage.At(row, column); + } + + /// + /// Sets the value of the given element without range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The value to set the element to. + /// +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + public void At(int row, int column, T value) + { + Storage.At(row, column, value); + } + + /// + /// Sets all values to zero. + /// + public void Clear() + { + Storage.Clear(); + } + + /// + /// Sets all values of a row to zero. + /// + public void ClearRow(int rowIndex) + { + if ((uint)rowIndex >= (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowIndex)); + } + + Storage.ClearUnchecked(rowIndex, 1, 0, ColumnCount); + } + + /// + /// Sets all values of a column to zero. + /// + public void ClearColumn(int columnIndex) + { + if ((uint)columnIndex >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + Storage.ClearUnchecked(0, RowCount, columnIndex, 1); + } + + /// + /// Sets all values for all of the chosen rows to zero. + /// + public void ClearRows(params int[] rowIndices) + { + Storage.ClearRows(rowIndices); + } + + /// + /// Sets all values for all of the chosen columns to zero. + /// + public void ClearColumns(params int[] columnIndices) + { + Storage.ClearColumns(columnIndices); + } + + /// + /// Sets all values of a sub-matrix to zero. + /// + public void ClearSubMatrix(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + Storage.Clear(rowIndex, rowCount, columnIndex, columnCount); + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero, in-place. + /// + public abstract void CoerceZero(double threshold); + + /// + /// Set all values that meet the predicate to zero, in-place. + /// + public void CoerceZero(Func zeroPredicate) + { + MapInplace(x => zeroPredicate(x) ? Zero : x, Zeros.AllowSkip); + } + + /// + /// Creates a clone of this instance. + /// + /// + /// A clone of the instance. + /// + public Matrix Clone() + { + var result = Build.SameAs(this); + Storage.CopyToUnchecked(result.Storage, ExistingData.AssumeZeros); + return result; + } + + /// + /// Copies the elements of this matrix to the given matrix. + /// + /// + /// The matrix to copy values into. + /// + /// + /// If target is . + /// + /// + /// If this and the target matrix do not have the same dimensions.. + /// + public void CopyTo(Matrix target) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + Storage.CopyTo(target.Storage); + } + + /// + /// Copies a row into an Vector. + /// + /// The row to copy. + /// A Vector containing the copied elements. + /// If is negative, + /// or greater than or equal to the number of rows. + public Vector Row(int index) + { + if ((uint)index >= (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + var ret = Vector.Build.SameAs(this, ColumnCount); + Storage.CopySubRowToUnchecked(ret.Storage, index, 0, 0, ColumnCount, ExistingData.AssumeZeros); + return ret; + } + + /// + /// Copies a row into to the given Vector. + /// + /// The row to copy. + /// The Vector to copy the row into. + /// If the result vector is . + /// If is negative, + /// or greater than or equal to the number of rows. + /// If this.Columns != result.Count. + public void Row(int index, Vector result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + Storage.CopyRowTo(result.Storage, index); + } + + /// + /// Copies the requested row elements into a new Vector. + /// + /// The row to copy elements from. + /// The column to start copying from. + /// The number of elements to copy. + /// A Vector containing the requested elements. + /// If: + /// is negative, + /// or greater than or equal to the number of rows. + /// is negative, + /// or greater than or equal to the number of columns. + /// (columnIndex + length) >= Columns. + /// If is not positive. + public Vector Row(int rowIndex, int columnIndex, int length) + { + var ret = Vector.Build.SameAs(this, length); + Storage.CopySubRowTo(ret.Storage, rowIndex, columnIndex, 0, length); + return ret; + } + + /// + /// Copies the requested row elements into a new Vector. + /// + /// The row to copy elements from. + /// The column to start copying from. + /// The number of elements to copy. + /// The Vector to copy the column into. + /// If the result Vector is . + /// If is negative, + /// or greater than or equal to the number of columns. + /// If is negative, + /// or greater than or equal to the number of rows. + /// If + + /// is greater than or equal to the number of rows. + /// If is not positive. + /// If result.Count < length. + public void Row(int rowIndex, int columnIndex, int length, Vector result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + Storage.CopySubRowTo(result.Storage, rowIndex, columnIndex, 0, length); + } + + /// + /// Copies a column into a new Vector>. + /// + /// The column to copy. + /// A Vector containing the copied elements. + /// If is negative, + /// or greater than or equal to the number of columns. + public Vector Column(int index) + { + if ((uint)index >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + var ret = Vector.Build.SameAs(this, RowCount); + Storage.CopySubColumnToUnchecked(ret.Storage, index, 0, 0, RowCount, ExistingData.AssumeZeros); + return ret; + } + + /// + /// Copies a column into to the given Vector. + /// + /// The column to copy. + /// The Vector to copy the column into. + /// If the result Vector is . + /// If is negative, + /// or greater than or equal to the number of columns. + /// If this.Rows != result.Count. + public void Column(int index, Vector result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + Storage.CopyColumnTo(result.Storage, index); + } + + /// + /// Copies the requested column elements into a new Vector. + /// + /// The column to copy elements from. + /// The row to start copying from. + /// The number of elements to copy. + /// A Vector containing the requested elements. + /// If: + /// is negative, + /// or greater than or equal to the number of columns. + /// is negative, + /// or greater than or equal to the number of rows. + /// (rowIndex + length) >= Rows. + /// + /// If is not positive. + public Vector Column(int columnIndex, int rowIndex, int length) + { + var ret = Vector.Build.SameAs(this, length); + Storage.CopySubColumnTo(ret.Storage, columnIndex, rowIndex, 0, length); + return ret; + } + + /// + /// Copies the requested column elements into the given vector. + /// + /// The column to copy elements from. + /// The row to start copying from. + /// The number of elements to copy. + /// The Vector to copy the column into. + /// If the result Vector is . + /// If is negative, + /// or greater than or equal to the number of columns. + /// If is negative, + /// or greater than or equal to the number of rows. + /// If + + /// is greater than or equal to the number of rows. + /// If is not positive. + /// If result.Count < length. + public void Column(int columnIndex, int rowIndex, int length, Vector result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + Storage.CopySubColumnTo(result.Storage, columnIndex, rowIndex, 0, length); + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public virtual Matrix UpperTriangle() + { + var result = Build.SameAs(this); + for (var row = 0; row < RowCount; row++) + { + for (var column = row; column < ColumnCount; column++) + { + result.At(row, column, At(row, column)); + } + } + + return result; + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public virtual Matrix LowerTriangle() + { + var result = Build.SameAs(this); + for (var row = 0; row < RowCount; row++) + { + for (var column = 0; column <= row && column < ColumnCount; column++) + { + result.At(row, column, At(row, column)); + } + } + + return result; + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public virtual void LowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + for (var row = 0; row < RowCount; row++) + { + for (var column = 0; column < ColumnCount; column++) + { + result.At(row, column, row >= column ? At(row, column) : Zero); + } + } + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public virtual void UpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + for (var row = 0; row < RowCount; row++) + { + for (var column = 0; column < ColumnCount; column++) + { + result.At(row, column, row <= column ? At(row, column) : Zero); + } + } + } + + /// + /// Creates a matrix that contains the values from the requested sub-matrix. + /// + /// The row to start copying from. + /// The number of rows to copy. Must be positive. + /// The column to start copying from. + /// The number of columns to copy. Must be positive. + /// The requested sub-matrix. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + /// If or + /// is not positive. + public virtual Matrix SubMatrix(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + var result = Build.SameAs(this, rowCount, columnCount); + Storage.CopySubMatrixTo(result.Storage, rowIndex, 0, rowCount, columnIndex, 0, columnCount, ExistingData.AssumeZeros); + return result; + } + + /// + /// Returns the elements of the diagonal in a Vector. + /// + /// The elements of the diagonal. + /// For non-square matrices, the method returns Min(Rows, Columns) elements where + /// i == j (i is the row index, and j is the column index). + public virtual Vector Diagonal() + { + var min = Math.Min(RowCount, ColumnCount); + var diagonal = Vector.Build.SameAs(this, min); + + for (var i = 0; i < min; i++) + { + diagonal.At(i, At(i, i)); + } + + return diagonal; + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public virtual Matrix StrictlyLowerTriangle() + { + var result = Build.SameAs(this); + for (var row = 0; row < RowCount; row++) + { + var columns = Math.Min(row, ColumnCount); + for (var column = 0; column < columns; column++) + { + result.At(row, column, At(row, column)); + } + } + + return result; + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public virtual void StrictlyLowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + for (var row = 0; row < RowCount; row++) + { + for (var column = 0; column < ColumnCount; column++) + { + result.At(row, column, row > column ? At(row, column) : Zero); + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public virtual Matrix StrictlyUpperTriangle() + { + var result = Build.SameAs(this); + for (var row = 0; row < RowCount; row++) + { + for (var column = row + 1; column < ColumnCount; column++) + { + result.At(row, column, At(row, column)); + } + } + + return result; + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public virtual void StrictlyUpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + for (var row = 0; row < RowCount; row++) + { + for (var column = 0; column < ColumnCount; column++) + { + result.At(row, column, row < column ? At(row, column) : Zero); + } + } + } + + /// + /// Creates a new matrix and inserts the given column at the given index. + /// + /// The index of where to insert the column. + /// The column to insert. + /// A new matrix with the inserted column. + /// If is . + /// If is < zero or > the number of columns. + /// If the size of != the number of rows. + public Matrix InsertColumn(int columnIndex, Vector column) + { + if (column == null) + { + throw new ArgumentNullException(nameof(column)); + } + + if ((uint)columnIndex > (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + if (column.Count != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, nameof(column)); + } + + var result = Build.SameAs(this, RowCount, ColumnCount + 1, fullyMutable: true); + Storage.CopySubMatrixTo(result.Storage, 0, 0, RowCount, 0, 0, columnIndex, ExistingData.AssumeZeros); + result.SetColumn(columnIndex, column); + Storage.CopySubMatrixTo(result.Storage, 0, 0, RowCount, columnIndex, columnIndex + 1, ColumnCount - columnIndex, ExistingData.AssumeZeros); + return result; + } + + /// + /// Creates a new matrix with the given column removed. + /// + /// The index of the column to remove. + /// A new matrix without the chosen column. + /// If is < zero or >= the number of columns. + public Matrix RemoveColumn(int columnIndex) + { + if ((uint)columnIndex >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + var result = Build.SameAs(this, RowCount, ColumnCount - 1, fullyMutable: true); + Storage.CopySubMatrixTo(result.Storage, 0, 0, RowCount, 0, 0, columnIndex, ExistingData.AssumeZeros); + Storage.CopySubMatrixTo(result.Storage, 0, 0, RowCount, columnIndex + 1, columnIndex, ColumnCount - columnIndex - 1, ExistingData.AssumeZeros); + return result; + } + + /// + /// Copies the values of the given Vector to the specified column. + /// + /// The column to copy the values to. + /// The vector to copy the values from. + /// If is . + /// If is less than zero, + /// or greater than or equal to the number of columns. + /// If the size of does not + /// equal the number of rows of this Matrix. + public void SetColumn(int columnIndex, Vector column) + { + if (column == null) + { + throw new ArgumentNullException(nameof(column)); + } + + column.Storage.CopyToColumn(Storage, columnIndex); + } + + /// + /// Copies the values of the given Vector to the specified sub-column. + /// + /// The column to copy the values to. + /// The row to start copying to. + /// The number of elements to copy. + /// The vector to copy the values from. + /// If is . + /// If is less than zero, + /// or greater than or equal to the number of columns. + /// If the size of does not + /// equal the number of rows of this Matrix. + public void SetColumn(int columnIndex, int rowIndex, int length, Vector column) + { + if (column == null) + { + throw new ArgumentNullException(nameof(column)); + } + + column.Storage.CopyToSubColumn(Storage, columnIndex, 0, rowIndex, length); + } + + /// + /// Copies the values of the given array to the specified column. + /// + /// The column to copy the values to. + /// The array to copy the values from. + /// If is . + /// If is less than zero, + /// or greater than or equal to the number of columns. + /// If the size of does not + /// equal the number of rows of this Matrix. + /// If the size of does not + /// equal the number of rows of this Matrix. + public void SetColumn(int columnIndex, T[] column) + { + if (column == null) + { + throw new ArgumentNullException(nameof(column)); + } + + new DenseVectorStorage(column.Length, column).CopyToColumn(Storage, columnIndex); + } + + /// + /// Creates a new matrix and inserts the given row at the given index. + /// + /// The index of where to insert the row. + /// The row to insert. + /// A new matrix with the inserted column. + /// If is . + /// If is < zero or > the number of rows. + /// If the size of != the number of columns. + public Matrix InsertRow(int rowIndex, Vector row) + { + if (row == null) + { + throw new ArgumentNullException(nameof(row)); + } + + if ((uint)rowIndex > (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowIndex)); + } + + if (row.Count != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, nameof(row)); + } + + var result = Build.SameAs(this, RowCount + 1, ColumnCount, fullyMutable: true); + Storage.CopySubMatrixTo(result.Storage, 0, 0, rowIndex, 0, 0, ColumnCount, ExistingData.AssumeZeros); + result.SetRow(rowIndex, row); + Storage.CopySubMatrixTo(result.Storage, rowIndex, rowIndex + 1, RowCount - rowIndex, 0, 0, ColumnCount, ExistingData.AssumeZeros); + return result; + } + + /// + /// Creates a new matrix with the given row removed. + /// + /// The index of the row to remove. + /// A new matrix without the chosen row. + /// If is < zero or >= the number of rows. + public Matrix RemoveRow(int rowIndex) + { + if ((uint)rowIndex >= (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowIndex)); + } + + var result = Build.SameAs(this, RowCount - 1, ColumnCount, fullyMutable: true); + Storage.CopySubMatrixTo(result.Storage, 0, 0, rowIndex, 0, 0, ColumnCount, ExistingData.AssumeZeros); + Storage.CopySubMatrixTo(result.Storage, rowIndex + 1, rowIndex, RowCount - rowIndex - 1, 0, 0, ColumnCount, ExistingData.AssumeZeros); + return result; + } + + /// + /// Copies the values of the given Vector to the specified row. + /// + /// The row to copy the values to. + /// The vector to copy the values from. + /// If is . + /// If is less than zero, + /// or greater than or equal to the number of rows. + /// If the size of does not + /// equal the number of columns of this Matrix. + public void SetRow(int rowIndex, Vector row) + { + if (row == null) + { + throw new ArgumentNullException(nameof(row)); + } + + row.Storage.CopyToRow(Storage, rowIndex); + } + + /// + /// Copies the values of the given Vector to the specified sub-row. + /// + /// The row to copy the values to. + /// The column to start copying to. + /// The number of elements to copy. + /// The vector to copy the values from. + /// If is . + /// If is less than zero, + /// or greater than or equal to the number of rows. + /// If the size of does not + /// equal the number of columns of this Matrix. + public void SetRow(int rowIndex, int columnIndex, int length, Vector row) + { + if (row == null) + { + throw new ArgumentNullException(nameof(row)); + } + + row.Storage.CopyToSubRow(Storage, rowIndex, 0, columnIndex, length); + } + + /// + /// Copies the values of the given array to the specified row. + /// + /// The row to copy the values to. + /// The array to copy the values from. + /// If is . + /// If is less than zero, + /// or greater than or equal to the number of rows. + /// If the size of does not + /// equal the number of columns of this Matrix. + public void SetRow(int rowIndex, T[] row) + { + if (row == null) + { + throw new ArgumentNullException(nameof(row)); + } + + new DenseVectorStorage(row.Length, row).CopyToRow(Storage, rowIndex); + } + + /// + /// Copies the values of a given matrix into a region in this matrix. + /// + /// The row to start copying to. + /// The column to start copying to. + /// The sub-matrix to copy from. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + public void SetSubMatrix(int rowIndex, int columnIndex, Matrix subMatrix) + { + subMatrix.Storage.CopySubMatrixTo(Storage, 0, rowIndex, subMatrix.RowCount, 0, columnIndex, subMatrix.ColumnCount); + } + + /// + /// Copies the values of a given matrix into a region in this matrix. + /// + /// The row to start copying to. + /// The number of rows to copy. Must be positive. + /// The column to start copying to. + /// The number of columns to copy. Must be positive. + /// The sub-matrix to copy from. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + /// the size of is not at least x . + /// If or + /// is not positive. + public void SetSubMatrix(int rowIndex, int rowCount, int columnIndex, int columnCount, Matrix subMatrix) + { + subMatrix.Storage.CopySubMatrixTo(Storage, 0, rowIndex, rowCount, 0, columnIndex, columnCount); + } + + /// + /// Copies the values of a given matrix into a region in this matrix. + /// + /// The row to start copying to. + /// The row of the sub-matrix to start copying from. + /// The number of rows to copy. Must be positive. + /// The column to start copying to. + /// The column of the sub-matrix to start copying from. + /// The number of columns to copy. Must be positive. + /// The sub-matrix to copy from. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + /// the size of is not at least x . + /// If or + /// is not positive. + public void SetSubMatrix(int rowIndex, int sorceRowIndex, int rowCount, int columnIndex, int sourceColumnIndex, int columnCount, Matrix subMatrix) + { + subMatrix.Storage.CopySubMatrixTo(Storage, sorceRowIndex, rowIndex, rowCount, sourceColumnIndex, columnIndex, columnCount); + } + + /// + /// Copies the values of the given Vector to the diagonal. + /// + /// The vector to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If is . + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public virtual void SetDiagonal(Vector source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + var min = Math.Min(RowCount, ColumnCount); + + if (source.Count != min) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(source)); + } + + for (var i = 0; i < min; i++) + { + At(i, i, source.At(i)); + } + } + + /// + /// Copies the values of the given array to the diagonal. + /// + /// The array to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If is . + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public virtual void SetDiagonal(T[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + var min = Math.Min(RowCount, ColumnCount); + + if (source.Length != min) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(source)); + } + + for (var i = 0; i < min; i++) + { + At(i, i, source[i]); + } + } + + /// + /// Returns the transpose of this matrix. + /// + /// The transpose of this matrix. + public Matrix Transpose() + { + var result = Build.SameAs(this, ColumnCount, RowCount); + Storage.TransposeToUnchecked(result.Storage, ExistingData.AssumeZeros); + return result; + } + + /// + /// Puts the transpose of this matrix into the result matrix. + /// + public void Transpose(Matrix result) + { + Storage.TransposeTo(result.Storage, ExistingData.Clear); + } + + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public abstract Matrix ConjugateTranspose(); + + /// + /// Puts the conjugate transpose of this matrix into the result matrix. + /// + public abstract void ConjugateTranspose(Matrix result); + + /// + /// Permute the rows of a matrix according to a permutation. + /// + /// The row permutation to apply to this matrix. + public virtual void PermuteRows(Permutation p) + { + if (p.Dimension != RowCount) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(p)); + } + + // Get a sequence of inversions from the permutation. + var inv = p.ToInversions(); + + for (var i = 0; i < inv.Length; i++) + { + if (inv[i] != i) + { + var q = inv[i]; + for (var j = 0; j < ColumnCount; j++) + { + var temp = At(q, j); + At(q, j, At(i, j)); + At(i, j, temp); + } + } + } + } + + /// + /// Permute the columns of a matrix according to a permutation. + /// + /// The column permutation to apply to this matrix. + public virtual void PermuteColumns(Permutation p) + { + if (p.Dimension != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(p)); + } + + // Get a sequence of inversions from the permutation. + var inv = p.ToInversions(); + + for (var i = 0; i < inv.Length; i++) + { + if (inv[i] != i) + { + var q = inv[i]; + for (var j = 0; j < RowCount; j++) + { + var temp = At(j, q); + At(j, q, At(j, i)); + At(j, i, temp); + } + } + } + } + + /// + /// Concatenates this matrix with the given matrix. + /// + /// The matrix to concatenate. + /// The combined matrix. + /// + /// + public Matrix Append(Matrix right) + { + if (right == null) + { + throw new ArgumentNullException(nameof(right)); + } + + if (right.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + var result = Build.SameAs(this, right, RowCount, ColumnCount + right.ColumnCount, fullyMutable: true); + Storage.CopySubMatrixToUnchecked(result.Storage, 0, 0, RowCount, 0, 0, ColumnCount, ExistingData.AssumeZeros); + right.Storage.CopySubMatrixToUnchecked(result.Storage, 0, 0, right.RowCount, 0, ColumnCount, right.ColumnCount, ExistingData.AssumeZeros); + return result; + } + + /// + /// Concatenates this matrix with the given matrix and places the result into the result matrix. + /// + /// The matrix to concatenate. + /// The combined matrix. + /// + /// + public void Append(Matrix right, Matrix result) + { + if (right == null) + { + throw new ArgumentNullException(nameof(right)); + } + + if (right.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.ColumnCount != (ColumnCount + right.ColumnCount) || result.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + Storage.CopySubMatrixToUnchecked(result.Storage, 0, 0, RowCount, 0, 0, ColumnCount, ExistingData.Clear); + right.Storage.CopySubMatrixToUnchecked(result.Storage, 0, 0, right.RowCount, 0, ColumnCount, right.ColumnCount, ExistingData.Clear); + } + + /// + /// Stacks this matrix on top of the given matrix and places the result into the result matrix. + /// + /// The matrix to stack this matrix upon. + /// The combined matrix. + /// If lower is . + /// If upper.Columns != lower.Columns. + /// + /// + public Matrix Stack(Matrix lower) + { + if (lower == null) + { + throw new ArgumentNullException(nameof(lower)); + } + + if (lower.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, nameof(lower)); + } + + var result = Build.SameAs(this, lower, RowCount + lower.RowCount, ColumnCount, fullyMutable: true); + Storage.CopySubMatrixToUnchecked(result.Storage, 0, 0, RowCount, 0, 0, ColumnCount, ExistingData.AssumeZeros); + lower.Storage.CopySubMatrixToUnchecked(result.Storage, 0, RowCount, lower.RowCount, 0, 0, lower.ColumnCount, ExistingData.AssumeZeros); + return result; + } + + /// + /// Stacks this matrix on top of the given matrix and places the result into the result matrix. + /// + /// The matrix to stack this matrix upon. + /// The combined matrix. + /// If lower is . + /// If upper.Columns != lower.Columns. + /// + /// + public void Stack(Matrix lower, Matrix result) + { + if (lower == null) + { + throw new ArgumentNullException(nameof(lower)); + } + + if (lower.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, nameof(lower)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != (RowCount + lower.RowCount) || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + Storage.CopySubMatrixToUnchecked(result.Storage, 0, 0, RowCount, 0, 0, ColumnCount, ExistingData.Clear); + lower.Storage.CopySubMatrixToUnchecked(result.Storage, 0, RowCount, lower.RowCount, 0, 0, lower.ColumnCount, ExistingData.Clear); + } + + /// + /// Diagonally stacks his matrix on top of the given matrix. The new matrix is a M-by-N matrix, + /// where M = this.Rows + lower.Rows and N = this.Columns + lower.Columns. + /// The values of off the off diagonal matrices/blocks are set to zero. + /// + /// The lower, right matrix. + /// If lower is . + /// the combined matrix + /// + /// + public Matrix DiagonalStack(Matrix lower) + { + if (lower == null) + { + throw new ArgumentNullException(nameof(lower)); + } + + var result = Build.SameAs(this, lower, RowCount + lower.RowCount, ColumnCount + lower.ColumnCount, RowCount != ColumnCount); + Storage.CopySubMatrixToUnchecked(result.Storage, 0, 0, RowCount, 0, 0, ColumnCount, ExistingData.AssumeZeros); + lower.Storage.CopySubMatrixToUnchecked(result.Storage, 0, RowCount, lower.RowCount, 0, ColumnCount, lower.ColumnCount, ExistingData.AssumeZeros); + return result; + } + + /// + /// Diagonally stacks his matrix on top of the given matrix and places the combined matrix into the result matrix. + /// + /// The lower, right matrix. + /// The combined matrix + /// If lower is . + /// If the result matrix is . + /// If the result matrix's dimensions are not (this.Rows + lower.rows) x (this.Columns + lower.Columns). + /// + /// + public void DiagonalStack(Matrix lower, Matrix result) + { + if (lower == null) + { + throw new ArgumentNullException(nameof(lower)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount + lower.RowCount || result.ColumnCount != ColumnCount + lower.ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + Storage.CopySubMatrixToUnchecked(result.Storage, 0, 0, RowCount, 0, 0, ColumnCount, ExistingData.Clear); + lower.Storage.CopySubMatrixToUnchecked(result.Storage, 0, RowCount, lower.RowCount, 0, ColumnCount, lower.ColumnCount, ExistingData.Clear); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public virtual bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + for (var row = 0; row < RowCount; row++) + { + for (var column = row + 1; column < ColumnCount; column++) + { + if (!At(row, column).Equals(At(column, row))) + { + return false; + } + } + } + + return true; + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public abstract bool IsHermitian(); + + /// + /// Returns this matrix as a multidimensional array. + /// The returned array will be independent from this matrix. + /// A new memory block will be allocated for the array. + /// + /// A multidimensional containing the values of this matrix. + public T[,] ToArray() + { + return Storage.ToArray(); + } + + /// + /// Returns the matrix's elements as an array with the data laid out column by column (column major). + /// The returned array will be independent from this matrix. + /// A new memory block will be allocated for the array. + /// + ///
+    /// 1, 2, 3
+    /// 4, 5, 6  will be returned as  1, 4, 7, 2, 5, 8, 3, 6, 9
+    /// 7, 8, 9
+    /// 
+ /// An array containing the matrix's elements. + /// + /// + public T[] ToColumnMajorArray() + { + return Storage.ToColumnMajorArray(); + } + + /// + /// Returns the matrix's elements as an array with the data laid row by row (row major). + /// The returned array will be independent from this matrix. + /// A new memory block will be allocated for the array. + /// + ///
+    /// 1, 2, 3
+    /// 4, 5, 6  will be returned as  1, 2, 3, 4, 5, 6, 7, 8, 9
+    /// 7, 8, 9
+    /// 
+ /// An array containing the matrix's elements. + /// + /// + public T[] ToRowMajorArray() + { + return Storage.ToRowMajorArray(); + } + + /// + /// Returns this matrix as array of row arrays. + /// The returned arrays will be independent from this matrix. + /// A new memory block will be allocated for the arrays. + /// + public T[][] ToRowArrays() + { + return Storage.ToRowArrays(); + } + + /// + /// Returns this matrix as array of column arrays. + /// The returned arrays will be independent from this matrix. + /// A new memory block will be allocated for the arrays. + /// + public T[][] ToColumnArrays() + { + return Storage.ToColumnArrays(); + } + + /// + /// Returns the internal multidimensional array of this matrix if, and only if, this matrix is stored by such an array internally. + /// Otherwise returns null. Changes to the returned array and the matrix will affect each other. + /// Use ToArray instead if you always need an independent array. + /// + public T[,] AsArray() + { + return Storage.AsArray(); + } + + /// + /// Returns the internal column by column (column major) array of this matrix if, and only if, this matrix is stored by such arrays internally. + /// Otherwise returns null. Changes to the returned arrays and the matrix will affect each other. + /// Use ToColumnMajorArray instead if you always need an independent array. + /// + ///
+    /// 1, 2, 3
+    /// 4, 5, 6  will be returned as  1, 4, 7, 2, 5, 8, 3, 6, 9
+    /// 7, 8, 9
+    /// 
+ /// An array containing the matrix's elements. + /// + /// + public T[] AsColumnMajorArray() + { + return Storage.AsColumnMajorArray(); + } + + /// + /// Returns the internal row by row (row major) array of this matrix if, and only if, this matrix is stored by such arrays internally. + /// Otherwise returns null. Changes to the returned arrays and the matrix will affect each other. + /// Use ToRowMajorArray instead if you always need an independent array. + /// + ///
+    /// 1, 2, 3
+    /// 4, 5, 6  will be returned as  1, 2, 3, 4, 5, 6, 7, 8, 9
+    /// 7, 8, 9
+    /// 
+ /// An array containing the matrix's elements. + /// + /// + public T[] AsRowMajorArray() + { + return Storage.AsRowMajorArray(); + } + + /// + /// Returns the internal row arrays of this matrix if, and only if, this matrix is stored by such arrays internally. + /// Otherwise returns null. Changes to the returned arrays and the matrix will affect each other. + /// Use ToRowArrays instead if you always need an independent array. + /// + public T[][] AsRowArrays() + { + return Storage.AsRowArrays(); + } + + /// + /// Returns the internal column arrays of this matrix if, and only if, this matrix is stored by such arrays internally. + /// Otherwise returns null. Changes to the returned arrays and the matrix will affect each other. + /// Use ToColumnArrays instead if you always need an independent array. + /// + public T[][] AsColumnArrays() + { + return Storage.AsColumnArrays(); + } + + /// + /// Returns an IEnumerable that can be used to iterate through all values of the matrix. + /// + /// + /// The enumerator will include all values, even if they are zero. + /// The ordering of the values is unspecified (not necessarily column-wise or row-wise). + /// + public IEnumerable Enumerate() + { + return Storage.Enumerate(); + } + + /// + /// Returns an IEnumerable that can be used to iterate through all values of the matrix. + /// + /// + /// The enumerator will include all values, even if they are zero. + /// The ordering of the values is unspecified (not necessarily column-wise or row-wise). + /// + public IEnumerable Enumerate(Zeros zeros = Zeros.Include) + { + switch (zeros) + { + case Zeros.AllowSkip: + return Storage.EnumerateNonZero(); + default: + return Storage.Enumerate(); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through all values of the matrix and their index. + /// + /// + /// The enumerator returns a Tuple with the first two values being the row and column index + /// and the third value being the value of the element at that index. + /// The enumerator will include all values, even if they are zero. + /// + public IEnumerable> EnumerateIndexed() + { + return Storage.EnumerateIndexed(); + } + + /// + /// Returns an IEnumerable that can be used to iterate through all values of the matrix and their index. + /// + /// + /// The enumerator returns a Tuple with the first two values being the row and column index + /// and the third value being the value of the element at that index. + /// The enumerator will include all values, even if they are zero. + /// + public IEnumerable> EnumerateIndexed(Zeros zeros = Zeros.Include) + { + switch (zeros) + { + case Zeros.AllowSkip: + return Storage.EnumerateNonZeroIndexed(); + default: + return Storage.EnumerateIndexed(); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through all columns of the matrix. + /// + public IEnumerable> EnumerateColumns() + { + for (var i = 0; i < ColumnCount; i++) + { + yield return Column(i); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through a subset of all columns of the matrix. + /// + /// The column to start enumerating over. + /// The number of columns to enumerating over. + public IEnumerable> EnumerateColumns(int index, int length) + { + var maxIndex = Math.Min(index + length, ColumnCount); + for (var i = Math.Max(index, 0); i < maxIndex; i++) + { + yield return Column(i); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through all columns of the matrix and their index. + /// + /// + /// The enumerator returns a Tuple with the first value being the column index + /// and the second value being the value of the column at that index. + /// + public IEnumerable>> EnumerateColumnsIndexed() + { + for (var i = 0; i < ColumnCount; i++) + { + yield return new Tuple>(i, Column(i)); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through a subset of all columns of the matrix and their index. + /// + /// The column to start enumerating over. + /// The number of columns to enumerating over. + /// + /// The enumerator returns a Tuple with the first value being the column index + /// and the second value being the value of the column at that index. + /// + public IEnumerable>> EnumerateColumnsIndexed(int index, int length) + { + var maxIndex = Math.Min(index + length, ColumnCount); + for (var i = Math.Max(index, 0); i < maxIndex; i++) + { + yield return new Tuple>(i, Column(i)); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through all rows of the matrix. + /// + public IEnumerable> EnumerateRows() + { + for (var i = 0; i < RowCount; i++) + { + yield return Row(i); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through a subset of all rows of the matrix. + /// + /// The row to start enumerating over. + /// The number of rows to enumerating over. + public IEnumerable> EnumerateRows(int index, int length) + { + var maxIndex = Math.Min(index + length, RowCount); + for (var i = Math.Max(index, 0); i < maxIndex; i++) + { + yield return Row(i); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through all rows of the matrix and their index. + /// + /// + /// The enumerator returns a Tuple with the first value being the row index + /// and the second value being the value of the row at that index. + /// + public IEnumerable>> EnumerateRowsIndexed() + { + for (var i = 0; i < RowCount; i++) + { + yield return new Tuple>(i, Row(i)); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through a subset of all rows of the matrix and their index. + /// + /// The row to start enumerating over. + /// The number of rows to enumerating over. + /// + /// The enumerator returns a Tuple with the first value being the row index + /// and the second value being the value of the row at that index. + /// + public IEnumerable>> EnumerateRowsIndexed(int index, int length) + { + var maxIndex = Math.Min(index + length, RowCount); + for (var i = Math.Max(index, 0); i < maxIndex; i++) + { + yield return new Tuple>(i, Row(i)); + } + } + + /// + /// Applies a function to each value of this matrix and replaces the value with its result. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse matrices). + /// + public void MapInplace(Func f, Zeros zeros = Zeros.AllowSkip) + { + Storage.MapInplace(f, zeros); + } + + /// + /// Applies a function to each value of this matrix and replaces the value with its result. + /// The row and column indices of each value (zero-based) are passed as first arguments to the function. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse matrices). + /// + public void MapIndexedInplace(Func f, Zeros zeros = Zeros.AllowSkip) + { + Storage.MapIndexedInplace(f, zeros); + } + + /// + /// Applies a function to each value of this matrix and replaces the value in the result matrix. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse matrices). + /// + public void Map(Func f, Matrix result, Zeros zeros = Zeros.AllowSkip) + { + if (ReferenceEquals(this, result)) + { + Storage.MapInplace(f, zeros); + } + else + { + Storage.MapTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear); + } + } + + /// + /// Applies a function to each value of this matrix and replaces the value in the result matrix. + /// The index of each value (zero-based) is passed as first argument to the function. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse matrices). + /// + public void MapIndexed(Func f, Matrix result, Zeros zeros = Zeros.AllowSkip) + { + if (ReferenceEquals(this, result)) + { + Storage.MapIndexedInplace(f, zeros); + } + else + { + Storage.MapIndexedTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear); + } + } + + /// + /// Applies a function to each value of this matrix and replaces the value in the result matrix. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse matrices). + /// + public void MapConvert(Func f, Matrix result, Zeros zeros = Zeros.AllowSkip) + where TU : struct, IEquatable, IFormattable + { + Storage.MapTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear); + } + + /// + /// Applies a function to each value of this matrix and replaces the value in the result matrix. + /// The index of each value (zero-based) is passed as first argument to the function. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse matrices). + /// + public void MapIndexedConvert(Func f, Matrix result, Zeros zeros = Zeros.AllowSkip) + where TU : struct, IEquatable, IFormattable + { + Storage.MapIndexedTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear); + } + + /// + /// Applies a function to each value of this matrix and returns the results as a new matrix. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse matrices). + /// + public Matrix Map(Func f, Zeros zeros = Zeros.AllowSkip) + where TU : struct, IEquatable, IFormattable + { + var result = Matrix.Build.SameAs(this, RowCount, ColumnCount, fullyMutable: zeros == Zeros.Include); + Storage.MapToUnchecked(result.Storage, f, zeros, ExistingData.AssumeZeros); + return result; + } + + /// + /// Applies a function to each value of this matrix and returns the results as a new matrix. + /// The index of each value (zero-based) is passed as first argument to the function. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse matrices). + /// + public Matrix MapIndexed(Func f, Zeros zeros = Zeros.AllowSkip) + where TU : struct, IEquatable, IFormattable + { + var result = Matrix.Build.SameAs(this, RowCount, ColumnCount, fullyMutable: zeros == Zeros.Include); + Storage.MapIndexedToUnchecked(result.Storage, f, zeros, ExistingData.AssumeZeros); + return result; + } + + /// + /// For each row, applies a function f to each element of the row, threading an accumulator argument through the computation. + /// Returns an array with the resulting accumulator states for each row. + /// + public TU[] FoldByRow(Func f, TU state, Zeros zeros = Zeros.AllowSkip) + { + var result = new TU[RowCount]; + if (!EqualityComparer.Default.Equals(state, default(TU))) + { + CommonParallel.For(0, result.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = state; + } + }); + } + + Storage.FoldByRowUnchecked(result, f, (x, c) => x, result, zeros); + return result; + } + + /// + /// For each column, applies a function f to each element of the column, threading an accumulator argument through the computation. + /// Returns an array with the resulting accumulator states for each column. + /// + public TU[] FoldByColumn(Func f, TU state, Zeros zeros = Zeros.AllowSkip) + { + var result = new TU[ColumnCount]; + if (!EqualityComparer.Default.Equals(state, default(TU))) + { + CommonParallel.For(0, result.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = state; + } + }); + } + + Storage.FoldByColumnUnchecked(result, f, (x, c) => x, result, zeros); + return result; + } + + /// + /// Applies a function f to each row vector, threading an accumulator vector argument through the computation. + /// Returns the resulting accumulator vector. + /// + public Vector FoldRows(Func, Vector, Vector> f, Vector state) + where TU : struct, IEquatable, IFormattable + { + foreach (var vector in EnumerateRows()) + { + state = f(state, vector); + } + + return state; + } + + /// + /// Applies a function f to each column vector, threading an accumulator vector argument through the computation. + /// Returns the resulting accumulator vector. + /// + public Vector FoldColumns(Func, Vector, Vector> f, Vector state) + where TU : struct, IEquatable, IFormattable + { + foreach (var vector in EnumerateColumns()) + { + state = f(state, vector); + } + + return state; + } + + /// + /// Reduces all row vectors by applying a function between two of them, until only a single vector is left. + /// + public Vector ReduceRows(Func, Vector, Vector> f) + { + return EnumerateRows().Aggregate(f); + } + + /// + /// Reduces all column vectors by applying a function between two of them, until only a single vector is left. + /// + public Vector ReduceColumns(Func, Vector, Vector> f) + { + return EnumerateColumns().Aggregate(f); + } + + /// + /// Applies a function to each value pair of two matrices and replaces the value in the result vector. + /// + public void Map2(Func f, Matrix other, Matrix result, Zeros zeros = Zeros.AllowSkip) + { + Storage.Map2To(result.Storage, other.Storage, f, zeros, ExistingData.Clear); + } + + /// + /// Applies a function to each value pair of two matrices and returns the results as a new vector. + /// + public Matrix Map2(Func f, Matrix other, Zeros zeros = Zeros.AllowSkip) + { + var result = Build.SameAs(this); + Storage.Map2To(result.Storage, other.Storage, f, zeros, ExistingData.AssumeZeros); + return result; + } + + /// + /// Applies a function to update the status with each value pair of two matrices and returns the resulting status. + /// + public TState Fold2(Func f, TState state, Matrix other, Zeros zeros = Zeros.AllowSkip) + where TOther : struct, IEquatable, IFormattable + { + return Storage.Fold2(other.Storage, f, state, zeros); + } + + /// + /// Returns a tuple with the index and value of the first element satisfying a predicate, or null if none is found. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public Tuple Find(Func predicate, Zeros zeros = Zeros.AllowSkip) + { + return Storage.Find(predicate, zeros); + } + + /// + /// Returns a tuple with the index and values of the first element pair of two matrices of the same size satisfying a predicate, or null if none is found. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public Tuple Find2(Func predicate, Matrix other, Zeros zeros = Zeros.AllowSkip) + where TOther : struct, IEquatable, IFormattable + { + return Storage.Find2(other.Storage, predicate, zeros); + } + + /// + /// Returns true if at least one element satisfies a predicate. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public bool Exists(Func predicate, Zeros zeros = Zeros.AllowSkip) + { + return Storage.Find(predicate, zeros) != null; + } + + /// + /// Returns true if at least one element pairs of two matrices of the same size satisfies a predicate. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public bool Exists2(Func predicate, Matrix other, Zeros zeros = Zeros.AllowSkip) + where TOther : struct, IEquatable, IFormattable + { + return Storage.Find2(other.Storage, predicate, zeros) != null; + } + + /// + /// Returns true if all elements satisfy a predicate. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public bool ForAll(Func predicate, Zeros zeros = Zeros.AllowSkip) + { + return Storage.Find(x => !predicate(x), zeros) == null; + } + + /// + /// Returns true if all element pairs of two matrices of the same size satisfy a predicate. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public bool ForAll2(Func predicate, Matrix other, Zeros zeros = Zeros.AllowSkip) + where TOther : struct, IEquatable, IFormattable + { + return Storage.Find2(other.Storage, (x, y) => !predicate(x, y), zeros) == null; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/MatrixExtensions.cs b/MathNet.Numerics/LinearAlgebra/MatrixExtensions.cs new file mode 100644 index 0000000..2d8a4e2 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/MatrixExtensions.cs @@ -0,0 +1,115 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.LinearAlgebra; + +using Complex64 = System.Numerics.Complex; + +public static class MatrixExtensions +{ + /// + /// Converts a matrix to single precision. + /// + public static Matrix ToSingle(this Matrix matrix) + { + return matrix.Map(x => (float)x, Zeros.AllowSkip); + } + + /// + /// Converts a matrix to double precision. + /// + public static Matrix ToDouble(this Matrix matrix) + { + return matrix.Map(x => (double)x, Zeros.AllowSkip); + } + + /// + /// Converts a matrix to single precision complex numbers. + /// + public static Matrix ToComplex32(this Matrix matrix) + { + return matrix.Map(x => new Numerics.Complex32((float)x.Real, (float)x.Imaginary), Zeros.AllowSkip); + } + + /// + /// Converts a matrix to double precision complex numbers. + /// + public static Matrix ToComplex(this Matrix matrix) + { + return matrix.Map(x => new Complex64(x.Real, x.Imaginary), Zeros.AllowSkip); + } + + /// + /// Gets a single precision complex matrix with the real parts from the given matrix. + /// + public static Matrix ToComplex32(this Matrix matrix) + { + return matrix.Map(x => new Numerics.Complex32(x, 0f), Zeros.AllowSkip); + } + + /// + /// Gets a double precision complex matrix with the real parts from the given matrix. + /// + public static Matrix ToComplex(this Matrix matrix) + { + return matrix.Map(x => new Complex64(x, 0d), Zeros.AllowSkip); + } + + /// + /// Gets a real matrix representing the real parts of a complex matrix. + /// + public static Matrix Real(this Matrix matrix) + { + return matrix.Map(x => x.Real, Zeros.AllowSkip); + } + + /// + /// Gets a real matrix representing the real parts of a complex matrix. + /// + public static Matrix Real(this Matrix matrix) + { + return matrix.Map(x => x.Real, Zeros.AllowSkip); + } + + /// + /// Gets a real matrix representing the imaginary parts of a complex matrix. + /// + public static Matrix Imaginary(this Matrix matrix) + { + return matrix.Map(x => x.Imaginary, Zeros.AllowSkip); + } + + /// + /// Gets a real matrix representing the imaginary parts of a complex matrix. + /// + public static Matrix Imaginary(this Matrix matrix) + { + return matrix.Map(x => x.Imaginary, Zeros.AllowSkip); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Options.cs b/MathNet.Numerics/LinearAlgebra/Options.cs new file mode 100644 index 0000000..4efd6d4 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Options.cs @@ -0,0 +1,82 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.LinearAlgebra; + +public enum ExistingData +{ + /// + /// Existing data may not be all zeros, so clearing may be necessary + /// if not all of it will be overwritten anyway. + /// + Clear = 0, + + /// + /// If existing data is assumed to be all zeros already, + /// clearing it may be skipped if applicable. + /// + AssumeZeros = 1 +} + +public enum Zeros +{ + /// + /// Allow skipping zero entries (without enforcing skipping them). + /// When enumerating sparse matrices this can significantly speed up operations. + /// + AllowSkip = 0, + + /// + /// Force applying the operation to all fields even if they are zero. + /// + Include = 1 +} + +public enum Symmetricity +{ + /// + /// It is not known yet whether a matrix is symmetric or not. + /// + Unknown = 0, + + /// + /// A matrix is symmetric + /// + Symmetric = 1, + + /// + /// A matrix is Hermitian (conjugate symmetric). + /// + Hermitian = 2, + + /// + /// A matrix is not symmetric + /// + Asymmetric = 3 +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/DenseMatrix.cs b/MathNet.Numerics/LinearAlgebra/Single/DenseMatrix.cs new file mode 100644 index 0000000..b08a912 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/DenseMatrix.cs @@ -0,0 +1,1262 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Single.Factorization; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Single; + +/// +/// A Matrix class with dense storage. The underlying storage is a one dimensional array in column-major order (column by column). +/// +[Serializable] +[DebuggerDisplay("DenseMatrix {RowCount}x{ColumnCount}-Single")] +public class DenseMatrix : Matrix +{ + /// + /// Number of rows. + /// + /// Using this instead of the RowCount property to speed up calculating + /// a matrix index in the data array. + readonly int _rowCount; + + /// + /// Number of columns. + /// + /// Using this instead of the ColumnCount property to speed up calculating + /// a matrix index in the data array. + readonly int _columnCount; + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + readonly float[] _values; + + /// + /// Create a new dense matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DenseMatrix(DenseColumnMajorMatrixStorage storage) + : base(storage) + { + _rowCount = storage.RowCount; + _columnCount = storage.ColumnCount; + _values = storage.Data; + } + + /// + /// Create a new square dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public DenseMatrix(int order) + : this(new DenseColumnMajorMatrixStorage(order, order)) + { + } + + /// + /// Create a new dense matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DenseMatrix(int rows, int columns) + : this(new DenseColumnMajorMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new dense matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to be in column-major order (column by column) and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + /// + public DenseMatrix(int rows, int columns, float[] storage) + : this(new DenseColumnMajorMatrixStorage(rows, columns, storage)) + { + } + + /// + /// Create a new dense matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfMatrix(Matrix matrix) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new dense matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfArray(float[,] array) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfArray(array)); + } + + /// + /// Create a new dense matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnMajor(int rows, int columns, IEnumerable columnMajor) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnMajorEnumerable(rows, columns, columnMajor)); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumns(IEnumerable> data) + { + return OfColumnArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumns(int rows, int columns, IEnumerable> data) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnArrays(params float[][] columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new dense matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnArrays(IEnumerable columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnArrays((columns as float[][]) ?? columns.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfColumnVectors(IEnumerable> columns) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRows(IEnumerable> data) + { + return OfRowArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new dense matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRows(int rows, int columns, IEnumerable> data) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new dense matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowArrays(params float[][] rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new dense matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowArrays(IEnumerable rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowArrays((rows as float[][]) ?? rows.ToArray())); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new dense matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfRowVectors(IEnumerable> rows) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalVector(Vector diagonal) + { + var m = new DenseMatrix(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = new DenseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalArray(float[] diagonal) + { + var m = new DenseMatrix(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static DenseMatrix OfDiagonalArray(int rows, int columns, float[] diagonal) + { + var m = new DenseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new dense matrix and initialize each value to the same provided value. + /// + public static DenseMatrix Create(int rows, int columns, float value) + { + if (value == 0f) + return new DenseMatrix(rows, columns); + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new dense matrix and initialize each value using the provided init function. + /// + public static DenseMatrix Create(int rows, int columns, Func init) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value to the same provided value. + /// + public static DenseMatrix CreateDiagonal(int rows, int columns, float value) + { + if (value == 0f) + return new DenseMatrix(rows, columns); + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal dense matrix and initialize each diagonal value using the provided init function. + /// + public static DenseMatrix CreateDiagonal(int rows, int columns, Func init) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static DenseMatrix CreateIdentity(int order) + { + return new DenseMatrix(DenseColumnMajorMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Create a new dense matrix with values sampled from the provided random distribution. + /// + public static DenseMatrix CreateRandom(int rows, int columns, IContinuousDistribution distribution) + { + return new DenseMatrix(new DenseColumnMajorMatrixStorage(rows, columns, Generate.RandomSingle(rows * columns, distribution))); + } + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + public float[] Values + { + get { return _values; } + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.OneNorm, _rowCount, _columnCount, _values); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.InfinityNorm, _rowCount, _columnCount, _values); + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + return LinearAlgebraControl.Provider.MatrixNorm(Norm.FrobeniusNorm, _rowCount, _columnCount, _values); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(-1, _values, denseResult._values); + return; + } + + base.DoNegate(result); + } + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected override void DoAdd(float scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoAdd(scalar, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = _values[i] + scalar; + } + }); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of add + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + // dense + dense = dense + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + var denseResult = result.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.AddArrays(_values, denseOther.Data, denseResult.Data); + return; + } + + // dense + diagonal = any + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + Storage.CopyToUnchecked(result.Storage, ExistingData.Clear); + var diagonal = diagonalOther.Data; + for (int i = 0; i < diagonal.Length; i++) + { + result.At(i, i, result.At(i, i) + diagonal[i]); + } + + return; + } + + base.DoAdd(other, result); + } + + /// + /// Subtracts a scalar from each element of the matrix and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(float scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoSubtract(scalar, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = _values[i] - scalar; + } + }); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(Matrix other, Matrix result) + { + // dense + dense = dense + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + var denseResult = result.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.SubtractArrays(_values, denseOther.Data, denseResult.Data); + return; + } + + // dense + diagonal = matrix + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + CopyTo(result); + var diagonal = diagonalOther.Data; + for (int i = 0; i < diagonal.Length; i++) + { + result.At(i, i, result.At(i, i) - diagonal[i]); + } + + return; + } + + base.DoSubtract(other, result); + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(float scalar, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoMultiply(scalar, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(scalar, _values, denseResult._values); + } + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + + if (denseRight == null || denseResult == null) + { + base.DoMultiply(rightSide, result); + } + else + { + LinearAlgebraControl.Provider.MatrixMultiply( + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + denseResult.Values); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiply( + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(ColumnCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.ColumnCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoMultiply(other, result); + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.DontTranspose, + Providers.LinearAlgebra.Transpose.Transpose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0f, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(ColumnCount, other.RowCount); + if (d < other.RowCount) + { + result.ClearSubMatrix(0, RowCount, ColumnCount, other.RowCount - ColumnCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < RowCount; i++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var denseRight = rightSide as DenseVector; + var denseResult = result as DenseVector; + + if (denseRight == null || denseResult == null) + { + base.DoTransposeThisAndMultiply(rightSide, result); + } + else + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.Transpose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseRight.Values, + denseRight.Count, + 1, + 0.0f, + denseResult.Values); + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.MatrixMultiplyWithUpdate( + Providers.LinearAlgebra.Transpose.Transpose, + Providers.LinearAlgebra.Transpose.DontTranspose, + 1.0f, + _values, + _rowCount, + _columnCount, + denseOther._values, + denseOther._rowCount, + denseOther._columnCount, + 0.0f, + denseResult._values); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null) + { + var diagonal = diagonalOther.Data; + var d = Math.Min(RowCount, other.ColumnCount); + if (d < other.ColumnCount) + { + result.ClearSubMatrix(0, ColumnCount, RowCount, other.ColumnCount - RowCount); + } + + int index = 0; + for (int i = 0; i < ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(i, j, _values[index] * diagonal[j]); + index++; + } + + index += (RowCount - d); + } + + return; + } + + base.DoTransposeThisAndMultiply(other, result); + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(float divisor, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(1.0f / divisor, _values, denseResult._values); + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + var denseOther = other as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseMultiply(other, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + var denseOther = divisor as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseDivideArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Matrix exponent, Matrix result) + { + var denseExponent = exponent as DenseMatrix; + var denseResult = result as DenseMatrix; + + if (denseExponent == null || denseResult == null) + { + base.DoPointwisePower(exponent, result); + } + else + { + LinearAlgebraControl.Provider.PointWisePowerArrays(_values, denseExponent._values, denseResult._values); + } + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoModulus(float divisor, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoModulus(divisor, result); + return; + } + + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + CommonParallel.For(0, _values.Length, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = Euclid.Modulus(v[i], divisor); + } + }); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoModulusByThis(float dividend, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoModulusByThis(dividend, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = Euclid.Modulus(dividend, _values[i]); + } + }); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoRemainder(float divisor, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoRemainder(divisor, result); + return; + } + + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + CommonParallel.For(0, _values.Length, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] %= divisor; + } + }); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoRemainderByThis(float dividend, Matrix result) + { + var denseResult = result as DenseMatrix; + if (denseResult == null) + { + base.DoRemainderByThis(dividend, result); + return; + } + + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + var v = denseResult._values; + for (int i = a; i < b; i++) + { + v[i] = dividend % _values[i]; + } + }); + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public override float Trace() + { + if (_rowCount != _columnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var sum = 0.0f; + for (var i = 0; i < _rowCount; i++) + { + sum += _values[(i * _rowCount) + i]; + } + + return sum; + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static DenseMatrix operator +(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide._rowCount != rightSide._rowCount || leftSide._columnCount != rightSide._columnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Add(rightSide); + } + + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static DenseMatrix operator +(DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Clone(); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static DenseMatrix operator -(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide._rowCount != rightSide._rowCount || leftSide._columnCount != rightSide._columnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Subtract(rightSide); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static DenseMatrix operator -(DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Negate(); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator *(DenseMatrix leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator *(float leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseMatrix)rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static DenseMatrix operator *(DenseMatrix leftSide, DenseMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide._columnCount != rightSide._rowCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (DenseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static DenseVector operator *(DenseMatrix leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static DenseVector operator *(DenseVector leftSide, DenseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.LeftMultiply(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static DenseMatrix operator %(DenseMatrix leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseMatrix)leftSide.Remainder(rightSide); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public override bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = j + 1; i < RowCount; i++) + { + if (_values[(i * ColumnCount) + j] != _values[index + i]) + { + return false; + } + } + } + + return true; + } + + public override Cholesky Cholesky() + { + return DenseCholesky.Create(this); + } + + public override LU LU() + { + return DenseLU.Create(this); + } + + public override QR QR(QRMethod method = QRMethod.Thin) + { + return DenseQR.Create(this, method); + } + + public override GramSchmidt GramSchmidt() + { + return DenseGramSchmidt.Create(this); + } + + public override Svd Svd(bool computeVectors = true) + { + return DenseSvd.Create(this, computeVectors); + } + + public override Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown) + { + return DenseEvd.Create(this, symmetricity); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/DenseVector.cs b/MathNet.Numerics/LinearAlgebra/Single/DenseVector.cs new file mode 100644 index 0000000..c9165c0 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/DenseVector.cs @@ -0,0 +1,865 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Single; + +using MathNet.Numerics.Providers.LinearAlgebra; + +/// +/// A vector using dense storage. +/// +[Serializable] +[DebuggerDisplay("DenseVector {" + nameof(Count) + "}-Single")] +public class DenseVector : Vector +{ + /// + /// Number of elements + /// + readonly int _length; + + /// + /// Gets the vector's data. + /// + readonly float[] _values; + + /// + /// Create a new dense vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DenseVector(DenseVectorStorage storage) + : base(storage) + { + _length = storage.Length; + _values = storage.Data; + } + + /// + /// Create a new dense vector with the given length. + /// All cells of the vector will be initialized to zero. + /// Zero-length vectors are not supported. + /// + /// If length is less than one. + public DenseVector(int length) + : this(new DenseVectorStorage(length)) + { + } + + /// + /// Create a new dense vector directly binding to a raw array. + /// The array is used directly without copying. + /// Very efficient, but changes to the array and the vector will affect each other. + /// + public DenseVector(float[] storage) + : this(new DenseVectorStorage(storage.Length, storage)) + { + } + + /// + /// Create a new dense vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfVector(Vector vector) + { + return new DenseVector(DenseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new dense vector as a copy of the given array. + /// This new vector will be independent from the array. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfArray(float[] array) + { + return new DenseVector(DenseVectorStorage.OfVector(new DenseVectorStorage(array.Length, array))); + } + + /// + /// Create a new dense vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfEnumerable(IEnumerable enumerable) + { + return new DenseVector(DenseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new dense vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static DenseVector OfIndexedEnumerable(int length, IEnumerable> enumerable) + { + return new DenseVector(DenseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new dense vector and initialize each value using the provided value. + /// + public static DenseVector Create(int length, float value) + { + if (value == 0f) + return new DenseVector(length); + return new DenseVector(DenseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new dense vector and initialize each value using the provided init function. + /// + public static DenseVector Create(int length, Func init) + { + return new DenseVector(DenseVectorStorage.OfInit(length, init)); + } + + /// + /// Create a new dense vector with values sampled from the provided random distribution. + /// + public static DenseVector CreateRandom(int length, IContinuousDistribution distribution) + { + var samples = Generate.RandomSingle(length, distribution); + return new DenseVector(new DenseVectorStorage(length, samples)); + } + + /// + /// Gets the vector's data. + /// + /// The vector's data. + public float[] Values + { + get { return _values; } + } + + /// + /// Returns a reference to the internal data structure. + /// + /// The DenseVector whose internal data we are + /// returning. + /// + /// A reference to the internal date of the given vector. + /// + public static explicit operator float[](DenseVector vector) + { + if (vector == null) + { + throw new ArgumentNullException(nameof(vector)); + } + + return vector.Values; + } + + /// + /// Returns a vector bound directly to a reference of the provided array. + /// + /// The array to bind to the DenseVector object. + /// + /// A DenseVector whose values are bound to the given array. + /// + public static implicit operator DenseVector(float[] array) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + return new DenseVector(array); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to add. + /// The vector to store the result of the addition. + protected override void DoAdd(float scalar, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoAdd(scalar, result); + } + else + { + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] + scalar; + } + }); + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + protected override void DoAdd(Vector other, Vector result) + { + var otherDense = other as DenseVector; + var resultDense = result as DenseVector; + + if (otherDense == null || resultDense == null) + { + base.DoAdd(other, result); + } + else + { + LinearAlgebraControl.Provider.AddArrays(_values, otherDense._values, resultDense._values); + } + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static DenseVector operator +(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Add(rightSide); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The vector to store the result of the subtraction. + protected override void DoSubtract(float scalar, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoSubtract(scalar, result); + } + else + { + CommonParallel.For(0, _values.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] - scalar; + } + }); + } + } + + /// + /// Subtracts another vector from this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + protected override void DoSubtract(Vector other, Vector result) + { + var otherDense = other as DenseVector; + var resultDense = result as DenseVector; + + if (otherDense == null || resultDense == null) + { + base.DoSubtract(other, result); + } + else + { + LinearAlgebraControl.Provider.SubtractArrays(_values, otherDense._values, resultDense._values); + } + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static DenseVector operator -(DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static DenseVector operator -(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Subtract(rightSide); + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + var denseResult = result as DenseVector; + if (denseResult == null) + { + base.DoNegate(result); + return; + } + + LinearAlgebraControl.Provider.ScaleArray(-1.0f, _values, denseResult.Values); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to multiply. + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(float scalar, Vector result) + { + var denseResult = result as DenseVector; + if (denseResult == null) + { + base.DoMultiply(scalar, result); + return; + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, _values, denseResult.Values); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override float DoDotProduct(Vector other) + { + var denseVector = other as DenseVector; + return denseVector == null + ? base.DoDotProduct(other) + : LinearAlgebraControl.Provider.DotProduct(_values, denseVector.Values); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The vector to scale. + /// The scalar value. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(DenseVector leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The scalar value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(float leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (DenseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static float operator *(DenseVector leftSide, DenseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a scalar. + /// + /// The vector to divide. + /// The scalar value. + /// The result of the division. + /// If is . + public static DenseVector operator /(DenseVector leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Divide(rightSide); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoModulus(float divisor, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoModulus(divisor, result); + } + else + { + CommonParallel.For(0, _length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = Euclid.Modulus(_values[i], divisor); + } + }); + } + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoRemainder(float divisor, Vector result) + { + var dense = result as DenseVector; + if (dense == null) + { + base.DoRemainder(divisor, result); + } + else + { + CommonParallel.For(0, _length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + dense._values[i] = _values[i] % divisor; + } + }); + } + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// of each element of the vector of the given divisor. + /// + /// The vector whose elements we want to compute the modulus of. + /// The divisor to use, + /// The result of the calculation + /// If is . + public static DenseVector operator %(DenseVector leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (DenseVector)leftSide.Remainder(rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = Math.Abs(_values[index]); + for (var i = 1; i < _length; i++) + { + var test = Math.Abs(_values[i]); + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = Math.Abs(_values[index]); + for (var i = 1; i < _length; i++) + { + var test = Math.Abs(_values[i]); + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public override int MaximumIndex() + { + var index = 0; + var max = _values[0]; + for (var i = 1; i < _length; i++) + { + if (max < _values[i]) + { + index = i; + max = _values[i]; + } + } + + return index; + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + var index = 0; + var min = _values[0]; + for (var i = 1; i < _length; i++) + { + if (min > _values[i]) + { + index = i; + min = _values[i]; + } + } + + return index; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override float Sum() + { + var sum = 0f; + for (var i = 0; i < _length; i++) + { + sum += _values[i]; + } + + return sum; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double sum = 0d; + for (var i = 0; i < _length; i++) + { + sum += Math.Abs(_values[i]); + } + + return sum; + } + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public override double L2Norm() + { + // TODO: native provider + return _values.Aggregate(0f, SpecialFunctions.Hypotenuse); + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(_values, (i, v) => Math.Abs(v), Math.Max, 0f); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = ( ∑|this[i]|^p )^(1/p) + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var index = 0; index < _length; index++) + { + sum += Math.Pow(Math.Abs(_values[index]), p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Pointwise multiply this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply this one by. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + var denseOther = other as DenseVector; + var denseResult = result as DenseVector; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseMultiply(other, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + /// + protected override void DoPointwiseDivide(Vector divisor, Vector result) + { + var denseOther = divisor as DenseVector; + var denseResult = result as DenseVector; + + if (denseOther == null || denseResult == null) + { + base.DoPointwiseDivide(divisor, result); + } + else + { + LinearAlgebraControl.Provider.PointWiseDivideArrays(_values, denseOther._values, denseResult._values); + } + } + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Vector exponent, Vector result) + { + var denseExponent = exponent as DenseVector; + var denseResult = result as DenseVector; + + if (denseExponent == null || denseResult == null) + { + base.DoPointwisePower(exponent, result); + } + else + { + LinearAlgebraControl.Provider.PointWisePowerArrays(_values, denseExponent._values, denseResult._values); + } + } + + #region Parse Functions + + /// + /// Creates a float dense vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n,n,..', '(n,n,..)', '[n,n,...]', where n is a float. + /// + /// + /// A float dense vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static DenseVector Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // parsing + var tokens = value.Split(new[] { formatProvider.GetTextInfo().ListSeparator, " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); + var data = tokens.Select(t => float.Parse(t, NumberStyles.Any, formatProvider)).ToArray(); + if (data.Length == 0) + throw new FormatException(); + return new DenseVector(data); + } + + /// + /// Converts the string representation of a real dense vector to float-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out DenseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a real dense vector to float-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out DenseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + + #endregion +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/DiagonalMatrix.cs b/MathNet.Numerics/LinearAlgebra/Single/DiagonalMatrix.cs new file mode 100644 index 0000000..7da404f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/DiagonalMatrix.cs @@ -0,0 +1,966 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Single; + +/// +/// A matrix type for diagonal matrices. +/// +/// +/// Diagonal matrices can be non-square matrices but the diagonal always starts +/// at element 0,0. A diagonal matrix will throw an exception if non diagonal +/// entries are set. The exception to this is when the off diagonal elements are +/// 0.0 or NaN; these settings will cause no change to the diagonal matrix. +/// +[Serializable] +[DebuggerDisplay("DiagonalMatrix {RowCount}x{ColumnCount}-Single")] +public class DiagonalMatrix : Matrix +{ + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + readonly float[] _data; + + /// + /// Create a new diagonal matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public DiagonalMatrix(DiagonalMatrixStorage storage) + : base(storage) + { + _data = storage.Data; + } + + /// + /// Create a new square diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public DiagonalMatrix(int order) + : this(new DiagonalMatrixStorage(order, order)) + { + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DiagonalMatrix(int rows, int columns) + : this(new DiagonalMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns. + /// All diagonal cells of the matrix will be initialized to the provided value, all non-diagonal ones to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public DiagonalMatrix(int rows, int columns, float diagonalValue) + : this(rows, columns) + { + for (var i = 0; i < _data.Length; i++) + { + _data[i] = diagonalValue; + } + } + + /// + /// Create a new diagonal matrix with the given number of rows and columns directly binding to a raw array. + /// The array is assumed to contain the diagonal elements only and is used directly without copying. + /// Very efficient, but changes to the array and the matrix will affect each other. + /// + public DiagonalMatrix(int rows, int columns, float[] diagonalStorage) + : this(new DiagonalMatrixStorage(rows, columns, diagonalStorage)) + { + } + + /// + /// Create a new diagonal matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// The matrix to copy from must be diagonal as well. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfMatrix(Matrix matrix) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new diagonal matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// The array to copy from must be diagonal as well. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfArray(float[,] array) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfArray(array)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value from the provided indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfIndexedDiagonal(int rows, int columns, IEnumerable> diagonal) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfIndexedEnumerable(rows, columns, diagonal)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value from the provided enumerable. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static DiagonalMatrix OfDiagonal(int rows, int columns, IEnumerable diagonal) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfEnumerable(rows, columns, diagonal)); + } + + /// + /// Create a new diagonal matrix and initialize each diagonal value using the provided init function. + /// + public static DiagonalMatrix Create(int rows, int columns, Func init) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static DiagonalMatrix CreateIdentity(int order) + { + return new DiagonalMatrix(DiagonalMatrixStorage.OfValue(order, order, One)); + } + + /// + /// Create a new diagonal matrix with diagonal values sampled from the provided random distribution. + /// + public static DiagonalMatrix CreateRandom(int rows, int columns, IContinuousDistribution distribution) + { + return new DiagonalMatrix(new DiagonalMatrixStorage(rows, columns, Generate.RandomSingle(Math.Min(rows, columns), distribution))); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(-1, _data, diagResult._data); + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, -_data[i]); + } + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + // diagonal + diagonal = diagonal + var diagOther = other as DiagonalMatrix; + var diagResult = result as DiagonalMatrix; + if (diagOther != null && diagResult != null) + { + LinearAlgebraControl.Provider.AddArrays(_data, diagOther._data, diagResult._data); + return; + } + + other.CopyTo(result); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, result.At(i, i) + _data[i]); + } + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract. + /// The matrix to store the result of the subtraction. + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + // diagonal - diagonal = diagonal + var diagOther = other as DiagonalMatrix; + var diagResult = result as DiagonalMatrix; + if (diagOther != null && diagResult != null) + { + LinearAlgebraControl.Provider.SubtractArrays(_data, diagOther._data, diagResult._data); + return; + } + + other.Negate(result); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, result.At(i, i) + _data[i]); + } + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + /// If the result matrix's dimensions are not the same as this matrix. + protected override void DoMultiply(float scalar, Matrix result) + { + if (scalar == 0.0) + { + result.Clear(); + return; + } + + if (scalar == 1.0) + { + CopyTo(result); + return; + } + + var diagResult = result as DiagonalMatrix; + if (diagResult == null) + { + base.DoMultiply(scalar, result); + } + else + { + LinearAlgebraControl.Provider.ScaleArray(scalar, _data, diagResult._data); + } + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubVector(ColumnCount, RowCount - ColumnCount); + } + + if (d == ColumnCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i] * rightSide.At(i)); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new float[diagonalResult._data.Length]; + var otherDataCopy = new float[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.RowCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.RowCount, RowCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + if (ColumnCount == RowCount) + { + other.Storage.MapIndexedTo(result.Storage, (i, j, x) => x * _data[i], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Clear(); + other.Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * _data[i], 0, 0, other.RowCount, 0, 0, other.ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new float[diagonalResult._data.Length]; + var otherDataCopy = new float[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.ColumnCount, RowCount); + if (d < RowCount) + { + result.ClearSubMatrix(denseOther.ColumnCount, RowCount - denseOther.ColumnCount, 0, denseOther.RowCount); + } + + int index = 0; + for (int j = 0; j < d; j++) + { + for (int i = 0; i < denseOther.RowCount; i++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + } + + return; + } + + base.DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + var diagonalOther = other as DiagonalMatrix; + var diagonalResult = result as DiagonalMatrix; + if (diagonalOther != null && diagonalResult != null) + { + var thisDataCopy = new float[diagonalResult._data.Length]; + var otherDataCopy = new float[diagonalResult._data.Length]; + Array.Copy(_data, 0, thisDataCopy, 0, (diagonalResult._data.Length > _data.Length) ? _data.Length : diagonalResult._data.Length); + Array.Copy(diagonalOther._data, 0, otherDataCopy, 0, (diagonalResult._data.Length > diagonalOther._data.Length) ? diagonalOther._data.Length : diagonalResult._data.Length); + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, diagonalResult._data); + return; + } + + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + var dense = denseOther.Data; + var diagonal = _data; + var d = Math.Min(denseOther.RowCount, ColumnCount); + if (d < ColumnCount) + { + result.ClearSubMatrix(denseOther.RowCount, ColumnCount - denseOther.RowCount, 0, denseOther.ColumnCount); + } + + int index = 0; + for (int i = 0; i < denseOther.ColumnCount; i++) + { + for (int j = 0; j < d; j++) + { + result.At(j, i, dense[index] * diagonal[j]); + index++; + } + + index += (denseOther.RowCount - d); + } + + return; + } + + if (ColumnCount == RowCount) + { + other.Storage.MapIndexedTo(result.Storage, (i, j, x) => x * _data[i], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Clear(); + other.Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * _data[i], 0, 0, other.RowCount, 0, 0, other.ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var d = Math.Min(ColumnCount, RowCount); + if (d < ColumnCount) + { + result.ClearSubVector(RowCount, ColumnCount - RowCount); + } + + if (d == RowCount) + { + var denseOther = rightSide.Storage as DenseVectorStorage; + var denseResult = result.Storage as DenseVectorStorage; + if (denseOther != null && denseResult != null) + { + LinearAlgebraControl.Provider.PointWiseMultiplyArrays(_data, denseOther.Data, denseResult.Data); + return; + } + } + + for (var i = 0; i < d; i++) + { + result.At(i, _data[i] * rightSide.At(i)); + } + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(float divisor, Matrix result) + { + if (divisor == 1.0f) + { + CopyTo(result); + return; + } + + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + LinearAlgebraControl.Provider.ScaleArray(1.0f / divisor, _data, diagResult._data); + return; + } + + result.Clear(); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i] / divisor); + } + } + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to add. + /// The matrix to store the result of the division. + protected override void DoDivideByThis(float dividend, Matrix result) + { + var diagResult = result as DiagonalMatrix; + if (diagResult != null) + { + var resultData = diagResult._data; + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + resultData[i] = dividend / _data[i]; + } + }); + return; + } + + result.Clear(); + for (int i = 0; i < _data.Length; i++) + { + result.At(i, i, dividend / _data[i]); + } + } + + /// + /// Computes the determinant of this matrix. + /// + /// The determinant of this matrix. + public override float Determinant() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + return _data.Aggregate(1.0f, (current, t) => current * t); + } + + /// + /// Returns the elements of the diagonal in a . + /// + /// The elements of the diagonal. + /// For non-square matrices, the method returns Min(Rows, Columns) elements where + /// i == j (i is the row index, and j is the column index). + public override Vector Diagonal() + { + return new DenseVector(_data).Clone(); + } + + /// + /// Copies the values of the given array to the diagonal. + /// + /// The array to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(float[] source) + { + if (source.Length != _data.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(source)); + } + + Buffer.BlockCopy(source, 0, _data, 0, source.Length * Constants.SizeOfFloat); + } + + /// + /// Copies the values of the given to the diagonal. + /// + /// The vector to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(Vector source) + { + var denseSource = source as DenseVector; + if (denseSource == null) + { + base.SetDiagonal(source); + return; + } + + if (_data.Length != denseSource.Values.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(source)); + } + + Buffer.BlockCopy(denseSource.Values, 0, _data, 0, denseSource.Values.Length * Constants.SizeOfFloat); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + return _data.Aggregate(0f, (current, t) => Math.Max(current, Math.Abs(t))); + } + + /// Calculates the induced L2 norm of the matrix. + /// The largest singular value of the matrix. + public override double L2Norm() + { + return _data.Aggregate(0f, (current, t) => Math.Max(current, Math.Abs(t))); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + return L1Norm(); + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + return Math.Sqrt(_data.Sum(t => t * t)); + } + + /// Calculates the condition number of this matrix. + /// The condition number of the matrix. + public override float ConditionNumber() + { + var maxSv = float.NegativeInfinity; + var minSv = float.PositiveInfinity; + foreach (var t in _data) + { + maxSv = Math.Max(maxSv, Math.Abs(t)); + minSv = Math.Min(minSv, Math.Abs(t)); + } + + return maxSv / minSv; + } + + /// Computes the inverse of this matrix. + /// If is not a square matrix. + /// If is singular. + /// The inverse of this matrix. + public override Matrix Inverse() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var inverse = (DiagonalMatrix)Clone(); + for (var i = 0; i < _data.Length; i++) + { + if (_data[i] != 0.0) + { + inverse._data[i] = 1.0f / _data[i]; + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixNotSingular); + } + } + + return inverse; + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + return Clone(); + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + return; + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i]); + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + return Clone(); + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + for (var i = 0; i < _data.Length; i++) + { + result.At(i, i, _data[i]); + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + result.Clear(); + } + + /// + /// Creates a matrix that contains the values from the requested sub-matrix. + /// + /// The row to start copying from. + /// The number of rows to copy. Must be positive. + /// The column to start copying from. + /// The number of columns to copy. Must be positive. + /// The requested sub-matrix. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + /// If or + /// is not positive. + public override Matrix SubMatrix(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + var target = rowIndex == columnIndex + ? (Matrix)new DiagonalMatrix(rowCount, columnCount) + : new SparseMatrix(rowCount, columnCount); + + Storage.CopySubMatrixTo(target.Storage, rowIndex, 0, rowCount, columnIndex, 0, columnCount, ExistingData.AssumeZeros); + return target; + } + + /// + /// Permute the columns of a matrix according to a permutation. + /// + /// The column permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteColumns(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Permute the rows of a matrix according to a permutation. + /// + /// The row permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteRows(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public sealed override bool IsSymmetric() + { + return true; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoModulus(float divisor, Matrix result) + { + var diagonalResult = result as DiagonalMatrix; + if (diagonalResult == null) + { + base.DoModulus(divisor, result); + return; + } + + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + var r = diagonalResult._data; + for (var i = a; i < b; i++) + { + r[i] = Euclid.Modulus(_data[i], divisor); + } + }); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoModulusByThis(float dividend, Matrix result) + { + var diagonalResult = result as DiagonalMatrix; + if (diagonalResult == null) + { + base.DoModulusByThis(dividend, result); + return; + } + + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + var r = diagonalResult._data; + for (var i = a; i < b; i++) + { + r[i] = Euclid.Modulus(dividend, _data[i]); + } + }); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoRemainder(float divisor, Matrix result) + { + var diagonalResult = result as DiagonalMatrix; + if (diagonalResult == null) + { + base.DoRemainder(divisor, result); + return; + } + + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + var r = diagonalResult._data; + for (var i = a; i < b; i++) + { + r[i] = _data[i] % divisor; + } + }); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoRemainderByThis(float dividend, Matrix result) + { + var diagonalResult = result as DiagonalMatrix; + if (diagonalResult == null) + { + base.DoRemainderByThis(dividend, result); + return; + } + + CommonParallel.For(0, _data.Length, 4096, (a, b) => + { + var r = diagonalResult._data; + for (var i = a; i < b; i++) + { + r[i] = dividend % _data[i]; + } + }); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/Cholesky.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/Cholesky.cs new file mode 100644 index 0000000..2bb91b8 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/Cholesky.cs @@ -0,0 +1,86 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +using System; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal abstract class Cholesky : Cholesky +{ + protected Cholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Gets the determinant of the matrix for which the Cholesky matrix was computed. + /// + public override float Determinant + { + get + { + var det = 1.0f; + for (var j = 0; j < Factor.RowCount; j++) + { + var d = Factor.At(j, j); + det *= d * d; + } + + return det; + } + } + + /// + /// Gets the log determinant of the matrix for which the Cholesky matrix was computed. + /// + public override float DeterminantLn + { + get + { + var det = 0.0f; + for (var j = 0; j < Factor.RowCount; j++) + { + det += 2.0f * Convert.ToSingle(Math.Log(Factor.At(j, j))); + } + + return det; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseCholesky.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseCholesky.cs new file mode 100644 index 0000000..720129d --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseCholesky.cs @@ -0,0 +1,187 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization for dense matrices. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal sealed class DenseCholesky : Cholesky +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public static DenseCholesky Create(DenseMatrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var factor = (DenseMatrix)matrix.Clone(); + LinearAlgebraControl.Provider.CholeskyFactor(factor.Values, factor.RowCount); + return new DenseCholesky(factor); + } + + DenseCholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + Buffer.BlockCopy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length * Constants.SizeOfFloat); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)Factor; + LinearAlgebraControl.Provider.CholeskySolveFactored(dfactor.Values, dfactor.RowCount, dresult.Values, dresult.ColumnCount); + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + Buffer.BlockCopy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length * Constants.SizeOfFloat); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)Factor; + LinearAlgebraControl.Provider.CholeskySolveFactored(dfactor.Values, dfactor.RowCount, dresult.Values, 1); + } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + /// If does not have the same dimensions as the existing factor. + public override void Factorize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + if (matrix.RowCount != Factor.RowCount || matrix.ColumnCount != Factor.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix, Factor); + } + + var dmatrix = matrix as DenseMatrix; + if (dmatrix == null) + { + throw new NotSupportedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dfactor = (DenseMatrix)Factor; + + // Overwrite the existing Factor matrix with the input. + Buffer.BlockCopy(dmatrix.Values, 0, dfactor.Values, 0, dmatrix.Values.Length * Constants.SizeOfFloat); + + // Perform factorization (while overwriting). + LinearAlgebraControl.Provider.CholeskyFactor(dfactor.Values, dfactor.RowCount); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseEvd.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseEvd.cs new file mode 100644 index 0000000..db12362 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseEvd.cs @@ -0,0 +1,1238 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal sealed class DenseEvd : Evd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If it is known whether the matrix is symmetric or not the routine can skip checking it itself. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public static DenseEvd Create(DenseMatrix matrix, Symmetricity symmetricity) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matrices for eigenvalues and eigenvectors + var eigenVectors = new DenseMatrix(order); + var blockDiagonal = new DenseMatrix(order); + var eigenValues = new LinearAlgebra.Complex.DenseVector(order); + + bool isSymmetric; + switch (symmetricity) + { + case Symmetricity.Symmetric: + case Symmetricity.Hermitian: + isSymmetric = true; + break; + case Symmetricity.Asymmetric: + isSymmetric = false; + break; + default: + isSymmetric = matrix.IsSymmetric(); + break; + } + + LinearAlgebraControl.Provider.EigenDecomp(isSymmetric, order, matrix.Values, eigenVectors.Values, eigenValues.Values, blockDiagonal.Values); + + return new DenseEvd(eigenVectors, eigenValues, blockDiagonal, isSymmetric); + } + + DenseEvd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Symmetric Householder reduction to tridiagonal form. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tred2 by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + internal static void SymmetricTridiagonalize(float[] a, float[] d, float[] e, int order) + { + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0f; + var h = 0.0f; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(d[k]); + } + + if (scale == 0.0f) + { + e[i] = d[i - 1]; + for (var j = 0; j < i; j++) + { + d[j] = a[(j * order) + i - 1]; + a[(j * order) + i] = 0.0f; + a[(i * order) + j] = 0.0f; + } + } + else + { + // Generate Householder vector. + for (var k = 0; k < i; k++) + { + d[k] /= scale; + h += d[k] * d[k]; + } + + var f = d[i - 1]; + var g = (float)Math.Sqrt(h); + if (f > 0) + { + g = -g; + } + + e[i] = scale * g; + h = h - (f * g); + d[i - 1] = f - g; + + for (var j = 0; j < i; j++) + { + e[j] = 0.0f; + } + + // Apply similarity transformation to remaining columns. + for (var j = 0; j < i; j++) + { + f = d[j]; + a[(i * order) + j] = f; + g = e[j] + (a[(j * order) + j] * f); + + for (var k = j + 1; k <= i - 1; k++) + { + g += a[(j * order) + k] * d[k]; + e[k] += a[(j * order) + k] * f; + } + + e[j] = g; + } + + f = 0.0f; + + for (var j = 0; j < i; j++) + { + e[j] /= h; + f += e[j] * d[j]; + } + + var hh = f / (h + h); + + for (var j = 0; j < i; j++) + { + e[j] -= hh * d[j]; + } + + for (var j = 0; j < i; j++) + { + f = d[j]; + g = e[j]; + + for (var k = j; k <= i - 1; k++) + { + a[(j * order) + k] -= (f * e[k]) + (g * d[k]); + } + + d[j] = a[(j * order) + i - 1]; + a[(j * order) + i] = 0.0f; + } + } + + d[i] = h; + } + + // Accumulate transformations. + for (var i = 0; i < order - 1; i++) + { + a[(i * order) + order - 1] = a[(i * order) + i]; + a[(i * order) + i] = 1.0f; + var h = d[i + 1]; + if (h != 0.0f) + { + for (var k = 0; k <= i; k++) + { + d[k] = a[((i + 1) * order) + k] / h; + } + + for (var j = 0; j <= i; j++) + { + var g = 0.0f; + for (var k = 0; k <= i; k++) + { + g += a[((i + 1) * order) + k] * a[(j * order) + k]; + } + + for (var k = 0; k <= i; k++) + { + a[(j * order) + k] -= g * d[k]; + } + } + } + + for (var k = 0; k <= i; k++) + { + a[((i + 1) * order) + k] = 0.0f; + } + } + + for (var j = 0; j < order; j++) + { + d[j] = a[(j * order) + order - 1]; + a[(j * order) + order - 1] = 0.0f; + } + + a[(order * order) - 1] = 1.0f; + e[0] = 0.0f; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + internal static void SymmetricDiagonalize(float[] a, float[] d, float[] e, int order) + { + const int maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0f; + + var f = 0.0f; + var tst1 = 0.0f; + var eps = Precision.SinglePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0f * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0f); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0f; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0f; + var s2 = 0.0f; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = a[((i + 1) * order) + k]; + a[((i + 1) * order) + k] = (s * a[(i * order) + k]) + (c * h); + a[(i * order) + k] = (c * a[(i * order) + k]) - (s * h); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + } while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0f; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = a[(i * order) + j]; + a[(i * order) + j] = a[(k * order) + j]; + a[(k * order) + j] = p; + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + internal static void NonsymmetricReduceToHessenberg(float[] a, float[] matrixH, int order) + { + var ort = new float[order]; + var high = order - 1; + for (var m = 1; m <= high - 1; m++) + { + var mm1 = m - 1; + var mm1O = mm1 * order; + // Scale column. + var scale = 0.0f; + for (var i = m; i <= high; i++) + { + scale += Math.Abs(matrixH[mm1O + i]); + } + + if (scale != 0.0f) + { + // Compute Householder transformation. + var h = 0.0f; + for (var i = high; i >= m; i--) + { + ort[i] = matrixH[mm1O + i] / scale; + h += ort[i] * ort[i]; + } + + var g = (float)Math.Sqrt(h); + if (ort[m] > 0) + { + g = -g; + } + + h = h - (ort[m] * g); + ort[m] = ort[m] - g; + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var jO = j * order; + var f = 0.0f; + for (var i = order - 1; i >= m; i--) + { + f += ort[i] * matrixH[jO + i]; + } + + f = f / h; + + for (var i = m; i <= high; i++) + { + matrixH[jO + i] -= f * ort[i]; + } + } + + for (var i = 0; i <= high; i++) + { + var f = 0.0f; + for (var j = high; j >= m; j--) + { + f += ort[j] * matrixH[j * order + i]; + } + + f = f / h; + + for (var j = m; j <= high; j++) + { + matrixH[j * order + i] -= f * ort[j]; + } + } + + ort[m] = scale * ort[m]; + matrixH[mm1O + m] = scale * g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + a[(j * order) + i] = i == j ? 1.0f : 0.0f; + } + } + + for (var m = high - 1; m >= 1; m--) + { + var mm1 = m - 1; + var mm1O = mm1 * order; + var mm1Om = mm1O + m; + if (matrixH[mm1Om] != 0.0) + { + for (var i = m + 1; i <= high; i++) + { + ort[i] = matrixH[mm1O + i]; + } + + for (var j = m; j <= high; j++) + { + var g = 0.0f; + var jO = j * order; + for (var i = m; i <= high; i++) + { + g += ort[i] * a[jO + i]; + } + + // Double division avoids possible underflow + g = (g / ort[m]) / matrixH[mm1Om]; + + for (var i = m; i <= high; i++) + { + a[jO + i] += g * ort[i]; + } + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + internal static void NonsymmetricReduceHessenberToRealSchur(float[] a, float[] matrixH, float[] d, float[] e, int order) + { + // Initialize + var n = order - 1; + var eps = (float)Precision.SinglePrecision; + var exshift = 0.0f; + float p = 0, q = 0, r = 0, s = 0, z = 0; + float w, x, y; + + // Store roots isolated by balanc and compute matrix norm + var norm = 0.0f; + for (var i = 0; i < order; i++) + { + for (var j = Math.Max(i - 1, 0); j < order; j++) + { + norm = norm + Math.Abs(matrixH[j * order + i]); + } + } + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + var lm1 = l - 1; + var lm1O = lm1 * order; + s = Math.Abs(matrixH[lm1O + lm1]) + Math.Abs(matrixH[l * order + l]); + + if (s == 0.0) + { + s = norm; + } + + if (Math.Abs(matrixH[lm1O + l]) < eps * s) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + var index = n * order + n; + matrixH[index] += exshift; + d[n] = matrixH[index]; + e[n] = 0.0f; + n--; + iter = 0; + + // Two roots found + } + else if (l == n - 1) + { + var nO = n * order; + var nm1 = n - 1; + var nm1O = nm1 * order; + var nOn = nO + n; + + w = matrixH[nm1O + n] * matrixH[nO + nm1]; + p = (matrixH[nm1O + nm1] - matrixH[nOn]) / 2.0f; + q = (p * p) + w; + z = (float)Math.Sqrt(Math.Abs(q)); + + matrixH[nOn] += exshift; + matrixH[nm1O + nm1] += exshift; + x = matrixH[nOn]; + + // Real pair + if (q >= 0) + { + if (p >= 0) + { + z = p + z; + } + else + { + z = p - z; + } + + d[nm1] = x + z; + + d[n] = d[nm1]; + if (z != 0.0) + { + d[n] = x - (w / z); + } + + e[n - 1] = 0.0f; + e[n] = 0.0f; + x = matrixH[nm1O + n]; + s = Math.Abs(x) + Math.Abs(z); + p = x / s; + q = z / s; + r = (float)Math.Sqrt((p * p) + (q * q)); + p = p / r; + q = q / r; + + // Row modification + for (var j = n - 1; j < order; j++) + { + var jO = j * order; + var jOn = jO + n; + z = matrixH[jO + nm1]; + matrixH[jO + nm1] = (q * z) + (p * matrixH[jOn]); + matrixH[jOn] = (q * matrixH[jOn]) - (p * z); + } + + // Column modification + for (var i = 0; i <= n; i++) + { + var nOi = nO + i; + z = matrixH[nm1O + i]; + matrixH[nm1O + i] = (q * z) + (p * matrixH[nOi]); + matrixH[nOi] = (q * matrixH[nOi]) - (p * z); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + var nOi = nO + i; + z = a[nm1O + i]; + a[nm1O + i] = (q * z) + (p * a[nOi]); + a[nOi] = (q * a[nOi]) - (p * z); + } + + // Complex pair + } + else + { + d[n - 1] = x + p; + d[n] = x + p; + e[n - 1] = z; + e[n] = -z; + } + + n = n - 2; + iter = 0; + + // No convergence yet + } + else + { + var nO = n * order; + var nm1 = n - 1; + var nm1O = nm1 * order; + var nOn = nO + n; + + // Form shift + x = matrixH[nOn]; + y = 0.0f; + w = 0.0f; + if (l < n) + { + y = matrixH[nm1O + nm1]; + w = matrixH[nm1O + n] * matrixH[nO + nm1]; + } + + // Wilkinson's original ad hoc shift + if (iter == 10) + { + exshift += x; + for (var i = 0; i <= n; i++) + { + matrixH[i * order + i] -= x; + } + + s = Math.Abs(matrixH[nm1O + n]) + Math.Abs(matrixH[(n - 2) * order + nm1]); + x = y = 0.75f * s; + w = (-0.4375f) * s * s; + } + + // MATLAB's new ad hoc shift + if (iter == 30) + { + s = (y - x) / 2.0f; + s = (s * s) + w; + if (s > 0) + { + s = (float)Math.Sqrt(s); + if (y < x) + { + s = -s; + } + + s = x - (w / (((y - x) / 2.0f) + s)); + for (var i = 0; i <= n; i++) + { + matrixH[i * order + i] -= s; + } + + exshift += s; + x = y = w = 0.964f; + } + } + + iter = iter + 1; + if (iter >= 30 * order) + { + throw new NonConvergenceException(); + } + + // Look for two consecutive small sub-diagonal elements + var m = n - 2; + while (m >= l) + { + var mp1 = m + 1; + var mm1 = m - 1; + var mO = m * order; + var mp1O = mp1 * order; + var mm1O = mm1 * order; + + z = matrixH[mO + m]; + r = x - z; + s = y - z; + p = (((r * s) - w) / matrixH[mO + mp1]) + matrixH[mp1O + m]; + q = matrixH[mp1O + mp1] - z - r - s; + r = matrixH[mp1O + (m + 2)]; + s = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + p = p / s; + q = q / s; + r = r / s; + + if (m == l) + { + break; + } + + if (Math.Abs(matrixH[mm1O + m]) * (Math.Abs(q) + Math.Abs(r)) < eps * (Math.Abs(p) * (Math.Abs(matrixH[mm1O + mm1]) + Math.Abs(z) + Math.Abs(matrixH[mp1O + mp1])))) + { + break; + } + + m--; + } + + var mp2 = m + 2; + for (var i = mp2; i <= n; i++) + { + matrixH[(i - 2) * order + i] = 0.0f; + if (i > mp2) + { + matrixH[(i - 3) * order + i] = 0.0f; + } + } + + // Double QR step involving rows l:n and columns m:n + for (var k = m; k <= n - 1; k++) + { + var notlast = k != n - 1; + var kO = k * order; + var km1 = k - 1; + var kp1 = k + 1; + var kp2 = k + 2; + var kp1O = kp1 * order; + var kp2O = kp2 * order; + var km1O = km1 * order; + if (k != m) + { + p = matrixH[km1O + k]; + q = matrixH[km1O + kp1]; + r = notlast ? matrixH[km1O + kp2] : 0.0f; + x = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + if (x == 0.0f) + { + continue; + } + + p = p / x; + q = q / x; + r = r / x; + } + + s = (float)Math.Sqrt((p * p) + (q * q) + (r * r)); + + if (p < 0) + { + s = -s; + } + + if (s != 0.0f) + { + if (k != m) + { + matrixH[km1O + k] = (-s) * x; + } + else if (l != m) + { + matrixH[km1O + k] = -matrixH[km1O + k]; + } + + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // Row modification + for (var j = k; j < order; j++) + { + var jO = j * order; + var jOk = jO + k; + var jOkp1 = jO + kp1; + var jOkp2 = jO + kp2; + p = matrixH[jOk] + (q * matrixH[jOkp1]); + if (notlast) + { + p = p + (r * matrixH[jOkp2]); + matrixH[jOkp2] -= (p * z); + } + + matrixH[jOk] -= (p * x); + matrixH[jOkp1] -= (p * y); + } + + // Column modification + for (var i = 0; i <= Math.Min(n, k + 3); i++) + { + p = (x * matrixH[kO + i]) + (y * matrixH[kp1O + i]); + + if (notlast) + { + p = p + (z * matrixH[kp2O + i]); + matrixH[kp2O + i] -= (p * r); + } + + matrixH[kO + i] -= p; + matrixH[kp1O + i] -= (p * q); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + p = (x * a[kO + i]) + (y * a[kp1O + i]); + + if (notlast) + { + p = p + (z * a[kp2O + i]); + a[kp2O + i] -= p * r; + } + + a[kO + i] -= p; + a[kp1O + i] -= p * q; + } + } // (s != 0) + } // k loop + } // check convergence + } // while (n >= low) + + // Backsubstitute to find vectors of upper triangular form + if (norm == 0.0f) + { + return; + } + + for (n = order - 1; n >= 0; n--) + { + var nO = n * order; + var nm1 = n - 1; + var nm1O = nm1 * order; + + p = d[n]; + q = e[n]; + + // Real vector + float t; + if (q == 0.0f) + { + var l = n; + matrixH[nO + n] = 1.0f; + for (var i = n - 1; i >= 0; i--) + { + var ip1 = i + 1; + var iO = i * order; + var ip1O = ip1 * order; + + w = matrixH[iO + i] - p; + r = 0.0f; + for (var j = l; j <= n; j++) + { + r = r + (matrixH[j * order + i] * matrixH[nO + j]); + } + + if (e[i] < 0.0) + { + z = w; + s = r; + } + else + { + l = i; + if (e[i] == 0.0f) + { + if (w != 0.0f) + { + matrixH[nO + i] = (-r) / w; + } + else + { + matrixH[nO + i] = (-r) / (eps * norm); + } + + // Solve real equations + } + else + { + x = matrixH[ip1O + i]; + y = matrixH[iO + ip1]; + q = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]); + t = ((x * s) - (z * r)) / q; + matrixH[nO + i] = t; + if (Math.Abs(x) > Math.Abs(z)) + { + matrixH[nO + ip1] = (-r - (w * t)) / x; + } + else + { + matrixH[nO + ip1] = (-s - (y * t)) / z; + } + } + + // Overflow control + t = Math.Abs(matrixH[nO + i]); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[nO + j] /= t; + } + } + } + } + + // Complex vector + } + else if (q < 0) + { + var l = n - 1; + + // Last vector component imaginary so matrix is triangular + if (Math.Abs(matrixH[nm1O + n]) > Math.Abs(matrixH[nO + nm1])) + { + matrixH[nm1O + nm1] = q / matrixH[nm1O + n]; + matrixH[nO + nm1] = (-(matrixH[nO + n] - p)) / matrixH[nm1O + n]; + } + else + { + var res = Cdiv(0.0f, -matrixH[nO + nm1], matrixH[nm1O + nm1] - p, q); + matrixH[nm1O + nm1] = res.Real; + matrixH[nO + nm1] = res.Imaginary; + } + + matrixH[nm1O + n] = 0.0f; + matrixH[nO + n] = 1.0f; + for (var i = n - 2; i >= 0; i--) + { + var ip1 = i + 1; + var iO = i * order; + var ip1O = ip1 * order; + var ra = 0.0f; + var sa = 0.0f; + for (var j = l; j <= n; j++) + { + var jO = j * order; + var jOi = jO + i; + ra = ra + (matrixH[jOi] * matrixH[nm1O + j]); + sa = sa + (matrixH[jOi] * matrixH[nO + j]); + } + + w = matrixH[iO + i] - p; + + if (e[i] < 0.0) + { + z = w; + r = ra; + s = sa; + } + else + { + l = i; + if (e[i] == 0.0) + { + var res = Cdiv(-ra, -sa, w, q); + matrixH[nm1O + i] = res.Real; + matrixH[nO + i] = res.Imaginary; + } + else + { + // Solve complex equations + x = matrixH[ip1O + i]; + y = matrixH[iO + ip1]; + + var vr = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]) - (q * q); + var vi = (d[i] - p) * 2.0f * q; + if ((vr == 0.0f) && (vi == 0.0f)) + { + vr = eps * norm * (Math.Abs(w) + Math.Abs(q) + Math.Abs(x) + Math.Abs(y) + Math.Abs(z)); + } + + var res = Cdiv((x * r) - (z * ra) + (q * sa), (x * s) - (z * sa) - (q * ra), vr, vi); + matrixH[nm1O + i] = res.Real; + matrixH[nO + i] = res.Imaginary; + if (Math.Abs(x) > (Math.Abs(z) + Math.Abs(q))) + { + matrixH[nm1O + ip1] = (-ra - (w * matrixH[nm1O + i]) + (q * matrixH[nO + i])) / x; + matrixH[nO + ip1] = (-sa - (w * matrixH[nO + i]) - (q * matrixH[nm1O + i])) / x; + } + else + { + res = Cdiv(-r - (y * matrixH[nm1O + i]), -s - (y * matrixH[nO + i]), z, q); + matrixH[nm1O + ip1] = res.Real; + matrixH[nO + ip1] = res.Imaginary; + } + } + + // Overflow control + t = Math.Max(Math.Abs(matrixH[nm1O + i]), Math.Abs(matrixH[nO + i])); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[nm1O + j] /= t; + matrixH[nO + j] /= t; + } + } + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j >= 0; j--) + { + var jO = j * order; + for (var i = 0; i < order; i++) + { + z = 0.0f; + for (var k = 0; k <= j; k++) + { + z = z + (a[k * order + i] * matrixH[jO + k]); + } + + a[jO + i] = z; + } + } + } + + /// + /// Complex scalar division X/Y. + /// + /// Real part of X + /// Imaginary part of X + /// Real part of Y + /// Imaginary part of Y + /// Division result as a number. + static Numerics.Complex32 Cdiv(float xreal, float ximag, float yreal, float yimag) + { + if (Math.Abs(yimag) < Math.Abs(yreal)) + { + return new Numerics.Complex32((xreal + (ximag * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal))), (ximag - (xreal * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal)))); + } + + return new Numerics.Complex32((ximag + (xreal * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag))), (-xreal + (ximag * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag)))); + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (EigenValues.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (EigenValues.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = EigenValues.Count; + var tmp = new float[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + float value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(j * order) + i] * input.At(i, k); + } + + value /= (float)EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + float value = 0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(i * order) + j] * tmp[i]; + } + + result.At(j, k, value); + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (EigenValues.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (EigenValues.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VT * b; + var order = EigenValues.Count; + var tmp = new float[order]; + float value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(j * order) + i] * input[i]; + } + + value /= (float)EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)EigenVectors).Values[(i * order) + j] * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseGramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseGramSchmidt.cs new file mode 100644 index 0000000..f938612 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseGramSchmidt.cs @@ -0,0 +1,198 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal sealed class DenseGramSchmidt : GramSchmidt +{ + /// + /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public static DenseGramSchmidt Create(Matrix matrix) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var q = (DenseMatrix)matrix.Clone(); + var r = new DenseMatrix(matrix.ColumnCount, matrix.ColumnCount); + Factorize(q.Values, q.RowCount, q.ColumnCount, r.Values); + + return new DenseGramSchmidt(q, r); + } + + DenseGramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Factorize matrix using the modified Gram-Schmidt method. + /// + /// Initial matrix. On exit is replaced by Q. + /// Number of rows in Q. + /// Number of columns in Q. + /// On exit is filled by R. + private static void Factorize(float[] q, int rowsQ, int columnsQ, float[] r) + { + for (var k = 0; k < columnsQ; k++) + { + var norm = 0.0f; + for (var i = 0; i < rowsQ; i++) + { + norm += q[(k * rowsQ) + i] * q[(k * rowsQ) + i]; + } + + norm = (float)Math.Sqrt(norm); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r[(k * columnsQ) + k] = norm; + for (var i = 0; i < rowsQ; i++) + { + q[(k * rowsQ) + i] /= norm; + } + + for (var j = k + 1; j < columnsQ; j++) + { + var k1 = k; + var j1 = j; + + var dot = 0.0f; + for (var index = 0; index < rowsQ; index++) + { + dot += q[(k1 * rowsQ) + index] * q[(j1 * rowsQ) + index]; + } + + r[(j * columnsQ) + k] = dot; + for (var i = 0; i < rowsQ; i++) + { + var value = q[(j * rowsQ) + i] - (q[(k * rowsQ) + i] * dot); + q[(j * rowsQ) + i] = value; + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (Q.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, null, dinput.Values, input.ColumnCount, dresult.Values, QRMethod.Thin); + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (Q.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(Q, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, null, dinput.Values, 1, dresult.Values, QRMethod.Thin); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseLU.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseLU.cs new file mode 100644 index 0000000..b8ab928 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseLU.cs @@ -0,0 +1,194 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal sealed class DenseLU : LU +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public static DenseLU Create(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var pivots = new int[matrix.RowCount]; + + // Create a new matrix for the LU factors, then perform factorization (while overwriting). + var factors = (DenseMatrix)matrix.Clone(); + LinearAlgebraControl.Provider.LUFactor(factors.Values, factors.RowCount, pivots); + + return new DenseLU(factors, pivots); + } + + DenseLU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do LU factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do LU factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + Buffer.BlockCopy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length * Constants.SizeOfFloat); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + LinearAlgebraControl.Provider.LUSolveFactored(input.ColumnCount, dfactors.Values, dfactors.RowCount, Pivots, dresult.Values); + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do LU factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do LU factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + Buffer.BlockCopy(dinput.Values, 0, dresult.Values, 0, dinput.Values.Length * Constants.SizeOfFloat); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + LinearAlgebraControl.Provider.LUSolveFactored(1, dfactors.Values, dfactors.RowCount, Pivots, dresult.Values); + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var result = (DenseMatrix)Factors.Clone(); + LinearAlgebraControl.Provider.LUInverseFactored(result.Values, result.RowCount, Pivots); + return result; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseQR.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseQR.cs new file mode 100644 index 0000000..043736c --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseQR.cs @@ -0,0 +1,169 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// +internal sealed class DenseQR : QR +{ + /// + /// Gets or sets Tau vector. Contains additional information on Q - used for native solver. + /// + float[] Tau { get; set; } + + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// The QR factorization method to use. + /// If is null. + /// If row count is less then column count + public static DenseQR Create(DenseMatrix matrix, QRMethod method = QRMethod.Full) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var tau = new float[Math.Min(matrix.RowCount, matrix.ColumnCount)]; + Matrix q; + Matrix r; + + if (method == QRMethod.Full) + { + r = matrix.Clone(); + q = new DenseMatrix(matrix.RowCount); + LinearAlgebraControl.Provider.QRFactor(((DenseMatrix)r).Values, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)q).Values, tau); + } + else + { + q = matrix.Clone(); + r = new DenseMatrix(matrix.ColumnCount); + LinearAlgebraControl.Provider.ThinQRFactor(((DenseMatrix)q).Values, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)r).Values, tau); + } + + return new DenseQR(q, r, method, tau); + } + + DenseQR(Matrix q, Matrix rFull, QRMethod method, float[] tau) + : base(q, rFull, method) + { + Tau = tau; + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (FullR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do QR factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do QR factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, Tau, dinput.Values, input.ColumnCount, dresult.Values, Method); + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (FullR.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(FullR, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do QR factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do QR factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.QRSolveFactored(((DenseMatrix)Q).Values, ((DenseMatrix)FullR).Values, Q.RowCount, FullR.ColumnCount, Tau, dinput.Values, 1, dresult.Values, Method); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseSvd.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseSvd.cs new file mode 100644 index 0000000..023134b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/DenseSvd.cs @@ -0,0 +1,161 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD) for . +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal sealed class DenseSvd : Svd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// If SVD algorithm failed to converge with matrix . + public static DenseSvd Create(DenseMatrix matrix, bool computeVectors) + { + var nm = Math.Min(matrix.RowCount, matrix.ColumnCount); + var s = new DenseVector(nm); + var u = new DenseMatrix(matrix.RowCount); + var vt = new DenseMatrix(matrix.ColumnCount); + LinearAlgebraControl.Provider.SingularValueDecomposition(computeVectors, ((DenseMatrix)matrix.Clone()).Values, matrix.RowCount, matrix.ColumnCount, s.Values, u.Values, vt.Values); + + return new DenseSvd(s, u, vt, computeVectors); + } + + DenseSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (U.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense matrices at the moment."); + } + + LinearAlgebraControl.Provider.SvdSolveFactored(U.RowCount, VT.ColumnCount, ((DenseVector)S).Values, ((DenseMatrix)U).Values, ((DenseMatrix)VT).Values, dinput.Values, input.ColumnCount, dresult.Values); + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (U.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VT.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(VT, result); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotSupportedException("Can only do SVD factorization for dense vectors at the moment."); + } + + LinearAlgebraControl.Provider.SvdSolveFactored(U.RowCount, VT.ColumnCount, ((DenseVector)S).Values, ((DenseMatrix)U).Values, ((DenseMatrix)VT).Values, dinput.Values, 1, dresult.Values); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/Evd.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/Evd.cs new file mode 100644 index 0000000..5a10ce9 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/Evd.cs @@ -0,0 +1,124 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal abstract class Evd : Evd +{ + protected Evd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Gets the absolute value of determinant of the square matrix for which the EVD was computed. + /// + public override float Determinant + { + get + { + var det = Complex.One; + for (var i = 0; i < EigenValues.Count; i++) + { + det *= EigenValues[i]; + + if (((Numerics.Complex32)EigenValues[i]).AlmostEqual(Numerics.Complex32.Zero)) + { + return 0; + } + } + + return Convert.ToSingle(det.Magnitude); + } + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public override int Rank + { + get + { + var rank = 0; + for (var i = 0; i < EigenValues.Count; i++) + { + if (((Numerics.Complex32)EigenValues[i]).AlmostEqual(Numerics.Complex32.Zero)) + { + continue; + } + + rank++; + } + + return rank; + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < EigenValues.Count; i++) + { + if (EigenValues[i].AlmostEqual(Complex.Zero)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/GramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/GramSchmidt.cs new file mode 100644 index 0000000..6f970fa --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/GramSchmidt.cs @@ -0,0 +1,96 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal abstract class GramSchmidt : GramSchmidt +{ + protected GramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public override float Determinant + { + get + { + if (FullR.RowCount != FullR.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = 1.0; + for (var i = 0; i < FullR.ColumnCount; i++) + { + det *= FullR.At(i, i); + if (Math.Abs(FullR.At(i, i)).AlmostEqual(0.0f)) + { + return 0; + } + } + + return Convert.ToSingle(Math.Abs(det)); + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < FullR.ColumnCount; i++) + { + if (Math.Abs(FullR.At(i, i)).AlmostEqual(0.0f)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/LU.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/LU.cs new file mode 100644 index 0000000..6231b12 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/LU.cs @@ -0,0 +1,74 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// In the Math.Net implementation we also store a set of pivot elements for increased +/// numerical stability. The pivot elements encode a permutation matrix P such that P*A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal abstract class LU : LU +{ + protected LU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Gets the determinant of the matrix for which the LU factorization was computed. + /// + public override float Determinant + { + get + { + var det = 1.0f; + for (var j = 0; j < Factors.RowCount; j++) + { + if (Pivots[j] != j) + { + det *= -Factors.At(j, j); + } + else + { + det *= Factors.At(j, j); + } + } + + return det; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/QR.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/QR.cs new file mode 100644 index 0000000..4b92a75 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/QR.cs @@ -0,0 +1,101 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A (m x n) may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// If a factorization is performed, the resulting Q matrix is an m x m matrix +/// and the R matrix is an m x n matrix. If a factorization is performed, the +/// resulting Q matrix is an m x n matrix and the R matrix is an n x n matrix. +/// +internal abstract class QR : QR +{ + protected QR(Matrix q, Matrix rFull, QRMethod method) + : base(q, rFull, method) + { + } + + /// + /// Gets the absolute determinant value of the matrix for which the QR matrix was computed. + /// + public override float Determinant + { + get + { + if (FullR.RowCount != FullR.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = 1.0; + for (var i = 0; i < FullR.ColumnCount; i++) + { + det *= FullR.At(i, i); + if (Math.Abs(FullR.At(i, i)).AlmostEqual(0.0f)) + { + return 0; + } + } + + return Convert.ToSingle(Math.Abs(det)); + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + for (var i = 0; i < FullR.ColumnCount; i++) + { + if (Math.Abs(FullR.At(i, i)).AlmostEqual(0.0f)) + { + return false; + } + } + + return true; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/Svd.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/Svd.cs new file mode 100644 index 0000000..fbf8119 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/Svd.cs @@ -0,0 +1,122 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD). +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal abstract class Svd : Svd +{ + protected Svd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public override int Rank + { + get + { + double tolerance = Precision.EpsilonOf(S.Maximum()) * Math.Max(U.RowCount, VT.RowCount); + return S.Count(t => Math.Abs(t) > tolerance); + } + } + + /// + /// Gets the two norm of the . + /// + /// The 2-norm of the . + public override double L2Norm + { + get + { + return Math.Abs(S[0]); + } + } + + /// + /// Gets the condition number max(S) / min(S) + /// + /// The condition number. + public override float ConditionNumber + { + get + { + var tmp = Math.Min(U.RowCount, VT.ColumnCount) - 1; + return Math.Abs(S[0]) / Math.Abs(S[tmp]); + } + } + + /// + /// Gets the determinant of the square matrix for which the SVD was computed. + /// + public override float Determinant + { + get + { + if (U.RowCount != VT.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = 1.0; + foreach (var value in S) + { + det *= value; + if (Math.Abs(value).AlmostEqual(0.0f)) + { + return 0; + } + } + + return Convert.ToSingle(Math.Abs(det)); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserCholesky.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserCholesky.cs new file mode 100644 index 0000000..dbfcc85 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserCholesky.cs @@ -0,0 +1,273 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of a Cholesky factorization for user matrices. +/// For a symmetric, positive definite matrix A, the Cholesky factorization +/// is an lower triangular matrix L so that A = L*L'. +/// +/// +/// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric +/// or positive definite, the constructor will throw an exception. +/// +internal sealed class UserCholesky : Cholesky +{ + /// + /// Computes the Cholesky factorization in-place. + /// + /// On entry, the matrix to factor. On exit, the Cholesky factor matrix + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + static void DoCholesky(Matrix factor) + { + if (factor.RowCount != factor.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var tmpColumn = new float[factor.RowCount]; + + // Main loop - along the diagonal + for (var ij = 0; ij < factor.RowCount; ij++) + { + // "Pivot" element + var tmpVal = factor.At(ij, ij); + + if (tmpVal > 0.0) + { + tmpVal = (float)Math.Sqrt(tmpVal); + factor.At(ij, ij, tmpVal); + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < factor.RowCount; i++) + { + factor.At(i, ij, factor.At(i, ij) / tmpVal); + tmpColumn[i] = factor.At(i, ij); + } + + // Remaining columns, below the diagonal + DoCholeskyStep(factor, factor.RowCount, ij + 1, factor.RowCount, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (var i = ij + 1; i < factor.RowCount; i++) + { + factor.At(ij, i, 0.0f); + } + } + } + + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public static UserCholesky Create(Matrix matrix) + { + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + var factor = matrix.Clone(); + DoCholesky(factor); + return new UserCholesky(factor); + } + + /// + /// Calculates the Cholesky factorization of the input matrix. + /// + /// The matrix to be factorized. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + /// If does not have the same dimensions as the existing factor. + public override void Factorize(Matrix matrix) + { + if (matrix.RowCount != Factor.RowCount || matrix.ColumnCount != Factor.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix, Factor); + } + + matrix.CopyTo(Factor); + DoCholesky(Factor); + } + + UserCholesky(Matrix factor) + : base(factor) + { + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(Matrix data, int rowDim, int firstCol, int colLimit, float[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data.At(i, j, data.At(i, j) - (multipliers[i] * tmpVal)); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + input.CopyTo(result); + var order = Factor.RowCount; + + for (var c = 0; c < result.ColumnCount; c++) + { + // Solve L*Y = B; + float sum; + for (var i = 0; i < order; i++) + { + sum = result.At(i, c); + for (var k = i - 1; k >= 0; k--) + { + sum -= Factor.At(i, k) * result.At(k, c); + } + + result.At(i, c, sum / Factor.At(i, i)); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result.At(i, c); + for (var k = i + 1; k < order; k++) + { + sum -= Factor.At(k, i) * result.At(k, c); + } + + result.At(i, c, sum / Factor.At(i, i)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factor.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factor); + } + + input.CopyTo(result); + var order = Factor.RowCount; + + // Solve L*Y = B; + float sum; + for (var i = 0; i < order; i++) + { + sum = result[i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= Factor.At(i, k) * result[k]; + } + + result[i] = sum / Factor.At(i, i); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result[i]; + for (var k = i + 1; k < order; k++) + { + sum -= Factor.At(k, i) * result[k]; + } + + result[i] = sum / Factor.At(i, i); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs new file mode 100644 index 0000000..f7e4b4c --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs @@ -0,0 +1,1214 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +using Numerics; + +using Complex = System.Numerics.Complex; + +/// +/// Eigenvalues and eigenvectors of a real matrix. +/// +/// +/// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is +/// diagonal and the eigenvector matrix V is orthogonal. +/// I.e. A = V*D*V' and V*VT=I. +/// If A is not symmetric, then the eigenvalue matrix D is block diagonal +/// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, +/// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The +/// columns of V represent the eigenvectors in the sense that A*V = V*D, +/// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly +/// conditioned, or even singular, so the validity of the equation +/// A = V*D*Inverse(V) depends upon V.Condition(). +/// +internal sealed class UserEvd : Evd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If it is known whether the matrix is symmetric or not the routine can skip checking it itself. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public static UserEvd Create(Matrix matrix, Symmetricity symmetricity) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matrices for eigenvalues and eigenvectors + var eigenVectors = Matrix.Build.SameAs(matrix, order, order, fullyMutable: true); + var blockDiagonal = Matrix.Build.SameAs(matrix, order, order); + var eigenValues = new LinearAlgebra.Complex.DenseVector(order); + + bool isSymmetric; + switch (symmetricity) + { + case Symmetricity.Symmetric: + case Symmetricity.Hermitian: + isSymmetric = true; + break; + case Symmetricity.Asymmetric: + isSymmetric = false; + break; + default: + isSymmetric = matrix.IsSymmetric(); + break; + } + + var d = new float[order]; + var e = new float[order]; + + if (isSymmetric) + { + matrix.CopyTo(eigenVectors); + d = eigenVectors.Row(order - 1).ToArray(); + + SymmetricTridiagonalize(eigenVectors, d, e, order); + SymmetricDiagonalize(eigenVectors, d, e, order); + } + else + { + var matrixH = matrix.ToArray(); + + NonsymmetricReduceToHessenberg(eigenVectors, matrixH, order); + NonsymmetricReduceHessenberToRealSchur(eigenVectors, matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + blockDiagonal.At(i, i, d[i]); + + if (e[i] > 0) + { + blockDiagonal.At(i, i + 1, e[i]); + } + else if (e[i] < 0) + { + blockDiagonal.At(i, i - 1, e[i]); + } + } + + for (var i = 0; i < order; i++) + { + eigenValues[i] = new Complex(d[i], e[i]); + } + + return new UserEvd(eigenVectors, eigenValues, blockDiagonal, isSymmetric); + } + + UserEvd(Matrix eigenVectors, Vector eigenValues, Matrix blockDiagonal, bool isSymmetric) + : base(eigenVectors, eigenValues, blockDiagonal, isSymmetric) + { + } + + /// + /// Symmetric Householder reduction to tridiagonal form. + /// + /// The eigen vectors to work on. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tred2 by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void SymmetricTridiagonalize(Matrix eigenVectors, float[] d, float[] e, int order) + { + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0f; + var h = 0.0f; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(d[k]); + } + + if (scale == 0.0f) + { + e[i] = d[i - 1]; + for (var j = 0; j < i; j++) + { + d[j] = eigenVectors.At(i - 1, j); + eigenVectors.At(i, j, 0.0f); + eigenVectors.At(j, i, 0.0f); + } + } + else + { + // Generate Householder vector. + for (var k = 0; k < i; k++) + { + d[k] /= scale; + h += d[k] * d[k]; + } + + var f = d[i - 1]; + var g = (float)Math.Sqrt(h); + if (f > 0) + { + g = -g; + } + + e[i] = scale * g; + h = h - (f * g); + d[i - 1] = f - g; + + for (var j = 0; j < i; j++) + { + e[j] = 0.0f; + } + + // Apply similarity transformation to remaining columns. + for (var j = 0; j < i; j++) + { + f = d[j]; + eigenVectors.At(j, i, f); + g = e[j] + (eigenVectors.At(j, j) * f); + + for (var k = j + 1; k <= i - 1; k++) + { + g += eigenVectors.At(k, j) * d[k]; + e[k] += eigenVectors.At(k, j) * f; + } + + e[j] = g; + } + + f = 0.0f; + + for (var j = 0; j < i; j++) + { + e[j] /= h; + f += e[j] * d[j]; + } + + var hh = f / (h + h); + + for (var j = 0; j < i; j++) + { + e[j] -= hh * d[j]; + } + + for (var j = 0; j < i; j++) + { + f = d[j]; + g = e[j]; + + for (var k = j; k <= i - 1; k++) + { + eigenVectors.At(k, j, eigenVectors.At(k, j) - (f * e[k]) - (g * d[k])); + } + + d[j] = eigenVectors.At(i - 1, j); + eigenVectors.At(i, j, 0.0f); + } + } + + d[i] = h; + } + + // Accumulate transformations. + for (var i = 0; i < order - 1; i++) + { + eigenVectors.At(order - 1, i, eigenVectors.At(i, i)); + eigenVectors.At(i, i, 1.0f); + var h = d[i + 1]; + if (h != 0.0f) + { + for (var k = 0; k <= i; k++) + { + d[k] = eigenVectors.At(k, i + 1) / h; + } + + for (var j = 0; j <= i; j++) + { + var g = 0.0f; + for (var k = 0; k <= i; k++) + { + g += eigenVectors.At(k, i + 1) * eigenVectors.At(k, j); + } + + for (var k = 0; k <= i; k++) + { + eigenVectors.At(k, j, eigenVectors.At(k, j) - g * d[k]); + } + } + } + + for (var k = 0; k <= i; k++) + { + eigenVectors.At(k, i + 1, 0.0f); + } + } + + for (var j = 0; j < order; j++) + { + d[j] = eigenVectors.At(order - 1, j); + eigenVectors.At(order - 1, j, 0.0f); + } + + eigenVectors.At(order - 1, order - 1, 1.0f); + e[0] = 0.0f; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// The eigen vectors to work on. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + /// + static void SymmetricDiagonalize(Matrix eigenVectors, float[] d, float[] e, int order) + { + const int maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0f; + + var f = 0.0f; + var tst1 = 0.0f; + var eps = Precision.DoublePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0f * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0f); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0f; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0f; + var s2 = 0.0f; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = eigenVectors.At(k, i + 1); + eigenVectors.At(k, i + 1, (s * eigenVectors.At(k, i)) + (c * h)); + eigenVectors.At(k, i, (c * eigenVectors.At(k, i)) - (s * h)); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + } while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0f; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = eigenVectors.At(j, i); + eigenVectors.At(j, i, eigenVectors.At(j, k)); + eigenVectors.At(j, k, p); + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// The eigen vectors to work on. + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + static void NonsymmetricReduceToHessenberg(Matrix eigenVectors, float[,] matrixH, int order) + { + var ort = new float[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0f; + for (var i = m; i < order; i++) + { + scale = scale + Math.Abs(matrixH[i, m - 1]); + } + + if (scale != 0.0f) + { + // Compute Householder transformation. + var h = 0.0f; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i] * ort[i]; + } + + var g = (float)Math.Sqrt(h); + if (ort[m] > 0) + { + g = -g; + } + + h = h - (ort[m] * g); + ort[m] = ort[m] - g; + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = 0.0f; + for (var i = order - 1; i >= m; i--) + { + f += ort[i] * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = 0.0f; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[i, j] -= f * ort[j]; + } + } + + ort[m] = scale * ort[m]; + matrixH[m, m - 1] = scale * g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + eigenVectors.At(i, j, i == j ? 1.0f : 0.0f); + } + } + + for (var m = order - 2; m >= 1; m--) + { + if (matrixH[m, m - 1] != 0.0f) + { + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j < order; j++) + { + var g = 0.0f; + for (var i = m; i < order; i++) + { + g += ort[i] * eigenVectors.At(i, j); + } + + // Double division avoids possible underflow + g = (g / ort[m]) / matrixH[m, m - 1]; + for (var i = m; i < order; i++) + { + eigenVectors.At(i, j, eigenVectors.At(i, j) + g * ort[i]); + } + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// The eigen vectors to work on. + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + static void NonsymmetricReduceHessenberToRealSchur(Matrix eigenVectors, float[,] matrixH, float[] d, float[] e, int order) + { + // Initialize + var n = order - 1; + var eps = (float)Precision.SinglePrecision; + var exshift = 0.0f; + float p = 0, q = 0, r = 0, s = 0, z = 0, w, x, y; + + // Store roots isolated by balanc and compute matrix norm + var norm = 0.0f; + for (var i = 0; i < order; i++) + { + for (var j = Math.Max(i - 1, 0); j < order; j++) + { + norm = norm + Math.Abs(matrixH[i, j]); + } + } + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + s = Math.Abs(matrixH[l - 1, l - 1]) + Math.Abs(matrixH[l, l]); + + if (s == 0.0f) + { + s = norm; + } + + if (Math.Abs(matrixH[l, l - 1]) < eps * s) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] = matrixH[n, n] + exshift; + d[n] = matrixH[n, n]; + e[n] = 0.0f; + n--; + iter = 0; + + // Two roots found + } + else if (l == n - 1) + { + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + p = (matrixH[n - 1, n - 1] - matrixH[n, n]) / 2.0f; + q = (p * p) + w; + z = (float)Math.Sqrt(Math.Abs(q)); + matrixH[n, n] = matrixH[n, n] + exshift; + matrixH[n - 1, n - 1] = matrixH[n - 1, n - 1] + exshift; + x = matrixH[n, n]; + + // Real pair + if (q >= 0) + { + if (p >= 0) + { + z = p + z; + } + else + { + z = p - z; + } + + d[n - 1] = x + z; + + d[n] = d[n - 1]; + if (z != 0.0f) + { + d[n] = x - (w / z); + } + + e[n - 1] = 0.0f; + e[n] = 0.0f; + x = matrixH[n, n - 1]; + s = Math.Abs(x) + Math.Abs(z); + p = x / s; + q = z / s; + r = (float)Math.Sqrt((p * p) + (q * q)); + p = p / r; + q = q / r; + + // Row modification + for (var j = n - 1; j < order; j++) + { + z = matrixH[n - 1, j]; + matrixH[n - 1, j] = (q * z) + (p * matrixH[n, j]); + matrixH[n, j] = (q * matrixH[n, j]) - (p * z); + } + + // Column modification + for (var i = 0; i <= n; i++) + { + z = matrixH[i, n - 1]; + matrixH[i, n - 1] = (q * z) + (p * matrixH[i, n]); + matrixH[i, n] = (q * matrixH[i, n]) - (p * z); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + z = eigenVectors.At(i, n - 1); + eigenVectors.At(i, n - 1, (q * z) + (p * eigenVectors.At(i, n))); + eigenVectors.At(i, n, (q * eigenVectors.At(i, n)) - (p * z)); + } + + // Complex pair + } + else + { + d[n - 1] = x + p; + d[n] = x + p; + e[n - 1] = z; + e[n] = -z; + } + + n = n - 2; + iter = 0; + + // No convergence yet + } + else + { + // Form shift + x = matrixH[n, n]; + y = 0.0f; + w = 0.0f; + if (l < n) + { + y = matrixH[n - 1, n - 1]; + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + } + + // Wilkinson's original ad hoc shift + if (iter == 10) + { + exshift += x; + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= x; + } + + s = Math.Abs(matrixH[n, n - 1]) + Math.Abs(matrixH[n - 1, n - 2]); + x = y = 0.75f * s; + w = (-0.4375f) * s * s; + } + + // MATLAB's new ad hoc shift + if (iter == 30) + { + s = (y - x) / 2.0f; + s = (s * s) + w; + if (s > 0) + { + s = (float)Math.Sqrt(s); + if (y < x) + { + s = -s; + } + + s = x - (w / (((y - x) / 2.0f) + s)); + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + x = y = w = 0.964f; + } + } + + iter = iter + 1; // (Could check iteration count here.) + + // Look for two consecutive small sub-diagonal elements + var m = n - 2; + while (m >= l) + { + z = matrixH[m, m]; + r = x - z; + s = y - z; + p = (((r * s) - w) / matrixH[m + 1, m]) + matrixH[m, m + 1]; + q = matrixH[m + 1, m + 1] - z - r - s; + r = matrixH[m + 2, m + 1]; + s = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + p = p / s; + q = q / s; + r = r / s; + + if (m == l) + { + break; + } + + if (Math.Abs(matrixH[m, m - 1]) * (Math.Abs(q) + Math.Abs(r)) < eps * (Math.Abs(p) * (Math.Abs(matrixH[m - 1, m - 1]) + Math.Abs(z) + Math.Abs(matrixH[m + 1, m + 1])))) + { + break; + } + + m--; + } + + for (var i = m + 2; i <= n; i++) + { + matrixH[i, i - 2] = 0.0f; + if (i > m + 2) + { + matrixH[i, i - 3] = 0.0f; + } + } + + // Double QR step involving rows l:n and columns m:n + for (var k = m; k <= n - 1; k++) + { + bool notlast = k != n - 1; + + if (k != m) + { + p = matrixH[k, k - 1]; + q = matrixH[k + 1, k - 1]; + r = notlast ? matrixH[k + 2, k - 1] : 0.0f; + x = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + if (x != 0.0f) + { + p = p / x; + q = q / x; + r = r / x; + } + } + + if (x == 0.0f) + { + break; + } + + s = (float)Math.Sqrt((p * p) + (q * q) + (r * r)); + if (p < 0) + { + s = -s; + } + + if (s != 0.0f) + { + if (k != m) + { + matrixH[k, k - 1] = (-s) * x; + } + else if (l != m) + { + matrixH[k, k - 1] = -matrixH[k, k - 1]; + } + + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // Row modification + for (var j = k; j < order; j++) + { + p = matrixH[k, j] + (q * matrixH[k + 1, j]); + + if (notlast) + { + p = p + (r * matrixH[k + 2, j]); + matrixH[k + 2, j] = matrixH[k + 2, j] - (p * z); + } + + matrixH[k, j] = matrixH[k, j] - (p * x); + matrixH[k + 1, j] = matrixH[k + 1, j] - (p * y); + } + + // Column modification + for (var i = 0; i <= Math.Min(n, k + 3); i++) + { + p = (x * matrixH[i, k]) + (y * matrixH[i, k + 1]); + + if (notlast) + { + p = p + (z * matrixH[i, k + 2]); + matrixH[i, k + 2] = matrixH[i, k + 2] - (p * r); + } + + matrixH[i, k] = matrixH[i, k] - p; + matrixH[i, k + 1] = matrixH[i, k + 1] - (p * q); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + p = (x * eigenVectors.At(i, k)) + (y * eigenVectors.At(i, k + 1)); + + if (notlast) + { + p = p + (z * eigenVectors.At(i, k + 2)); + eigenVectors.At(i, k + 2, eigenVectors.At(i, k + 2) - (p * r)); + } + + eigenVectors.At(i, k, eigenVectors.At(i, k) - p); + eigenVectors.At(i, k + 1, eigenVectors.At(i, k + 1) - (p * q)); + } + } // (s != 0) + } // k loop + } // check convergence + } // while (n >= low) + + // Backsubstitute to find vectors of upper triangular form + if (norm == 0.0f) + { + return; + } + + for (n = order - 1; n >= 0; n--) + { + float t; + + p = d[n]; + q = e[n]; + + // Real vector + if (q == 0.0f) + { + var l = n; + matrixH[n, n] = 1.0f; + for (var i = n - 1; i >= 0; i--) + { + w = matrixH[i, i] - p; + r = 0.0f; + for (var j = l; j <= n; j++) + { + r = r + (matrixH[i, j] * matrixH[j, n]); + } + + if (e[i] < 0.0f) + { + z = w; + s = r; + } + else + { + l = i; + if (e[i] == 0.0f) + { + if (w != 0.0f) + { + matrixH[i, n] = (-r) / w; + } + else + { + matrixH[i, n] = (-r) / (eps * norm); + } + + // Solve real equations + } + else + { + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + q = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]); + t = ((x * s) - (z * r)) / q; + matrixH[i, n] = t; + if (Math.Abs(x) > Math.Abs(z)) + { + matrixH[i + 1, n] = (-r - (w * t)) / x; + } + else + { + matrixH[i + 1, n] = (-s - (y * t)) / z; + } + } + + // Overflow control + t = Math.Abs(matrixH[i, n]); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + + // Complex vector + } + else if (q < 0) + { + var l = n - 1; + + // Last vector component imaginary so matrix is triangular + if (Math.Abs(matrixH[n, n - 1]) > Math.Abs(matrixH[n - 1, n])) + { + matrixH[n - 1, n - 1] = q / matrixH[n, n - 1]; + matrixH[n - 1, n] = (-(matrixH[n, n] - p)) / matrixH[n, n - 1]; + } + else + { + var res = Cdiv(0.0f, -matrixH[n - 1, n], matrixH[n - 1, n - 1] - p, q); + matrixH[n - 1, n - 1] = res.Real; + matrixH[n - 1, n] = res.Imaginary; + } + + matrixH[n, n - 1] = 0.0f; + matrixH[n, n] = 1.0f; + for (var i = n - 2; i >= 0; i--) + { + float ra = 0.0f; + float sa = 0.0f; + for (var j = l; j <= n; j++) + { + ra = ra + (matrixH[i, j] * matrixH[j, n - 1]); + sa = sa + (matrixH[i, j] * matrixH[j, n]); + } + + w = matrixH[i, i] - p; + + if (e[i] < 0.0f) + { + z = w; + r = ra; + s = sa; + } + else + { + l = i; + if (e[i] == 0.0f) + { + var res = Cdiv(-ra, -sa, w, q); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + } + else + { + // Solve complex equations + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + + float vr = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]) - (q * q); + float vi = (d[i] - p) * 2.0f * q; + if ((vr == 0.0f) && (vi == 0.0f)) + { + vr = eps * norm * (Math.Abs(w) + Math.Abs(q) + Math.Abs(x) + Math.Abs(y) + Math.Abs(z)); + } + + var res = Cdiv((x * r) - (z * ra) + (q * sa), (x * s) - (z * sa) - (q * ra), vr, vi); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + if (Math.Abs(x) > (Math.Abs(z) + Math.Abs(q))) + { + matrixH[i + 1, n - 1] = (-ra - (w * matrixH[i, n - 1]) + (q * matrixH[i, n])) / x; + matrixH[i + 1, n] = (-sa - (w * matrixH[i, n]) - (q * matrixH[i, n - 1])) / x; + } + else + { + res = Cdiv(-r - (y * matrixH[i, n - 1]), -s - (y * matrixH[i, n]), z, q); + matrixH[i + 1, n - 1] = res.Real; + matrixH[i + 1, n] = res.Imaginary; + } + } + + // Overflow control + t = Math.Max(Math.Abs(matrixH[i, n - 1]), Math.Abs(matrixH[i, n])); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n - 1] = matrixH[j, n - 1] / t; + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j >= 0; j--) + { + for (var i = 0; i < order; i++) + { + z = 0.0f; + for (var k = 0; k <= j; k++) + { + z = z + (eigenVectors.At(i, k) * matrixH[k, j]); + } + + eigenVectors.At(i, j, z); + } + } + } + + /// + /// Complex scalar division X/Y. + /// + /// Real part of X + /// Imaginary part of X + /// Real part of Y + /// Imaginary part of Y + /// Division result as a number. + static Complex32 Cdiv(float xreal, float ximag, float yreal, float yimag) + { + if (Math.Abs(yimag) < Math.Abs(yreal)) + { + return new Complex32((xreal + (ximag * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal))), (ximag - (xreal * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal)))); + } + + return new Complex32((ximag + (xreal * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag))), (-xreal + (ximag * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag)))); + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (EigenValues.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (EigenValues.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = EigenValues.Count; + var tmp = new float[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + float value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(i, j) * input.At(i, k); + } + + value /= (float)EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + float value = 0; + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(j, i) * tmp[i]; + } + + result.At(j, k, value); + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (EigenValues.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (EigenValues.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VT * b; + var order = EigenValues.Count; + var tmp = new float[order]; + float value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += EigenVectors.At(i, j) * input[i]; + } + + value /= (float)EigenValues[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (int i = 0; i < order; i++) + { + value += EigenVectors.At(j, i) * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserGramSchmidt.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserGramSchmidt.cs new file mode 100644 index 0000000..d4f1e9a --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserGramSchmidt.cs @@ -0,0 +1,226 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. +/// +/// +/// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. +/// +internal sealed class UserGramSchmidt : GramSchmidt +{ + /// + /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public static UserGramSchmidt Create(Matrix matrix) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + var q = matrix.Clone(); + var r = Matrix.Build.SameAs(matrix, matrix.ColumnCount, matrix.ColumnCount, fullyMutable: true); + + for (var k = 0; k < q.ColumnCount; k++) + { + var norm = (float)q.Column(k).L2Norm(); + if (norm == 0f) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r.At(k, k, norm); + for (var i = 0; i < q.RowCount; i++) + { + q.At(i, k, q.At(i, k) / norm); + } + + for (var j = k + 1; j < q.ColumnCount; j++) + { + var dot = q.Column(k).DotProduct(q.Column(j)); + r.At(k, j, dot); + for (var i = 0; i < q.RowCount; i++) + { + var value = q.At(i, j) - (q.At(i, k) * dot); + q.At(i, j, value); + } + } + } + + return new UserGramSchmidt(q, r); + } + + UserGramSchmidt(Matrix q, Matrix rFull) + : base(q, rFull) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (Q.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (Q.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new float[Q.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < Q.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < Q.ColumnCount; i++) + { + float s = 0; + for (var k = 0; k < Q.RowCount; k++) + { + s += Q.At(k, i) * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = Q.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / FullR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * FullR.At(i, k))); + } + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (Q.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (Q.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(Q, result); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new float[Q.RowCount]; + for (var k = 0; k < Q.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < Q.ColumnCount; i++) + { + float s = 0; + for (var k = 0; k < Q.RowCount; k++) + { + s += Q.At(k, i) * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = Q.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= FullR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * FullR.At(i, k); + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserLU.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserLU.cs new file mode 100644 index 0000000..2353377 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserLU.cs @@ -0,0 +1,306 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of an LU factorization. +/// For a matrix A, the LU factorization is a pair of lower triangular matrix L and +/// upper triangular matrix U so that A = L*U. +/// +/// +/// The computation of the LU factorization is done at construction time. +/// +internal sealed class UserLU : LU +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public static UserLU Create(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var order = matrix.RowCount; + var factors = matrix.Clone(); + var pivots = new int[order]; + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + pivots[i] = i; + } + + var vectorLUcolj = new float[order]; + for (var j = 0; j < order; j++) + { + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vectorLUcolj[i] = factors.At(i, j); + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + var kmax = Math.Min(i, j); + var s = 0.0f; + for (var k = 0; k < kmax; k++) + { + s += factors.At(i, k) * vectorLUcolj[k]; + } + + vectorLUcolj[i] -= s; + factors.At(i, j, vectorLUcolj[i]); + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (Math.Abs(vectorLUcolj[i]) > Math.Abs(vectorLUcolj[p])) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var temp = factors.At(p, k); + factors.At(p, k, factors.At(j, k)); + factors.At(j, k, temp); + } + + pivots[j] = p; + } + + // Compute multipliers. + if (j < order & factors.At(j, j) != 0.0) + { + for (var i = j + 1; i < order; i++) + { + factors.At(i, j, (factors.At(i, j) / factors.At(j, j))); + } + } + } + + return new UserLU(factors, pivots); + } + + UserLU(Matrix factors, int[] pivots) + : base(factors, pivots) + { + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(p, j); + result.At(p, j, result.At(i, j)); + result.At(i, j, temp); + } + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + for (var j = 0; j < result.ColumnCount; j++) + { + result.At(k, j, (result.At(k, j) / Factors.At(k, k))); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw Matrix.DimensionsDontMatch(input, Factors); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + var temp = result[p]; + result[p] = result[i]; + result[i] = temp; + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + result[k] /= Factors.At(k, k); + for (var i = 0; i < k; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var order = Factors.RowCount; + var inverse = Matrix.Build.SameAs(Factors, order, order); + for (var i = 0; i < order; i++) + { + inverse.At(i, i, 1.0f); + } + + return Solve(inverse); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserQR.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserQR.cs new file mode 100644 index 0000000..cdd8175 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserQR.cs @@ -0,0 +1,350 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the QR decomposition. +/// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix +/// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix +/// (also called right triangular matrix). +/// +/// +/// The computation of the QR decomposition is done at construction time by Householder transformation. +/// +internal sealed class UserQR : QR +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// The QR factorization method to use. + /// If is null. + public static UserQR Create(Matrix matrix, QRMethod method = QRMethod.Full) + { + if (matrix.RowCount < matrix.ColumnCount) + { + throw Matrix.DimensionsDontMatch(matrix); + } + + Matrix q; + Matrix r; + + var minmn = Math.Min(matrix.RowCount, matrix.ColumnCount); + var u = new float[minmn][]; + + if (method == QRMethod.Full) + { + r = matrix.Clone(); + q = Matrix.Build.SameAs(matrix, matrix.RowCount, matrix.RowCount, fullyMutable: true); + + for (var i = 0; i < matrix.RowCount; i++) + { + q.At(i, i, 1.0f); + } + + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(r, i, i); + ComputeQR(u[i], r, i, matrix.RowCount, i + 1, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], q, i, matrix.RowCount, i, matrix.RowCount, Control.MaxDegreeOfParallelism); + } + } + else + { + q = matrix.Clone(); + + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(q, i, i); + ComputeQR(u[i], q, i, matrix.RowCount, i + 1, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + + r = q.SubMatrix(0, matrix.ColumnCount, 0, matrix.ColumnCount); + q.Clear(); + + for (var i = 0; i < matrix.ColumnCount; i++) + { + q.At(i, i, 1.0f); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], q, i, matrix.RowCount, i, matrix.ColumnCount, Control.MaxDegreeOfParallelism); + } + } + + return new UserQR(q, r, method); + } + + UserQR(Matrix q, Matrix rFull, QRMethod method) + : base(q, rFull, method) + { + } + + /// + /// Generate column from initial matrix to work array + /// + /// Initial matrix + /// The first row + /// Column index + /// Generated vector + static float[] GenerateColumn(Matrix a, int row, int column) + { + var ru = a.RowCount - row; + var u = new float[ru]; + + for (var i = row; i < a.RowCount; i++) + { + u[i - row] = a.At(i, row); + a.At(i, row, 0.0f); + } + + var norm = u.Sum(t => t * t); + norm = (float)Math.Sqrt(norm); + + if (row == a.RowCount - 1 || norm == 0) + { + a.At(row, column, -u[0]); + u[0] = (float)Constants.Sqrt2; + return u; + } + + var scale = 1.0f / norm; + if (u[0] < 0.0) + { + scale *= -1.0f; + } + + a.At(row, column, -1.0f / scale); + + for (var i = 0; i < ru; i++) + { + u[i] *= scale; + } + + u[0] += 1.0f; + var s = (float)Math.Sqrt(1.0 / u[0]); + + for (var i = 0; i < ru; i++) + { + u[i] *= s; + } + + return u; + } + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Q or R matrices + /// The first row + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(float[] u, Matrix a, int rowStart, int rowDim, int columnStart, int columnDim, int availableCores) + { + if (rowDim < rowStart || columnDim < columnStart) + { + return; + } + + var tmpColCount = columnDim - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(u, a, rowStart, rowDim, columnStart, tmpSplit, tmpCores), + () => ComputeQR(u, a, rowStart, rowDim, tmpSplit, columnDim, tmpCores)); + } + else + { + for (var j = columnStart; j < columnDim; j++) + { + var scale = 0.0f; + for (var i = rowStart; i < rowDim; i++) + { + scale += u[i - rowStart] * a.At(i, j); + } + + for (var i = rowStart; i < rowDim; i++) + { + a.At(i, j, a.At(i, j) - (u[i - rowStart] * scale)); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (FullR.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (FullR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new float[FullR.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < FullR.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < FullR.RowCount; i++) + { + float s = 0; + for (var k = 0; k < FullR.RowCount; k++) + { + s += Q.At(k, i) * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = FullR.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / FullR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * FullR.At(i, k))); + } + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + for (var j = 0; j < inputCopy.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (FullR.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (FullR.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(FullR, result); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new float[FullR.RowCount]; + for (var k = 0; k < FullR.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < FullR.RowCount; i++) + { + float s = 0; + for (var k = 0; k < FullR.RowCount; k++) + { + s += Q.At(k, i) * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = FullR.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= FullR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * FullR.At(i, k); + } + } + + for (var i = 0; i < FullR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserSvd.cs b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserSvd.cs new file mode 100644 index 0000000..661c66b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Factorization/UserSvd.cs @@ -0,0 +1,903 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Factorization; + +/// +/// A class which encapsulates the functionality of the singular value decomposition (SVD) for . +/// Suppose M is an m-by-n matrix whose entries are real numbers. +/// Then there exists a factorization of the form M = UΣVT where: +/// - U is an m-by-m unitary matrix; +/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; +/// - VT denotes transpose of V, an n-by-n unitary matrix; +/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal +/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined +/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. +/// +/// +/// The computation of the singular value decomposition is done at construction time. +/// +internal sealed class UserSvd : Svd +{ + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// + public static UserSvd Create(Matrix matrix, bool computeVectors) + { + var nm = Math.Min(matrix.RowCount + 1, matrix.ColumnCount); + var matrixCopy = matrix.Clone(); + + var s = Vector.Build.SameAs(matrixCopy, nm); + var u = Matrix.Build.SameAs(matrixCopy, matrixCopy.RowCount, matrixCopy.RowCount, fullyMutable: true); + var vt = Matrix.Build.SameAs(matrixCopy, matrixCopy.ColumnCount, matrixCopy.ColumnCount, fullyMutable: true); + + const int maxiter = 1000; + var e = new float[matrixCopy.ColumnCount]; + var work = new float[matrixCopy.RowCount]; + + int i, j; + int l, lp1; + float t; + + var ncu = matrixCopy.RowCount; + + // Reduce matrixCopy to bidiagonal form, storing the diagonal elements + // In s and the super-diagonal elements in e. + var nct = Math.Min(matrixCopy.RowCount - 1, matrixCopy.ColumnCount); + var nrt = Math.Max(0, Math.Min(matrixCopy.ColumnCount - 2, matrixCopy.RowCount)); + var lu = Math.Max(nct, nrt); + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and place the l-th diagonal in VectorS[l]. + var xnorm = Dnrm2Column(matrixCopy, matrixCopy.RowCount, l, l); + s[l] = xnorm; + if (s[l] != 0.0) + { + if (matrixCopy.At(l, l) != 0.0) + { + s[l] = Dsign(s[l], matrixCopy.At(l, l)); + } + + DscalColumn(matrixCopy, matrixCopy.RowCount, l, l, 1.0f / s[l]); + matrixCopy.At(l, l, (1.0f + matrixCopy.At(l, l))); + } + + s[l] = -s[l]; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (l < nct) + { + if (s[l] != 0.0) + { + // Apply the transformation. + t = -Ddot(matrixCopy, matrixCopy.RowCount, l, j, l) / matrixCopy.At(l, l); + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (t * matrixCopy.At(ii, l))); + } + } + } + + // Place the l-th row of matrixCopy into e for the + // Subsequent calculation of the row transformation. + e[j] = matrixCopy.At(l, j); + } + + if (computeVectors && l < nct) + { + // Place the transformation in u for subsequent back multiplication. + for (i = l; i < matrixCopy.RowCount; i++) + { + u.At(i, l, matrixCopy.At(i, l)); + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = Dnrm2Vector(e, lp1); + e[l] = enorm; + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = Dsign(e[l], e[lp1]); + } + + DscalVector(e, lp1, 1.0f / e[l]); + e[lp1] = 1.0f + e[lp1]; + } + + e[l] = -e[l]; + if (lp1 < matrixCopy.RowCount && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < matrixCopy.RowCount; i++) + { + work[i] = 0.0f; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + work[ii] += e[j] * matrixCopy.At(ii, j); + } + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + var ww = -e[j] / e[lp1]; + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (ww * work[ii])); + } + } + } + + if (computeVectors) + { + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < matrixCopy.ColumnCount; i++) + { + vt.At(i, l, e[i]); + } + } + } + + // Set up the final bidiagonal matrixCopy or order m. + var m = Math.Min(matrixCopy.ColumnCount, matrixCopy.RowCount + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < matrixCopy.ColumnCount) + { + s[nctp1 - 1] = matrixCopy.At((nctp1 - 1), (nctp1 - 1)); + } + + if (matrixCopy.RowCount < m) + { + s[m - 1] = 0.0f; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = matrixCopy.At((nrtp1 - 1), (m - 1)); + } + + e[m - 1] = 0.0f; + + // If required, generate u. + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + u.At(i, j, 0.0f); + } + + u.At(j, j, 1.0f); + } + + for (l = nct - 1; l >= 0; l--) + { + if (s[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = -Ddot(u, matrixCopy.RowCount, l, j, l) / u.At(l, l); + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + u.At(ii, j, u.At(ii, j) + (t * u.At(ii, l))); + } + } + + DscalColumn(u, matrixCopy.RowCount, l, l, -1.0f); + u.At(l, l, 1.0f + u.At(l, l)); + for (i = 0; i < l; i++) + { + u.At(i, l, 0.0f); + } + } + else + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + u.At(i, l, 0.0f); + } + + u.At(l, l, 1.0f); + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = matrixCopy.ColumnCount - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + t = -Ddot(vt, matrixCopy.ColumnCount, l, j, lp1) / vt.At(lp1, l); + for (var ii = l; ii < matrixCopy.ColumnCount; ii++) + { + vt.At(ii, j, vt.At(ii, j) + (t * vt.At(ii, l))); + } + } + } + } + + for (i = 0; i < matrixCopy.ColumnCount; i++) + { + vt.At(i, l, 0.0f); + } + + vt.At(l, l, 1.0f); + } + } + + // Transform s and e so that they are float . + for (i = 0; i < m; i++) + { + float r; + if (s[i] != 0.0) + { + t = s[i]; + r = s[i] / t; + s[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + DscalColumn(u, matrixCopy.RowCount, i, 0, r); + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] != 0.0) + { + t = e[i]; + r = t / e[i]; + e[i] = t; + s[i + 1] = s[i + 1] * r; + if (computeVectors) + { + DscalColumn(vt, matrixCopy.ColumnCount, i + 1, 0, r); + } + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays. On + // completion the variables case and l are set as follows. + // Case = 1 if VectorS[m] and e[l-1] are negligible and l < m + // Case = 2 if VectorS[l] is negligible and l < m + // Case = 3 if e[l-1] is negligible, l < m, and VectorS[l, ..., VectorS[m] are not negligible (qr step). + // Case = 4 if e[m-1] is negligible (convergence). + float ztest; + float test; + for (l = m - 2; l >= 0; l--) + { + test = Math.Abs(s[l]) + Math.Abs(s[l + 1]); + ztest = test + Math.Abs(e[l]); + if (ztest.AlmostEqualRelative(test, 7)) + { + e[l] = 0.0f; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0f; + if (ls != m - 1) + { + test = test + Math.Abs(e[ls]); + } + + if (ls != l + 1) + { + test = test + Math.Abs(e[ls - 1]); + } + + ztest = test + Math.Abs(s[ls]); + if (ztest.AlmostEqualRelative(test, 7)) + { + s[ls] = 0.0f; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + float f; + float sn; + float cs; + switch (kase) + { + // Deflate negligible VectorS[m]. + case 1: + f = e[m - 2]; + e[m - 2] = 0.0f; + float t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = s[k]; + Drotg(ref t1, ref f, out cs, out sn); + s[k] = t1; + if (k != l) + { + f = -sn * e[k - 1]; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + Drot(vt, matrixCopy.ColumnCount, k, m - 1, cs, sn); + } + } + + break; + + // Split at negligible VectorS[l]. + case 2: + f = e[l - 1]; + e[l - 1] = 0.0f; + for (k = l; k < m; k++) + { + t1 = s[k]; + Drotg(ref t1, ref f, out cs, out sn); + s[k] = t1; + f = -sn * e[k]; + e[k] = cs * e[k]; + if (computeVectors) + { + Drot(u, matrixCopy.RowCount, k, l - 1, cs, sn); + } + } + + break; + + // Perform one qr step. + case 3: + // Calculate the shift. + var scale = 0.0f; + scale = Math.Max(scale, Math.Abs(s[m - 1])); + scale = Math.Max(scale, Math.Abs(s[m - 2])); + scale = Math.Max(scale, Math.Abs(e[m - 2])); + scale = Math.Max(scale, Math.Abs(s[l])); + scale = Math.Max(scale, Math.Abs(e[l])); + var sm = s[m - 1] / scale; + var smm1 = s[m - 2] / scale; + var emm1 = e[m - 2] / scale; + var sl = s[l] / scale; + var el = e[l] / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0f; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0f; + if (b != 0.0 || c != 0.0) + { + shift = (float)Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros. + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * s[k]) + (sn * e[k]); + e[k] = (cs * e[k]) - (sn * s[k]); + g = sn * s[k + 1]; + s[k + 1] = cs * s[k + 1]; + if (computeVectors) + { + Drot(vt, matrixCopy.ColumnCount, k, k + 1, cs, sn); + } + + Drotg(ref f, ref g, out cs, out sn); + s[k] = f; + f = (cs * e[k]) + (sn * s[k + 1]); + s[k + 1] = (-sn * e[k]) + (cs * s[k + 1]); + g = sn * e[k + 1]; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < matrixCopy.RowCount) + { + Drot(u, matrixCopy.RowCount, k, k + 1, cs, sn); + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence. + case 4: + // Make the singular value positive + if (s[l] < 0.0) + { + s[l] = -s[l]; + if (computeVectors) + { + DscalColumn(vt, matrixCopy.ColumnCount, l, 0, -1.0f); + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (s[l] >= s[l + 1]) + { + break; + } + + t = s[l]; + s[l] = s[l + 1]; + s[l + 1] = t; + if (computeVectors && l < matrixCopy.ColumnCount) + { + Dswap(vt, matrixCopy.ColumnCount, l, l + 1); + } + + if (computeVectors && l < matrixCopy.RowCount) + { + Dswap(u, matrixCopy.RowCount, l, l + 1); + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + vt = vt.Transpose(); + } + + // Adjust the size of s if rows < columns. We are using ported copy of linpack's svd code and it uses + // a singular vector of length mRows+1 when mRows < mColumns. The last element is not used and needs to be removed. + // we should port lapack's svd routine to remove this problem. + if (matrixCopy.RowCount < matrixCopy.ColumnCount) + { + nm--; + var tmp = Vector.Build.SameAs(matrixCopy, nm); + for (i = 0; i < nm; i++) + { + tmp[i] = s[i]; + } + + s = tmp; + } + + return new UserSvd(s, u, vt, computeVectors); + } + + UserSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed) + : base(s, u, vt, vectorsComputed) + { + } + + /// + /// Calculates absolute value of multiplied on signum function of + /// + /// Double value z1 + /// Double value z2 + /// Result multiplication of signum function and absolute value + static float Dsign(float z1, float z2) + { + return Math.Abs(z1) * (z2 / Math.Abs(z2)); + } + + /// + /// Swap column and + /// + /// Source matrix + /// The number of rows in + /// Column A index to swap + /// Column B index to swap + static void Dswap(Matrix a, int rowCount, int columnA, int columnB) + { + for (var i = 0; i < rowCount; i++) + { + var z = a.At(i, columnA); + a.At(i, columnA, a.At(i, columnB)); + a.At(i, columnB, z); + } + } + + /// + /// Scale column by starting from row + /// + /// Source matrix + /// The number of rows in + /// Column to scale + /// Row to scale from + /// Scale value + static void DscalColumn(Matrix a, int rowCount, int column, int rowStart, float z) + { + for (var i = rowStart; i < rowCount; i++) + { + a.At(i, column, a.At(i, column) * z); + } + } + + /// + /// Scale vector by starting from index + /// + /// Source vector + /// Row to scale from + /// Scale value + static void DscalVector(float[] a, int start, float z) + { + for (var i = start; i < a.Length; i++) + { + a[i] = a[i] * z; + } + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + static void Drotg(ref float da, ref float db, out float c, out float s) + { + float r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0) + { + c = 1.0f; + s = 0.0f; + r = 0.0f; + z = 0.0f; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * (float)Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0f; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0) + { + z = 1.0f / c; + } + } + + da = r; + db = z; + } + + /// + /// Calculate Norm 2 of the column in matrix starting from row + /// + /// Source matrix + /// The number of rows in + /// Column index + /// Start row index + /// Norm2 (Euclidean norm) of the column + static float Dnrm2Column(Matrix a, int rowCount, int column, int rowStart) + { + float s = 0; + for (var i = rowStart; i < rowCount; i++) + { + s += a.At(i, column) * a.At(i, column); + } + + return (float)Math.Sqrt(s); + } + + /// + /// Calculate Norm 2 of the vector starting from index + /// + /// Source vector + /// Start index + /// Norm2 (Euclidean norm) of the vector + static float Dnrm2Vector(float[] a, int rowStart) + { + float s = 0; + for (var i = rowStart; i < a.Length; i++) + { + s += a[i] * a[i]; + } + + return (float)Math.Sqrt(s); + } + + /// + /// Calculate dot product of and + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Starting row index + /// Dot product value + static float Ddot(Matrix a, int rowCount, int columnA, int columnB, int rowStart) + { + var z = 0.0f; + for (var i = rowStart; i < rowCount; i++) + { + z += a.At(i, columnB) * a.At(i, columnA); + } + + return z; + } + + /// + /// Performs rotation of points in the plane. Given two vectors x and y , + /// each vector element of these vectors is replaced as follows: x(i) = c*x(i) + s*y(i); y(i) = c*y(i) - s*x(i) + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Scalar "c" value + /// Scalar "s" value + static void Drot(Matrix a, int rowCount, int columnA, int columnB, float c, float s) + { + for (var i = 0; i < rowCount; i++) + { + var z = (c * a.At(i, columnA)) + (s * a.At(i, columnB)); + var tmp = (c * a.At(i, columnB)) - (s * a.At(i, columnA)); + a.At(i, columnB, tmp); + a.At(i, columnA, z); + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (U.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var mn = Math.Min(U.RowCount, VT.ColumnCount); + var bn = input.ColumnCount; + + var tmp = new float[VT.ColumnCount]; + + for (var k = 0; k < bn; k++) + { + for (var j = 0; j < VT.ColumnCount; j++) + { + float value = 0; + if (j < mn) + { + for (var i = 0; i < U.RowCount; i++) + { + value += U.At(i, j) * input.At(i, k); + } + + value /= S[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < VT.ColumnCount; j++) + { + float value = 0; + for (var i = 0; i < VT.ColumnCount; i++) + { + value += VT.At(i, j) * tmp[i]; + } + + result.At(j, k, value); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (!VectorsComputed) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (U.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VT.ColumnCount != result.Count) + { + throw Matrix.DimensionsDontMatch(VT, result); + } + + var mn = Math.Min(U.RowCount, VT.ColumnCount); + var tmp = new float[VT.ColumnCount]; + float value; + for (var j = 0; j < VT.ColumnCount; j++) + { + value = 0; + if (j < mn) + { + for (var i = 0; i < U.RowCount; i++) + { + value += U.At(i, j) * input[i]; + } + + value /= S[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < VT.ColumnCount; j++) + { + value = 0; + for (int i = 0; i < VT.ColumnCount; i++) + { + value += VT.At(i, j) * tmp[i]; + } + + result[j] = value; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Matrix.cs b/MathNet.Numerics/LinearAlgebra/Single/Matrix.cs new file mode 100644 index 0000000..78e3f7a --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Matrix.cs @@ -0,0 +1,814 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearAlgebra.Single.Factorization; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single; + +/// +/// float version of the class. +/// +[Serializable] +public abstract class Matrix : Matrix +{ + /// + /// Initializes a new instance of the Matrix class. + /// + protected Matrix(MatrixStorage storage) + : base(storage) + { + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero. + /// + public override void CoerceZero(double threshold) + { + MapInplace(x => Math.Abs(x) < threshold ? 0f : x, Zeros.AllowSkip); + } + + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public sealed override Matrix ConjugateTranspose() + { + return Transpose(); + } + + /// + /// Puts the conjugate transpose of this matrix into the result matrix. + /// + public sealed override void ConjugateTranspose(Matrix result) + { + Transpose(result); + } + + /// + /// Complex conjugates each element of this matrix and place the results into the result matrix. + /// + /// The result of the conjugation. + protected sealed override void DoConjugate(Matrix result) + { + if (ReferenceEquals(this, result)) + { + return; + } + + CopyTo(result); + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + Map(x => -x, result, Zeros.AllowSkip); + } + + /// + /// Add a scalar to each element of the matrix and stores the result in the result vector. + /// + /// The scalar to add. + /// The matrix to store the result of the addition. + protected override void DoAdd(float scalar, Matrix result) + { + Map(x => x + scalar, result, Zeros.Include); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + Map2((x, y) => x + y, other, result, Zeros.AllowSkip); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The matrix to store the result of the subtraction. + protected override void DoSubtract(float scalar, Matrix result) + { + Map(x => x - scalar, result, Zeros.Include); + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract to this matrix. + /// The matrix to store the result of subtraction. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + Map2((x, y) => x - y, other, result, Zeros.AllowSkip); + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(float scalar, Matrix result) + { + Map(x => x * scalar, result, Zeros.AllowSkip); + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + for (var i = 0; i < RowCount; i++) + { + var s = 0.0f; + for (var j = 0; j < ColumnCount; j++) + { + s += At(i, j) * rightSide[j]; + } + + result[i] = s; + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + for (var i = 0; i < RowCount; i++) + { + for (var j = 0; j < other.ColumnCount; j++) + { + var s = 0.0f; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Divides each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to divide the matrix with. + /// The matrix to store the result of the division. + protected override void DoDivide(float divisor, Matrix result) + { + Map(x => x / divisor, result, divisor == 0.0f ? Zeros.Include : Zeros.AllowSkip); + } + + /// + /// Divides a scalar by each element of the matrix and stores the result in the result matrix. + /// + /// The scalar to divide by each element of the matrix. + /// The matrix to store the result of the division. + protected override void DoDivideByThis(float dividend, Matrix result) + { + Map(x => dividend / x, result, Zeros.Include); + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.RowCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + var s = 0.0f; + for (var k = 0; k < ColumnCount; k++) + { + s += At(i, k) * other.At(j, k); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies this matrix with the conjugate transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected sealed override void DoConjugateTransposeAndMultiply(Matrix other, Matrix result) + { + DoTransposeAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Matrix other, Matrix result) + { + for (var j = 0; j < other.ColumnCount; j++) + { + for (var i = 0; i < ColumnCount; i++) + { + var s = 0.0f; + for (var k = 0; k < RowCount; k++) + { + s += At(k, i) * other.At(k, j); + } + + result.At(i, j, s); + } + } + } + + /// + /// Multiplies the transpose of this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected sealed override void DoConjugateTransposeThisAndMultiply(Matrix other, Matrix result) + { + DoTransposeThisAndMultiply(other, result); + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + for (var j = 0; j < ColumnCount; j++) + { + var s = 0.0f; + for (var i = 0; i < RowCount; i++) + { + s += At(i, j) * rightSide[i]; + } + + result[j] = s; + } + } + + /// + /// Multiplies the conjugate transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected sealed override void DoConjugateTransposeThisAndMultiply(Vector rightSide, Vector result) + { + DoTransposeThisAndMultiply(rightSide, result); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoModulus(float divisor, Matrix result) + { + Map(x => Euclid.Modulus(x, divisor), result, Zeros.Include); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoModulusByThis(float dividend, Matrix result) + { + Map(x => Euclid.Modulus(dividend, x), result, Zeros.Include); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoRemainder(float divisor, Matrix result) + { + Map(x => Euclid.Remainder(x, divisor), result, Zeros.Include); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the matrix. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoRemainderByThis(float dividend, Matrix result) + { + Map(x => Euclid.Remainder(dividend, x), result, Zeros.Include); + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + Map2((x, y) => x * y, other, result, Zeros.AllowSkip); + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + Map2((x, y) => x / y, divisor, result, Zeros.Include); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The matrix to store the result of the pointwise power. + protected override void DoPointwisePower(float exponent, Matrix result) + { + Map(x => (float)Math.Pow(x, exponent), result, exponent > 0.0f ? Zeros.AllowSkip : Zeros.Include); + } + + /// + /// Pointwise raise this matrix to an exponent and store the result into the result matrix. + /// + /// The exponent to raise this matrix values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Matrix exponent, Matrix result) + { + Map2((x, y) => (float)Math.Pow(x, y), result, Zeros.Include); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected override void DoPointwiseModulus(Matrix divisor, Matrix result) + { + Map2(Euclid.Modulus, divisor, result, Zeros.Include); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this matrix with another matrix and stores the result into the result matrix. + /// + /// The pointwise denominator matrix to use + /// The result of the modulus. + protected override void DoPointwiseRemainder(Matrix divisor, Matrix result) + { + Map2(Euclid.Remainder, divisor, result, Zeros.Include); + } + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected override void DoPointwiseExp(Matrix result) + { + Map(x => (float)Math.Exp(x), result, Zeros.Include); + } + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result matrix. + /// + /// The matrix to store the result. + protected override void DoPointwiseLog(Matrix result) + { + Map(x => (float)Math.Log(x), result, Zeros.Include); + } + + protected override void DoPointwiseAbs(Matrix result) + { + Map(x => (float)Math.Abs(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAcos(Matrix result) + { + Map(x => (float)Math.Acos(x), result, Zeros.Include); + } + protected override void DoPointwiseAsin(Matrix result) + { + Map(x => (float)Math.Asin(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan(Matrix result) + { + Map(x => (float)Math.Atan(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan2(Matrix other, Matrix result) + { + Map2((x, y) => (float)Math.Atan2((double)x, (double)y), other, result, Zeros.Include); + } + protected override void DoPointwiseCeiling(Matrix result) + { + Map(x => (float)Math.Ceiling(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseCos(Matrix result) + { + Map(x => (float)Math.Cos(x), result, Zeros.Include); + } + protected override void DoPointwiseCosh(Matrix result) + { + Map(x => (float)Math.Cosh(x), result, Zeros.Include); + } + protected override void DoPointwiseFloor(Matrix result) + { + Map(x => (float)Math.Floor(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseLog10(Matrix result) + { + Map(x => (float)Math.Log10(x), result, Zeros.Include); + } + protected override void DoPointwiseRound(Matrix result) + { + Map(x => (float)Math.Round(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSign(Matrix result) + { + Map(x => (float)Math.Sign(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSin(Matrix result) + { + Map(x => (float)Math.Sin(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSinh(Matrix result) + { + Map(x => (float)Math.Sinh(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSqrt(Matrix result) + { + Map(x => (float)Math.Sqrt(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseTan(Matrix result) + { + Map(x => (float)Math.Tan(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseTanh(Matrix result) + { + Map(x => (float)Math.Tanh(x), result, Zeros.AllowSkip); + } + + /// + /// Computes the Moore-Penrose Pseudo-Inverse of this matrix. + /// + public override Matrix PseudoInverse() + { + var svd = Svd(true); + var w = svd.W; + var s = svd.S; + float tolerance = (float)(Math.Max(RowCount, ColumnCount) * svd.L2Norm * Precision.SinglePrecision); + + for (int i = 0; i < s.Count; i++) + { + s[i] = s[i] < tolerance ? 0 : 1 / s[i]; + } + + w.SetDiagonal(s); + return (svd.U * w * svd.VT).Transpose(); + } + + /// + /// Computes the trace of this matrix. + /// + /// The trace of this matrix + /// If the matrix is not square + public override float Trace() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var sum = 0.0f; + for (var i = 0; i < RowCount; i++) + { + sum += At(i, i); + } + + return sum; + } + + protected override void DoPointwiseMinimum(float scalar, Matrix result) + { + Map(x => Math.Min(scalar, x), result, scalar >= 0d ? Zeros.AllowSkip : Zeros.Include); + } + + protected override void DoPointwiseMaximum(float scalar, Matrix result) + { + Map(x => Math.Max(scalar, x), result, scalar <= 0d ? Zeros.AllowSkip : Zeros.Include); + } + + protected override void DoPointwiseAbsoluteMinimum(float scalar, Matrix result) + { + float absolute = Math.Abs(scalar); + Map(x => Math.Min(absolute, Math.Abs(x)), result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(float scalar, Matrix result) + { + float absolute = Math.Abs(scalar); + Map(x => Math.Max(absolute, Math.Abs(x)), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(Matrix other, Matrix result) + { + Map2(Math.Min, other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseMaximum(Matrix other, Matrix result) + { + Map2(Math.Max, other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMinimum(Matrix other, Matrix result) + { + Map2((x, y) => Math.Min(Math.Abs(x), Math.Abs(y)), other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Matrix other, Matrix result) + { + Map2((x, y) => Math.Max(Math.Abs(x), Math.Abs(y)), other, result, Zeros.AllowSkip); + } + + /// Calculates the induced L1 norm of this matrix. + /// The maximum absolute column sum of the matrix. + public override double L1Norm() + { + var norm = 0d; + for (var j = 0; j < ColumnCount; j++) + { + var s = 0d; + for (var i = 0; i < RowCount; i++) + { + s += Math.Abs(At(i, j)); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + var s = 0d; + for (var j = 0; j < ColumnCount; j++) + { + s += Math.Abs(At(i, j)); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + var transpose = Transpose(); + var aat = this * transpose; + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + norm += aat.At(i, i); + } + + return Math.Sqrt(norm); + } + + /// + /// Calculates the p-norms of all row vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public override Vector RowNorms(double norm) + { + if (norm <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(norm), Resources.ArgumentMustBePositive); + } + + var ret = new double[RowCount]; + if (norm == 2.0) + { + Storage.FoldByRowUnchecked(ret, (s, x) => s + x * x, (x, c) => Math.Sqrt(x), ret, Zeros.AllowSkip); + } + else if (norm == 1.0) + { + Storage.FoldByRowUnchecked(ret, (s, x) => s + Math.Abs(x), (x, c) => x, ret, Zeros.AllowSkip); + } + else if (double.IsPositiveInfinity(norm)) + { + Storage.FoldByRowUnchecked(ret, (s, x) => Math.Max(s, Math.Abs(x)), (x, c) => x, ret, Zeros.AllowSkip); + } + else + { + double invnorm = 1.0 / norm; + Storage.FoldByRowUnchecked(ret, (s, x) => s + Math.Pow(Math.Abs(x), norm), (x, c) => Math.Pow(x, invnorm), ret, Zeros.AllowSkip); + } + + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the p-norms of all column vectors. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public override Vector ColumnNorms(double norm) + { + if (norm <= 0.0) + { + throw new ArgumentOutOfRangeException(nameof(norm), Resources.ArgumentMustBePositive); + } + + var ret = new double[ColumnCount]; + if (norm == 2.0) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x * x, (x, c) => Math.Sqrt(x), ret, Zeros.AllowSkip); + } + else if (norm == 1.0) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => s + Math.Abs(x), (x, c) => x, ret, Zeros.AllowSkip); + } + else if (double.IsPositiveInfinity(norm)) + { + Storage.FoldByColumnUnchecked(ret, (s, x) => Math.Max(s, Math.Abs(x)), (x, c) => x, ret, Zeros.AllowSkip); + } + else + { + double invnorm = 1.0 / norm; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + Math.Pow(Math.Abs(x), norm), (x, c) => Math.Pow(x, invnorm), ret, Zeros.AllowSkip); + } + + return Vector.Build.Dense(ret); + } + + /// + /// Normalizes all row vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public sealed override Matrix NormalizeRows(double norm) + { + var norminv = ((DenseVectorStorage)RowNorms(norm).Storage).Data; + for (int i = 0; i < norminv.Length; i++) + { + norminv[i] = norminv[i] == 0d ? 1d : 1d / norminv[i]; + } + + var result = Build.SameAs(this, RowCount, ColumnCount); + Storage.MapIndexedTo(result.Storage, (i, j, x) => (float)norminv[i] * x, Zeros.AllowSkip, ExistingData.AssumeZeros); + return result; + } + + /// + /// Normalizes all column vectors to a unit p-norm. + /// Typical values for p are 1.0 (L1, Manhattan norm), 2.0 (L2, Euclidean norm) and positive infinity (infinity norm) + /// + public sealed override Matrix NormalizeColumns(double norm) + { + var norminv = ((DenseVectorStorage)ColumnNorms(norm).Storage).Data; + for (int i = 0; i < norminv.Length; i++) + { + norminv[i] = norminv[i] == 0d ? 1d : 1d / norminv[i]; + } + + var result = Build.SameAs(this, RowCount, ColumnCount); + Storage.MapIndexedTo(result.Storage, (i, j, x) => (float)norminv[j] * x, Zeros.AllowSkip, ExistingData.AssumeZeros); + return result; + } + + /// + /// Calculates the value sum of each row vector. + /// + public override Vector RowSums() + { + var ret = new float[RowCount]; + Storage.FoldByRowUnchecked(ret, (s, x) => s + x, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the absolute value sum of each row vector. + /// + public override Vector RowAbsoluteSums() + { + var ret = new float[RowCount]; + Storage.FoldByRowUnchecked(ret, (s, x) => s + Math.Abs(x), (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the value sum of each column vector. + /// + public override Vector ColumnSums() + { + var ret = new float[ColumnCount]; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + x, (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Calculates the absolute value sum of each column vector. + /// + public override Vector ColumnAbsoluteSums() + { + var ret = new float[ColumnCount]; + Storage.FoldByColumnUnchecked(ret, (s, x) => s + Math.Abs(x), (x, c) => x, ret, Zeros.AllowSkip); + return Vector.Build.Dense(ret); + } + + /// + /// Evaluates whether this matrix is Hermitian (conjugate symmetric). + /// + public sealed override bool IsHermitian() + { + return IsSymmetric(); + } + + public override Cholesky Cholesky() + { + return UserCholesky.Create(this); + } + + public override LU LU() + { + return UserLU.Create(this); + } + + public override QR QR(QRMethod method = QRMethod.Thin) + { + return UserQR.Create(this, method); + } + + public override GramSchmidt GramSchmidt() + { + return UserGramSchmidt.Create(this); + } + + public override Svd Svd(bool computeVectors = true) + { + return UserSvd.Create(this, computeVectors); + } + + public override Evd Evd(Symmetricity symmetricity = Symmetricity.Unknown) + { + return UserEvd.Create(this, symmetricity); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/BiCgStab.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/BiCgStab.cs new file mode 100644 index 0000000..27d6218 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/BiCgStab.cs @@ -0,0 +1,273 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// A Bi-Conjugate Gradient stabilized iterative matrix solver. +/// +/// +/// +/// The Bi-Conjugate Gradient Stabilized (BiCGStab) solver is an 'improvement' +/// of the standard Conjugate Gradient (CG) solver. Unlike the CG solver the +/// BiCGStab can be used on non-symmetric matrices.
+/// Note that much of the success of the solver depends on the selection of the +/// proper preconditioner. +///
+/// +/// The Bi-CGSTAB algorithm was taken from:
+/// Templates for the solution of linear systems: Building blocks +/// for iterative methods +///
+/// Richard Barrett, Michael Berry, Tony F. Chan, James Demmel, +/// June M. Donato, Jack Dongarra, Victor Eijkhout, Roldan Pozo, +/// Charles Romine and Henk van der Vorst +///
+/// Url: http://www.netlib.org/templates/Templates.html +///
+/// Algorithm is described in Chapter 2, section 2.3.8, page 27 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class BiCgStab : IIterativeSolver +{ + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + + // Do not use residual = residual.Negate() because it creates another object + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient , A. + /// The solution , b. + /// The result , x. + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // Compute r_0 = b - Ax_0 for some initial guess x_0 + // In this case we take x_0 = vector + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, result, input); + + // Choose r~ (for example, r~ = r_0) + var tempResiduals = residuals.Clone(); + + // create seven temporary vectors needed to hold temporary + // coefficients. All vectors are mangled in each iteration. + // These are defined here to prevent stressing the garbage collector + var vecP = new DenseVector(residuals.Count); + var vecPdash = new DenseVector(residuals.Count); + var nu = new DenseVector(residuals.Count); + var vecS = new DenseVector(residuals.Count); + var vecSdash = new DenseVector(residuals.Count); + var temp = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + + // create some temporary float variables that are needed + // to hold values in between iterations + float currentRho = 0; + float alpha = 0; + float omega = 0; + + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, result, input, residuals) == IterationStatus.Continue) + { + // rho_(i-1) = r~^T r_(i-1) // dotproduct r~ and r_(i-1) + var oldRho = currentRho; + currentRho = tempResiduals.DotProduct(residuals); + + // if (rho_(i-1) == 0) // METHOD FAILS + // If rho is only 1 ULP from zero then we fail. + if (currentRho.AlmostEqualNumbersBetween(0, 1)) + { + // Rho-type breakdown + throw new NumericalBreakdownException(); + } + + if (iterationNumber != 0) + { + // beta_(i-1) = (rho_(i-1)/rho_(i-2))(alpha_(i-1)/omega(i-1)) + var beta = (currentRho / oldRho) * (alpha / omega); + + // p_i = r_(i-1) + beta_(i-1)(p_(i-1) - omega_(i-1) * nu_(i-1)) + nu.Multiply(-omega, temp); + vecP.Add(temp, temp2); + temp2.CopyTo(vecP); + + vecP.Multiply(beta, vecP); + vecP.Add(residuals, temp2); + temp2.CopyTo(vecP); + } + else + { + // p_i = r_(i-1) + residuals.CopyTo(vecP); + } + + // SOLVE Mp~ = p_i // M = preconditioner + preconditioner.Approximate(vecP, vecPdash); + + // nu_i = Ap~ + matrix.Multiply(vecPdash, nu); + + // alpha_i = rho_(i-1)/ (r~^T nu_i) = rho / dotproduct(r~ and nu_i) + alpha = currentRho * 1 / tempResiduals.DotProduct(nu); + + // s = r_(i-1) - alpha_i nu_i + nu.Multiply(-alpha, temp); + residuals.Add(temp, vecS); + + // Check if we're converged. If so then stop. Otherwise continue; + // Calculate the temporary result. + // Be careful not to change any of the temp vectors, except for + // temp. Others will be used in the calculation later on. + // x_i = x_(i-1) + alpha_i * p^_i + s^_i + vecPdash.Multiply(alpha, temp); + temp.Add(vecSdash, temp2); + temp2.CopyTo(temp); + temp.Add(result, temp2); + temp2.CopyTo(temp); + + // Check convergence and stop if we are converged. + if (iterator.DetermineStatus(iterationNumber, temp, input, vecS) != IterationStatus.Continue) + { + temp.CopyTo(result); + + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, result, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // We're all good now. + return; + } + + // Continue the calculation + iterationNumber++; + continue; + } + + // SOLVE Ms~ = s + preconditioner.Approximate(vecS, vecSdash); + + // temp = As~ + matrix.Multiply(vecSdash, temp); + + // omega_i = temp^T s / temp^T temp + omega = temp.DotProduct(vecS) / temp.DotProduct(temp); + + // x_i = x_(i-1) + alpha_i p^ + omega_i s^ + temp.Multiply(-omega, residuals); + residuals.Add(vecS, temp2); + temp2.CopyTo(residuals); + + vecSdash.Multiply(omega, temp); + result.Add(temp, temp2); + temp2.CopyTo(result); + + vecPdash.Multiply(alpha, temp); + result.Add(temp, temp2); + temp2.CopyTo(result); + + // for continuation it is necessary that omega_i != 0.0 + // If omega is only 1 ULP from zero then we fail. + if (omega.AlmostEqualNumbersBetween(0, 1)) + { + // Omega-type breakdown + throw new NumericalBreakdownException(); + } + + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + // The residual calculation based on omega_i * s can be off by a factor 10. So here + // we calculate the real residual (which can be expensive) but we only do it if we're + // sufficiently close to the finish. + CalculateTrueResidual(matrix, residuals, result, input); + } + + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/CompositeSolver.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/CompositeSolver.cs new file mode 100644 index 0000000..a6e98ba --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/CompositeSolver.cs @@ -0,0 +1,152 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// A composite matrix solver. The actual solver is made by a sequence of +/// matrix solvers. +/// +/// +/// +/// Solver based on:
+/// Faster PDE-based simulations using robust composite linear solvers
+/// S. Bhowmicka, P. Raghavan a,*, L. McInnes b, B. Norris
+/// Future Generation Computer Systems, Vol 20, 2004, pp 373387
+///
+/// +/// Note that if an iterator is passed to this solver it will be used for all the sub-solvers. +/// +///
+public sealed class CompositeSolver : IIterativeSolver +{ + /// + /// The collection of solvers that will be used + /// + readonly List, IPreconditioner>> _solvers; + + public CompositeSolver(IEnumerable> solvers) + { + _solvers = solvers.Select(setup => new Tuple, IPreconditioner>(setup.CreateSolver(), setup.CreatePreconditioner() ?? new UnitPreconditioner())).ToList(); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + // Create a copy of the solution and result vectors so we can use them + // later on + var internalInput = input.Clone(); + var internalResult = result.Clone(); + + foreach (var solver in _solvers) + { + // Store a reference to the solver so we can stop it. + + IterationStatus status; + try + { + // Reset the iterator and pass it to the solver + iterator.Reset(); + + // Start the solver + solver.Item1.Solve(matrix, internalInput, internalResult, iterator, solver.Item2 ?? preconditioner); + status = iterator.Status; + } + catch (Exception) + { + // The solver broke down. + // Log a message about this + // Switch to the next preconditioner. + // Reset the solution vector to the previous solution + input.CopyTo(internalInput); + continue; + } + + // There was no fatal breakdown so check the status + if (status == IterationStatus.Converged) + { + // We're done + internalResult.CopyTo(result); + break; + } + + // We're not done + // Either: + // - calculation finished without convergence + if (status == IterationStatus.StoppedWithoutConvergence) + { + // Copy the internal result to the result vector and + // continue with the calculation. + internalResult.CopyTo(result); + } + else + { + // - calculation failed --> restart with the original vector + // - calculation diverged --> restart with the original vector + // - Some unknown status occurred --> To be safe restart. + input.CopyTo(internalInput); + } + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/DiagonalPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/DiagonalPreconditioner.cs new file mode 100644 index 0000000..3304087 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/DiagonalPreconditioner.cs @@ -0,0 +1,106 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// A diagonal preconditioner. The preconditioner uses the inverse +/// of the matrix diagonal as preconditioning values. +/// +public sealed class DiagonalPreconditioner : IPreconditioner +{ + /// + /// The inverse of the matrix diagonal. + /// + float[] _inverseDiagonals; + + /// + /// Returns the decomposed matrix diagonal. + /// + /// The matrix diagonal. + internal DiagonalMatrix DiagonalEntries() + { + var result = new DiagonalMatrix(_inverseDiagonals.Length); + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + result[i, i] = 1 / _inverseDiagonals[i]; + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _inverseDiagonals = new float[matrix.RowCount]; + for (var i = 0; i < matrix.RowCount; i++) + { + _inverseDiagonals[i] = 1 / matrix[i, i]; + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_inverseDiagonals == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _inverseDiagonals.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rhs)); + } + + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + lhs[i] = rhs[i] * _inverseDiagonals[i]; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/GpBiCg.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/GpBiCg.cs new file mode 100644 index 0000000..06dbb82 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/GpBiCg.cs @@ -0,0 +1,377 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// A Generalized Product Bi-Conjugate Gradient iterative matrix solver. +/// +/// +/// +/// The Generalized Product Bi-Conjugate Gradient (GPBiCG) solver is an +/// alternative version of the Bi-Conjugate Gradient stabilized (CG) solver. +/// Unlike the CG solver the GPBiCG solver can be used on +/// non-symmetric matrices.
+/// Note that much of the success of the solver depends on the selection of the +/// proper preconditioner. +///
+/// +/// The GPBiCG algorithm was taken from:
+/// GPBiCG(m,l): A hybrid of BiCGSTAB and GPBiCG methods with +/// efficiency and robustness +///
+/// S. Fujino +///
+/// Applied Numerical Mathematics, Volume 41, 2002, pp 107 - 117 +///
+///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class GpBiCg : IIterativeSolver +{ + /// + /// Indicates the number of BiCGStab steps should be taken + /// before switching. + /// + int _numberOfBiCgStabSteps = 1; + + /// + /// Indicates the number of GPBiCG steps should be taken + /// before switching. + /// + int _numberOfGpbiCgSteps = 4; + + /// + /// Gets or sets the number of steps taken with the BiCgStab algorithm + /// before switching over to the GPBiCG algorithm. + /// + public int NumberOfBiCgStabSteps + { + get { return _numberOfBiCgStabSteps; } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfBiCgStabSteps = value; + } + } + + /// + /// Gets or sets the number of steps taken with the GPBiCG algorithm + /// before switching over to the BiCgStab algorithm. + /// + public int NumberOfGpBiCgSteps + { + get { return _numberOfGpbiCgSteps; } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfGpbiCgSteps = value; + } + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Decide if to do steps with BiCgStab + /// + /// Number of iteration + /// true if yes, otherwise false + bool ShouldRunBiCgStabSteps(int iterationNumber) + { + // Run the first steps as BiCGStab + // The number of steps past a whole iteration set + var difference = iterationNumber % (_numberOfBiCgStabSteps + _numberOfGpbiCgSteps); + + // Do steps with BiCGStab if: + // - The difference is zero or more (i.e. we have done zero or more complete cycles) + // - The difference is less than the number of BiCGStab steps that should be taken + return (difference >= 0) && (difference < _numberOfBiCgStabSteps); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // x_0 is initial guess + // Take x_0 = 0 + var xtemp = new DenseVector(input.Count); + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary scalars + float beta = 0; + + // Define the temporary vectors + // rDash_0 = r_0 + var rdash = DenseVector.OfVector(residuals); + + // t_-1 = 0 + var t = new DenseVector(residuals.Count); + var t0 = new DenseVector(residuals.Count); + + // w_-1 = 0 + var w = new DenseVector(residuals.Count); + + // Define the remaining temporary vectors + var c = new DenseVector(residuals.Count); + var p = new DenseVector(residuals.Count); + var s = new DenseVector(residuals.Count); + var u = new DenseVector(residuals.Count); + var y = new DenseVector(residuals.Count); + var z = new DenseVector(residuals.Count); + + var temp = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + var temp3 = new DenseVector(residuals.Count); + + // for (k = 0, 1, .... ) + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue) + { + // p_k = r_k + beta_(k-1) * (p_(k-1) - u_(k-1)) + p.Subtract(u, temp); + + temp.Multiply(beta, temp2); + residuals.Add(temp2, p); + + // Solve M b_k = p_k + preconditioner.Approximate(p, temp); + + // s_k = A b_k + matrix.Multiply(temp, s); + + // alpha_k = (r*_0 * r_k) / (r*_0 * s_k) + var alpha = rdash.DotProduct(residuals) / rdash.DotProduct(s); + + // y_k = t_(k-1) - r_k - alpha_k * w_(k-1) + alpha_k s_k + s.Subtract(w, temp); + t.Subtract(residuals, y); + + temp.Multiply(alpha, temp2); + y.Add(temp2, temp3); + temp3.CopyTo(y); + + // Store the old value of t in t0 + t.CopyTo(t0); + + // t_k = r_k - alpha_k s_k + s.Multiply(-alpha, temp2); + residuals.Add(temp2, t); + + // Solve M d_k = t_k + preconditioner.Approximate(t, temp); + + // c_k = A d_k + matrix.Multiply(temp, c); + var cdot = c.DotProduct(c); + + // cDot can only be zero if c is a zero vector + // We'll set cDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // c.DotProduct(t) will be zero and so will c.DotProduct(y) + if (cdot.AlmostEqualNumbersBetween(0, 1)) + { + cdot = 1.0f; + } + + // Even if we don't want to do any BiCGStab steps we'll still have + // to do at least one at the start to initialize the + // system, but we'll only have to take special measures + // if we don't do any so ... + var ctdot = c.DotProduct(t); + float eta; + float sigma; + if (((_numberOfBiCgStabSteps == 0) && (iterationNumber == 0)) || ShouldRunBiCgStabSteps(iterationNumber)) + { + // sigma_k = (c_k * t_k) / (c_k * c_k) + sigma = ctdot / cdot; + + // eta_k = 0 + eta = 0; + } + else + { + var ydot = y.DotProduct(y); + + // yDot can only be zero if y is a zero vector + // We'll set yDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // y.DotProduct(t) will be zero and so will c.DotProduct(y) + if (ydot.AlmostEqualNumbersBetween(0, 1)) + { + ydot = 1.0f; + } + + var ytdot = y.DotProduct(t); + var cydot = c.DotProduct(y); + + var denom = (cdot * ydot) - (cydot * cydot); + + // sigma_k = ((y_k * y_k)(c_k * t_k) - (y_k * t_k)(c_k * y_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + sigma = ((ydot * ctdot) - (ytdot * cydot)) / denom; + + // eta_k = ((c_k * c_k)(y_k * t_k) - (y_k * c_k)(c_k * t_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + eta = ((cdot * ytdot) - (cydot * ctdot)) / denom; + } + + // u_k = sigma_k s_k + eta_k (t_(k-1) - r_k + beta_(k-1) u_(k-1)) + u.Multiply(beta, temp2); + t0.Add(temp2, temp); + + temp.Subtract(residuals, temp3); + temp3.CopyTo(temp); + temp.Multiply(eta, temp); + + s.Multiply(sigma, temp2); + temp.Add(temp2, u); + + // z_k = sigma_k r_k +_ eta_k z_(k-1) - alpha_k u_k + z.Multiply(eta, z); + u.Multiply(-alpha, temp2); + z.Add(temp2, temp3); + temp3.CopyTo(z); + + residuals.Multiply(sigma, temp2); + z.Add(temp2, temp3); + temp3.CopyTo(z); + + // x_(k+1) = x_k + alpha_k p_k + z_k + p.Multiply(alpha, temp2); + xtemp.Add(temp2, temp3); + temp3.CopyTo(xtemp); + + xtemp.Add(z, temp3); + temp3.CopyTo(xtemp); + + // r_(k+1) = t_k - eta_k y_k - sigma_k c_k + // Copy the old residuals to a temp vector because we'll + // need those in the next step + residuals.CopyTo(t0); + + y.Multiply(-eta, temp2); + t.Add(temp2, residuals); + + c.Multiply(-sigma, temp2); + residuals.Add(temp2, temp3); + temp3.CopyTo(residuals); + + // beta_k = alpha_k / sigma_k * (r*_0 * r_(k+1)) / (r*_0 * r_k) + // But first we check if there is a possible NaN. If so just reset beta to zero. + beta = (!sigma.AlmostEqualNumbersBetween(0, 1)) ? alpha / sigma * rdash.DotProduct(residuals) / rdash.DotProduct(t0) : 0; + + // w_k = c_k + beta_k s_k + s.Multiply(beta, temp2); + c.Add(temp2, w); + + // Get the real value + preconditioner.Approximate(xtemp, result); + + // Now check for convergence + if (iterator.DetermineStatus(iterationNumber, result, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, result, input); + } + + // Next iteration. + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/ILU0Preconditioner.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/ILU0Preconditioner.cs new file mode 100644 index 0000000..b13c359 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/ILU0Preconditioner.cs @@ -0,0 +1,221 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// An incomplete, level 0, LU factorization preconditioner. +/// +/// +/// The ILU(0) algorithm was taken from:
+/// Iterative methods for sparse linear systems
+/// Yousef Saad
+/// Algorithm is described in Chapter 10, section 10.3.2, page 275
+///
+public sealed class ILU0Preconditioner : IPreconditioner +{ + /// + /// The matrix holding the lower (L) and upper (U) matrices. The + /// decomposition matrices are combined to reduce storage. + /// + SparseMatrix _decompositionLU; + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = i; j < _decompositionLU.ColumnCount; j++) + { + result[i, j] = _decompositionLU[i, j]; + } + } + + return result; + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = 0; j <= i; j++) + { + if (i == j) + { + result[i, j] = 1.0f; + } + else + { + result[i, j] = _decompositionLU[i, j]; + } + } + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _decompositionLU = SparseMatrix.OfMatrix(matrix); + + // M == A + // for i = 2, ... , n do + // for k = 1, .... , i - 1 do + // if (i,k) == NZ(Z) then + // compute z(i,k) = z(i,k) / z(k,k); + // for j = k + 1, ...., n do + // if (i,j) == NZ(Z) then + // compute z(i,j) = z(i,j) - z(i,k) * z(k,j) + // end + // end + // end + // end + // end + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var k = 0; k < i; k++) + { + if (_decompositionLU[i, k] != 0.0) + { + var t = _decompositionLU[i, k] / _decompositionLU[k, k]; + _decompositionLU[i, k] = t; + if (_decompositionLU[k, i] != 0.0) + { + _decompositionLU[i, i] = _decompositionLU[i, i] - (t * _decompositionLU[k, i]); + } + + for (var j = k + 1; j < _decompositionLU.RowCount; j++) + { + if (j == i) + { + continue; + } + + if (_decompositionLU[i, j] != 0.0) + { + _decompositionLU[i, j] = _decompositionLU[i, j] - (t * _decompositionLU[k, j]); + } + } + } + } + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_decompositionLU == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _decompositionLU.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Solve: + // Lz = y + // Which gives + // for (int i = 1; i < matrix.RowLength; i++) + // { + // z_i = l_ii^-1 * (y_i - SUM_(j -1; i--) + // { + // x_i = u_ii^-1 * (z_i - SUM_(j > i) u_ij * x_j) + // } + for (var i = _decompositionLU.RowCount - 1; i > -1; i--) + { + _decompositionLU.Row(i, rowValues); + + var sum = 0.0f; + for (var j = _decompositionLU.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/ILUTPPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/ILUTPPreconditioner.cs new file mode 100644 index 0000000..aff078c --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/ILUTPPreconditioner.cs @@ -0,0 +1,869 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// This class performs an Incomplete LU factorization with drop tolerance +/// and partial pivoting. The drop tolerance indicates which additional entries +/// will be dropped from the factorized LU matrices. +/// +/// +/// The ILUTP-Mem algorithm was taken from:
+/// ILUTP_Mem: a Space-Efficient Incomplete LU Preconditioner +///
+/// Tzu-Yi Chen, Department of Mathematics and Computer Science,
+/// Pomona College, Claremont CA 91711, USA
+/// Published in:
+/// Lecture Notes in Computer Science
+/// Volume 3046 / 2004
+/// pp. 20 - 28
+/// Algorithm is described in Section 2, page 22 +///
+public sealed class ILUTPPreconditioner : IPreconditioner +{ + /// + /// The default fill level. + /// + public const double DefaultFillLevel = 200.0; + + /// + /// The default drop tolerance. + /// + public const double DefaultDropTolerance = 0.0001; + + /// + /// The decomposed upper triangular matrix. + /// + SparseMatrix _upper; + + /// + /// The decomposed lower triangular matrix. + /// + SparseMatrix _lower; + + /// + /// The array containing the pivot values. + /// + int[] _pivots; + + /// + /// The fill level. + /// + double _fillLevel = DefaultFillLevel; + + /// + /// The drop tolerance. + /// + double _dropTolerance = DefaultDropTolerance; + + /// + /// The pivot tolerance. + /// + double _pivotTolerance; + + /// + /// Initializes a new instance of the class with the default settings. + /// + public ILUTPPreconditioner() + { + } + + /// + /// Initializes a new instance of the class with the specified settings. + /// + /// + /// The amount of fill that is allowed in the matrix. The value is a fraction of + /// the number of non-zero entries in the original matrix. Values should be positive. + /// + /// + /// The absolute drop tolerance which indicates below what absolute value an entry + /// will be dropped from the matrix. A drop tolerance of 0.0 means that no values + /// will be dropped. Values should always be positive. + /// + /// + /// The pivot tolerance which indicates at what level pivoting will take place. A + /// value of 0.0 means that no pivoting will take place. + /// + public ILUTPPreconditioner(double fillLevel, double dropTolerance, double pivotTolerance) + { + if (fillLevel < 0) + { + throw new ArgumentOutOfRangeException(nameof(fillLevel)); + } + + if (dropTolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(dropTolerance)); + } + + if (pivotTolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(pivotTolerance)); + } + + _fillLevel = fillLevel; + _dropTolerance = dropTolerance; + _pivotTolerance = pivotTolerance; + } + + /// + /// Gets or sets the amount of fill that is allowed in the matrix. The + /// value is a fraction of the number of non-zero entries in the original + /// matrix. The standard value is 200. + /// + /// + /// + /// Values should always be positive and can be higher than 1.0. A value lower + /// than 1.0 means that the eventual preconditioner matrix will have fewer + /// non-zero entries as the original matrix. A value higher than 1.0 means that + /// the eventual preconditioner can have more non-zero values than the original + /// matrix. + /// + /// + /// Note that any changes to the FillLevel after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double FillLevel + { + get { return _fillLevel; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _fillLevel = value; + } + } + + /// + /// Gets or sets the absolute drop tolerance which indicates below what absolute value + /// an entry will be dropped from the matrix. The standard value is 0.0001. + /// + /// + /// + /// The values should always be positive and can be larger than 1.0. A low value will + /// keep more small numbers in the preconditioner matrix. A high value will remove + /// more small numbers from the preconditioner matrix. + /// + /// + /// Note that any changes to the DropTolerance after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double DropTolerance + { + get { return _dropTolerance; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _dropTolerance = value; + } + } + + /// + /// Gets or sets the pivot tolerance which indicates at what level pivoting will + /// take place. The standard value is 0.0 which means pivoting will never take place. + /// + /// + /// + /// The pivot tolerance is used to calculate if pivoting is necessary. Pivoting + /// will take place if any of the values in a row is bigger than the + /// diagonal value of that row divided by the pivot tolerance, i.e. pivoting + /// will take place if row(i,j) > row(i,i) / PivotTolerance for + /// any j that is not equal to i. + /// + /// + /// Note that any changes to the PivotTolerance after creating the preconditioner + /// will invalidate the created preconditioner and will require a re-initialization of + /// the preconditioner. + /// + /// + /// Thrown if a negative value is provided. + public double PivotTolerance + { + get { return _pivotTolerance; } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _pivotTolerance = value; + } + } + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + return _upper.Clone(); + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + return _lower.Clone(); + } + + /// + /// Returns the pivot array. This array is not needed for normal use because + /// the preconditioner will return the solution vector values in the proper order. + /// + /// + /// This method is used for debugging purposes only and should normally not be used. + /// + /// The pivot array. + internal int[] Pivots() + { + var result = new int[_pivots.Length]; + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = _pivots[i]; + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. Note that the + /// method takes a general matrix type. However internally the data is stored + /// as a sparse matrix. Therefore it is not recommended to pass a dense matrix. + /// + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + SparseMatrix sparseMatrix = matrix as SparseMatrix ?? SparseMatrix.OfMatrix(matrix); + + // The creation of the preconditioner follows the following algorithm. + // spaceLeft = lfilNnz * nnz(A) + // for i = 1, .. , n + // { + // w = a(i,*) + // for j = 1, .. , i - 1 + // { + // if (w(j) != 0) + // { + // w(j) = w(j) / a(j,j) + // if (w(j) < dropTol) + // { + // w(j) = 0; + // } + // if (w(j) != 0) + // { + // w = w - w(j) * U(j,*) + // } + // } + // } + // + // for j = i, .. ,n + // { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + // } + // + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + // lfil = spaceRow / 2 // space for this row of L + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + // + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + // u(i,j) = w(j) for j = i, .. , n // only the largest lfil - 1 elements + // w = 0 + // + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + // } + // Create the lower triangular matrix + _lower = new SparseMatrix(sparseMatrix.RowCount); + + // Create the upper triangular matrix and copy the values + _upper = new SparseMatrix(sparseMatrix.RowCount); + + // Create the pivot array + _pivots = new int[sparseMatrix.RowCount]; + for (var i = 0; i < _pivots.Length; i++) + { + _pivots[i] = i; + } + + var workVector = new DenseVector(sparseMatrix.RowCount); + var rowVector = new DenseVector(sparseMatrix.ColumnCount); + var indexSorting = new int[sparseMatrix.RowCount]; + + // spaceLeft = lfilNnz * nnz(A) + var spaceLeft = (int)_fillLevel * sparseMatrix.NonZerosCount; + + // for i = 1, .. , n + for (var i = 0; i < sparseMatrix.RowCount; i++) + { + // w = a(i,*) + sparseMatrix.Row(i, workVector); + + // pivot the row + PivotRow(workVector); + var vectorNorm = workVector.InfinityNorm(); + + // for j = 1, .. , i - 1) + for (var j = 0; j < i; j++) + { + // if (w(j) != 0) + // { + // w(j) = w(j) / a(j,j) + // if (w(j) < dropTol) + // { + // w(j) = 0; + // } + // if (w(j) != 0) + // { + // w = w - w(j) * U(j,*) + // } + if (workVector[j] != 0.0) + { + // Calculate the multiplication factors that go into the L matrix + workVector[j] = workVector[j] / _upper[j, j]; + if (Math.Abs(workVector[j]) < _dropTolerance) + { + workVector[j] = 0.0f; + } + + // Calculate the addition factor + if (workVector[j] != 0.0) + { + // vector update all in one go + _upper.Row(j, rowVector); + + // zero out columnVector[k] because we don't need that + // one anymore for k = 0 to k = j + for (var k = 0; k <= j; k++) + { + rowVector[k] = 0.0f; + } + + rowVector.Multiply(workVector[j], rowVector); + workVector.Subtract(rowVector, workVector); + } + } + } + + // for j = i, .. ,n + for (var j = i; j < sparseMatrix.RowCount; j++) + { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + if (Math.Abs(workVector[j]) <= _dropTolerance * vectorNorm) + { + workVector[j] = 0.0f; + } + } + + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + var spaceRow = spaceLeft / (sparseMatrix.RowCount - i + 1); + + // lfil = spaceRow / 2 // space for this row of L + var fillLevel = spaceRow / 2; + FindLargestItems(0, i - 1, indexSorting, workVector); + + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + var lowerNonZeroCount = 0; + var count = 0; + for (var j = 0; j < i; j++) + { + if ((count > fillLevel) || (indexSorting[j] == -1)) + { + break; + } + + _lower[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + lowerNonZeroCount += 1; + } + + FindLargestItems(i + 1, sparseMatrix.RowCount - 1, indexSorting, workVector); + + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + fillLevel = spaceRow - lowerNonZeroCount; + + // u(i,j) = w(j) for j = i + 1, .. , n // only the largest lfil - 1 elements + var upperNonZeroCount = 0; + count = 0; + for (var j = 0; j < sparseMatrix.RowCount - i; j++) + { + if ((count > fillLevel - 1) || (indexSorting[j] == -1)) + { + break; + } + + _upper[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + upperNonZeroCount += 1; + } + + // Simply copy the diagonal element. Next step is to see if we pivot + _upper[i, i] = workVector[i]; + + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + + // Check if we really need to pivot. If (i+1) >=(mCoefficientMatrix.Rows -1) then + // we are working on the last row. That means that there is only one number + // And pivoting is useless. Also the indexSorting array will only contain + // -1 values. + if ((i + 1) < (sparseMatrix.RowCount - 1)) + { + if (Math.Abs(workVector[i]) < _pivotTolerance * Math.Abs(workVector[indexSorting[0]])) + { + // swap columns of u (which holds the values of A in the + // sections that haven't been partitioned yet. + SwapColumns(_upper, i, indexSorting[0]); + + // Update P + var temp = _pivots[i]; + _pivots[i] = _pivots[indexSorting[0]]; + _pivots[indexSorting[0]] = temp; + } + } + + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + spaceLeft -= lowerNonZeroCount + upperNonZeroCount; + } + + for (var i = 0; i < _lower.RowCount; i++) + { + _lower[i, i] = 1.0f; + } + } + + /// + /// Pivot elements in the according to internal pivot array + /// + /// Row to pivot in + void PivotRow(Vector row) + { + var knownPivots = new Dictionary(); + + // pivot the row + for (var i = 0; i < row.Count; i++) + { + if ((_pivots[i] != i) && (!PivotMapFound(knownPivots, i))) + { + // store the pivots in the hashtable + knownPivots.Add(_pivots[i], i); + + var t = row[i]; + row[i] = row[_pivots[i]]; + row[_pivots[i]] = t; + } + } + } + + /// + /// Was pivoting already performed + /// + /// Pivots already done + /// Current item to pivot + /// true if performed, otherwise false + bool PivotMapFound(Dictionary knownPivots, int currentItem) + { + if (knownPivots.ContainsKey(_pivots[currentItem])) + { + if (knownPivots[_pivots[currentItem]].Equals(currentItem)) + { + return true; + } + } + + if (knownPivots.ContainsKey(currentItem)) + { + if (knownPivots[currentItem].Equals(_pivots[currentItem])) + { + return true; + } + } + + return false; + } + + /// + /// Swap columns in the + /// + /// Source . + /// First column index to swap + /// Second column index to swap + static void SwapColumns(Matrix matrix, int firstColumn, int secondColumn) + { + for (var i = 0; i < matrix.RowCount; i++) + { + var temp = matrix[i, firstColumn]; + matrix[i, firstColumn] = matrix[i, secondColumn]; + matrix[i, secondColumn] = temp; + } + } + + /// + /// Sort vector descending, not changing vector but placing sorted indices to + /// + /// Start sort form + /// Sort till upper bound + /// Array with sorted vector indices + /// Source + static void FindLargestItems(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + // Copy the indices for the values into the array + for (var i = 0; i < upperBound + 1 - lowerBound; i++) + { + sortedIndices[i] = lowerBound + i; + } + + for (var i = upperBound + 1 - lowerBound; i < sortedIndices.Length; i++) + { + sortedIndices[i] = -1; + } + + // Sort the first set of items. + // Sorting starts at index 0 because the index array + // starts at zero + // and ends at index upperBound - lowerBound + ILUTPElementSorter.SortDoubleIndicesDecreasing(0, upperBound - lowerBound, sortedIndices, values); + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + public void Approximate(Vector rhs, Vector lhs) + { + if (_upper == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _upper.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(rhs)); + } + + // Solve equation here + // Pivot(vector, result); + // Solve L*Y = B(piv,:) + var rowValues = new DenseVector(_lower.RowCount); + for (var i = 0; i < _lower.RowCount; i++) + { + _lower.Row(i, rowValues); + + var sum = 0.0f; + for (var j = 0; j < i; j++) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = rhs[i] - sum; + } + + // Solve U*X = Y; + for (var i = _upper.RowCount - 1; i > -1; i--) + { + _upper.Row(i, rowValues); + + var sum = 0.0f; + for (var j = _upper.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + + // We have a column pivot so we only need to pivot the + // end result not the incoming right hand side vector + var temp = lhs.Clone(); + + Pivot(temp, lhs); + } + + /// + /// Pivot elements in according to internal pivot array + /// + /// Source . + /// Result after pivoting. + void Pivot(Vector vector, Vector result) + { + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = vector[_pivots[i]]; + } + } +} + +/// +/// An element sort algorithm for the class. +/// +/// +/// This sort algorithm is used to sort the columns in a sparse matrix based on +/// the value of the element on the diagonal of the matrix. +/// +internal static class ILUTPElementSorter +{ + /// + /// Sorts the elements of the vector in decreasing + /// fashion. The vector itself is not affected. + /// + /// The starting index. + /// The stopping index. + /// An array that will contain the sorted indices once the algorithm finishes. + /// The that contains the values that need to be sorted. + public static void SortDoubleIndicesDecreasing(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + // Move all the indices that we're interested in to the beginning of the + // array. Ignore the rest of the indices. + if (lowerBound > 0) + { + for (var i = 0; i < (upperBound - lowerBound + 1); i++) + { + Exchange(sortedIndices, i, i + lowerBound); + } + + upperBound -= lowerBound; + lowerBound = 0; + } + + HeapSortDoublesIndices(lowerBound, upperBound, sortedIndices, values); + } + + /// + /// Sorts the elements of the vector in decreasing + /// fashion using heap sort algorithm. The vector itself is not affected. + /// + /// The starting index. + /// The stopping index. + /// An array that will contain the sorted indices once the algorithm finishes. + /// The that contains the values that need to be sorted. + private static void HeapSortDoublesIndices(int lowerBound, int upperBound, int[] sortedIndices, Vector values) + { + var start = ((upperBound - lowerBound + 1) / 2) - 1 + lowerBound; + var end = (upperBound - lowerBound + 1) - 1 + lowerBound; + + BuildDoubleIndexHeap(start, upperBound - lowerBound + 1, sortedIndices, values); + + while (end >= lowerBound) + { + Exchange(sortedIndices, end, lowerBound); + SiftDoubleIndices(sortedIndices, values, lowerBound, end); + end -= 1; + } + } + + /// + /// Build heap for double indices + /// + /// Root position + /// Length of + /// Indices of + /// Target + private static void BuildDoubleIndexHeap(int start, int count, int[] sortedIndices, Vector values) + { + while (start >= 0) + { + SiftDoubleIndices(sortedIndices, values, start, count); + start -= 1; + } + } + + /// + /// Sift double indices + /// + /// Indices of + /// Target + /// Root position + /// Length of + private static void SiftDoubleIndices(int[] sortedIndices, Vector values, int begin, int count) + { + var root = begin; + + while (root * 2 < count) + { + var child = root * 2; + if ((child < count - 1) && (values[sortedIndices[child]] > values[sortedIndices[child + 1]])) + { + child += 1; + } + + if (values[sortedIndices[root]] <= values[sortedIndices[child]]) + { + return; + } + + Exchange(sortedIndices, root, child); + root = child; + } + } + + /// + /// Sorts the given integers in a decreasing fashion. + /// + /// The values. + public static void SortIntegersDecreasing(int[] values) + { + HeapSortIntegers(values, values.Length); + } + + /// + /// Sort the given integers in a decreasing fashion using heapsort algorithm + /// + /// Array of values to sort + /// Length of + private static void HeapSortIntegers(int[] values, int count) + { + var start = (count / 2) - 1; + var end = count - 1; + + BuildHeap(values, start, count); + + while (end >= 0) + { + Exchange(values, end, 0); + Sift(values, 0, end); + end -= 1; + } + } + + /// + /// Build heap + /// + /// Target values array + /// Root position + /// Length of + private static void BuildHeap(int[] values, int start, int count) + { + while (start >= 0) + { + Sift(values, start, count); + start -= 1; + } + } + + /// + /// Sift values + /// + /// Target value array + /// Root position + /// Length of + private static void Sift(int[] values, int start, int count) + { + var root = start; + + while (root * 2 < count) + { + var child = root * 2; + if ((child < count - 1) && (values[child] > values[child + 1])) + { + child += 1; + } + + if (values[root] > values[child]) + { + Exchange(values, root, child); + root = child; + } + else + { + return; + } + } + } + + /// + /// Exchange values in array + /// + /// Target values array + /// First value to exchange + /// Second value to exchange + private static void Exchange(int[] values, int first, int second) + { + var t = values[first]; + values[first] = values[second]; + values[second] = t; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/MILU0Preconditioner.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/MILU0Preconditioner.cs new file mode 100644 index 0000000..b6607b9 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/MILU0Preconditioner.cs @@ -0,0 +1,261 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// A simple milu(0) preconditioner. +/// +/// +/// Original Fortran code by Yousef Saad (07 January 2004) +/// +public sealed class MILU0Preconditioner : IPreconditioner +{ + // Matrix stored in Modified Sparse Row (MSR) format containing the L and U + // factors together. + + // The diagonal (stored in alu(0:n-1) ) is inverted. Each i-th row of the matrix + // contains the i-th row of L (excluding the diagonal entry = 1) followed by + // the i-th row of U. + private float[] _alu; + + // The row pointers (stored in jlu(0:n) ) and column indices to off-diagonal elements. + private int[] _jlu; + + // Pointer to the diagonal elements in MSR storage (for faster LU solving). + private int[] _diag; + + /// Use modified or standard ILU(0) + public MILU0Preconditioner(bool modified = true) + { + UseModified = modified; + } + + /// + /// Gets or sets a value indicating whether to use modified or standard ILU(0). + /// + public bool UseModified { get; set; } + + /// + /// Gets a value indicating whether the preconditioner is initialized. + /// + public bool IsInitialized { get; private set; } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square or is not an + /// instance of SparseCompressedRowMatrixStorage. + public void Initialize(Matrix matrix) + { + var csr = matrix.Storage as SparseCompressedRowMatrixStorage; + if (csr == null) + { + throw new ArgumentException(Resources.MatrixMustBeSparse, nameof(matrix)); + } + + // Dimension of matrix + int n = csr.RowCount; + if (n != csr.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + // Original matrix compressed sparse row storage. + float[] a = csr.Values; + int[] ja = csr.ColumnIndices; + int[] ia = csr.RowPointers; + + _alu = new float[ia[n] + 1]; + _jlu = new int[ia[n] + 1]; + _diag = new int[n]; + + int code = Compute(n, a, ja, ia, _alu, _jlu, _diag, UseModified); + if (code > -1) + { + throw new NumericalBreakdownException("Zero pivot encountered on row " + code + " during ILU process"); + } + + IsInitialized = true; + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector b. + /// The left hand side vector x. + public void Approximate(Vector input, Vector result) + { + if (_alu == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((result.Count != input.Count) || (result.Count != _diag.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + int n = _diag.Length; + + // Forward solve. + for (int i = 0; i < n; i++) + { + result[i] = input[i]; + for (int k = _jlu[i]; k < _diag[i]; k++) + { + result[i] = result[i] - _alu[k] * result[_jlu[k]]; + } + } + + // Backward solve. + for (int i = n - 1; i >= 0; i--) + { + for (int k = _diag[i]; k < _jlu[i + 1]; k++) + { + result[i] = result[i] - _alu[k] * result[_jlu[k]]; + } + + result[i] = _alu[i] * result[i]; + } + } + + /// + /// MILU0 is a simple milu(0) preconditioner. + /// + /// Order of the matrix. + /// Matrix values in CSR format (input). + /// Column indices (input). + /// Row pointers (input). + /// Matrix values in MSR format (output). + /// Row pointers and column indices (output). + /// Pointer to diagonal elements (output). + /// True if the modified/MILU algorithm should be used (recommended) + /// Returns 0 on success or k > 0 if a zero pivot was encountered at step k. + private int Compute(int n, float[] a, int[] ja, int[] ia, float[] alu, int[] jlu, int[] ju, bool modified) + { + var iw = new int[n]; + int i; + + // Set initial pointer value. + int p = n + 1; + jlu[0] = p; + + // Initialize work vector. + for (i = 0; i < n; i++) + { + iw[i] = -1; + } + + // The main loop. + for (i = 0; i < n; i++) + { + int pold = p; + + // Generating row i of L and U. + int j; + for (j = ia[i]; j < ia[i + 1]; j++) + { + // Copy row i of A, JA, IA into row i of ALU, JLU (LU matrix). + int jcol = ja[j]; + + if (jcol == i) + { + alu[i] = a[j]; + iw[jcol] = i; + ju[i] = p; + } + else + { + alu[p] = a[j]; + jlu[p] = ja[j]; + iw[jcol] = p; + p = p + 1; + } + } + + jlu[i + 1] = p; + + float s = 0.0f; + + int k; + for (j = pold; j < ju[i]; j++) + { + int jrow = jlu[j]; + float tl = alu[j] * alu[jrow]; + alu[j] = tl; + + // Perform linear combination. + for (k = ju[jrow]; k < jlu[jrow + 1]; k++) + { + int jw = iw[jlu[k]]; + if (jw != -1) + { + alu[jw] = alu[jw] - tl * alu[k]; + } + else + { + // Accumulate fill-in values. + s = s + tl * alu[k]; + } + } + } + + if (modified) + { + alu[i] = alu[i] - s; + } + + if (alu[i] == 0.0f) + { + return i; + } + + // Invert and store diagonal element. + alu[i] = 1.0f / alu[i]; + + // Reset pointers in work array. + iw[i] = -1; + for (k = pold; k < p; k++) + { + iw[jlu[k]] = -1; + } + } + + return -1; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/MlkBiCgStab.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/MlkBiCgStab.cs new file mode 100644 index 0000000..e409707 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/MlkBiCgStab.cs @@ -0,0 +1,545 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// A Multiple-Lanczos Bi-Conjugate Gradient stabilized iterative matrix solver. +/// +/// +/// +/// The Multiple-Lanczos Bi-Conjugate Gradient stabilized (ML(k)-BiCGStab) solver is an 'improvement' +/// of the standard BiCgStab solver. +/// +/// +/// The algorithm was taken from:
+/// ML(k)BiCGSTAB: A BiCGSTAB variant based on multiple Lanczos starting vectors +///
+/// Man-Chung Yeung and Tony F. Chan +///
+/// SIAM Journal of Scientific Computing +///
+/// Volume 21, Number 4, pp. 1263 - 1290 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class MlkBiCgStab : IIterativeSolver +{ + /// + /// The default number of starting vectors. + /// + const int DefaultNumberOfStartingVectors = 50; + + /// + /// The collection of starting vectors which are used as the basis for the Krylov sub-space. + /// + IList> _startingVectors; + + /// + /// The number of starting vectors used by the algorithm + /// + int _numberOfStartingVectors = DefaultNumberOfStartingVectors; + + /// + /// Gets or sets the number of starting vectors. + /// + /// + /// Must be larger than 1 and smaller than the number of variables in the matrix that + /// for which this solver will be used. + /// + public int NumberOfStartingVectors + { + [DebuggerStepThrough] + get + { + return _numberOfStartingVectors; + } + + [DebuggerStepThrough] + set + { + if (value <= 1) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _numberOfStartingVectors = value; + } + } + + /// + /// Resets the number of starting vectors to the default value. + /// + public void ResetNumberOfStartingVectors() + { + _numberOfStartingVectors = DefaultNumberOfStartingVectors; + } + + /// + /// Gets or sets a series of orthonormal vectors which will be used as basis for the + /// Krylov sub-space. + /// + public IList> StartingVectors + { + [DebuggerStepThrough] + get + { + return _startingVectors; + } + + [DebuggerStepThrough] + set + { + if ((value == null) || (value.Count == 0)) + { + _startingVectors = null; + } + else + { + _startingVectors = value; + } + } + } + + /// + /// Gets the number of starting vectors to create + /// + /// Maximum number + /// Number of variables + /// Number of starting vectors to create + static int NumberOfStartingVectorsToCreate(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + return Math.Min(maximumNumberOfStartingVectors, (numberOfVariables - 1)); + } + + /// + /// Returns an array of starting vectors. + /// + /// The maximum number of starting vectors that should be created. + /// The number of variables. + /// + /// An array with starting vectors. The array will never be larger than the + /// but it may be smaller if + /// the is smaller than + /// the . + /// + static IList> CreateStartingVectors(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + // Get random values and then orthogonalize them with + // modified Gramm - Schmidt + var count = NumberOfStartingVectorsToCreate(maximumNumberOfStartingVectors, numberOfVariables); + + // Get a random set of samples based on the standard normal distribution with + // mean = 0 and sd = 1 + var distribution = new Normal(); + + var matrix = new DenseMatrix(numberOfVariables, count); + for (var i = 0; i < matrix.ColumnCount; i++) + { + var samples = new float[matrix.RowCount]; + for (var j = 0; j < matrix.RowCount; j++) + { + samples[j] = (float)distribution.Sample(); + } + + // Set the column + matrix.SetColumn(i, samples); + } + + // Compute the orthogonalization. + var gs = matrix.GramSchmidt(); + var orthogonalMatrix = gs.Q; + + // Now transfer this to vectors + var result = new List>(orthogonalMatrix.ColumnCount); + for (var i = 0; i < orthogonalMatrix.ColumnCount; i++) + { + result.Add(orthogonalMatrix.Column(i)); + + // Normalize the result vector + result[i].Multiply(1 / (float)result[i].L2Norm(), result[i]); + } + + return result; + } + + /// + /// Create random vectors array + /// + /// Number of vectors + /// Size of each vector + /// Array of random vectors + static Vector[] CreateVectorArray(int arraySize, int vectorSize) + { + var result = new Vector[arraySize]; + for (var i = 0; i < result.Length; i++) + { + result[i] = new DenseVector(vectorSize); + } + + return result; + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Source A. + /// Residual data. + /// x data. + /// b data. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + // Choose an initial guess x_0 + // Take x_0 = 0 + var xtemp = new DenseVector(input.Count); + + // Choose k vectors q_1, q_2, ..., q_k + // Build a new set if: + // a) the stored set doesn't exist (i.e. == null) + // b) Is of an incorrect length (i.e. too long) + // c) The vectors are of an incorrect length (i.e. too long or too short) + var useOld = false; + if (_startingVectors != null) + { + // We don't accept collections with zero starting vectors so ... + if (_startingVectors.Count <= NumberOfStartingVectorsToCreate(_numberOfStartingVectors, input.Count)) + { + // Only check the first vector for sizing. If that matches we assume the + // other vectors match too. If they don't the process will crash + if (_startingVectors[0].Count == input.Count) + { + useOld = true; + } + } + } + + _startingVectors = useOld ? _startingVectors : CreateStartingVectors(_numberOfStartingVectors, input.Count); + + // Store the number of starting vectors. Not really necessary but easier to type :) + var k = _startingVectors.Count; + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + var residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary scalars + var c = new float[k]; + + // Define the temporary vectors + var gtemp = new DenseVector(residuals.Count); + + var u = new DenseVector(residuals.Count); + var utemp = new DenseVector(residuals.Count); + var temp = new DenseVector(residuals.Count); + var temp1 = new DenseVector(residuals.Count); + var temp2 = new DenseVector(residuals.Count); + + var zd = new DenseVector(residuals.Count); + var zg = new DenseVector(residuals.Count); + var zw = new DenseVector(residuals.Count); + + var d = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // g_0 = r_0 + var g = CreateVectorArray(_startingVectors.Count, residuals.Count); + residuals.CopyTo(g[k - 1]); + + var w = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // FOR (j = 0, 1, 2 ....) + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue) + { + // SOLVE M g~_((j-1)k+k) = g_((j-1)k+k) + preconditioner.Approximate(g[k - 1], gtemp); + + // w_((j-1)k+k) = A g~_((j-1)k+k) + matrix.Multiply(gtemp, w[k - 1]); + + // c_((j-1)k+k) = q^T_1 w_((j-1)k+k) + c[k - 1] = _startingVectors[0].DotProduct(w[k - 1]); + if (c[k - 1].AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // alpha_(jk+1) = q^T_1 r_((j-1)k+k) / c_((j-1)k+k) + var alpha = _startingVectors[0].DotProduct(residuals) / c[k - 1]; + + // u_(jk+1) = r_((j-1)k+k) - alpha_(jk+1) w_((j-1)k+k) + w[k - 1].Multiply(-alpha, temp); + residuals.Add(temp, u); + + // SOLVE M u~_(jk+1) = u_(jk+1) + preconditioner.Approximate(u, temp1); + temp1.CopyTo(utemp); + + // rho_(j+1) = -u^t_(jk+1) A u~_(jk+1) / ||A u~_(jk+1)||^2 + matrix.Multiply(temp1, temp); + var rho = temp.DotProduct(temp); + + // If rho is zero then temp is a zero vector and we're probably + // about to have zero residuals (i.e. an exact solution). + // So set rho to 1.0 because in the next step it will turn to zero. + if (rho.AlmostEqualNumbersBetween(0, 1)) + { + rho = 1.0f; + } + + rho = -u.DotProduct(temp) / rho; + + // r_(jk+1) = rho_(j+1) A u~_(jk+1) + u_(jk+1) + u.CopyTo(residuals); + + // Reuse temp + temp.Multiply(rho, temp); + residuals.Add(temp, temp2); + temp2.CopyTo(residuals); + + // x_(jk+1) = x_((j-1)k_k) - rho_(j+1) u~_(jk+1) + alpha_(jk+1) g~_((j-1)k+k) + utemp.Multiply(-rho, temp); + xtemp.Add(temp, temp2); + temp2.CopyTo(xtemp); + + gtemp.Multiply(alpha, gtemp); + xtemp.Add(gtemp, temp2); + temp2.CopyTo(xtemp); + + // Check convergence and stop if we are converged. + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // We're all good now. + // Exit from the while loop. + break; + } + } + + // FOR (i = 1,2, ...., k) + for (var i = 0; i < k; i++) + { + // z_d = u_(jk+1) + u.CopyTo(zd); + + // z_g = r_(jk+i) + residuals.CopyTo(zg); + + // z_w = 0 + zw.Clear(); + + // FOR (s = i, ...., k-1) AND j >= 1 + float beta; + if (iterationNumber >= 1) + { + for (var s = i; s < k - 1; s++) + { + // beta^(jk+i)_((j-1)k+s) = -q^t_(s+1) z_d / c_((j-1)k+s) + beta = -_startingVectors[s + 1].DotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_((j-1)k+s) d_((j-1)k+s) + d[s].Multiply(beta, temp); + zd.Add(temp, temp2); + temp2.CopyTo(zd); + + // z_g = z_g + beta^(jk+i)_((j-1)k+s) g_((j-1)k+s) + g[s].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + + // z_w = z_w + beta^(jk+i)_((j-1)k+s) w_((j-1)k+s) + w[s].Multiply(beta, temp); + zw.Add(temp, temp2); + temp2.CopyTo(zw); + } + } + + beta = rho * c[k - 1]; + if (beta.AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // beta^(jk+i)_((j-1)k+k) = -(q^T_1 (r_(jk+1) + rho_(j+1) z_w)) / (rho_(j+1) c_((j-1)k+k)) + zw.Multiply(rho, temp2); + residuals.Add(temp2, temp); + beta = -_startingVectors[0].DotProduct(temp) / beta; + + // z_g = z_g + beta^(jk+i)_((j-1)k+k) g_((j-1)k+k) + g[k - 1].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + + // z_w = rho_(j+1) (z_w + beta^(jk+i)_((j-1)k+k) w_((j-1)k+k)) + w[k - 1].Multiply(beta, temp); + zw.Add(temp, temp2); + temp2.CopyTo(zw); + zw.Multiply(rho, zw); + + // z_d = r_(jk+i) + z_w + residuals.Add(zw, zd); + + // FOR (s = 1, ... i - 1) + for (var s = 0; s < i - 1; s++) + { + // beta^(jk+i)_(jk+s) = -q^T_s+1 z_d / c_(jk+s) + beta = -_startingVectors[s + 1].DotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_(jk+s) * d_(jk+s) + d[s].Multiply(beta, temp); + zd.Add(temp, temp2); + temp2.CopyTo(zd); + + // z_g = z_g + beta^(jk+i)_(jk+s) * g_(jk+s) + g[s].Multiply(beta, temp); + zg.Add(temp, temp2); + temp2.CopyTo(zg); + } + + // d_(jk+i) = z_d - u_(jk+i) + zd.Subtract(u, d[i]); + + // g_(jk+i) = z_g + z_w + zg.Add(zw, g[i]); + + // IF (i < k - 1) + if (i < k - 1) + { + // c_(jk+1) = q^T_i+1 d_(jk+i) + c[i] = _startingVectors[i + 1].DotProduct(d[i]); + if (c[i].AlmostEqualNumbersBetween(0, 1)) + { + throw new NumericalBreakdownException(); + } + + // alpha_(jk+i+1) = q^T_(i+1) u_(jk+i) / c_(jk+i) + alpha = _startingVectors[i + 1].DotProduct(u) / c[i]; + + // u_(jk+i+1) = u_(jk+i) - alpha_(jk+i+1) d_(jk+i) + d[i].Multiply(-alpha, temp); + u.Add(temp, temp2); + temp2.CopyTo(u); + + // SOLVE M g~_(jk+i) = g_(jk+i) + preconditioner.Approximate(g[i], gtemp); + + // x_(jk+i+1) = x_(jk+i) + rho_(j+1) alpha_(jk+i+1) g~_(jk+i) + gtemp.Multiply(rho * alpha, temp); + xtemp.Add(temp, temp2); + temp2.CopyTo(xtemp); + + // w_(jk+i) = A g~_(jk+i) + matrix.Multiply(gtemp, w[i]); + + // r_(jk+i+1) = r_(jk+i) - rho_(j+1) alpha_(jk+i+1) w_(jk+i) + w[i].Multiply(-rho * alpha, temp); + residuals.Add(temp, temp2); + temp2.CopyTo(residuals); + + // We can check the residuals here if they're close + if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, xtemp, input); + } + } + } // END ITERATION OVER i + + iterationNumber++; + } + + // copy the temporary result to the real result vector + xtemp.CopyTo(result); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Solvers/TFQMR.cs b/MathNet.Numerics/LinearAlgebra/Single/Solvers/TFQMR.cs new file mode 100644 index 0000000..81f2486 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Solvers/TFQMR.cs @@ -0,0 +1,277 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Solvers; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single.Solvers; + +/// +/// A Transpose Free Quasi-Minimal Residual (TFQMR) iterative matrix solver. +/// +/// +/// +/// The TFQMR algorithm was taken from:
+/// Iterative methods for sparse linear systems. +///
+/// Yousef Saad +///
+/// Algorithm is described in Chapter 7, section 7.4.3, page 219 +///
+/// +/// The example code below provides an indication of the possible use of the +/// solver. +/// +///
+public sealed class TFQMR : IIterativeSolver +{ + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1, residual); + + // residual + b + residual.Add(b, residual); + } + + /// + /// Is even? + /// + /// Number to check + /// true if even, otherwise false + static bool IsEven(int number) + { + return number % 2 == 0; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != matrix.RowCount) + { + throw Matrix.DimensionsDontMatch(input, matrix); + } + + if (iterator == null) + { + iterator = new Iterator(); + } + + if (preconditioner == null) + { + preconditioner = new UnitPreconditioner(); + } + + preconditioner.Initialize(matrix); + + var d = new DenseVector(input.Count); + var r = DenseVector.OfVector(input); + + var uodd = new DenseVector(input.Count); + var ueven = new DenseVector(input.Count); + + var v = new DenseVector(input.Count); + var pseudoResiduals = DenseVector.OfVector(input); + + var x = new DenseVector(input.Count); + var yodd = new DenseVector(input.Count); + var yeven = DenseVector.OfVector(input); + + // Temp vectors + var temp = new DenseVector(input.Count); + var temp1 = new DenseVector(input.Count); + var temp2 = new DenseVector(input.Count); + + // Define the scalars + float alpha = 0; + float eta = 0; + float theta = 0; + + // Initialize + var tau = (float)input.L2Norm(); + var rho = tau * tau; + + // Calculate the initial values for v + // M temp = yEven + preconditioner.Approximate(yeven, temp); + + // v = A temp + matrix.Multiply(temp, v); + + // Set uOdd + v.CopyTo(ueven); + + // Start the iteration + var iterationNumber = 0; + while (iterator.DetermineStatus(iterationNumber, result, input, pseudoResiduals) == IterationStatus.Continue) + { + // First part of the step, the even bit + if (IsEven(iterationNumber)) + { + // sigma = (v, r) + var sigma = v.DotProduct(r); + if (sigma.AlmostEqualNumbersBetween(0, 1)) + { + // FAIL HERE + iterator.Cancel(); + break; + } + + // alpha = rho / sigma + alpha = rho / sigma; + + // yOdd = yEven - alpha * v + v.Multiply(-alpha, temp1); + yeven.Add(temp1, yodd); + + // Solve M temp = yOdd + preconditioner.Approximate(yodd, temp); + + // uOdd = A temp + matrix.Multiply(temp, uodd); + } + + // The intermediate step which is equal for both even and + // odd iteration steps. + // Select the correct vector + var uinternal = IsEven(iterationNumber) ? ueven : uodd; + var yinternal = IsEven(iterationNumber) ? yeven : yodd; + + // pseudoResiduals = pseudoResiduals - alpha * uOdd + uinternal.Multiply(-alpha, temp1); + pseudoResiduals.Add(temp1, temp2); + temp2.CopyTo(pseudoResiduals); + + // d = yOdd + theta * theta * eta / alpha * d + d.Multiply(theta * theta * eta / alpha, temp); + yinternal.Add(temp, d); + + // theta = ||pseudoResiduals||_2 / tau + theta = (float)pseudoResiduals.L2Norm() / tau; + var c = 1 / (float)Math.Sqrt(1 + (theta * theta)); + + // tau = tau * theta * c + tau *= theta * c; + + // eta = c^2 * alpha + eta = c * c * alpha; + + // x = x + eta * d + d.Multiply(eta, temp1); + x.Add(temp1, temp2); + temp2.CopyTo(x); + + // Check convergence and see if we can bail + if (iterator.DetermineStatus(iterationNumber, result, input, pseudoResiduals) != IterationStatus.Continue) + { + // Calculate the real values + preconditioner.Approximate(x, result); + + // Calculate the true residual. Use the temp vector for that + // so that we don't pollute the pseudoResidual vector for no + // good reason. + CalculateTrueResidual(matrix, temp, result, input); + + // Now recheck the convergence + if (iterator.DetermineStatus(iterationNumber, result, input, temp) != IterationStatus.Continue) + { + // We're all good now. + return; + } + } + + // The odd step + if (!IsEven(iterationNumber)) + { + if (rho.AlmostEqualNumbersBetween(0, 1)) + { + // FAIL HERE + iterator.Cancel(); + break; + } + + var rhoNew = pseudoResiduals.DotProduct(r); + var beta = rhoNew / rho; + + // Update rho for the next loop + rho = rhoNew; + + // yOdd = pseudoResiduals + beta * yOdd + yodd.Multiply(beta, temp1); + pseudoResiduals.Add(temp1, yeven); + + // Solve M temp = yOdd + preconditioner.Approximate(yeven, temp); + + // uOdd = A temp + matrix.Multiply(temp, ueven); + + // v = uEven + beta * (uOdd + beta * v) + v.Multiply(beta, temp1); + uodd.Add(temp1, temp); + + temp.Multiply(beta, temp1); + ueven.Add(temp1, v); + } + + // Calculate the real values + preconditioner.Approximate(x, result); + + iterationNumber++; + } + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/SparseMatrix.cs b/MathNet.Numerics/LinearAlgebra/Single/SparseMatrix.cs new file mode 100644 index 0000000..ed24f8c --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/SparseMatrix.cs @@ -0,0 +1,1583 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Single; + +using MathNet.Numerics.Providers.LinearAlgebra; + +/// +/// A Matrix with sparse storage, intended for very large matrices where most of the cells are zero. +/// The underlying storage scheme is 3-array compressed-sparse-row (CSR) Format. +/// Wikipedia - CSR. +/// +[Serializable] +[DebuggerDisplay("SparseMatrix {RowCount}x{ColumnCount}-Single {NonZerosCount}-NonZero")] +public class SparseMatrix : Matrix +{ + readonly SparseCompressedRowMatrixStorage _storage; + + /// + /// Gets the number of non zero elements in the matrix. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get { return _storage.ValueCount; } + } + + /// + /// Create a new sparse matrix straight from an initialized matrix storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public SparseMatrix(SparseCompressedRowMatrixStorage storage) + : base(storage) + { + _storage = storage; + } + + /// + /// Create a new square sparse matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the order is less than one. + public SparseMatrix(int order) + : this(order, order) + { + } + + /// + /// Create a new sparse matrix with the given number of rows and columns. + /// All cells of the matrix will be initialized to zero. + /// Zero-length matrices are not supported. + /// + /// If the row or column count is less than one. + public SparseMatrix(int rows, int columns) + : this(new SparseCompressedRowMatrixStorage(rows, columns)) + { + } + + /// + /// Create a new sparse matrix as a copy of the given other matrix. + /// This new matrix will be independent from the other matrix. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfMatrix(Matrix matrix) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfMatrix(matrix.Storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given two-dimensional array. + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfArray(float[,] array) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfArray(array)); + } + + /// + /// Create a new sparse matrix as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfIndexed(int rows, int columns, IEnumerable> enumerable) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfIndexedEnumerable(rows, columns, enumerable)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable. + /// The enumerable is assumed to be in row-major order (row by row). + /// This new matrix will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + /// + public static SparseMatrix OfRowMajor(int rows, int columns, IEnumerable rowMajor) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowMajorEnumerable(rows, columns, rowMajor)); + } + + /// + /// Create a new sparse matrix with the given number of rows and columns as a copy of the given array. + /// The array is assumed to be in column-major order (column by column). + /// This new matrix will be independent from the provided array. + /// A new memory block will be allocated for storing the matrix. + /// + /// + public static SparseMatrix OfColumnMajor(int rows, int columns, IList columnMajor) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnMajorList(rows, columns, columnMajor)); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumns(IEnumerable> data) + { + return OfColumnArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable columns. + /// Each enumerable in the master enumerable specifies a column. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumns(int rows, int columns, IEnumerable> data) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnArrays(params float[][] columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnArrays(columns)); + } + + /// + /// Create a new sparse matrix as a copy of the given column arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnArrays(IEnumerable columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnArrays((columns as float[][]) ?? columns.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnVectors(params Vector[] columns) + { + var storage = new VectorStorage[columns.Length]; + for (int i = 0; i < columns.Length; i++) + { + storage[i] = columns[i].Storage; + } + + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given column vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfColumnVectors(IEnumerable> columns) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfColumnVectors(columns.Select(c => c.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRows(IEnumerable> data) + { + return OfRowArrays(data.Select(v => v.ToArray()).ToArray()); + } + + /// + /// Create a new sparse matrix as a copy of the given enumerable of enumerable rows. + /// Each enumerable in the master enumerable specifies a row. + /// This new matrix will be independent from the enumerables. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRows(int rows, int columns, IEnumerable> data) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowEnumerables(rows, columns, data)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowArrays(params float[][] rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowArrays(rows)); + } + + /// + /// Create a new sparse matrix as a copy of the given row arrays. + /// This new matrix will be independent from the arrays. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowArrays(IEnumerable rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowArrays((rows as float[][]) ?? rows.ToArray())); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowVectors(params Vector[] rows) + { + var storage = new VectorStorage[rows.Length]; + for (int i = 0; i < rows.Length; i++) + { + storage[i] = rows[i].Storage; + } + + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowVectors(storage)); + } + + /// + /// Create a new sparse matrix as a copy of the given row vectors. + /// This new matrix will be independent from the vectors. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfRowVectors(IEnumerable> rows) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfRowVectors(rows.Select(r => r.Storage).ToArray())); + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalVector(Vector diagonal) + { + var m = new SparseMatrix(diagonal.Count, diagonal.Count); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given vector. + /// This new matrix will be independent from the vector. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalVector(int rows, int columns, Vector diagonal) + { + var m = new SparseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalArray(float[] diagonal) + { + var m = new SparseMatrix(diagonal.Length, diagonal.Length); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix with the diagonal as a copy of the given array. + /// This new matrix will be independent from the array. + /// A new memory block will be allocated for storing the matrix. + /// + public static SparseMatrix OfDiagonalArray(int rows, int columns, float[] diagonal) + { + var m = new SparseMatrix(rows, columns); + m.SetDiagonal(diagonal); + return m; + } + + /// + /// Create a new sparse matrix and initialize each value to the same provided value. + /// + public static SparseMatrix Create(int rows, int columns, float value) + { + if (value == 0f) + return new SparseMatrix(rows, columns); + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfValue(rows, columns, value)); + } + + /// + /// Create a new sparse matrix and initialize each value using the provided init function. + /// + public static SparseMatrix Create(int rows, int columns, Func init) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfInit(rows, columns, init)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value to the same provided value. + /// + public static SparseMatrix CreateDiagonal(int rows, int columns, float value) + { + if (value == 0f) + return new SparseMatrix(rows, columns); + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, i => value)); + } + + /// + /// Create a new diagonal sparse matrix and initialize each diagonal value using the provided init function. + /// + public static SparseMatrix CreateDiagonal(int rows, int columns, Func init) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(rows, columns, init)); + } + + /// + /// Create a new square sparse identity matrix where each diagonal value is set to One. + /// + public static SparseMatrix CreateIdentity(int order) + { + return new SparseMatrix(SparseCompressedRowMatrixStorage.OfDiagonalInit(order, order, i => One)); + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + var result = Build.SameAs(this); + LowerTriangleImpl(result); + return result; + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + LowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + LowerTriangleImpl(result); + } + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void LowerTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row >= columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + var result = Build.SameAs(this); + UpperTriangleImpl(result); + return result; + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + UpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + UpperTriangleImpl(result); + } + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void UpperTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row <= columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + var result = Build.SameAs(this); + StrictlyLowerTriangleImpl(result); + return result; + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + StrictlyLowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyLowerTriangleImpl(result); + } + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyLowerTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row > columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + var result = Build.SameAs(this); + StrictlyUpperTriangleImpl(result); + return result; + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw DimensionsDontMatch(this, result, "result"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = Build.SameAs(result); + StrictlyUpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyUpperTriangleImpl(result); + } + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyUpperTriangleImpl(Matrix result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < result.RowCount; row++) + { + var endIndex = rowPointers[row + 1]; + for (var j = rowPointers[row]; j < endIndex; j++) + { + if (row < columnIndices[j]) + { + result.At(row, columnIndices[j], values[j]); + } + } + } + } + + /// + /// Negate each element of this matrix and place the results into the result matrix. + /// + /// The result of the negation. + protected override void DoNegate(Matrix result) + { + CopyTo(result); + DoMultiply(-1, result); + } + + /// Calculates the induced infinity norm of this matrix. + /// The maximum absolute row sum of the matrix. + public override double InfinityNorm() + { + var rowPointers = _storage.RowPointers; + var values = _storage.Values; + var norm = 0d; + for (var i = 0; i < RowCount; i++) + { + // Get the begin / end index for the current row + var startIndex = rowPointers[i]; + var endIndex = rowPointers[i + 1]; + + // Get the values for the current row + if (startIndex == endIndex) + { + // Begin and end are equal. There are no values in the row, Move to the next row + continue; + } + + var s = 0d; + for (var j = startIndex; j < endIndex; j++) + { + s += Math.Abs(values[j]); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the entry-wise Frobenius norm of this matrix. + /// The square root of the sum of the squared values. + public override double FrobeniusNorm() + { + var aat = (SparseCompressedRowMatrixStorage)(this * Transpose()).Storage; + var norm = 0d; + for (var i = 0; i < aat.RowCount; i++) + { + // Get the begin / end index for the current row + var startIndex = aat.RowPointers[i]; + var endIndex = aat.RowPointers[i + 1]; + + // Get the values for the current row + if (startIndex == endIndex) + { + // Begin and end are equal. There are no values in the row, Move to the next row + continue; + } + + for (var j = startIndex; j < endIndex; j++) + { + if (i == aat.ColumnIndices[j]) + { + norm += Math.Abs(aat.Values[j]); + } + } + } + + return Math.Sqrt(norm); + } + + /// + /// Adds another matrix to this matrix. + /// + /// The matrix to add to this matrix. + /// The matrix to store the result of the addition. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoAdd(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther == null || sparseResult == null) + { + base.DoAdd(other, result); + return; + } + + if (ReferenceEquals(this, other)) + { + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + LinearAlgebraControl.Provider.ScaleArray(2.0f, sparseResult._storage.Values, sparseResult._storage.Values); + return; + } + + SparseMatrix left; + + if (ReferenceEquals(sparseOther, sparseResult)) + { + left = this; + } + else if (ReferenceEquals(this, sparseResult)) + { + left = sparseOther; + } + else + { + CopyTo(sparseResult); + left = sparseOther; + } + + var leftStorage = left._storage; + for (var i = 0; i < leftStorage.RowCount; i++) + { + var endIndex = leftStorage.RowPointers[i + 1]; + for (var j = leftStorage.RowPointers[i]; j < endIndex; j++) + { + var columnIndex = leftStorage.ColumnIndices[j]; + var resVal = leftStorage.Values[j] + result.At(i, columnIndex); + result.At(i, columnIndex, resVal); + } + } + } + + /// + /// Subtracts another matrix from this matrix. + /// + /// The matrix to subtract to this matrix. + /// The matrix to store the result of subtraction. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + protected override void DoSubtract(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther == null || sparseResult == null) + { + base.DoSubtract(other, result); + return; + } + + if (ReferenceEquals(this, other)) + { + result.Clear(); + return; + } + + var otherStorage = sparseOther._storage; + + if (ReferenceEquals(this, sparseResult)) + { + for (var i = 0; i < otherStorage.RowCount; i++) + { + var endIndex = otherStorage.RowPointers[i + 1]; + for (var j = otherStorage.RowPointers[i]; j < endIndex; j++) + { + var columnIndex = otherStorage.ColumnIndices[j]; + var resVal = sparseResult.At(i, columnIndex) - otherStorage.Values[j]; + result.At(i, columnIndex, resVal); + } + } + } + else + { + if (!ReferenceEquals(sparseOther, sparseResult)) + { + sparseOther.CopyTo(sparseResult); + } + + sparseResult.Negate(sparseResult); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + var columnIndex = columnIndices[j]; + var resVal = sparseResult.At(i, columnIndex) + values[j]; + result.At(i, columnIndex, resVal); + } + } + } + } + + /// + /// Multiplies each element of the matrix by a scalar and places results into the result matrix. + /// + /// The scalar to multiply the matrix with. + /// The matrix to store the result of the multiplication. + protected override void DoMultiply(float scalar, Matrix result) + { + if (scalar == 1.0) + { + CopyTo(result); + return; + } + + if (scalar == 0.0 || NonZerosCount == 0) + { + result.Clear(); + return; + } + + var sparseResult = result as SparseMatrix; + if (sparseResult == null) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + result.At(row, column, values[index] * scalar); + } + } + } + else + { + if (!ReferenceEquals(this, result)) + { + CopyTo(sparseResult); + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, sparseResult._storage.Values, sparseResult._storage.Values); + } + } + + /// + /// Multiplies this matrix with another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Matrix other, Matrix result) + { + var sparseOther = other as SparseMatrix; + var sparseResult = result as SparseMatrix; + if (sparseOther != null && sparseResult != null) + { + DoMultiplySparse(sparseOther, sparseResult); + return; + } + + var diagonalOther = other.Storage as DiagonalMatrixStorage; + if (diagonalOther != null && sparseResult != null) + { + var diagonal = diagonalOther.Data; + if (other.ColumnCount == other.RowCount) + { + Storage.MapIndexedTo(result.Storage, (i, j, x) => x * diagonal[j], Zeros.AllowSkip, ExistingData.Clear); + } + else + { + result.Storage.Clear(); + Storage.MapSubMatrixIndexedTo(result.Storage, (i, j, x) => x * diagonal[j], 0, 0, RowCount, 0, 0, ColumnCount, Zeros.AllowSkip, ExistingData.AssumeZeros); + } + + return; + } + + result.Clear(); + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + var denseOther = other.Storage as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + // in this case we can directly address the underlying data-array + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var column = 0; column < other.ColumnCount; column++) + { + int otherColumnStartPosition = column * other.RowCount; + var sum = 0f; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * denseOther.Data[otherColumnStartPosition + columnIndices[index]]; + } + + result.At(row, column, sum); + } + } + + return; + } + + var columnVector = new DenseVector(other.RowCount); + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + for (var column = 0; column < other.ColumnCount; column++) + { + // Multiply row of matrix A on column of matrix B + other.Column(column, columnVector); + + var sum = 0f; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * columnVector[columnIndices[index]]; + } + + result.At(row, column, sum); + } + } + } + + void DoMultiplySparse(SparseMatrix other, SparseMatrix result) + { + result.Clear(); + + var ax = _storage.Values; + var ap = _storage.RowPointers; + var ai = _storage.ColumnIndices; + + var bx = other._storage.Values; + var bp = other._storage.RowPointers; + var bi = other._storage.ColumnIndices; + + int rows = RowCount; + int cols = other.ColumnCount; + + int[] cp = result._storage.RowPointers; + + var marker = new int[cols]; + for (int ib = 0; ib < cols; ib++) + { + marker[ib] = -1; + } + + int count = 0; + for (int i = 0; i < rows; i++) + { + // For each row of A + for (int j = ap[i]; j < ap[i + 1]; j++) + { + // Row number to be added + int a = ai[j]; + for (int k = bp[a]; k < bp[a + 1]; k++) + { + int b = bi[k]; + if (marker[b] != i) + { + marker[b] = i; + count++; + } + } + } + + // Record non-zero count. + cp[i + 1] = count; + } + + var ci = new int[count]; + var cx = new float[count]; + + for (int ib = 0; ib < cols; ib++) + { + marker[ib] = -1; + } + + count = 0; + for (int i = 0; i < rows; i++) + { + int rowStart = cp[i]; + for (int j = ap[i]; j < ap[i + 1]; j++) + { + int a = ai[j]; + float aEntry = ax[j]; + for (int k = bp[a]; k < bp[a + 1]; k++) + { + int b = bi[k]; + float bEntry = bx[k]; + if (marker[b] < rowStart) + { + marker[b] = count; + ci[marker[b]] = b; + cx[marker[b]] = aEntry * bEntry; + count++; + } + else + { + cx[marker[b]] += aEntry * bEntry; + } + } + } + } + + result._storage.Values = cx; + result._storage.ColumnIndices = ci; + result._storage.Normalize(); + } + + /// + /// Multiplies this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoMultiply(Vector rightSide, Vector result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var sum = 0f; + for (var index = startIndex; index < endIndex; index++) + { + sum += values[index] * rightSide[columnIndices[index]]; + } + + result[row] = sum; + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + protected override void DoTransposeAndMultiply(Matrix other, Matrix result) + { + var otherSparse = other as SparseMatrix; + var resultSparse = result as SparseMatrix; + + if (otherSparse == null || resultSparse == null) + { + base.DoTransposeAndMultiply(other, result); + return; + } + + resultSparse.Clear(); + + var rowPointers = _storage.RowPointers; + var values = _storage.Values; + + var otherStorage = otherSparse._storage; + + for (var j = 0; j < RowCount; j++) + { + var startIndexOther = otherStorage.RowPointers[j]; + var endIndexOther = otherStorage.RowPointers[j + 1]; + + if (startIndexOther == endIndexOther) + { + continue; + } + + for (var i = 0; i < RowCount; i++) + { + // Multiply row of matrix A on row of matrix B + + var startIndexThis = rowPointers[i]; + var endIndexThis = rowPointers[i + 1]; + + if (startIndexThis == endIndexThis) + { + continue; + } + + var sum = 0f; + for (var index = startIndexOther; index < endIndexOther; index++) + { + var ind = _storage.FindItem(i, otherStorage.ColumnIndices[index]); + if (ind >= 0) + { + sum += otherStorage.Values[index] * values[ind]; + } + } + + resultSparse._storage.At(i, j, sum + result.At(i, j)); + } + } + } + + /// + /// Multiplies the transpose of this matrix with a vector and places the results into the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + protected override void DoTransposeThisAndMultiply(Vector rightSide, Vector result) + { + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var startIndex = rowPointers[row]; + var endIndex = rowPointers[row + 1]; + + if (startIndex == endIndex) + { + continue; + } + + var rightSideValue = rightSide[row]; + for (var index = startIndex; index < endIndex; index++) + { + result[columnIndices[index]] += values[index] * rightSideValue; + } + } + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Matrix other, Matrix result) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + var resVal = values[j] * other.At(i, columnIndices[j]); + if (resVal != 0f) + { + result.At(i, columnIndices[j], resVal); + } + } + } + } + + /// + /// Pointwise divide this matrix by another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise divide this one by. + /// The matrix to store the result of the pointwise division. + protected override void DoPointwiseDivide(Matrix divisor, Matrix result) + { + result.Clear(); + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + if (values[j] != 0f) + { + result.At(i, columnIndices[j], values[j] / divisor.At(i, columnIndices[j])); + } + } + } + } + + public override void KroneckerProduct(Matrix other, Matrix result) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.RowCount != (RowCount * other.RowCount) || result.ColumnCount != (ColumnCount * other.ColumnCount)) + { + throw DimensionsDontMatch(this, other, result); + } + + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var i = 0; i < RowCount; i++) + { + var endIndex = rowPointers[i + 1]; + for (var j = rowPointers[i]; j < endIndex; j++) + { + if (values[j] != 0f) + { + result.SetSubMatrix(i * other.RowCount, other.RowCount, columnIndices[j] * other.ColumnCount, other.ColumnCount, values[j] * other); + } + } + } + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoModulus(float divisor, Matrix result) + { + var sparseResult = result as SparseMatrix; + if (sparseResult == null) + { + base.DoModulus(divisor, result); + return; + } + + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + var resultStorage = sparseResult._storage; + for (var index = 0; index < resultStorage.Values.Length; index++) + { + resultStorage.Values[index] = Euclid.Modulus(resultStorage.Values[index], divisor); + } + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given divisor each element of the matrix. + /// + /// The scalar denominator to use. + /// Matrix to store the results in. + protected override void DoRemainder(float divisor, Matrix result) + { + var sparseResult = result as SparseMatrix; + if (sparseResult == null) + { + base.DoRemainder(divisor, result); + return; + } + + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + var resultStorage = sparseResult._storage; + for (var index = 0; index < resultStorage.Values.Length; index++) + { + resultStorage.Values[index] %= divisor; + } + } + + /// + /// Evaluates whether this matrix is symmetric. + /// + public override bool IsSymmetric() + { + if (RowCount != ColumnCount) + { + return false; + } + + // todo: we might be able to speed this up by caching one half of the matrix + var rowPointers = _storage.RowPointers; + var columnIndices = _storage.ColumnIndices; + var values = _storage.Values; + + for (var row = 0; row < RowCount; row++) + { + var start = rowPointers[row]; + var end = rowPointers[row + 1]; + + if (start == end) + { + continue; + } + + for (var index = start; index < end; index++) + { + var column = columnIndices[index]; + var opposite = At(column, row); + if (!values[index].Equals(opposite)) + { + return false; + } + } + } + + return true; + } + + /// + /// Adds two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to add. + /// The right matrix to add. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static SparseMatrix operator +(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.RowCount != rightSide.RowCount || leftSide.ColumnCount != rightSide.ColumnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Add(rightSide); + } + + /// + /// Returns a Matrix containing the same values of . + /// + /// The matrix to get the values from. + /// A matrix containing a the same values as . + /// If is . + public static SparseMatrix operator +(SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Clone(); + } + + /// + /// Subtracts two matrices together and returns the results. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to subtract. + /// The right matrix to subtract. + /// The result of the addition. + /// If and don't have the same dimensions. + /// If or is . + public static SparseMatrix operator -(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (leftSide.RowCount != rightSide.RowCount || leftSide.ColumnCount != rightSide.ColumnCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Subtract(rightSide); + } + + /// + /// Negates each element of the matrix. + /// + /// The matrix to negate. + /// A matrix containing the negated values. + /// If is . + public static SparseMatrix operator -(SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Negate(); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator *(SparseMatrix leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator *(float leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseMatrix)rightSide.Multiply(leftSide); + } + + /// + /// Multiplies two matrices. + /// + /// This operator will allocate new memory for the result. It will + /// choose the representation of either or depending on which + /// is denser. + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static SparseMatrix operator *(SparseMatrix leftSide, SparseMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + if (leftSide.ColumnCount != rightSide.RowCount) + { + throw DimensionsDontMatch(leftSide, rightSide); + } + + return (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Matrix and a Vector. + /// + /// The matrix to multiply. + /// The vector to multiply. + /// The result of multiplication. + /// If or is . + public static SparseVector operator *(SparseMatrix leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a Vector and a Matrix. + /// + /// The vector to multiply. + /// The matrix to multiply. + /// The result of multiplication. + /// If or is . + public static SparseVector operator *(SparseVector leftSide, SparseMatrix rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.LeftMultiply(leftSide); + } + + /// + /// Multiplies a Matrix by a constant and returns the result. + /// + /// The matrix to multiply. + /// The constant to multiply the matrix by. + /// The result of the multiplication. + /// If is . + public static SparseMatrix operator %(SparseMatrix leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseMatrix)leftSide.Remainder(rightSide); + } + + public override string ToTypeString() + { + return string.Format("SparseMatrix {0}x{1}-Single {2:P2} Filled", RowCount, ColumnCount, NonZerosCount / (RowCount * (double)ColumnCount)); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/SparseVector.cs b/MathNet.Numerics/LinearAlgebra/Single/SparseVector.cs new file mode 100644 index 0000000..e733712 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/SparseVector.cs @@ -0,0 +1,975 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Single; + +/// +/// A vector with sparse storage, intended for very large vectors where most of the cells are zero. +/// +/// The sparse vector is not thread safe. +[Serializable] +[DebuggerDisplay("SparseVector {Count}-Single {NonZerosCount}-NonZero")] +public class SparseVector : Vector +{ + readonly SparseVectorStorage _storage; + + /// + /// Gets the number of non zero elements in the vector. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get { return _storage.ValueCount; } + } + + /// + /// Create a new sparse vector straight from an initialized vector storage instance. + /// The storage is used directly without copying. + /// Intended for advanced scenarios where you're working directly with + /// storage for performance or interop reasons. + /// + public SparseVector(SparseVectorStorage storage) + : base(storage) + { + _storage = storage; + } + + /// + /// Create a new sparse vector with the given length. + /// All cells of the vector will be initialized to zero. + /// Zero-length vectors are not supported. + /// + /// If length is less than one. + public SparseVector(int length) + : this(new SparseVectorStorage(length)) + { + } + + /// + /// Create a new sparse vector as a copy of the given other vector. + /// This new vector will be independent from the other vector. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfVector(Vector vector) + { + return new SparseVector(SparseVectorStorage.OfVector(vector.Storage)); + } + + /// + /// Create a new sparse vector as a copy of the given enumerable. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfEnumerable(IEnumerable enumerable) + { + return new SparseVector(SparseVectorStorage.OfEnumerable(enumerable)); + } + + /// + /// Create a new sparse vector as a copy of the given indexed enumerable. + /// Keys must be provided at most once, zero is assumed if a key is omitted. + /// This new vector will be independent from the enumerable. + /// A new memory block will be allocated for storing the vector. + /// + public static SparseVector OfIndexedEnumerable(int length, IEnumerable> enumerable) + { + return new SparseVector(SparseVectorStorage.OfIndexedEnumerable(length, enumerable)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided value. + /// + public static SparseVector Create(int length, float value) + { + return new SparseVector(SparseVectorStorage.OfValue(length, value)); + } + + /// + /// Create a new sparse vector and initialize each value using the provided init function. + /// + public static SparseVector Create(int length, Func init) + { + return new SparseVector(SparseVectorStorage.OfInit(length, init)); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// Warning, the new 'sparse vector' with a non-zero scalar added to it will be a 100% filled + /// sparse vector and very inefficient. Would be better to work with a dense vector instead. + /// + /// + /// The scalar to add. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(float scalar, Vector result) + { + if (scalar == 0.0f) + { + if (!ReferenceEquals(this, result)) + { + CopyTo(result); + } + + return; + } + + if (ReferenceEquals(this, result)) + { + //populate a new vector with the scalar + var vnonZeroValues = new float[Count]; + var vnonZeroIndices = new int[Count]; + for (int index = 0; index < Count; index++) + { + vnonZeroIndices[index] = index; + vnonZeroValues[index] = scalar; + } + + //populate the non zero values from this + var indices = _storage.Indices; + var values = _storage.Values; + for (int j = 0; j < _storage.ValueCount; j++) + { + vnonZeroValues[indices[j]] = values[j] + scalar; + } + + //assign this vectors array to the new arrays. + _storage.Values = vnonZeroValues; + _storage.Indices = vnonZeroIndices; + _storage.ValueCount = Count; + } + + else + { + for (var index = 0; index < Count; index++) + { + result.At(index, At(index) + scalar); + } + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to add to this one. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Vector other, Vector result) + { + var otherSparse = other as SparseVector; + if (otherSparse == null) + { + base.DoAdd(other, result); + return; + } + + var resultSparse = result as SparseVector; + if (resultSparse == null) + { + base.DoAdd(other, result); + return; + } + + // TODO (ruegg, 2011-10-11): Options to optimize? + + var otherStorage = otherSparse._storage; + if (ReferenceEquals(this, resultSparse)) + { + int i = 0, j = 0; + while (j < otherStorage.ValueCount) + { + if (i >= _storage.ValueCount || _storage.Indices[i] > otherStorage.Indices[j]) + { + var otherValue = otherStorage.Values[j]; + if (otherValue != 0.0f) + { + _storage.InsertAtIndexUnchecked(i++, otherStorage.Indices[j], otherValue); + } + + j++; + } + else if (_storage.Indices[i] == otherStorage.Indices[j]) + { + // TODO: result can be zero, remove? + _storage.Values[i++] += otherStorage.Values[j++]; + } + else + { + i++; + } + } + } + else + { + result.Clear(); + int i = 0, j = 0, last = -1; + while (i < _storage.ValueCount || j < otherStorage.ValueCount) + { + if (j >= otherStorage.ValueCount || i < _storage.ValueCount && _storage.Indices[i] <= otherStorage.Indices[j]) + { + var next = _storage.Indices[i]; + if (next != last) + { + last = next; + result.At(next, _storage.Values[i] + otherSparse.At(next)); + } + + i++; + } + else + { + var next = otherStorage.Indices[j]; + if (next != last) + { + last = next; + result.At(next, At(next) + otherStorage.Values[j]); + } + + j++; + } + } + } + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to subtract. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(float scalar, Vector result) + { + DoAdd(-scalar, result); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to subtract from this one. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Vector other, Vector result) + { + if (ReferenceEquals(this, other)) + { + result.Clear(); + return; + } + + var otherSparse = other as SparseVector; + if (otherSparse == null) + { + base.DoSubtract(other, result); + return; + } + + var resultSparse = result as SparseVector; + if (resultSparse == null) + { + base.DoSubtract(other, result); + return; + } + + // TODO (ruegg, 2011-10-11): Options to optimize? + + var otherStorage = otherSparse._storage; + if (ReferenceEquals(this, resultSparse)) + { + int i = 0, j = 0; + while (j < otherStorage.ValueCount) + { + if (i >= _storage.ValueCount || _storage.Indices[i] > otherStorage.Indices[j]) + { + var otherValue = otherStorage.Values[j]; + if (otherValue != 0.0f) + { + _storage.InsertAtIndexUnchecked(i++, otherStorage.Indices[j], -otherValue); + } + + j++; + } + else if (_storage.Indices[i] == otherStorage.Indices[j]) + { + // TODO: result can be zero, remove? + _storage.Values[i++] -= otherStorage.Values[j++]; + } + else + { + i++; + } + } + } + else + { + result.Clear(); + int i = 0, j = 0, last = -1; + while (i < _storage.ValueCount || j < otherStorage.ValueCount) + { + if (j >= otherStorage.ValueCount || i < _storage.ValueCount && _storage.Indices[i] <= otherStorage.Indices[j]) + { + var next = _storage.Indices[i]; + if (next != last) + { + last = next; + result.At(next, _storage.Values[i] - otherSparse.At(next)); + } + + i++; + } + else + { + var next = otherStorage.Indices[j]; + if (next != last) + { + last = next; + result.At(next, At(next) - otherStorage.Values[j]); + } + + j++; + } + } + } + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult == null) + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], -_storage.Values[index]); + } + + return; + } + + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new float[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ScaleArray(-1.0f, sparseResult._storage.Values, sparseResult._storage.Values); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to multiply. + /// + /// + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(float scalar, Vector result) + { + var sparseResult = result as SparseVector; + if (sparseResult == null) + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], scalar * _storage.Values[index]); + } + } + else + { + if (!ReferenceEquals(this, result)) + { + sparseResult._storage.ValueCount = _storage.ValueCount; + sparseResult._storage.Indices = new int[_storage.ValueCount]; + Buffer.BlockCopy(_storage.Indices, 0, sparseResult._storage.Indices, 0, _storage.ValueCount * Constants.SizeOfInt); + sparseResult._storage.Values = new float[_storage.ValueCount]; + Array.Copy(_storage.Values, 0, sparseResult._storage.Values, 0, _storage.ValueCount); + } + + LinearAlgebraControl.Provider.ScaleArray(scalar, sparseResult._storage.Values, sparseResult._storage.Values); + } + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override float DoDotProduct(Vector other) + { + var result = 0f; + if (ReferenceEquals(this, other)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i] * _storage.Values[i]; + } + } + else + { + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i] * other.At(_storage.Indices[i]); + } + } + + return result; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoModulus(float divisor, Vector result) + { + if (ReferenceEquals(this, result)) + { + for (var index = 0; index < _storage.ValueCount; index++) + { + _storage.Values[index] = Euclid.Modulus(_storage.Values[index], divisor); + } + } + else + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], Euclid.Modulus(_storage.Values[index], divisor)); + } + } + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoRemainder(float divisor, Vector result) + { + if (ReferenceEquals(this, result)) + { + for (var index = 0; index < _storage.ValueCount; index++) + { + _storage.Values[index] %= divisor; + } + } + else + { + result.Clear(); + for (var index = 0; index < _storage.ValueCount; index++) + { + result.At(_storage.Indices[index], _storage.Values[index] % divisor); + } + } + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static SparseVector operator +(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Add(rightSide); + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static SparseVector operator -(SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static SparseVector operator -(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Subtract(rightSide); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The vector to scale. + /// The scalar value. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(SparseVector leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The scalar value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(float leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException(nameof(rightSide)); + } + + return (SparseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static float operator *(SparseVector leftSide, SparseVector rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a scalar. + /// + /// The vector to divide. + /// The scalar value. + /// The result of the division. + /// If is . + public static SparseVector operator /(SparseVector leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Divide(rightSide); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// of each element of the vector of the given divisor. + /// + /// The vector whose elements we want to compute the modulus of. + /// The divisor to use, + /// The result of the calculation + /// If is . + public static SparseVector operator %(SparseVector leftSide, float rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException(nameof(leftSide)); + } + + return (SparseVector)leftSide.Remainder(rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + if (_storage.ValueCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var min = Math.Abs(_storage.Values[index]); + for (var i = 1; i < _storage.ValueCount; i++) + { + var test = Math.Abs(_storage.Values[i]); + if (test < min) + { + index = i; + min = test; + } + } + + return _storage.Indices[index]; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + if (_storage.ValueCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var max = Math.Abs(_storage.Values[index]); + for (var i = 1; i < _storage.ValueCount; i++) + { + var test = Math.Abs(_storage.Values[i]); + if (test > max) + { + index = i; + max = test; + } + } + + return _storage.Indices[index]; + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public override int MaximumIndex() + { + if (_storage.ValueCount == 0) + { + return 0; + } + + var index = 0; + var max = _storage.Values[0]; + for (var i = 1; i < _storage.ValueCount; i++) + { + if (max < _storage.Values[i]) + { + index = i; + max = _storage.Values[i]; + } + } + + return _storage.Indices[index]; + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + if (_storage.ValueCount == 0) + { + return 0; + } + + var index = 0; + var min = _storage.Values[0]; + for (var i = 1; i < _storage.ValueCount; i++) + { + if (min > _storage.Values[i]) + { + index = i; + min = _storage.Values[i]; + } + } + + return _storage.Indices[index]; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override float Sum() + { + var result = 0.0f; + for (var i = 0; i < _storage.ValueCount; i++) + { + result += _storage.Values[i]; + } + + return result; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double result = 0d; + for (var i = 0; i < _storage.ValueCount; i++) + { + result += Math.Abs(_storage.Values[i]); + } + + return result; + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(0, _storage.ValueCount, i => Math.Abs(_storage.Values[i]), Math.Max, 0f); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = ( ∑|this[i]|^p )^(1/p) + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (_storage.ValueCount == 0) + { + return 0d; + } + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var index = 0; index < _storage.ValueCount; index++) + { + sum += Math.Pow(Math.Abs(_storage.Values[index]), p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + if (ReferenceEquals(this, other) && ReferenceEquals(this, result)) + { + for (var i = 0; i < _storage.ValueCount; i++) + { + _storage.Values[i] *= _storage.Values[i]; + } + } + else + { + base.DoPointwiseMultiply(other, result); + } + } + + #region Parse Functions + + /// + /// Creates a float sparse vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n,n,..', '(n,n,..)', '[n,n,...]', where n is a float. + /// + /// + /// A float sparse vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static SparseVector Parse(string value, IFormatProvider formatProvider = null) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // parsing + var tokens = value.Split(new[] { formatProvider.GetTextInfo().ListSeparator, " ", "\t" }, StringSplitOptions.RemoveEmptyEntries); + var data = tokens.Select(t => float.Parse(t, NumberStyles.Any, formatProvider)).ToList(); + if (data.Count == 0) + throw new FormatException(); + return OfEnumerable(data); + } + + /// + /// Converts the string representation of a real sparse vector to float-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out SparseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a real sparse vector to float-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a real vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out SparseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + + #endregion + + public override string ToTypeString() + { + return string.Format("SparseVector {0}-Single {1:P2} Filled", Count, NonZerosCount / (double)Count); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Single/Vector.cs b/MathNet.Numerics/LinearAlgebra/Single/Vector.cs new file mode 100644 index 0000000..8a5c24b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Single/Vector.cs @@ -0,0 +1,648 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Threading; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Single; + +/// +/// float version of the class. +/// +[Serializable] +public abstract class Vector : Vector +{ + /// + /// Initializes a new instance of the Vector class. + /// + protected Vector(VectorStorage storage) + : base(storage) + { + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero. + /// + public override void CoerceZero(double threshold) + { + MapInplace(x => Math.Abs(x) < threshold ? 0f : x, Zeros.AllowSkip); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + protected sealed override void DoConjugate(Vector result) + { + if (ReferenceEquals(this, result)) + { + return; + } + + CopyTo(result); + } + + /// + /// Negates vector and saves result to + /// + /// Target vector + protected override void DoNegate(Vector result) + { + Map(x => -x, result, Zeros.AllowSkip); + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to add. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(float scalar, Vector result) + { + Map(x => x + scalar, result, Zeros.Include); + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to add to this one. + /// + /// + /// The vector to store the result of the addition. + /// + protected override void DoAdd(Vector other, Vector result) + { + Map2((x, y) => x + y, other, result, Zeros.AllowSkip); + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to subtract. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(float scalar, Vector result) + { + Map(x => x - scalar, result, Zeros.Include); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// + /// The vector to subtract from this one. + /// + /// + /// The vector to store the result of the subtraction. + /// + protected override void DoSubtract(Vector other, Vector result) + { + Map2((x, y) => x - y, other, result, Zeros.AllowSkip); + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// + /// The scalar to multiply. + /// + /// + /// The vector to store the result of the multiplication. + /// + protected override void DoMultiply(float scalar, Vector result) + { + Map(x => x * scalar, result, Zeros.AllowSkip); + } + + /// + /// Divides each element of the vector by a scalar and stores the result in the result vector. + /// + /// + /// The scalar to divide with. + /// + /// + /// The vector to store the result of the division. + /// + protected override void DoDivide(float divisor, Vector result) + { + Map(x => x / divisor, result, divisor == 0.0f ? Zeros.Include : Zeros.AllowSkip); + } + + /// + /// Divides a scalar by each element of the vector and stores the result in the result vector. + /// + /// The scalar to divide. + /// The vector to store the result of the division. + protected override void DoDivideByThis(float dividend, Vector result) + { + Map(x => dividend / x, result, Zeros.Include); + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected override void DoPointwiseMultiply(Vector other, Vector result) + { + Map2((x, y) => x * y, other, result, Zeros.AllowSkip); + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + protected override void DoPointwiseDivide(Vector divisor, Vector result) + { + Map2((x, y) => x / y, divisor, result, Zeros.Include); + } + + /// + /// Pointwise raise this vector to an exponent and store the result into the result vector. + /// + /// The exponent to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(float exponent, Vector result) + { + Map(x => (float)Math.Pow(x, exponent), result, exponent > 0.0f ? Zeros.AllowSkip : Zeros.Include); + } + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected override void DoPointwisePower(Vector exponent, Vector result) + { + Map2((x, y) => (float)Math.Pow(x, y), exponent, result, Zeros.Include); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected override void DoPointwiseModulus(Vector divisor, Vector result) + { + Map2(Euclid.Modulus, divisor, result, Zeros.Include); + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected override void DoPointwiseRemainder(Vector divisor, Vector result) + { + Map2(Euclid.Remainder, divisor, result, Zeros.Include); + } + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected override void DoPointwiseExp(Vector result) + { + Map(x => (float)Math.Exp(x), result, Zeros.Include); + } + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected override void DoPointwiseLog(Vector result) + { + Map(x => (float)Math.Log(x), result, Zeros.Include); + } + + protected override void DoPointwiseAbs(Vector result) + { + Map(x => (float)Math.Abs(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAcos(Vector result) + { + Map(x => (float)Math.Acos(x), result, Zeros.Include); + } + protected override void DoPointwiseAsin(Vector result) + { + Map(x => (float)Math.Asin(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan(Vector result) + { + Map(x => (float)Math.Atan(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseAtan2(Vector other, Vector result) + { + Map2((x, y) => (float)Math.Atan2((double)x, (double)y), other, result, Zeros.Include); + } + protected override void DoPointwiseAtan2(float scalar, Vector result) + { + Map(x => (float)Math.Atan2((double)x, (double)scalar), result, Zeros.Include); + } + protected override void DoPointwiseCeiling(Vector result) + { + Map(x => (float)Math.Ceiling(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseCos(Vector result) + { + Map(x => (float)Math.Cos(x), result, Zeros.Include); + } + protected override void DoPointwiseCosh(Vector result) + { + Map(x => (float)Math.Cosh(x), result, Zeros.Include); + } + protected override void DoPointwiseFloor(Vector result) + { + Map(x => (float)Math.Floor(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseLog10(Vector result) + { + Map(x => (float)Math.Log10(x), result, Zeros.Include); + } + protected override void DoPointwiseRound(Vector result) + { + Map(x => (float)Math.Round(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSign(Vector result) + { + Map(x => (float)Math.Sign(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSin(Vector result) + { + Map(x => (float)Math.Sin(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSinh(Vector result) + { + Map(x => (float)Math.Sinh(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseSqrt(Vector result) + { + Map(x => (float)Math.Sqrt(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseTan(Vector result) + { + Map(x => (float)Math.Tan(x), result, Zeros.AllowSkip); + } + protected override void DoPointwiseTanh(Vector result) + { + Map(x => (float)Math.Tanh(x), result, Zeros.AllowSkip); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected override float DoDotProduct(Vector other) + { + var dot = 0.0f; + for (var i = 0; i < Count; i++) + { + dot += At(i) * other.At(i); + } + + return dot; + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected sealed override float DoConjugateDotProduct(Vector other) + { + return DoDotProduct(other); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoModulus(float divisor, Vector result) + { + Map(x => Euclid.Modulus(x, divisor), result, Zeros.Include); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoModulusByThis(float dividend, Vector result) + { + Map(x => Euclid.Modulus(dividend, x), result, Zeros.Include); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected override void DoRemainder(float divisor, Vector result) + { + Map(x => Euclid.Remainder(x, divisor), result, Zeros.Include); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected override void DoRemainderByThis(float dividend, Vector result) + { + Map(x => Euclid.Remainder(dividend, x), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(float scalar, Vector result) + { + Map(x => Math.Min(scalar, x), result, scalar >= 0d ? Zeros.AllowSkip : Zeros.Include); + } + + protected override void DoPointwiseMaximum(float scalar, Vector result) + { + Map(x => Math.Max(scalar, x), result, scalar <= 0d ? Zeros.AllowSkip : Zeros.Include); + } + + protected override void DoPointwiseAbsoluteMinimum(float scalar, Vector result) + { + float absolute = Math.Abs(scalar); + Map(x => Math.Min(absolute, Math.Abs(x)), result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(float scalar, Vector result) + { + float absolute = Math.Abs(scalar); + Map(x => Math.Max(absolute, Math.Abs(x)), result, Zeros.Include); + } + + protected override void DoPointwiseMinimum(Vector other, Vector result) + { + Map2(Math.Min, other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseMaximum(Vector other, Vector result) + { + Map2(Math.Max, other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMinimum(Vector other, Vector result) + { + Map2((x, y) => Math.Min(Math.Abs(x), Math.Abs(y)), other, result, Zeros.AllowSkip); + } + + protected override void DoPointwiseAbsoluteMaximum(Vector other, Vector result) + { + Map2((x, y) => Math.Max(Math.Abs(x), Math.Abs(y)), other, result, Zeros.AllowSkip); + } + + /// + /// Returns the value of the absolute minimum element. + /// + /// The value of the absolute minimum element. + public override float AbsoluteMinimum() + { + return Math.Abs(At(AbsoluteMinimumIndex())); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = Math.Abs(At(index)); + for (var i = 1; i < Count; i++) + { + var test = Math.Abs(At(i)); + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the value of the absolute maximum element. + /// + /// The value of the absolute maximum element. + public override float AbsoluteMaximum() + { + return Math.Abs(At(AbsoluteMaximumIndex())); + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = Math.Abs(At(index)); + for (var i = 1; i < Count; i++) + { + var test = Math.Abs(At(i)); + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override float Sum() + { + var sum = 0.0f; + for (var i = 0; i < Count; i++) + { + sum += At(i); + } + + return sum; + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public override double L1Norm() + { + double sum = 0d; + for (var i = 0; i < Count; i++) + { + sum += Math.Abs(At(i)); + } + + return sum; + } + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public override double L2Norm() + { + return Math.Sqrt(DoDotProduct(this)); + } + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public override double InfinityNorm() + { + return CommonParallel.Aggregate(0, Count, i => Math.Abs(At(i)), Math.Max, 0f); + } + + /// + /// Computes the p-Norm. + /// + /// + /// The p value. + /// + /// + /// Scalar ret = ( ∑|At(i)|^p )^(1/p) + /// + public override double Norm(double p) + { + if (p < 0d) + throw new ArgumentOutOfRangeException(nameof(p)); + + if (p == 1d) + return L1Norm(); + if (p == 2d) + return L2Norm(); + if (double.IsPositiveInfinity(p)) + return InfinityNorm(); + + double sum = 0d; + for (var index = 0; index < Count; index++) + { + sum += Math.Pow(Math.Abs(At(index)), p); + } + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public override int MaximumIndex() + { + var index = 0; + var max = At(index); + for (var i = 1; i < Count; i++) + { + var test = At(i); + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + var index = 0; + var min = At(index); + for (var i = 1; i < Count; i++) + { + var test = At(i); + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Normalizes this vector to a unit vector with respect to the p-norm. + /// + /// + /// The p value. + /// + /// + /// This vector normalized to a unit vector with respect to the p-norm. + /// + public override Vector Normalize(double p) + { + if (p < 0d) + { + throw new ArgumentOutOfRangeException(nameof(p)); + } + + double norm = Norm(p); + var clone = Clone(); + if (norm == 0d) + { + return clone; + } + + clone.Multiply((float)(1d / norm), clone); + + return clone; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/CancellationStopCriterion.cs b/MathNet.Numerics/LinearAlgebra/Solvers/CancellationStopCriterion.cs new file mode 100644 index 0000000..8a9f971 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/CancellationStopCriterion.cs @@ -0,0 +1,110 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Diagnostics; +using System.Threading; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Defines an that uses a cancellation token as stop criterion. +/// +public sealed class CancellationStopCriterion : IIterationStopCriterion where T : struct, IEquatable, IFormattable +{ + readonly CancellationToken _masterToken; + CancellationTokenSource _currentTcs; + + /// + /// Initializes a new instance of the class. + /// + public CancellationStopCriterion() + { + _masterToken = CancellationToken.None; + _currentTcs = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken.None); + } + + /// + /// Initializes a new instance of the class. + /// + public CancellationStopCriterion(CancellationToken masterToken) + { + _masterToken = masterToken; + _currentTcs = CancellationTokenSource.CreateLinkedTokenSource(masterToken); + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public IterationStatus DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + return _currentTcs.Token.IsCancellationRequested ? IterationStatus.Cancelled : IterationStatus.Continue; + } + + /// + /// Gets the current calculation status. + /// + public IterationStatus Status + { + [DebuggerStepThrough] + get { return _currentTcs.Token.IsCancellationRequested ? IterationStatus.Cancelled : IterationStatus.Continue; } + } + + public void Cancel() + { + _currentTcs.Cancel(); + } + + /// + /// Resets the to the pre-calculation state. + /// + public void Reset() + { + _currentTcs = CancellationTokenSource.CreateLinkedTokenSource(_masterToken); + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterion Clone() + { + return new CancellationStopCriterion(_masterToken); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/DelegateStopCriterion.cs b/MathNet.Numerics/LinearAlgebra/Solvers/DelegateStopCriterion.cs new file mode 100644 index 0000000..5588738 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/DelegateStopCriterion.cs @@ -0,0 +1,93 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Stop criterion that delegates the status determination to a delegate. +/// +public class DelegateStopCriterion : IIterationStopCriterion + where T : struct, IEquatable, IFormattable +{ + readonly Func, Vector, Vector, IterationStatus> _determine; + IterationStatus _status = IterationStatus.Continue; + + /// + /// Create a new instance of this criterion with a custom implementation. + /// + /// Custom implementation with the same signature and semantics as the DetermineStatus method. + public DelegateStopCriterion(Func, Vector, Vector, IterationStatus> determine) + { + _determine = determine; + } + + /// + /// Determines the status of the iterative calculation by delegating it to the provided delegate. + /// Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public IterationStatus DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + return _status = _determine(iterationNumber, solutionVector, sourceVector, residualVector); + } + + /// + /// Gets the current calculation status. + /// + public IterationStatus Status + { + get { return _status; } + } + + /// + /// Resets the IIterationStopCriterion to the pre-calculation state. + /// + public void Reset() + { + _status = IterationStatus.Continue; + } + + /// + /// Clones this criterion and its settings. + /// + public IIterationStopCriterion Clone() + { + return new DelegateStopCriterion(_determine); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/DivergenceStopCriterion.cs b/MathNet.Numerics/LinearAlgebra/Solvers/DivergenceStopCriterion.cs new file mode 100644 index 0000000..d83857c --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/DivergenceStopCriterion.cs @@ -0,0 +1,253 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Diagnostics; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Monitors an iterative calculation for signs of divergence. +/// +public sealed class DivergenceStopCriterion : IIterationStopCriterion where T : struct, IEquatable, IFormattable +{ + /// + /// The maximum relative increase the residual may experience without triggering a divergence warning. + /// + double _maximumRelativeIncrease; + + /// + /// The number of iterations over which a residual increase should be tracked before issuing a divergence warning. + /// + int _minimumNumberOfIterations; + + /// + /// The status of the calculation + /// + IterationStatus _status = IterationStatus.Continue; + + /// + /// The array that holds the tracking information. + /// + double[] _residualHistory; + + /// + /// The iteration number of the last iteration. + /// + int _lastIteration = -1; + + /// + /// Initializes a new instance of the class with the specified maximum + /// relative increase and the specified minimum number of tracking iterations. + /// + /// The maximum relative increase that the residual may experience before a divergence warning is issued. + /// The minimum number of iterations over which the residual must grow before a divergence warning is issued. + public DivergenceStopCriterion(double maximumRelativeIncrease = 0.08, int minimumIterations = 10) + { + if (maximumRelativeIncrease <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maximumRelativeIncrease)); + } + + // There must be at least three iterations otherwise we can't calculate the relative increase + if (minimumIterations < 3) + { + throw new ArgumentOutOfRangeException(nameof(minimumIterations)); + } + + _maximumRelativeIncrease = maximumRelativeIncrease; + _minimumNumberOfIterations = minimumIterations; + } + + /// + /// Gets or sets the maximum relative increase that the residual may experience before a divergence warning is issued. + /// + /// Thrown if the Maximum is set to zero or below. + public double MaximumRelativeIncrease + { + [DebuggerStepThrough] + get { return _maximumRelativeIncrease; } + + [DebuggerStepThrough] + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _maximumRelativeIncrease = value; + } + } + + /// + /// Gets or sets the minimum number of iterations over which the residual must grow before + /// issuing a divergence warning. + /// + /// Thrown if the value is set to less than one. + public int MinimumNumberOfIterations + { + [DebuggerStepThrough] + get { return _minimumNumberOfIterations; } + + [DebuggerStepThrough] + set + { + // There must be at least three iterations otherwise we can't calculate + // the relative increase + if (value < 3) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _minimumNumberOfIterations = value; + } + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public IterationStatus DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException(nameof(iterationNumber)); + } + + if (_lastIteration >= iterationNumber) + { + // We have already stored the actual last iteration number + // For now do nothing. We only care about the next step. + return _status; + } + + if ((_residualHistory == null) || (_residualHistory.Length != RequiredHistoryLength)) + { + _residualHistory = new double[RequiredHistoryLength]; + } + + // We always track the residual. + // Move the old versions one element up in the array. + for (var i = 1; i < _residualHistory.Length; i++) + { + _residualHistory[i - 1] = _residualHistory[i]; + } + + // Store the infinity norms of both the solution and residual vectors + // These values will be used to calculate the relative drop in residuals later on. + _residualHistory[_residualHistory.Length - 1] = residualVector.InfinityNorm(); + + // Check if we have NaN's. If so we've gone way beyond normal divergence. + // Stop the iteration. + if (double.IsNaN(_residualHistory[_residualHistory.Length - 1])) + { + _status = IterationStatus.Diverged; + return _status; + } + + // Check if we are diverging and if so set the status + _status = IsDiverging() ? IterationStatus.Diverged : IterationStatus.Continue; + + _lastIteration = iterationNumber; + return _status; + } + + /// + /// Detect if solution is diverging + /// + /// true if diverging, otherwise false + bool IsDiverging() + { + // Run for each variable + for (var i = 1; i < _residualHistory.Length; i++) + { + var difference = _residualHistory[i] - _residualHistory[i - 1]; + + // Divergence is occurring if: + // - the last residual is larger than the previous one + // - the relative increase of the residual is larger than the setting allows + if ((difference < 0) || (_residualHistory[i - 1] * (1 + _maximumRelativeIncrease) >= _residualHistory[i])) + { + // No divergence taking place within the required number of iterations + // So reset and stop the iteration. There is no way we can get to the + // required number of iterations anymore. + return false; + } + } + + return true; + } + + /// + /// Gets required history Length + /// + int RequiredHistoryLength + { + [DebuggerStepThrough] + get { return _minimumNumberOfIterations + 1; } + } + + /// + /// Gets the current calculation status. + /// + public IterationStatus Status + { + [DebuggerStepThrough] + get { return _status; } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void Reset() + { + _status = IterationStatus.Continue; + _lastIteration = -1; + _residualHistory = null; + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterion Clone() + { + return new DivergenceStopCriterion(_maximumRelativeIncrease, _minimumNumberOfIterations); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/FailureStopCriterion.cs b/MathNet.Numerics/LinearAlgebra/Solvers/FailureStopCriterion.cs new file mode 100644 index 0000000..aabc0cd --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/FailureStopCriterion.cs @@ -0,0 +1,120 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Diagnostics; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Defines an that monitors residuals for NaN's. +/// +public sealed class FailureStopCriterion : IIterationStopCriterion where T : struct, IEquatable, IFormattable +{ + /// + /// The status of the calculation + /// + IterationStatus _status = IterationStatus.Continue; + + /// + /// The iteration number of the last iteration. + /// + int _lastIteration = -1; + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public IterationStatus DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException(nameof(iterationNumber)); + } + + if (solutionVector.Count != residualVector.Count) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (_lastIteration >= iterationNumber) + { + // We have already stored the actual last iteration number + // For now do nothing. We only care about the next step. + return _status; + } + + // Store the infinity norms of both the solution and residual vectors + double residualNorm = residualVector.InfinityNorm(); + double solutionNorm = solutionVector.InfinityNorm(); + + _status = double.IsNaN(solutionNorm) || double.IsNaN(residualNorm) ? IterationStatus.Failure : IterationStatus.Continue; + + _lastIteration = iterationNumber; + return _status; + } + + /// + /// Gets the current calculation status. + /// + public IterationStatus Status + { + [DebuggerStepThrough] + get { return _status; } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void Reset() + { + _status = IterationStatus.Continue; + _lastIteration = -1; + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterion Clone() + { + return new FailureStopCriterion(); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/IIterationStopCriterion.cs b/MathNet.Numerics/LinearAlgebra/Solvers/IIterationStopCriterion.cs new file mode 100644 index 0000000..82b4eb3 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/IIterationStopCriterion.cs @@ -0,0 +1,69 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// The base interface for classes that provide stop criteria for iterative calculations. +/// +public interface IIterationStopCriterion where T : struct, IEquatable, IFormattable +{ + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current IIterationStopCriterion. Status is set to Status field of current object. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + IterationStatus DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector); + + /// + /// Gets the current calculation status. + /// + /// is not a legal value. Status should be set in implementation. + IterationStatus Status { get; } + + /// + /// Resets the IIterationStopCriterion to the pre-calculation state. + /// + /// To implementers: Invoking this method should not clear the user defined + /// property values, only the state that is used to track the progress of the + /// calculation. + void Reset(); + + IIterationStopCriterion Clone(); +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/IIterativeSolver.cs b/MathNet.Numerics/LinearAlgebra/Solvers/IIterativeSolver.cs new file mode 100644 index 0000000..b6965ab --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/IIterativeSolver.cs @@ -0,0 +1,50 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Defines the interface for classes that solve the matrix equation Ax = b in +/// an iterative manner. +/// +public interface IIterativeSolver where T : struct, IEquatable, IFormattable +{ + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + /// The iterator to use to control when to stop iterating. + /// The preconditioner to use for approximations. + void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner); +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/IIterativeSolverSetup.cs b/MathNet.Numerics/LinearAlgebra/Solvers/IIterativeSolverSetup.cs new file mode 100644 index 0000000..e8bcf62 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/IIterativeSolverSetup.cs @@ -0,0 +1,72 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Defines the interface for objects that can create an iterative solver with +/// specific settings. This interface is used to pass iterative solver creation +/// setup information around. +/// +public interface IIterativeSolverSetup where T : struct, IEquatable, IFormattable +{ + /// + /// Gets the type of the solver that will be created by this setup object. + /// + Type SolverType { get; } + + /// + /// Gets type of preconditioner, if any, that will be created by this setup object. + /// + Type PreconditionerType { get; } + + /// + /// Creates the iterative solver to be used. + /// + IIterativeSolver CreateSolver(); + + /// + /// Creates the preconditioner to be used by default (can be overwritten). + /// + IPreconditioner CreatePreconditioner(); + + /// + /// Gets the relative speed of the solver. + /// + /// Returns a value between 0 and 1, inclusive. + double SolutionSpeed { get; } + + /// + /// Gets the relative reliability of the solver. + /// + /// Returns a value between 0 and 1 inclusive. + double Reliability { get; } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/IPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Solvers/IPreconditioner.cs new file mode 100644 index 0000000..7e2e853 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/IPreconditioner.cs @@ -0,0 +1,66 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// The base interface for preconditioner classes. +/// +/// +/// +/// Preconditioners are used by iterative solvers to improve the convergence +/// speed of the solving process. Increase in convergence speed +/// is related to the number of iterations necessary to get a converged solution. +/// So while in general the use of a preconditioner means that the iterative +/// solver will perform fewer iterations it does not guarantee that the actual +/// solution time decreases given that some preconditioners can be expensive to +/// setup and run. +/// +/// +/// Note that in general changes to the matrix will invalidate the preconditioner +/// if the changes occur after creating the preconditioner. +/// +/// +public interface IPreconditioner where T : struct, IEquatable, IFormattable +{ + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix on which the preconditioner is based. + void Initialize(Matrix matrix); + + /// + /// Approximates the solution to the matrix equation Mx = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + void Approximate(Vector rhs, Vector lhs); +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/IterationCountStopCriterion.cs b/MathNet.Numerics/LinearAlgebra/Solvers/IterationCountStopCriterion.cs new file mode 100644 index 0000000..433351b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/IterationCountStopCriterion.cs @@ -0,0 +1,159 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Diagnostics; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Defines an that monitors the numbers of iteration +/// steps as stop criterion. +/// +public sealed class IterationCountStopCriterion : IIterationStopCriterion where T : struct, IEquatable, IFormattable +{ + /// + /// The default value for the maximum number of iterations the process is allowed + /// to perform. + /// + public const int DefaultMaximumNumberOfIterations = 1000; + + /// + /// The maximum number of iterations the calculation is allowed to perform. + /// + int _maximumNumberOfIterations; + + /// + /// The status of the calculation + /// + IterationStatus _status = IterationStatus.Continue; + + /// + /// Initializes a new instance of the class with the default maximum + /// number of iterations. + /// + public IterationCountStopCriterion() : this(DefaultMaximumNumberOfIterations) + { + } + + /// + /// Initializes a new instance of the class with the specified maximum + /// number of iterations. + /// + /// The maximum number of iterations the calculation is allowed to perform. + public IterationCountStopCriterion(int maximumNumberOfIterations) + { + if (maximumNumberOfIterations < 1) + { + throw new ArgumentOutOfRangeException(nameof(maximumNumberOfIterations)); + } + + _maximumNumberOfIterations = maximumNumberOfIterations; + } + + /// + /// Gets or sets the maximum number of iterations the calculation is allowed to perform. + /// + /// Thrown if the Maximum is set to a negative value. + public int MaximumNumberOfIterations + { + [DebuggerStepThrough] + get { return _maximumNumberOfIterations; } + + [DebuggerStepThrough] + set + { + if (value < 1) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _maximumNumberOfIterations = value; + } + } + + /// + /// Returns the maximum number of iterations to the default. + /// + public void ResetMaximumNumberOfIterationsToDefault() + { + _maximumNumberOfIterations = DefaultMaximumNumberOfIterations; + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public IterationStatus DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException(nameof(iterationNumber)); + } + + _status = iterationNumber >= _maximumNumberOfIterations ? IterationStatus.StoppedWithoutConvergence : IterationStatus.Continue; + + return _status; + } + + /// + /// Gets the current calculation status. + /// + public IterationStatus Status + { + [DebuggerStepThrough] + get { return _status; } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void Reset() + { + _status = IterationStatus.Continue; + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterion Clone() + { + return new IterationCountStopCriterion(_maximumNumberOfIterations); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/IterationStatus.cs b/MathNet.Numerics/LinearAlgebra/Solvers/IterationStatus.cs new file mode 100644 index 0000000..5b8047b --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/IterationStatus.cs @@ -0,0 +1,43 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Iterative Calculation Status +/// +public enum IterationStatus +{ + Continue = 0, + Converged, + Diverged, + StoppedWithoutConvergence, + Cancelled, + Failure +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/Iterator.cs b/MathNet.Numerics/LinearAlgebra/Solvers/Iterator.cs new file mode 100644 index 0000000..37fffce --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/Iterator.cs @@ -0,0 +1,176 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// An iterator that is used to check if an iterative calculation should continue or stop. +/// +public sealed class Iterator where T : struct, IEquatable, IFormattable +{ + /// + /// The collection that holds all the stop criteria and the flag indicating if they should be added + /// to the child iterators. + /// + readonly List> _stopCriteria; + + /// + /// The status of the iterator. + /// + IterationStatus _status = IterationStatus.Continue; + + /// + /// Initializes a new instance of the class with the default stop criteria. + /// + public Iterator() + { + _stopCriteria = new List>(Matrix.Build.IterativeSolverStopCriteria()); + } + + /// + /// Initializes a new instance of the class with the specified stop criteria. + /// + /// + /// The specified stop criteria. Only one stop criterion of each type can be passed in. None + /// of the stop criteria will be passed on to child iterators. + /// + public Iterator(params IIterationStopCriterion[] stopCriteria) + { + _stopCriteria = new List>(stopCriteria); + } + + /// + /// Initializes a new instance of the class with the specified stop criteria. + /// + /// + /// The specified stop criteria. Only one stop criterion of each type can be passed in. None + /// of the stop criteria will be passed on to child iterators. + /// + public Iterator(IEnumerable> stopCriteria) + { + _stopCriteria = new List>(stopCriteria); + } + + /// + /// Gets the current calculation status. + /// + public IterationStatus Status + { + get { return _status; } + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual iterators may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public IterationStatus DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (_stopCriteria.Count == 0) + { + throw new ArgumentException(Resources.StopCriterionMissing); + } + + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException(nameof(iterationNumber)); + } + + // While we're cancelled we don't call on the stop-criteria. + if (_status == IterationStatus.Cancelled) + { + return _status; + } + + foreach (var stopCriterion in _stopCriteria) + { + var status = stopCriterion.DetermineStatus(iterationNumber, solutionVector, sourceVector, residualVector); + if (status == IterationStatus.Continue) + { + continue; + } + + _status = status; + return _status; + } + + // Got all the way through + // So we're running because we had vectors passed to us. + _status = IterationStatus.Continue; + + return _status; + } + + /// + /// Indicates to the iterator that the iterative process has been cancelled. + /// + /// + /// Does not reset the stop-criteria. + /// + public void Cancel() + { + _status = IterationStatus.Cancelled; + } + + /// + /// Resets the to the pre-calculation state. + /// + public void Reset() + { + _status = IterationStatus.Continue; + + foreach (var stopCriterion in _stopCriteria) + { + stopCriterion.Reset(); + } + } + + /// + /// Creates a deep clone of the current iterator. + /// + /// The deep clone of the current iterator. + public Iterator Clone() + { + return new Iterator(_stopCriteria.Select(sc => sc.Clone())); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/ResidualStopCriterion.cs b/MathNet.Numerics/LinearAlgebra/Solvers/ResidualStopCriterion.cs new file mode 100644 index 0000000..460a12e --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/ResidualStopCriterion.cs @@ -0,0 +1,242 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Diagnostics; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// Defines an that monitors residuals as stop criterion. +/// +public sealed class ResidualStopCriterion : IIterationStopCriterion where T : struct, IEquatable, IFormattable +{ + /// + /// The maximum value for the residual below which the calculation is considered converged. + /// + double _maximum; + + /// + /// The minimum number of iterations for which the residual has to be below the maximum before + /// the calculation is considered converged. + /// + int _minimumIterationsBelowMaximum; + + /// + /// The status of the calculation + /// + IterationStatus _status = IterationStatus.Continue; + + /// + /// The number of iterations since the residuals got below the maximum. + /// + int _iterationCount; + + /// + /// The iteration number of the last iteration. + /// + int _lastIteration = -1; + + /// + /// Initializes a new instance of the class with the specified + /// maximum residual and minimum number of iterations. + /// + /// + /// The maximum value for the residual below which the calculation is considered converged. + /// + /// + /// The minimum number of iterations for which the residual has to be below the maximum before + /// the calculation is considered converged. + /// + public ResidualStopCriterion(double maximum, int minimumIterationsBelowMaximum = 0) + { + if (maximum < 0) + { + throw new ArgumentOutOfRangeException(nameof(maximum)); + } + + if (minimumIterationsBelowMaximum < 0) + { + throw new ArgumentOutOfRangeException(nameof(minimumIterationsBelowMaximum)); + } + + _maximum = maximum; + _minimumIterationsBelowMaximum = minimumIterationsBelowMaximum; + } + + /// + /// Gets or sets the maximum value for the residual below which the calculation is considered + /// converged. + /// + /// Thrown if the Maximum is set to a negative value. + public double Maximum + { + [DebuggerStepThrough] + get { return _maximum; } + + [DebuggerStepThrough] + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _maximum = value; + } + } + + /// + /// Gets or sets the minimum number of iterations for which the residual has to be + /// below the maximum before the calculation is considered converged. + /// + /// Thrown if the BelowMaximumFor is set to a value less than 1. + public int MinimumIterationsBelowMaximum + { + [DebuggerStepThrough] + get { return _minimumIterationsBelowMaximum; } + + [DebuggerStepThrough] + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _minimumIterationsBelowMaximum = value; + } + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public IterationStatus DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException(nameof(iterationNumber)); + } + + if (solutionVector.Count != sourceVector.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(sourceVector)); + } + + if (solutionVector.Count != residualVector.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(residualVector)); + } + + // Store the infinity norms of both the solution and residual vectors + // These values will be used to calculate the relative drop in residuals + // later on. + var residualNorm = residualVector.InfinityNorm(); + + // This is criterion 1 from Templates for the solution of linear systems. + // The problem with this criterion is that it's not limiting enough. For now + // we won't use it. Later on we might get back to it. + // return mMaximumResidual * (System.Math.Abs(mMatrixNorm) * System.Math.Abs(solutionNorm) + System.Math.Abs(mVectorNorm)); + + // For now use criterion 2 from Templates for the solution of linear systems. See page 60. + + // Check the residuals by calculating: + // ||r_i|| <= stop_tol * ||b|| + var stopCriterion = _maximum * sourceVector.InfinityNorm(); + + // First check that we have real numbers not NaN's. + // NaN's can occur when the iterative process diverges so we + // stop if that is the case. + if (double.IsNaN(stopCriterion) || double.IsNaN(residualNorm)) + { + _iterationCount = 0; + _status = IterationStatus.Diverged; + return _status; + } + + // ||r_i|| <= stop_tol * ||b|| + // Stop the calculation if it's clearly smaller than the tolerance + if (residualNorm < stopCriterion) + { + if (_lastIteration <= iterationNumber) + { + _iterationCount = iterationNumber - _lastIteration; + _status = _iterationCount >= _minimumIterationsBelowMaximum ? IterationStatus.Converged : IterationStatus.Continue; + } + } + else + { + _iterationCount = 0; + _status = IterationStatus.Continue; + } + + _lastIteration = iterationNumber; + return _status; + } + + /// + /// Gets the current calculation status. + /// + public IterationStatus Status + { + [DebuggerStepThrough] + get { return _status; } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void Reset() + { + _status = IterationStatus.Continue; + _iterationCount = 0; + _lastIteration = -1; + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterion Clone() + { + return new ResidualStopCriterion(_maximum, _minimumIterationsBelowMaximum); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/SolverSetup.cs b/MathNet.Numerics/LinearAlgebra/Solvers/SolverSetup.cs new file mode 100644 index 0000000..b1b5500 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/SolverSetup.cs @@ -0,0 +1,135 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +public static class SolverSetup where T : struct, IEquatable, IFormattable +{ + /// + /// Loads the available objects from the specified assembly. + /// + /// The assembly which will be searched for setup objects. + /// If true, types that fail to load are simply ignored. Otherwise the exception is rethrown. + /// The types that should not be loaded. + public static IEnumerable> LoadFromAssembly(Assembly assembly, bool ignoreFailed = true, params Type[] typesToExclude) + { +#if NETSTANDARD1_3 + TypeInfo setupInterfaceType = typeof(IIterativeSolverSetup).GetTypeInfo(); + IEnumerable candidates = assembly.DefinedTypes + .Where(typeInfo => !typeInfo.IsAbstract && !typeInfo.IsEnum && !typeInfo.IsInterface && typeInfo.IsVisible) + .Where(setupInterfaceType.IsAssignableFrom) + .Select(typeInfo => typeInfo.GetType()); +#else + Type setupInterfaceType = typeof(IIterativeSolverSetup); + IEnumerable candidates = assembly.GetTypes() + .Where(type => !type.IsAbstract && !type.IsEnum && !type.IsInterface && type.IsVisible) + .Where(type => type.GetInterfaces().Any(setupInterfaceType.IsAssignableFrom)); +#endif + + var setups = new List>(); + foreach (var type in candidates) + { + try + { + setups.Add((IIterativeSolverSetup)Activator.CreateInstance(type)); + } + catch + { + if (!ignoreFailed) + { + throw; + } + } + } + +#if NETSTANDARD1_3 + var excludedTypes = new List(typesToExclude.Select(type => type.GetTypeInfo())); + return setups + .Where(s => !excludedTypes.Any(t => t.IsAssignableFrom(s.SolverType.GetTypeInfo()) || t.IsAssignableFrom(s.PreconditionerType.GetTypeInfo()))) + .OrderBy(s => s.SolutionSpeed/s.Reliability); +#else + var excludedTypes = new List(typesToExclude); + return setups + .Where(s => !excludedTypes.Any(t => t.IsAssignableFrom(s.SolverType) || t.IsAssignableFrom(s.PreconditionerType))) + .OrderBy(s => s.SolutionSpeed / s.Reliability); +#endif + } + + /// + /// Loads the available objects from the specified assembly. + /// + /// The type in the assembly which should be searched for setup objects. + /// If true, types that fail to load are simply ignored. Otherwise the exception is rethrown. + /// The types that should not be loaded. + public static IEnumerable> LoadFromAssembly(Type typeInAssembly, bool ignoreFailed = true, params Type[] typesToExclude) + { +#if NETSTANDARD1_3 + return LoadFromAssembly(typeInAssembly.GetTypeInfo().Assembly, ignoreFailed, typesToExclude); +#else + return LoadFromAssembly(typeInAssembly.Assembly, ignoreFailed, typesToExclude); +#endif + } + + /// + /// Loads the available objects from the specified assembly. + /// + /// The of the assembly that should be searched for setup objects. + /// If true, types that fail to load are simply ignored. Otherwise the exception is rethrown. + /// The types that should not be loaded. + public static IEnumerable> LoadFromAssembly(AssemblyName assemblyName, bool ignoreFailed = true, params Type[] typesToExclude) + { +#if NETSTANDARD1_3 + return LoadFromAssembly(Assembly.Load(assemblyName), ignoreFailed, typesToExclude); +#else + return LoadFromAssembly(Assembly.Load(assemblyName.FullName), ignoreFailed, typesToExclude); +#endif + } + + /// + /// Loads the available objects from the Math.NET Numerics assembly. + /// + /// The types that should not be loaded. + public static IEnumerable> Load(Type[] typesToExclude) + { + return LoadFromAssembly(typeof(SolverSetup), false, typesToExclude); + } + + /// + /// Loads the available objects from the Math.NET Numerics assembly. + /// + public static IEnumerable> Load() + { + return LoadFromAssembly(typeof(SolverSetup), false); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Solvers/UnitPreconditioner.cs b/MathNet.Numerics/LinearAlgebra/Solvers/UnitPreconditioner.cs new file mode 100644 index 0000000..c1df298 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Solvers/UnitPreconditioner.cs @@ -0,0 +1,91 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Solvers; + +/// +/// A unit preconditioner. This preconditioner does not actually do anything +/// it is only used when running an without +/// a preconditioner. +/// +public sealed class UnitPreconditioner : IPreconditioner where T : struct, IEquatable, IFormattable +{ + /// + /// The coefficient matrix on which this preconditioner operates. + /// Is used to check dimensions on the different vectors that are processed. + /// + int _size; + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The matrix upon which the preconditioner is based. + /// + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, nameof(matrix)); + } + + _size = matrix.RowCount; + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + /// + /// + /// If and do not have the same size. + /// + /// + /// - or - + /// + /// + /// If the size of is different the number of rows of the coefficient matrix. + /// + /// + public void Approximate(Vector rhs, Vector lhs) + { + if ((lhs.Count != rhs.Count) || (lhs.Count != _size)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + rhs.CopyTo(lhs); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Storage/DenseColumnMajorMatrixStorage.cs b/MathNet.Numerics/LinearAlgebra/Storage/DenseColumnMajorMatrixStorage.cs new file mode 100644 index 0000000..4ff78e6 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/DenseColumnMajorMatrixStorage.cs @@ -0,0 +1,1075 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/LinearAlgebra")] +public class DenseColumnMajorMatrixStorage : MatrixStorage + where T : struct, IEquatable, IFormattable +{ + // [ruegg] public fields are OK here + + [DataMember(Order = 1)] + public readonly T[] Data; + + internal DenseColumnMajorMatrixStorage(int rows, int columns) + : base(rows, columns) + { + Data = new T[rows * columns]; + } + + internal DenseColumnMajorMatrixStorage(int rows, int columns, T[] data) + : base(rows, columns) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (data.Length != rows * columns) + { + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, rows * columns)); + } + + Data = data; + } + + /// + /// True if the matrix storage format is dense. + /// + public override bool IsDense + { + get { return true; } + } + + /// + /// True if all fields of this matrix can be set to any value. + /// False if some fields are fixed, like on a diagonal matrix. + /// + public override bool IsFullyMutable + { + get { return true; } + } + + /// + /// True if the specified field can be set to any value. + /// False if the field is fixed, like an off-diagonal field on a diagonal matrix. + /// + public override bool IsMutableAt(int row, int column) + { + return true; + } + + /// + /// Retrieves the requested element without range checking. + /// + public override T At(int row, int column) + { + return Data[(column * RowCount) + row]; + } + + /// + /// Sets the element without range checking. + /// + public override void At(int row, int column, T value) + { + Data[(column * RowCount) + row] = value; + } + + /// + /// Evaluate the row and column at a specific data index. + /// + void RowColumnAtIndex(int index, out int row, out int column) + { +#if NETSTANDARD1_3 + row = index % RowCount; + column = index / RowCount; +#else + column = Math.DivRem(index, RowCount, out row); +#endif + } + + // CLEARING + + public override void Clear() + { + Array.Clear(Data, 0, Data.Length); + } + + internal override void ClearUnchecked(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + if (rowIndex == 0 && columnIndex == 0 && rowCount == RowCount && columnCount == ColumnCount) + { + Array.Clear(Data, 0, Data.Length); + return; + } + + for (int j = columnIndex; j < columnIndex + columnCount; j++) + { + Array.Clear(Data, j * RowCount + rowIndex, rowCount); + } + } + + internal override void ClearRowsUnchecked(int[] rowIndices) + { + for (var j = 0; j < ColumnCount; j++) + { + int offset = j * RowCount; + for (var k = 0; k < rowIndices.Length; k++) + { + Data[offset + rowIndices[k]] = Zero; + } + } + } + + internal override void ClearColumnsUnchecked(int[] columnIndices) + { + for (int k = 0; k < columnIndices.Length; k++) + { + Array.Clear(Data, columnIndices[k] * RowCount, RowCount); + } + } + + // INITIALIZATION + + public static DenseColumnMajorMatrixStorage OfMatrix(MatrixStorage matrix) + { + var storage = new DenseColumnMajorMatrixStorage(matrix.RowCount, matrix.ColumnCount); + matrix.CopyToUnchecked(storage, ExistingData.AssumeZeros); + return storage; + } + + public static DenseColumnMajorMatrixStorage OfValue(int rows, int columns, T value) + { + var storage = new DenseColumnMajorMatrixStorage(rows, columns); + var data = storage.Data; + CommonParallel.For(0, data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + data[i] = value; + } + }); + return storage; + } + + public static DenseColumnMajorMatrixStorage OfInit(int rows, int columns, Func init) + { + var storage = new DenseColumnMajorMatrixStorage(rows, columns); + int index = 0; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + storage.Data[index++] = init(i, j); + } + } + + return storage; + } + + public static DenseColumnMajorMatrixStorage OfDiagonalInit(int rows, int columns, Func init) + { + var storage = new DenseColumnMajorMatrixStorage(rows, columns); + int index = 0; + int stride = rows + 1; + for (var i = 0; i < Math.Min(rows, columns); i++) + { + storage.Data[index] = init(i); + index += stride; + } + + return storage; + } + + public static DenseColumnMajorMatrixStorage OfArray(T[,] array) + { + var storage = new DenseColumnMajorMatrixStorage(array.GetLength(0), array.GetLength(1)); + int index = 0; + for (var j = 0; j < storage.ColumnCount; j++) + { + for (var i = 0; i < storage.RowCount; i++) + { + storage.Data[index++] = array[i, j]; + } + } + + return storage; + } + + public static DenseColumnMajorMatrixStorage OfColumnArrays(T[][] data) + { + if (data.Length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(data), Resources.MatrixCanNotBeEmpty); + } + + int columns = data.Length; + int rows = data[0].Length; + var array = new T[rows * columns]; + for (int j = 0; j < data.Length; j++) + { + Array.Copy(data[j], 0, array, j * rows, rows); + } + + return new DenseColumnMajorMatrixStorage(rows, columns, array); + } + + public static DenseColumnMajorMatrixStorage OfRowArrays(T[][] data) + { + if (data.Length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(data), Resources.MatrixCanNotBeEmpty); + } + + int rows = data.Length; + int columns = data[0].Length; + var array = new T[rows * columns]; + for (int j = 0; j < columns; j++) + { + int offset = j * rows; + for (int i = 0; i < rows; i++) + { + array[offset + i] = data[i][j]; + } + } + + return new DenseColumnMajorMatrixStorage(rows, columns, array); + } + + public static DenseColumnMajorMatrixStorage OfColumnMajorArray(int rows, int columns, T[] data) + { + T[] ret = new T[rows * columns]; + Array.Copy(data, 0, ret, 0, Math.Min(ret.Length, data.Length)); + return new DenseColumnMajorMatrixStorage(rows, columns, ret); + } + + public static DenseColumnMajorMatrixStorage OfRowMajorArray(int rows, int columns, T[] data) + { + T[] ret = new T[rows * columns]; + for (int i = 0; i < rows; i++) + { + int offset = i * columns; + for (int j = 0; j < columns; j++) + { + ret[(j * rows) + i] = data[offset + j]; + } + } + + return new DenseColumnMajorMatrixStorage(rows, columns, ret); + } + + public static DenseColumnMajorMatrixStorage OfColumnVectors(VectorStorage[] data) + { + if (data.Length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(data), Resources.MatrixCanNotBeEmpty); + } + + int columns = data.Length; + int rows = data[0].Length; + var array = new T[rows * columns]; + for (int j = 0; j < data.Length; j++) + { + var column = data[j]; + var denseColumn = column as DenseVectorStorage; + if (denseColumn != null) + { + Array.Copy(denseColumn.Data, 0, array, j * rows, rows); + } + else + { + // FALL BACK + int offset = j * rows; + for (int i = 0; i < rows; i++) + { + array[offset + i] = column.At(i); + } + } + } + + return new DenseColumnMajorMatrixStorage(rows, columns, array); + } + + public static DenseColumnMajorMatrixStorage OfRowVectors(VectorStorage[] data) + { + if (data.Length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(data), Resources.MatrixCanNotBeEmpty); + } + + int rows = data.Length; + int columns = data[0].Length; + var array = new T[rows * columns]; + for (int j = 0; j < columns; j++) + { + int offset = j * rows; + for (int i = 0; i < rows; i++) + { + array[offset + i] = data[i].At(j); + } + } + + return new DenseColumnMajorMatrixStorage(rows, columns, array); + } + + public static DenseColumnMajorMatrixStorage OfIndexedEnumerable(int rows, int columns, IEnumerable> data) + { + var array = new T[rows * columns]; + foreach (var item in data) + { + array[(item.Item2 * rows) + item.Item1] = item.Item3; + } + + return new DenseColumnMajorMatrixStorage(rows, columns, array); + } + + public static DenseColumnMajorMatrixStorage OfColumnMajorEnumerable(int rows, int columns, IEnumerable data) + { + var arrayData = data as T[]; + if (arrayData != null) + { + return OfColumnMajorArray(rows, columns, arrayData); + } + + return new DenseColumnMajorMatrixStorage(rows, columns, data.ToArray()); + } + + public static DenseColumnMajorMatrixStorage OfRowMajorEnumerable(int rows, int columns, IEnumerable data) + { + return OfRowMajorArray(rows, columns, data as T[] ?? data.ToArray()); + } + + public static DenseColumnMajorMatrixStorage OfColumnEnumerables(int rows, int columns, IEnumerable> data) + { + var array = new T[rows * columns]; + using (var columnIterator = data.GetEnumerator()) + { + for (int column = 0; column < columns; column++) + { + if (!columnIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, columns)); + var arrayColumn = columnIterator.Current as T[]; + if (arrayColumn != null) + { + Array.Copy(arrayColumn, 0, array, column * rows, rows); + } + else + { + using (var rowIterator = columnIterator.Current.GetEnumerator()) + { + var end = (column + 1) * rows; + for (int index = column * rows; index < end; index++) + { + if (!rowIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, rows)); + array[index] = rowIterator.Current; + } + + if (rowIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, rows)); + } + } + } + + if (columnIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, columns)); + } + + return new DenseColumnMajorMatrixStorage(rows, columns, array); + } + + public static DenseColumnMajorMatrixStorage OfRowEnumerables(int rows, int columns, IEnumerable> data) + { + var array = new T[rows * columns]; + using (var rowIterator = data.GetEnumerator()) + { + for (int row = 0; row < rows; row++) + { + if (!rowIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, rows)); + using (var columnIterator = rowIterator.Current.GetEnumerator()) + { + for (int index = row; index < array.Length; index += rows) + { + if (!columnIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, columns)); + array[index] = columnIterator.Current; + } + + if (columnIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, columns)); + } + } + + if (rowIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, rows)); + } + + return new DenseColumnMajorMatrixStorage(rows, columns, array); + } + + // MATRIX COPY + + internal override void CopyToUnchecked(MatrixStorage target, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + CopyToUnchecked(denseTarget); + return; + } + + // FALL BACK + + for (int j = 0, offset = 0; j < ColumnCount; j++, offset += RowCount) + { + for (int i = 0; i < RowCount; i++) + { + target.At(i, j, Data[i + offset]); + } + } + } + + void CopyToUnchecked(DenseColumnMajorMatrixStorage target) + { + //Buffer.BlockCopy(Data, 0, target.Data, 0, Data.Length * System.Runtime.InteropServices.Marshal.SizeOf(typeof(T))); + Array.Copy(Data, 0, target.Data, 0, Data.Length); + } + + internal override void CopySubMatrixToUnchecked(MatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + CopySubMatrixToUnchecked(denseTarget, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount); + return; + } + + // TODO: Proper Sparse Implementation + + // FALL BACK + + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + int index = sourceRowIndex + j * RowCount; + for (int ii = targetRowIndex; ii < targetRowIndex + rowCount; ii++) + { + target.At(ii, jj, Data[index++]); + } + } + } + + void CopySubMatrixToUnchecked(DenseColumnMajorMatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount) + { + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + //Buffer.BlockCopy(Data, j*RowCount + sourceRowIndex, target.Data, jj*target.RowCount + targetRowIndex, rowCount * System.Runtime.InteropServices.Marshal.SizeOf(typeof(T))); + Array.Copy(Data, j * RowCount + sourceRowIndex, target.Data, jj * target.RowCount + targetRowIndex, rowCount); + } + } + + // ROW COPY + + internal override void CopySubRowToUnchecked(VectorStorage target, int rowIndex, int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData) + { + var targetDense = target as DenseVectorStorage; + if (targetDense != null) + { + for (int j = 0; j < columnCount; j++) + { + targetDense.Data[j + targetColumnIndex] = Data[(j + sourceColumnIndex) * RowCount + rowIndex]; + } + + return; + } + + // FALL BACK + + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + target.At(jj, Data[(j * RowCount) + rowIndex]); + } + } + + // COLUMN COPY + + internal override void CopySubColumnToUnchecked(VectorStorage target, int columnIndex, int sourceRowIndex, int targetRowIndex, int rowCount, + ExistingData existingData) + { + var targetDense = target as DenseVectorStorage; + if (targetDense != null) + { + Array.Copy(Data, columnIndex * RowCount + sourceRowIndex, targetDense.Data, targetRowIndex, rowCount); + return; + } + + // FALL BACK + + var offset = columnIndex * RowCount; + for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) + { + target.At(ii, Data[offset + i]); + } + } + + // TRANSPOSE + + internal override void TransposeToUnchecked(MatrixStorage target, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + TransposeToUnchecked(denseTarget); + return; + } + + var sparseTarget = target as SparseCompressedRowMatrixStorage; + if (sparseTarget != null) + { + TransposeToUnchecked(sparseTarget); + return; + } + + // FALL BACK + + for (int j = 0, offset = 0; j < ColumnCount; j++, offset += RowCount) + { + for (int i = 0; i < RowCount; i++) + { + target.At(j, i, Data[i + offset]); + } + } + } + + void TransposeToUnchecked(DenseColumnMajorMatrixStorage target) + { + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = 0; i < RowCount; i++) + { + target.Data[(i * ColumnCount) + j] = Data[index + i]; + } + } + } + + void TransposeToUnchecked(SparseCompressedRowMatrixStorage target) + { + var rowPointers = target.RowPointers; + var columnIndices = new List(); + var values = new List(); + + for (int j = 0; j < ColumnCount; j++) + { + rowPointers[j] = values.Count; + var index = j * RowCount; + for (int i = 0; i < RowCount; i++) + { + if (!Zero.Equals(Data[index + i])) + { + values.Add(Data[index + i]); + columnIndices.Add(i); + } + } + } + + rowPointers[ColumnCount] = values.Count; + target.ColumnIndices = columnIndices.ToArray(); + target.Values = values.ToArray(); + } + + internal override void TransposeSquareInplaceUnchecked() + { + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = 0; i < j; i++) + { + T swap = Data[index + i]; + Data[index + i] = Data[i * ColumnCount + j]; + Data[i * ColumnCount + j] = swap; + } + } + } + + // EXTRACT + + public override T[] ToRowMajorArray() + { + var ret = new T[Data.Length]; + for (int i = 0; i < RowCount; i++) + { + var offset = i * ColumnCount; + for (int j = 0; j < ColumnCount; j++) + { + ret[offset + j] = Data[(j * RowCount) + i]; + } + } + + return ret; + } + + public override T[] ToColumnMajorArray() + { + var ret = new T[Data.Length]; + Array.Copy(Data, 0, ret, 0, Data.Length); + return ret; + } + + public override T[][] ToRowArrays() + { + var ret = new T[RowCount][]; + CommonParallel.For(0, RowCount, Math.Max(4096 / ColumnCount, 32), (a, b) => + { + for (int i = a; i < b; i++) + { + var row = new T[ColumnCount]; + for (int j = 0; j < ColumnCount; j++) + { + row[j] = Data[j * RowCount + i]; + } + + ret[i] = row; + } + }); + return ret; + } + + public override T[][] ToColumnArrays() + { + var ret = new T[ColumnCount][]; + CommonParallel.For(0, ColumnCount, Math.Max(4096 / RowCount, 32), (a, b) => + { + for (int j = a; j < b; j++) + { + var column = new T[RowCount]; + Array.Copy(Data, j * RowCount, column, 0, RowCount); + ret[j] = column; + } + }); + return ret; + } + + public override T[,] ToArray() + { + var ret = new T[RowCount, ColumnCount]; + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + ret[i, j] = Data[(j * RowCount) + i]; + } + } + + return ret; + } + + public override T[] AsColumnMajorArray() + { + return Data; + } + + // ENUMERATION + + public override IEnumerable Enumerate() + { + return Data; + } + + public override IEnumerable> EnumerateIndexed() + { + int index = 0; + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + yield return new Tuple(i, j, Data[index]); + index++; + } + } + } + + public override IEnumerable EnumerateNonZero() + { + return Data.Where(x => !Zero.Equals(x)); + } + + public override IEnumerable> EnumerateNonZeroIndexed() + { + int index = 0; + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + var x = Data[index]; + if (!Zero.Equals(x)) + { + yield return new Tuple(i, j, x); + } + + index++; + } + } + } + + // FIND + + public override Tuple Find(Func predicate, Zeros zeros) + { + for (int i = 0; i < Data.Length; i++) + { + if (predicate(Data[i])) + { + int row, column; + RowColumnAtIndex(i, out row, out column); + return new Tuple(row, column, Data[i]); + } + } + + return null; + } + + internal override Tuple Find2Unchecked(MatrixStorage other, Func predicate, Zeros zeros) + { + var denseOther = other as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + for (int i = 0; i < Data.Length; i++) + { + if (predicate(Data[i], otherData[i])) + { + int row, column; + RowColumnAtIndex(i, out row, out column); + return new Tuple(row, column, Data[i], otherData[i]); + + } + } + + return null; + } + + var diagonalOther = other as DiagonalMatrixStorage; + if (diagonalOther != null) + { + TOther[] otherData = diagonalOther.Data; + TOther otherZero = BuilderInstance.Matrix.Zero; + int k = 0; + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + if (predicate(Data[k], i == j ? otherData[i] : otherZero)) + { + return new Tuple(i, j, Data[k], i == j ? otherData[i] : otherZero); + } + + k++; + } + } + + return null; + } + + var sparseOther = other as SparseCompressedRowMatrixStorage; + if (sparseOther != null) + { + int[] otherRowPointers = sparseOther.RowPointers; + int[] otherColumnIndices = sparseOther.ColumnIndices; + TOther[] otherValues = sparseOther.Values; + TOther otherZero = BuilderInstance.Matrix.Zero; + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + if (k < otherRowPointers[row + 1] && otherColumnIndices[k] == col) + { + if (predicate(Data[col * RowCount + row], otherValues[k])) + { + return new Tuple(row, col, Data[col * RowCount + row], otherValues[k]); + } + + k++; + } + else + { + if (predicate(Data[col * RowCount + row], otherZero)) + { + return new Tuple(row, col, Data[col * RowCount + row], otherValues[k]); + } + } + } + } + + return null; + } + + // FALL BACK + + return base.Find2Unchecked(other, predicate, zeros); + } + + // FUNCTIONAL COMBINATORS: MAP + + public override void MapInplace(Func f, Zeros zeros) + { + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + Data[i] = f(Data[i]); + } + }); + } + + public override void MapIndexedInplace(Func f, Zeros zeros) + { + CommonParallel.For(0, ColumnCount, Math.Max(4096 / RowCount, 32), (a, b) => + { + int index = a * RowCount; + for (int j = a; j < b; j++) + { + for (int i = 0; i < RowCount; i++) + { + Data[index] = f(i, j, Data[index]); + index++; + } + } + }); + } + + internal override void MapToUnchecked(MatrixStorage target, Func f, + Zeros zeros, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + denseTarget.Data[i] = f(Data[i]); + } + }); + return; + } + + // FALL BACK + + int index = 0; + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + target.At(i, j, f(Data[index++])); + } + } + } + + internal override void MapIndexedToUnchecked(MatrixStorage target, Func f, + Zeros zeros, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + CommonParallel.For(0, ColumnCount, Math.Max(4096 / RowCount, 32), (a, b) => + { + int index = a * RowCount; + for (int j = a; j < b; j++) + { + for (int i = 0; i < RowCount; i++) + { + denseTarget.Data[index] = f(i, j, Data[index]); + index++; + } + } + }); + return; + } + + // FALL BACK + + int index2 = 0; + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + target.At(i, j, f(i, j, Data[index2++])); + } + } + } + + internal override void MapSubMatrixIndexedToUnchecked(MatrixStorage target, Func f, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + Zeros zeros, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + CommonParallel.For(0, columnCount, Math.Max(4096 / rowCount, 32), (a, b) => + { + for (int j = a; j < b; j++) + { + int sourceIndex = sourceRowIndex + (j + sourceColumnIndex) * RowCount; + int targetIndex = targetRowIndex + (j + targetColumnIndex) * target.RowCount; + for (int i = 0; i < rowCount; i++) + { + denseTarget.Data[targetIndex++] = f(targetRowIndex + i, targetColumnIndex + j, Data[sourceIndex++]); + } + } + }); + return; + } + + // TODO: Proper Sparse Implementation + + // FALL BACK + + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + int index = sourceRowIndex + j * RowCount; + for (int ii = targetRowIndex; ii < targetRowIndex + rowCount; ii++) + { + target.At(ii, jj, f(ii, jj, Data[index++])); + } + } + } + + // FUNCTIONAL COMBINATORS: FOLD + + internal override void FoldByRowUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + for (int i = 0; i < RowCount; i++) + { + TU s = state[i]; + for (int j = 0; j < ColumnCount; j++) + { + s = f(s, Data[j * RowCount + i]); + } + + target[i] = finalize(s, ColumnCount); + } + } + + internal override void FoldByColumnUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + for (int j = 0; j < ColumnCount; j++) + { + int offset = j * RowCount; + TU s = state[j]; + for (int i = 0; i < RowCount; i++) + { + s = f(s, Data[offset + i]); + } + + target[j] = finalize(s, RowCount); + } + } + + internal override TState Fold2Unchecked(MatrixStorage other, Func f, TState state, Zeros zeros) + { + var denseOther = other as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + for (int i = 0; i < Data.Length; i++) + { + state = f(state, Data[i], otherData[i]); + } + + return state; + } + + var diagonalOther = other as DiagonalMatrixStorage; + if (diagonalOther != null) + { + TOther[] otherData = diagonalOther.Data; + TOther otherZero = BuilderInstance.Matrix.Zero; + int k = 0; + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + state = f(state, Data[k], i == j ? otherData[i] : otherZero); + k++; + } + } + + return state; + } + + var sparseOther = other as SparseCompressedRowMatrixStorage; + if (sparseOther != null) + { + int[] otherRowPointers = sparseOther.RowPointers; + int[] otherColumnIndices = sparseOther.ColumnIndices; + TOther[] otherValues = sparseOther.Values; + TOther otherZero = BuilderInstance.Matrix.Zero; + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + if (k < otherRowPointers[row + 1] && otherColumnIndices[k] == col) + { + state = f(state, Data[col * RowCount + row], otherValues[k++]); + } + else + { + state = f(state, Data[col * RowCount + row], otherZero); + } + } + } + + return state; + } + + // FALL BACK + + return base.Fold2Unchecked(other, f, state, zeros); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Storage/DenseVectorStorage.cs b/MathNet.Numerics/LinearAlgebra/Storage/DenseVectorStorage.cs new file mode 100644 index 0000000..8f57cdb --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/DenseVectorStorage.cs @@ -0,0 +1,615 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/LinearAlgebra")] +public class DenseVectorStorage : VectorStorage + where T : struct, IEquatable, IFormattable +{ + // [ruegg] public fields are OK here + + [DataMember(Order = 1)] + public readonly T[] Data; + + internal DenseVectorStorage(int length) + : base(length) + { + Data = new T[length]; + } + + internal DenseVectorStorage(int length, T[] data) + : base(length) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (data.Length != length) + { + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, length)); + } + + Data = data; + } + + /// + /// True if the vector storage format is dense. + /// + public override bool IsDense + { + get { return true; } + } + + /// + /// Retrieves the requested element without range checking. + /// + public override T At(int index) + { + return Data[index]; + } + + /// + /// Sets the element without range checking. + /// + public override void At(int index, T value) + { + Data[index] = value; + } + + // CLEARING + + public override void Clear() + { + Array.Clear(Data, 0, Data.Length); + } + + public override void Clear(int index, int count) + { + Array.Clear(Data, index, count); + } + + // INITIALIZATION + + public static DenseVectorStorage OfVector(VectorStorage vector) + { + var storage = new DenseVectorStorage(vector.Length); + vector.CopyToUnchecked(storage, ExistingData.AssumeZeros); + return storage; + } + + public static DenseVectorStorage OfValue(int length, T value) + { + if (length < 1) + { + throw new ArgumentOutOfRangeException(nameof(length), string.Format(Resources.ArgumentLessThanOne, length)); + } + + var data = new T[length]; + CommonParallel.For(0, data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + data[i] = value; + } + }); + return new DenseVectorStorage(length, data); + } + + public static DenseVectorStorage OfInit(int length, Func init) + { + if (length < 1) + { + throw new ArgumentOutOfRangeException(nameof(length), string.Format(Resources.ArgumentLessThanOne, length)); + } + + var data = new T[length]; + CommonParallel.For(0, data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + data[i] = init(i); + } + }); + return new DenseVectorStorage(length, data); + } + + public static DenseVectorStorage OfEnumerable(IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var arrayData = data as T[]; + if (arrayData != null) + { + var copy = new T[arrayData.Length]; + Array.Copy(arrayData, 0, copy, 0, arrayData.Length); + return new DenseVectorStorage(copy.Length, copy); + } + + var array = data.ToArray(); + return new DenseVectorStorage(array.Length, array); + } + + public static DenseVectorStorage OfIndexedEnumerable(int length, IEnumerable> data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var array = new T[length]; + foreach (var item in data) + { + array[item.Item1] = item.Item2; + } + + return new DenseVectorStorage(array.Length, array); + } + + // VECTOR COPY + + internal override void CopyToUnchecked(VectorStorage target, ExistingData existingData) + { + var denseTarget = target as DenseVectorStorage; + if (denseTarget != null) + { + if (!ReferenceEquals(this, denseTarget)) + { + Array.Copy(Data, 0, denseTarget.Data, 0, Data.Length); + } + + return; + } + + var sparseTarget = target as SparseVectorStorage; + if (sparseTarget != null) + { + var indices = new List(); + var values = new List(); + + for (int i = 0; i < Data.Length; i++) + { + var item = Data[i]; + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(i); + } + } + + sparseTarget.Indices = indices.ToArray(); + sparseTarget.Values = values.ToArray(); + sparseTarget.ValueCount = values.Count; + return; + } + + // FALL BACK + + for (int i = 0; i < Data.Length; i++) + { + target.At(i, Data[i]); + } + } + + // ROW COPY + + internal override void CopyToRowUnchecked(MatrixStorage target, int rowIndex, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + for (int j = 0; j < Data.Length; j++) + { + denseTarget.Data[j * target.RowCount + rowIndex] = Data[j]; + } + + return; + } + + // FALL BACK + + for (int j = 0; j < Length; j++) + { + target.At(rowIndex, j, Data[j]); + } + } + + // COLUMN COPY + + internal override void CopyToColumnUnchecked(MatrixStorage target, int columnIndex, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + Array.Copy(Data, 0, denseTarget.Data, columnIndex * denseTarget.RowCount, Data.Length); + return; + } + + // FALL BACK + + for (int i = 0; i < Length; i++) + { + target.At(i, columnIndex, Data[i]); + } + } + + // SUB-VECTOR COPY + + internal override void CopySubVectorToUnchecked(VectorStorage target, + int sourceIndex, int targetIndex, int count, ExistingData existingData) + { + var denseTarget = target as DenseVectorStorage; + if (denseTarget != null) + { + Array.Copy(Data, sourceIndex, denseTarget.Data, targetIndex, count); + return; + } + + // FALL BACK + + base.CopySubVectorToUnchecked(target, sourceIndex, targetIndex, count, existingData); + } + + // SUB-ROW COPY + + internal override void CopyToSubRowUnchecked(MatrixStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + for (int j = 0; j < Data.Length; j++) + { + denseTarget.Data[(j + targetColumnIndex) * target.RowCount + rowIndex] = Data[j + sourceColumnIndex]; + } + + return; + } + + // FALL BACK + + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + target.At(rowIndex, jj, Data[j]); + } + } + + // SUB-COLUMN COPY + + internal override void CopyToSubColumnUnchecked(MatrixStorage target, int columnIndex, + int sourceRowIndex, int targetRowIndex, int rowCount, ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + Array.Copy(Data, sourceRowIndex, denseTarget.Data, columnIndex * denseTarget.RowCount + targetRowIndex, rowCount); + return; + } + + // FALL BACK + + for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) + { + target.At(ii, columnIndex, Data[i]); + } + } + + // EXTRACT + + public override T[] ToArray() + { + var ret = new T[Data.Length]; + Array.Copy(Data, 0, ret, 0, Data.Length); + return ret; + } + + public override T[] AsArray() + { + return Data; + } + + // ENUMERATION + + public override IEnumerable Enumerate() + { + return Data; + } + + public override IEnumerable> EnumerateIndexed() + { + return Data.Select((t, i) => new Tuple(i, t)); + } + + public override IEnumerable EnumerateNonZero() + { + return Data.Where(x => !Zero.Equals(x)); + } + + public override IEnumerable> EnumerateNonZeroIndexed() + { + for (var i = 0; i < Data.Length; i++) + { + if (!Zero.Equals(Data[i])) + { + yield return new Tuple(i, Data[i]); + } + } + } + + // FIND + + public override Tuple Find(Func predicate, Zeros zeros) + { + for (int i = 0; i < Data.Length; i++) + { + if (predicate(Data[i])) + { + return new Tuple(i, Data[i]); + } + } + + return null; + } + + internal override Tuple Find2Unchecked(VectorStorage other, Func predicate, Zeros zeros) + { + var denseOther = other as DenseVectorStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + for (int i = 0; i < Data.Length; i++) + { + if (predicate(Data[i], otherData[i])) + { + return new Tuple(i, Data[i], otherData[i]); + + } + } + + return null; + } + + var sparseOther = other as SparseVectorStorage; + if (sparseOther != null) + { + int[] otherIndices = sparseOther.Indices; + TOther[] otherValues = sparseOther.Values; + int otherValueCount = sparseOther.ValueCount; + TOther otherZero = BuilderInstance.Matrix.Zero; + int k = 0; + for (int i = 0; i < Data.Length; i++) + { + if (k < otherValueCount && otherIndices[k] == i) + { + if (predicate(Data[i], otherValues[k])) + { + return new Tuple(i, Data[i], otherValues[k]); + } + + k++; + } + else + { + if (predicate(Data[i], otherZero)) + { + return new Tuple(i, Data[i], otherZero); + } + } + } + + return null; + } + + // FALLBACK + + return base.Find2Unchecked(other, predicate, zeros); + } + + // FUNCTIONAL COMBINATORS: MAP + + public override void MapInplace(Func f, Zeros zeros) + { + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + Data[i] = f(Data[i]); + } + }); + } + + public override void MapIndexedInplace(Func f, Zeros zeros) + { + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + Data[i] = f(i, Data[i]); + } + }); + } + + internal override void MapToUnchecked(VectorStorage target, Func f, Zeros zeros, ExistingData existingData) + { + var denseTarget = target as DenseVectorStorage; + if (denseTarget != null) + { + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + denseTarget.Data[i] = f(Data[i]); + } + }); + return; + } + + // FALL BACK + + for (int i = 0; i < Length; i++) + { + target.At(i, f(Data[i])); + } + } + + internal override void MapIndexedToUnchecked(VectorStorage target, Func f, Zeros zeros, ExistingData existingData) + { + var denseTarget = target as DenseVectorStorage; + if (denseTarget != null) + { + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + denseTarget.Data[i] = f(i, Data[i]); + } + }); + return; + } + + // FALL BACK + + for (int i = 0; i < Length; i++) + { + target.At(i, f(i, Data[i])); + } + } + + internal override void Map2ToUnchecked(VectorStorage target, VectorStorage other, Func f, Zeros zeros, ExistingData existingData) + { + if (target is SparseVectorStorage) + { + // Recursive to dense target at first, since the operation is + // effectively dense anyway because at least one operand is dense + var intermediate = new DenseVectorStorage(target.Length); + Map2ToUnchecked(intermediate, other, f, zeros, ExistingData.AssumeZeros); + intermediate.CopyTo(target, existingData); + return; + } + + var denseTarget = target as DenseVectorStorage; + var denseOther = other as DenseVectorStorage; + if (denseTarget != null && denseOther != null) + { + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + denseTarget.Data[i] = f(Data[i], denseOther.Data[i]); + } + }); + + return; + } + + var sparseOther = other as SparseVectorStorage; + if (denseTarget != null && sparseOther != null) + { + T[] targetData = denseTarget.Data; + int[] otherIndices = sparseOther.Indices; + T[] otherValues = sparseOther.Values; + int otherValueCount = sparseOther.ValueCount; + + int k = 0; + for (int i = 0; i < Data.Length; i++) + { + if (k < otherValueCount && otherIndices[k] == i) + { + targetData[i] = f(Data[i], otherValues[k]); + k++; + } + else + { + targetData[i] = f(Data[i], Zero); + } + } + + return; + } + + base.Map2ToUnchecked(target, other, f, zeros, existingData); + } + + // FUNCTIONAL COMBINATORS: FOLD + + internal override TState Fold2Unchecked(VectorStorage other, Func f, TState state, Zeros zeros) + { + var denseOther = other as DenseVectorStorage; + if (denseOther != null) + { + var otherData = denseOther.Data; + for (int i = 0; i < Data.Length; i++) + { + state = f(state, Data[i], otherData[i]); + } + + return state; + } + + var sparseOther = other as SparseVectorStorage; + if (sparseOther != null) + { + int[] otherIndices = sparseOther.Indices; + TOther[] otherValues = sparseOther.Values; + int otherValueCount = sparseOther.ValueCount; + TOther otherZero = BuilderInstance.Vector.Zero; + + int k = 0; + for (int i = 0; i < Data.Length; i++) + { + if (k < otherValueCount && otherIndices[k] == i) + { + state = f(state, Data[i], otherValues[k]); + k++; + } + else + { + state = f(state, Data[i], otherZero); + } + } + + return state; + } + + return base.Fold2Unchecked(other, f, state, zeros); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Storage/DiagonalMatrixStorage.cs b/MathNet.Numerics/LinearAlgebra/Storage/DiagonalMatrixStorage.cs new file mode 100644 index 0000000..7736df9 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/DiagonalMatrixStorage.cs @@ -0,0 +1,1226 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/LinearAlgebra")] +public class DiagonalMatrixStorage : MatrixStorage + where T : struct, IEquatable, IFormattable +{ + // [ruegg] public fields are OK here + + [DataMember(Order = 1)] + public readonly T[] Data; + + internal DiagonalMatrixStorage(int rows, int columns) + : base(rows, columns) + { + Data = new T[Math.Min(rows, columns)]; + } + + internal DiagonalMatrixStorage(int rows, int columns, T[] data) + : base(rows, columns) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (data.Length != Math.Min(rows, columns)) + { + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, Math.Min(rows, columns))); + } + + Data = data; + } + + /// + /// True if the matrix storage format is dense. + /// + public override bool IsDense + { + get { return false; } + } + + /// + /// True if all fields of this matrix can be set to any value. + /// False if some fields are fixed, like on a diagonal matrix. + /// + public override bool IsFullyMutable + { + get { return false; } + } + + /// + /// True if the specified field can be set to any value. + /// False if the field is fixed, like an off-diagonal field on a diagonal matrix. + /// + public override bool IsMutableAt(int row, int column) + { + return row == column; + } + + /// + /// Retrieves the requested element without range checking. + /// + public override T At(int row, int column) + { + return row == column ? Data[row] : Zero; + } + + /// + /// Sets the element without range checking. + /// + public override void At(int row, int column, T value) + { + if (row == column) + { + Data[row] = value; + } + else if (!Zero.Equals(value)) + { + throw new IndexOutOfRangeException("Cannot set an off-diagonal element in a diagonal matrix."); + } + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + var hashNum = Math.Min(Data.Length, 25); + int hash = 17; + unchecked + { + for (var i = 0; i < hashNum; i++) + { + hash = hash * 31 + Data[i].GetHashCode(); + } + } + + return hash; + } + + // CLEARING + + public override void Clear() + { + Array.Clear(Data, 0, Data.Length); + } + + internal override void ClearUnchecked(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + var beginInclusive = Math.Max(rowIndex, columnIndex); + var endExclusive = Math.Min(rowIndex + rowCount, columnIndex + columnCount); + if (endExclusive > beginInclusive) + { + Array.Clear(Data, beginInclusive, endExclusive - beginInclusive); + } + } + + internal override void ClearRowsUnchecked(int[] rowIndices) + { + for (int i = 0; i < rowIndices.Length; i++) + { + Data[rowIndices[i]] = Zero; + } + } + + internal override void ClearColumnsUnchecked(int[] columnIndices) + { + for (int i = 0; i < columnIndices.Length; i++) + { + Data[columnIndices[i]] = Zero; + } + } + + // INITIALIZATION + + public static DiagonalMatrixStorage OfMatrix(MatrixStorage matrix) + { + var storage = new DiagonalMatrixStorage(matrix.RowCount, matrix.ColumnCount); + matrix.CopyToUnchecked(storage, ExistingData.AssumeZeros); + return storage; + } + + public static DiagonalMatrixStorage OfArray(T[,] array) + { + var storage = new DiagonalMatrixStorage(array.GetLength(0), array.GetLength(1)); + for (var i = 0; i < storage.RowCount; i++) + { + for (var j = 0; j < storage.ColumnCount; j++) + { + if (i == j) + { + storage.Data[i] = array[i, j]; + } + else if (!Zero.Equals(array[i, j])) + { + throw new ArgumentException("Cannot set an off-diagonal element in a diagonal matrix."); + } + } + } + + return storage; + } + + public static DiagonalMatrixStorage OfValue(int rows, int columns, T diagonalValue) + { + var storage = new DiagonalMatrixStorage(rows, columns); + for (var i = 0; i < storage.Data.Length; i++) + { + storage.Data[i] = diagonalValue; + } + + return storage; + } + + public static DiagonalMatrixStorage OfInit(int rows, int columns, Func init) + { + var storage = new DiagonalMatrixStorage(rows, columns); + for (var i = 0; i < storage.Data.Length; i++) + { + storage.Data[i] = init(i); + } + + return storage; + } + + public static DiagonalMatrixStorage OfEnumerable(int rows, int columns, IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var arrayData = data as T[]; + if (arrayData != null) + { + var copy = new T[arrayData.Length]; + Array.Copy(arrayData, 0, copy, 0, arrayData.Length); + return new DiagonalMatrixStorage(rows, columns, copy); + } + + return new DiagonalMatrixStorage(rows, columns, data.ToArray()); + } + + public static DiagonalMatrixStorage OfIndexedEnumerable(int rows, int columns, IEnumerable> data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var storage = new DiagonalMatrixStorage(rows, columns); + foreach (var item in data) + { + storage.Data[item.Item1] = item.Item2; + } + + return storage; + } + + // MATRIX COPY + + internal override void CopyToUnchecked(MatrixStorage target, ExistingData existingData) + { + var diagonalTarget = target as DiagonalMatrixStorage; + if (diagonalTarget != null) + { + CopyToUnchecked(diagonalTarget); + return; + } + + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + CopyToUnchecked(denseTarget, existingData); + return; + } + + var sparseTarget = target as SparseCompressedRowMatrixStorage; + if (sparseTarget != null) + { + CopyToUnchecked(sparseTarget, existingData); + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear) + { + target.Clear(); + } + + for (int i = 0; i < Data.Length; i++) + { + target.At(i, i, Data[i]); + } + } + + void CopyToUnchecked(DiagonalMatrixStorage target) + { + //Buffer.BlockCopy(Data, 0, target.Data, 0, Data.Length * System.Runtime.InteropServices.Marshal.SizeOf(typeof(T))); + Array.Copy(Data, 0, target.Data, 0, Data.Length); + } + + void CopyToUnchecked(SparseCompressedRowMatrixStorage target, ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.Clear(); + } + + for (int i = 0; i < Data.Length; i++) + { + target.At(i, i, Data[i]); + } + } + + void CopyToUnchecked(DenseColumnMajorMatrixStorage target, ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.Clear(); + } + + for (int i = 0; i < Data.Length; i++) + { + target.Data[i * (target.RowCount + 1)] = Data[i]; + } + } + + internal override void CopySubMatrixToUnchecked(MatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData) + { + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + CopySubMatrixToUnchecked(denseTarget, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount, existingData); + return; + } + + var diagonalTarget = target as DiagonalMatrixStorage; + if (diagonalTarget != null) + { + CopySubMatrixToUnchecked(diagonalTarget, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount); + return; + } + + // TODO: Proper Sparse Implementation + + // FALL BACK + + if (existingData == ExistingData.Clear) + { + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + } + + if (sourceRowIndex == sourceColumnIndex) + { + for (var i = 0; i < Math.Min(columnCount, rowCount); i++) + { + target.At(targetRowIndex + i, targetColumnIndex + i, Data[sourceRowIndex + i]); + } + } + else if (sourceRowIndex > sourceColumnIndex && sourceColumnIndex + columnCount > sourceRowIndex) + { + // column by column, but skip resulting zero columns at the beginning + int columnInit = sourceRowIndex - sourceColumnIndex; + for (var i = 0; i < Math.Min(columnCount - columnInit, rowCount); i++) + { + target.At(targetRowIndex + i, columnInit + targetColumnIndex + i, Data[sourceRowIndex + i]); + } + } + else if (sourceRowIndex < sourceColumnIndex && sourceRowIndex + rowCount > sourceColumnIndex) + { + // row by row, but skip resulting zero rows at the beginning + int rowInit = sourceColumnIndex - sourceRowIndex; + for (var i = 0; i < Math.Min(columnCount, rowCount - rowInit); i++) + { + target.At(rowInit + targetRowIndex + i, targetColumnIndex + i, Data[sourceColumnIndex + i]); + } + } + } + + void CopySubMatrixToUnchecked(DiagonalMatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount) + { + if (sourceRowIndex - sourceColumnIndex != targetRowIndex - targetColumnIndex) + { + if (Data.Any(x => !Zero.Equals(x))) + { + throw new NotSupportedException(); + } + + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + return; + } + + var beginInclusive = Math.Max(sourceRowIndex, sourceColumnIndex); + var endExclusive = Math.Min(sourceRowIndex + rowCount, sourceColumnIndex + columnCount); + if (endExclusive > beginInclusive) + { + var beginTarget = Math.Max(targetRowIndex, targetColumnIndex); + Array.Copy(Data, beginInclusive, target.Data, beginTarget, endExclusive - beginInclusive); + } + } + + void CopySubMatrixToUnchecked(DenseColumnMajorMatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + } + + if (sourceRowIndex > sourceColumnIndex && sourceColumnIndex + columnCount > sourceRowIndex) + { + // column by column, but skip resulting zero columns at the beginning + + int columnInit = sourceRowIndex - sourceColumnIndex; + int offset = (columnInit + targetColumnIndex) * target.RowCount + targetRowIndex; + int step = target.RowCount + 1; + int end = Math.Min(columnCount - columnInit, rowCount) + sourceRowIndex; + + for (int i = sourceRowIndex, j = offset; i < end; i++, j += step) + { + target.Data[j] = Data[i]; + } + } + else if (sourceRowIndex < sourceColumnIndex && sourceRowIndex + rowCount > sourceColumnIndex) + { + // row by row, but skip resulting zero rows at the beginning + + int rowInit = sourceColumnIndex - sourceRowIndex; + int offset = targetColumnIndex * target.RowCount + rowInit + targetRowIndex; + int step = target.RowCount + 1; + int end = Math.Min(columnCount, rowCount - rowInit) + sourceColumnIndex; + + for (int i = sourceColumnIndex, j = offset; i < end; i++, j += step) + { + target.Data[j] = Data[i]; + } + } + else + { + int offset = targetColumnIndex * target.RowCount + targetRowIndex; + int step = target.RowCount + 1; + var end = Math.Min(columnCount, rowCount) + sourceRowIndex; + + for (int i = sourceRowIndex, j = offset; i < end; i++, j += step) + { + target.Data[j] = Data[i]; + } + } + } + + // ROW COPY + + internal override void CopySubRowToUnchecked(VectorStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.Clear(targetColumnIndex, columnCount); + } + + if (rowIndex >= sourceColumnIndex && rowIndex < sourceColumnIndex + columnCount && rowIndex < Data.Length) + { + target.At(rowIndex - sourceColumnIndex + targetColumnIndex, Data[rowIndex]); + } + } + + // COLUMN COPY + + internal override void CopySubColumnToUnchecked(VectorStorage target, int columnIndex, + int sourceRowIndex, int targetRowIndex, int rowCount, ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.Clear(targetRowIndex, rowCount); + } + + if (columnIndex >= sourceRowIndex && columnIndex < sourceRowIndex + rowCount && columnIndex < Data.Length) + { + target.At(columnIndex - sourceRowIndex + targetRowIndex, Data[columnIndex]); + } + } + + // TRANSPOSE + + internal override void TransposeToUnchecked(MatrixStorage target, ExistingData existingData) + { + CopyToUnchecked(target, existingData); + } + + internal override void TransposeSquareInplaceUnchecked() + { + // nothing to do + } + + // EXTRACT + + public override T[] ToRowMajorArray() + { + var ret = new T[RowCount * ColumnCount]; + var stride = ColumnCount + 1; + for (int i = 0; i < Data.Length; i++) + { + ret[i * stride] = Data[i]; + } + + return ret; + } + + public override T[] ToColumnMajorArray() + { + var ret = new T[RowCount * ColumnCount]; + var stride = RowCount + 1; + for (int i = 0; i < Data.Length; i++) + { + ret[i * stride] = Data[i]; + } + + return ret; + } + + public override T[][] ToRowArrays() + { + var ret = new T[RowCount][]; + for (int i = 0; i < RowCount; i++) + { + ret[i] = new T[ColumnCount]; + } + + for (int i = 0; i < Data.Length; i++) + { + ret[i][i] = Data[i]; + } + + return ret; + } + + public override T[][] ToColumnArrays() + { + var ret = new T[ColumnCount][]; + for (int j = 0; j < ColumnCount; j++) + { + ret[j] = new T[RowCount]; + } + + for (int i = 0; i < Data.Length; i++) + { + ret[i][i] = Data[i]; + } + + return ret; + } + + public override T[,] ToArray() + { + var ret = new T[RowCount, ColumnCount]; + for (int i = 0; i < Data.Length; i++) + { + ret[i, i] = Data[i]; + } + + return ret; + } + + // ENUMERATION + + public override IEnumerable Enumerate() + { + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + // PERF: consider to break up loop to avoid branching + yield return i == j ? Data[i] : Zero; + } + } + } + + public override IEnumerable> EnumerateIndexed() + { + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + // PERF: consider to break up loop to avoid branching + yield return i == j + ? new Tuple(i, i, Data[i]) + : new Tuple(i, j, Zero); + } + } + } + + public override IEnumerable EnumerateNonZero() + { + return Data.Where(x => !Zero.Equals(x)); + } + + public override IEnumerable> EnumerateNonZeroIndexed() + { + for (int i = 0; i < Data.Length; i++) + { + if (!Zero.Equals(Data[i])) + { + yield return new Tuple(i, i, Data[i]); + } + } + } + + // FIND + + public override Tuple Find(Func predicate, Zeros zeros) + { + for (int i = 0; i < Data.Length; i++) + { + if (predicate(Data[i])) + { + return new Tuple(i, i, Data[i]); + } + } + + if (zeros == Zeros.Include && (RowCount > 1 || ColumnCount > 1)) + { + if (predicate(Zero)) + { + return new Tuple(RowCount > 1 ? 1 : 0, RowCount > 1 ? 0 : 1, Zero); + } + } + + return null; + } + + internal override Tuple Find2Unchecked(MatrixStorage other, Func predicate, Zeros zeros) + { + var denseOther = other as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + int k = 0; + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + if (predicate(i == j ? Data[i] : Zero, otherData[k])) + { + return new Tuple(i, j, i == j ? Data[i] : Zero, otherData[k]); + } + + k++; + } + } + + return null; + } + + var diagonalOther = other as DiagonalMatrixStorage; + if (diagonalOther != null) + { + TOther[] otherData = diagonalOther.Data; + for (int i = 0; i < Data.Length; i++) + { + if (predicate(Data[i], otherData[i])) + { + return new Tuple(i, i, Data[i], otherData[i]); + } + } + + if (zeros == Zeros.Include && (RowCount > 1 || ColumnCount > 1)) + { + TOther otherZero = BuilderInstance.Matrix.Zero; + if (predicate(Zero, otherZero)) + { + return new Tuple(RowCount > 1 ? 1 : 0, RowCount > 1 ? 0 : 1, Zero, otherZero); + } + } + + return null; + } + + var sparseOther = other as SparseCompressedRowMatrixStorage; + if (sparseOther != null) + { + int[] otherRowPointers = sparseOther.RowPointers; + int[] otherColumnIndices = sparseOther.ColumnIndices; + TOther[] otherValues = sparseOther.Values; + TOther otherZero = BuilderInstance.Matrix.Zero; + for (int row = 0; row < RowCount; row++) + { + bool diagonal = false; + var startIndex = otherRowPointers[row]; + var endIndex = otherRowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + if (otherColumnIndices[j] == row) + { + diagonal = true; + if (predicate(Data[row], otherValues[j])) + { + return new Tuple(row, row, Data[row], otherValues[j]); + } + } + else + { + if (predicate(Zero, otherValues[j])) + { + return new Tuple(row, otherColumnIndices[j], Zero, otherValues[j]); + } + } + } + + if (!diagonal && row < ColumnCount) + { + if (predicate(Data[row], otherZero)) + { + return new Tuple(row, row, Data[row], otherZero); + } + } + } + + if (zeros == Zeros.Include && sparseOther.ValueCount < (RowCount * ColumnCount)) + { + if (predicate(Zero, otherZero)) + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + if (k < otherRowPointers[row + 1] && otherColumnIndices[k] == col) + { + k++; + } + else if (row != col) + { + return new Tuple(row, col, Zero, otherZero); + } + } + } + } + } + + return null; + } + + // FALL BACK + + return base.Find2Unchecked(other, predicate, zeros); + } + + // FUNCTIONAL COMBINATORS: MAP + + public override void MapInplace(Func f, Zeros zeros) + { + if (zeros == Zeros.Include) + { + throw new NotSupportedException("Cannot map non-zero off-diagonal values into a diagonal matrix"); + } + + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + Data[i] = f(Data[i]); + } + }); + } + + public override void MapIndexedInplace(Func f, Zeros zeros) + { + if (zeros == Zeros.Include) + { + throw new NotSupportedException("Cannot map non-zero off-diagonal values into a diagonal matrix"); + } + + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + Data[i] = f(i, i, Data[i]); + } + }); + } + + internal override void MapToUnchecked(MatrixStorage target, Func f, + Zeros zeros, ExistingData existingData) + { + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(Zero)); + + var diagonalTarget = target as DiagonalMatrixStorage; + if (diagonalTarget != null) + { + if (processZeros) + { + throw new NotSupportedException("Cannot map non-zero off-diagonal values into a diagonal matrix"); + } + + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + diagonalTarget.Data[i] = f(Data[i]); + } + }); + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear && !processZeros) + { + target.Clear(); + } + + if (processZeros) + { + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + target.At(i, j, f(i == j ? Data[i] : Zero)); + } + } + } + else + { + for (int i = 0; i < Data.Length; i++) + { + target.At(i, i, f(Data[i])); + } + } + } + + internal override void MapIndexedToUnchecked(MatrixStorage target, Func f, + Zeros zeros, ExistingData existingData) + { + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero)); + + var diagonalTarget = target as DiagonalMatrixStorage; + if (diagonalTarget != null) + { + if (processZeros) + { + throw new NotSupportedException("Cannot map non-zero off-diagonal values into a diagonal matrix"); + } + + CommonParallel.For(0, Data.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + diagonalTarget.Data[i] = f(i, i, Data[i]); + } + }); + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear && !processZeros) + { + target.Clear(); + } + + if (processZeros) + { + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + target.At(i, j, f(i, j, i == j ? Data[i] : Zero)); + } + } + } + else + { + for (int i = 0; i < Data.Length; i++) + { + target.At(i, i, f(i, i, Data[i])); + } + } + } + + internal override void MapSubMatrixIndexedToUnchecked(MatrixStorage target, Func f, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + Zeros zeros, ExistingData existingData) + { + var diagonalTarget = target as DiagonalMatrixStorage; + if (diagonalTarget != null) + { + MapSubMatrixIndexedToUnchecked(diagonalTarget, f, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount, zeros); + return; + } + + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + MapSubMatrixIndexedToUnchecked(denseTarget, f, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount, zeros, existingData); + return; + } + + // TODO: Proper Sparse Implementation + + // FALL BACK + + if (existingData == ExistingData.Clear) + { + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + } + + if (sourceRowIndex == sourceColumnIndex) + { + int targetRow = targetRowIndex; + int targetColumn = targetColumnIndex; + for (var i = 0; i < Math.Min(columnCount, rowCount); i++) + { + target.At(targetRow, targetColumn, f(targetRow, targetColumn, Data[sourceRowIndex + i])); + targetRow++; + targetColumn++; + } + } + else if (sourceRowIndex > sourceColumnIndex && sourceColumnIndex + columnCount > sourceRowIndex) + { + // column by column, but skip resulting zero columns at the beginning + int columnInit = sourceRowIndex - sourceColumnIndex; + int targetRow = targetRowIndex; + int targetColumn = targetColumnIndex + columnInit; + for (var i = 0; i < Math.Min(columnCount - columnInit, rowCount); i++) + { + target.At(targetRow, targetColumn, f(targetRow, targetColumn, Data[sourceRowIndex + i])); + targetRow++; + targetColumn++; + } + } + else if (sourceRowIndex < sourceColumnIndex && sourceRowIndex + rowCount > sourceColumnIndex) + { + // row by row, but skip resulting zero rows at the beginning + int rowInit = sourceColumnIndex - sourceRowIndex; + int targetRow = targetRowIndex + rowInit; + int targetColumn = targetColumnIndex; + for (var i = 0; i < Math.Min(columnCount, rowCount - rowInit); i++) + { + target.At(targetRow, targetColumn, f(targetRow, targetColumn, Data[sourceColumnIndex + i])); + targetRow++; + targetColumn++; + } + } + } + + void MapSubMatrixIndexedToUnchecked(DiagonalMatrixStorage target, Func f, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + Zeros zeros) + where TU : struct, IEquatable, IFormattable + { + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero)); + if (processZeros || sourceRowIndex - sourceColumnIndex != targetRowIndex - targetColumnIndex) + { + throw new NotSupportedException("Cannot map non-zero off-diagonal values into a diagonal matrix"); + } + + var beginInclusive = Math.Max(sourceRowIndex, sourceColumnIndex); + var count = Math.Min(sourceRowIndex + rowCount, sourceColumnIndex + columnCount) - beginInclusive; + if (count > 0) + { + var beginTarget = Math.Max(targetRowIndex, targetColumnIndex); + CommonParallel.For(0, count, 4096, (a, b) => + { + int targetIndex = beginTarget + a; + for (int i = a; i < b; i++) + { + target.Data[targetIndex] = f(targetIndex, targetIndex, Data[beginInclusive + i]); + targetIndex++; + } + }); + } + } + + void MapSubMatrixIndexedToUnchecked(DenseColumnMajorMatrixStorage target, Func f, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero)); + if (existingData == ExistingData.Clear && !processZeros) + { + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + } + + if (processZeros) + { + CommonParallel.For(0, columnCount, Math.Max(4096 / rowCount, 32), (a, b) => + { + int sourceColumn = sourceColumnIndex + a; + int targetColumn = targetColumnIndex + a; + for (int j = a; j < b; j++) + { + int targetIndex = targetRowIndex + (j + targetColumnIndex) * target.RowCount; + int sourceRow = sourceRowIndex; + int targetRow = targetRowIndex; + for (int i = 0; i < rowCount; i++) + { + target.Data[targetIndex++] = f(targetRow++, targetColumn, sourceRow++ == sourceColumn ? Data[sourceColumn] : Zero); + } + + sourceColumn++; + targetColumn++; + } + }); + } + else + { + if (sourceRowIndex > sourceColumnIndex && sourceColumnIndex + columnCount > sourceRowIndex) + { + // column by column, but skip resulting zero columns at the beginning + + int columnInit = sourceRowIndex - sourceColumnIndex; + int offset = (columnInit + targetColumnIndex) * target.RowCount + targetRowIndex; + int step = target.RowCount + 1; + int count = Math.Min(columnCount - columnInit, rowCount); + + for (int k = 0, j = offset; k < count; j += step, k++) + { + target.Data[j] = f(targetRowIndex + k, targetColumnIndex + columnInit + k, Data[sourceRowIndex + k]); + } + } + else if (sourceRowIndex < sourceColumnIndex && sourceRowIndex + rowCount > sourceColumnIndex) + { + // row by row, but skip resulting zero rows at the beginning + + int rowInit = sourceColumnIndex - sourceRowIndex; + int offset = targetColumnIndex * target.RowCount + rowInit + targetRowIndex; + int step = target.RowCount + 1; + int count = Math.Min(columnCount, rowCount - rowInit); + + for (int k = 0, j = offset; k < count; j += step, k++) + { + target.Data[j] = f(targetRowIndex + rowInit + k, targetColumnIndex + k, Data[sourceColumnIndex + k]); + } + } + else + { + int offset = targetColumnIndex * target.RowCount + targetRowIndex; + int step = target.RowCount + 1; + var count = Math.Min(columnCount, rowCount); + + for (int k = 0, j = offset; k < count; j += step, k++) + { + target.Data[j] = f(targetRowIndex + k, targetColumnIndex + k, Data[sourceRowIndex + k]); + } + } + } + } + + // FUNCTIONAL COMBINATORS: FOLD + + internal override void FoldByRowUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + if (zeros == Zeros.AllowSkip) + { + for (int k = 0; k < Data.Length; k++) + { + target[k] = finalize(f(state[k], Data[k]), 1); + } + + for (int k = Data.Length; k < RowCount; k++) + { + target[k] = finalize(state[k], 0); + } + } + else + { + for (int i = 0; i < RowCount; i++) + { + TU s = state[i]; + for (int j = 0; j < ColumnCount; j++) + { + s = f(s, i == j ? Data[i] : Zero); + } + + target[i] = finalize(s, ColumnCount); + } + } + } + + internal override void FoldByColumnUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + if (zeros == Zeros.AllowSkip) + { + for (int k = 0; k < Data.Length; k++) + { + target[k] = finalize(f(state[k], Data[k]), 1); + } + + for (int k = Data.Length; k < ColumnCount; k++) + { + target[k] = finalize(state[k], 0); + } + } + else + { + for (int j = 0; j < ColumnCount; j++) + { + TU s = state[j]; + for (int i = 0; i < RowCount; i++) + { + s = f(s, i == j ? Data[i] : Zero); + } + + target[j] = finalize(s, RowCount); + } + } + } + + internal override TState Fold2Unchecked(MatrixStorage other, Func f, TState state, Zeros zeros) + { + var denseOther = other as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + int k = 0; + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + state = f(state, i == j ? Data[i] : Zero, otherData[k]); + k++; + } + } + + return state; + } + + var diagonalOther = other as DiagonalMatrixStorage; + if (diagonalOther != null) + { + TOther[] otherData = diagonalOther.Data; + for (int i = 0; i < Data.Length; i++) + { + state = f(state, Data[i], otherData[i]); + } + + // Do we really need to do this? + if (zeros == Zeros.Include) + { + TOther otherZero = BuilderInstance.Matrix.Zero; + int count = RowCount * ColumnCount - Data.Length; + for (int i = 0; i < count; i++) + { + state = f(state, Zero, otherZero); + } + } + + return state; + } + + var sparseOther = other as SparseCompressedRowMatrixStorage; + if (sparseOther != null) + { + int[] otherRowPointers = sparseOther.RowPointers; + int[] otherColumnIndices = sparseOther.ColumnIndices; + TOther[] otherValues = sparseOther.Values; + TOther otherZero = BuilderInstance.Matrix.Zero; + + if (zeros == Zeros.Include) + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + if (k < otherRowPointers[row + 1] && otherColumnIndices[k] == col) + { + state = f(state, row == col ? Data[row] : Zero, otherValues[k++]); + } + else + { + state = f(state, row == col ? Data[row] : Zero, otherZero); + } + } + } + + return state; + } + + for (int row = 0; row < RowCount; row++) + { + bool diagonal = false; + + var startIndex = otherRowPointers[row]; + var endIndex = otherRowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + if (otherColumnIndices[j] == row) + { + diagonal = true; + state = f(state, Data[row], otherValues[j]); + } + else + { + state = f(state, Zero, otherValues[j]); + } + } + + if (!diagonal && row < ColumnCount) + { + state = f(state, Data[row], otherZero); + } + } + + return state; + } + + // FALL BACK + + return base.Fold2Unchecked(other, f, state, zeros); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Storage/MatrixStorage.Validation.cs b/MathNet.Numerics/LinearAlgebra/Storage/MatrixStorage.Validation.cs new file mode 100644 index 0000000..9f0110a --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/MatrixStorage.Validation.cs @@ -0,0 +1,226 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +// ReSharper disable UnusedParameter.Local + +public partial class MatrixStorage +{ + void ValidateRange(int row, int column) + { + if ((uint)row >= (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(row)); + } + + if ((uint)column >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(column)); + } + } + + void ValidateSubMatrixRange(MatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount) + where TU : struct, IEquatable, IFormattable + { + if (rowCount < 1) + { + throw new ArgumentOutOfRangeException(nameof(rowCount), Resources.ArgumentMustBePositive); + } + + if (columnCount < 1) + { + throw new ArgumentOutOfRangeException(nameof(columnCount), Resources.ArgumentMustBePositive); + } + + // Verify Source + + if ((uint)sourceRowIndex >= (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(sourceRowIndex)); + } + + if ((uint)sourceColumnIndex >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(sourceColumnIndex)); + } + + var sourceRowMax = sourceRowIndex + rowCount; + var sourceColumnMax = sourceColumnIndex + columnCount; + + if (sourceRowMax > RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowCount)); + } + + if (sourceColumnMax > ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnCount)); + } + + // Verify Target + + if ((uint)targetRowIndex >= (uint)target.RowCount) + { + throw new ArgumentOutOfRangeException(nameof(targetRowIndex)); + } + + if ((uint)targetColumnIndex >= (uint)target.ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(targetColumnIndex)); + } + + var targetRowMax = targetRowIndex + rowCount; + var targetColumnMax = targetColumnIndex + columnCount; + + if (targetRowMax > target.RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowCount)); + } + + if (targetColumnMax > target.ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnCount)); + } + } + + void ValidateRowRange(VectorStorage target, int rowIndex) + where TU : struct, IEquatable, IFormattable + { + if ((uint)rowIndex >= (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowIndex)); + } + + if (ColumnCount != target.Length) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, nameof(target)); + } + } + + void ValidateColumnRange(VectorStorage target, int columnIndex) + where TU : struct, IEquatable, IFormattable + { + if ((uint)columnIndex >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + if (RowCount != target.Length) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, nameof(target)); + } + } + + void ValidateSubRowRange(VectorStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount) + where TU : struct, IEquatable, IFormattable + { + if (columnCount < 1) + { + throw new ArgumentOutOfRangeException(nameof(columnCount), Resources.ArgumentMustBePositive); + } + + // Verify Source + + if ((uint)rowIndex >= (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowIndex)); + } + + if ((uint)sourceColumnIndex >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(sourceColumnIndex)); + } + + if (sourceColumnIndex + columnCount > ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnCount)); + } + + // Verify Target + + if ((uint)targetColumnIndex >= (uint)target.Length) + { + throw new ArgumentOutOfRangeException(nameof(targetColumnIndex)); + } + + if (targetColumnIndex + columnCount > target.Length) + { + throw new ArgumentOutOfRangeException(nameof(columnCount)); + } + } + + void ValidateSubColumnRange(VectorStorage target, int columnIndex, + int sourceRowIndex, int targetRowIndex, int rowCount) + where TU : struct, IEquatable, IFormattable + { + if (rowCount < 1) + { + throw new ArgumentOutOfRangeException(nameof(rowCount), Resources.ArgumentMustBePositive); + } + + // Verify Source + + if ((uint)columnIndex >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + if ((uint)sourceRowIndex >= (uint)RowCount) + { + throw new ArgumentOutOfRangeException(nameof(sourceRowIndex)); + } + + if (sourceRowIndex + rowCount > RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowCount)); + } + + // Verify Target + + if ((uint)targetRowIndex >= (uint)target.Length) + { + throw new ArgumentOutOfRangeException(nameof(targetRowIndex)); + } + + if (targetRowIndex + rowCount > target.Length) + { + throw new ArgumentOutOfRangeException(nameof(rowCount)); + } + } +} + +// ReSharper restore UnusedParameter.Local diff --git a/MathNet.Numerics/LinearAlgebra/Storage/MatrixStorage.cs b/MathNet.Numerics/LinearAlgebra/Storage/MatrixStorage.cs new file mode 100644 index 0000000..0835556 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/MatrixStorage.cs @@ -0,0 +1,1008 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/LinearAlgebra")] +public abstract partial class MatrixStorage : IEquatable> + where T : struct, IEquatable, IFormattable +{ + // [ruegg] public fields are OK here + + protected static readonly T Zero = BuilderInstance.Matrix.Zero; + + [DataMember(Order = 1)] + public readonly int RowCount; + + [DataMember(Order = 2)] + public readonly int ColumnCount; + + protected MatrixStorage(int rowCount, int columnCount) + { + if (rowCount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(rowCount), Resources.MatrixRowsMustBePositive); + } + + if (columnCount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(columnCount), Resources.MatrixColumnsMustBePositive); + } + + RowCount = rowCount; + ColumnCount = columnCount; + } + + /// + /// True if the matrix storage format is dense. + /// + public abstract bool IsDense { get; } + + /// + /// True if all fields of this matrix can be set to any value. + /// False if some fields are fixed, like on a diagonal matrix. + /// + public abstract bool IsFullyMutable { get; } + + /// + /// True if the specified field can be set to any value. + /// False if the field is fixed, like an off-diagonal field on a diagonal matrix. + /// + public abstract bool IsMutableAt(int row, int column); + + /// + /// Gets or sets the value at the given row and column, with range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// The value to get or set. + /// This method is ranged checked. and + /// to get and set values without range checking. + public T this[int row, int column] + { + get + { + ValidateRange(row, column); + return At(row, column); + } + + set + { + ValidateRange(row, column); + At(row, column, value); + } + } + + /// + /// Retrieves the requested element without range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The requested element. + /// + /// Not range-checked. + public abstract T At(int row, int column); + + /// + /// Sets the element without range checking. + /// + /// The row of the element. + /// The column of the element. + /// The value to set the element to. + /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks. + public abstract void At(int row, int column, T value); + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// An object to compare with this object. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(MatrixStorage other) + { + // Reject equality when the argument is null or has a different shape. + if (other == null) + { + return false; + } + + if (ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + return false; + } + + // Accept if the argument is the same object as this. + if (ReferenceEquals(this, other)) + { + return true; + } + + // Perform element wise comparison. + return Find2Unchecked(other, (a, b) => !a.Equals(b), Zeros.AllowSkip) == null; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// true if the specified is equal to the current ; otherwise, false. + /// + /// The to compare with the current . + public sealed override bool Equals(object obj) + { + return Equals(obj as MatrixStorage); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + public override int GetHashCode() + { + var hashNum = Math.Min(RowCount * ColumnCount, 25); + int hash = 17; + unchecked + { + for (var i = 0; i < hashNum; i++) + { +#if NETSTANDARD1_3 + int col = i%ColumnCount; + int row = i/ColumnCount; +#else + int col; + int row = Math.DivRem(i, ColumnCount, out col); +#endif + hash = hash * 31 + At(row, col).GetHashCode(); + } + } + + return hash; + } + + // CLEARING + + public virtual void Clear() + { + for (var i = 0; i < RowCount; i++) + { + for (var j = 0; j < ColumnCount; j++) + { + At(i, j, Zero); + } + } + } + + public void Clear(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + if (rowCount < 1 || columnCount < 1) + { + return; + } + + if (rowIndex + rowCount > RowCount || rowIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(rowIndex)); + } + + if (columnIndex + columnCount > ColumnCount || columnIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + ClearUnchecked(rowIndex, rowCount, columnIndex, columnCount); + } + + internal virtual void ClearUnchecked(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + for (var i = rowIndex; i < rowIndex + rowCount; i++) + { + for (var j = columnIndex; j < columnIndex + columnCount; j++) + { + At(i, j, Zero); + } + } + } + + public void ClearRows(int[] rowIndices) + { + if (rowIndices.Length == 0) + { + return; + } + + for (int k = 0; k < rowIndices.Length; k++) + { + if (rowIndices[k] < 0 || rowIndices[k] >= RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowIndices)); + } + } + + ClearRowsUnchecked(rowIndices); + } + + public void ClearColumns(int[] columnIndices) + { + if (columnIndices.Length == 0) + { + return; + } + + for (int k = 0; k < columnIndices.Length; k++) + { + if ((uint)columnIndices[k] >= (uint)ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnIndices)); + } + } + + ClearColumnsUnchecked(columnIndices); + } + + internal virtual void ClearRowsUnchecked(int[] rowIndices) + { + for (var k = 0; k < rowIndices.Length; k++) + { + int row = rowIndices[k]; + for (var j = 0; j < ColumnCount; j++) + { + At(row, j, Zero); + } + } + } + + internal virtual void ClearColumnsUnchecked(int[] columnIndices) + { + for (var k = 0; k < columnIndices.Length; k++) + { + int column = columnIndices[k]; + for (var i = 0; i < RowCount; i++) + { + At(i, column, Zero); + } + } + } + + // MATRIX COPY + + public void CopyTo(MatrixStorage target, ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (ReferenceEquals(this, target)) + { + return; + } + + if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, RowCount + "x" + ColumnCount, target.RowCount + "x" + target.ColumnCount); + throw new ArgumentException(message, nameof(target)); + } + + CopyToUnchecked(target, existingData); + } + + internal virtual void CopyToUnchecked(MatrixStorage target, ExistingData existingData) + { + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + target.At(i, j, At(i, j)); + } + } + } + + public void CopySubMatrixTo(MatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (rowCount == 0 || columnCount == 0) + { + return; + } + + if (ReferenceEquals(this, target)) + { + throw new NotSupportedException(); + } + + ValidateSubMatrixRange(target, + sourceRowIndex, targetRowIndex, rowCount, + sourceColumnIndex, targetColumnIndex, columnCount); + + CopySubMatrixToUnchecked(target, sourceRowIndex, targetRowIndex, rowCount, + sourceColumnIndex, targetColumnIndex, columnCount, existingData); + } + + internal virtual void CopySubMatrixToUnchecked(MatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData) + { + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) + { + target.At(ii, jj, At(i, j)); + } + } + } + + // ROW COPY + + public void CopyRowTo(VectorStorage target, int rowIndex, ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + ValidateRowRange(target, rowIndex); + CopySubRowToUnchecked(target, rowIndex, 0, 0, ColumnCount, existingData); + } + + public void CopySubRowTo(VectorStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (columnCount == 0) + { + return; + } + + ValidateSubRowRange(target, rowIndex, sourceColumnIndex, targetColumnIndex, columnCount); + CopySubRowToUnchecked(target, rowIndex, sourceColumnIndex, targetColumnIndex, columnCount, existingData); + } + + internal virtual void CopySubRowToUnchecked(VectorStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData) + { + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + target.At(jj, At(rowIndex, j)); + } + } + + // COLUMN COPY + + public void CopyColumnTo(VectorStorage target, int columnIndex, ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + ValidateColumnRange(target, columnIndex); + CopySubColumnToUnchecked(target, columnIndex, 0, 0, RowCount, existingData); + } + + public void CopySubColumnTo(VectorStorage target, int columnIndex, + int sourceRowIndex, int targetRowIndex, int rowCount, + ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (rowCount == 0) + { + return; + } + + ValidateSubColumnRange(target, columnIndex, sourceRowIndex, targetRowIndex, rowCount); + CopySubColumnToUnchecked(target, columnIndex, sourceRowIndex, targetRowIndex, rowCount, existingData); + } + + internal virtual void CopySubColumnToUnchecked(VectorStorage target, int columnIndex, + int sourceRowIndex, int targetRowIndex, int rowCount, ExistingData existingData) + { + for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) + { + target.At(ii, At(i, columnIndex)); + } + } + + // TRANSPOSE + + public void TransposeTo(MatrixStorage target, ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (RowCount != target.ColumnCount || ColumnCount != target.RowCount) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, RowCount + "x" + ColumnCount, target.RowCount + "x" + target.ColumnCount); + throw new ArgumentException(message, nameof(target)); + } + + if (ReferenceEquals(this, target)) + { + TransposeSquareInplaceUnchecked(); + return; + } + + TransposeToUnchecked(target, existingData); + } + + internal virtual void TransposeToUnchecked(MatrixStorage target, ExistingData existingData) + { + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + target.At(j, i, At(i, j)); + } + } + } + + internal virtual void TransposeSquareInplaceUnchecked() + { + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < j; i++) + { + T swap = At(i, j); + At(i, j, At(j, i)); + At(j, i, swap); + } + } + } + + // EXTRACT + + public virtual T[] ToRowMajorArray() + { + var ret = new T[RowCount * ColumnCount]; + for (int i = 0; i < RowCount; i++) + { + var offset = i * ColumnCount; + for (int j = 0; j < ColumnCount; j++) + { + ret[offset + j] = At(i, j); + } + } + + return ret; + } + + public virtual T[] ToColumnMajorArray() + { + var ret = new T[RowCount * ColumnCount]; + for (int j = 0; j < ColumnCount; j++) + { + var offset = j * RowCount; + for (int i = 0; i < RowCount; i++) + { + ret[offset + i] = At(i, j); + } + } + + return ret; + } + + public virtual T[][] ToRowArrays() + { + var ret = new T[RowCount][]; + for (int i = 0; i < RowCount; i++) + { + var row = new T[ColumnCount]; + for (int j = 0; j < ColumnCount; j++) + { + row[j] = At(i, j); + } + + ret[i] = row; + } + + return ret; + } + + public virtual T[][] ToColumnArrays() + { + var ret = new T[ColumnCount][]; + for (int j = 0; j < ColumnCount; j++) + { + var column = new T[RowCount]; + for (int i = 0; i < RowCount; i++) + { + column[i] = At(i, j); + } + + ret[j] = column; + } + + return ret; + } + + public virtual T[,] ToArray() + { + var ret = new T[RowCount, ColumnCount]; + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + ret[i, j] = At(i, j); + } + } + + return ret; + } + + public virtual T[] AsRowMajorArray() + { + return null; + } + + public virtual T[] AsColumnMajorArray() + { + return null; + } + + public virtual T[][] AsRowArrays() + { + return null; + } + + public virtual T[][] AsColumnArrays() + { + return null; + } + + public virtual T[,] AsArray() + { + return null; + } + + // ENUMERATION + + public virtual IEnumerable Enumerate() + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + yield return At(i, j); + } + } + } + + public virtual IEnumerable> EnumerateIndexed() + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + yield return new Tuple(i, j, At(i, j)); + } + } + } + + public virtual IEnumerable EnumerateNonZero() + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + var x = At(i, j); + if (!Zero.Equals(x)) + { + yield return x; + } + } + } + } + + public virtual IEnumerable> EnumerateNonZeroIndexed() + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + var x = At(i, j); + if (!Zero.Equals(x)) + { + yield return new Tuple(i, j, x); + } + } + } + } + + // FIND + + public virtual Tuple Find(Func predicate, Zeros zeros) + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + var item = At(i, j); + if (predicate(item)) + { + return new Tuple(i, j, item); + } + } + } + + return null; + } + + public Tuple Find2(MatrixStorage other, Func predicate, Zeros zeros) + where TOther : struct, IEquatable, IFormattable + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (RowCount != other.RowCount || ColumnCount != other.ColumnCount) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, RowCount + "x" + ColumnCount, other.RowCount + "x" + other.ColumnCount); + throw new ArgumentException(message, nameof(other)); + } + + return Find2Unchecked(other, predicate, zeros); + } + + internal virtual Tuple Find2Unchecked(MatrixStorage other, Func predicate, Zeros zeros) + where TOther : struct, IEquatable, IFormattable + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + var item = At(i, j); + var otherItem = other.At(i, j); + if (predicate(item, otherItem)) + { + return new Tuple(i, j, item, otherItem); + } + } + } + + return null; + } + + // FUNCTIONAL COMBINATORS: MAP + + public virtual void MapInplace(Func f, Zeros zeros) + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + At(i, j, f(At(i, j))); + } + } + } + + public virtual void MapIndexedInplace(Func f, Zeros zeros) + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + At(i, j, f(i, j, At(i, j))); + } + } + } + + public void MapTo(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, RowCount + "x" + ColumnCount, target.RowCount + "x" + target.ColumnCount); + throw new ArgumentException(message, nameof(target)); + } + + MapToUnchecked(target, f, zeros, existingData); + } + + internal virtual void MapToUnchecked(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + target.At(i, j, f(At(i, j))); + } + } + } + + public void MapIndexedTo(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, RowCount + "x" + ColumnCount, target.RowCount + "x" + target.ColumnCount); + throw new ArgumentException(message, nameof(target)); + } + + MapIndexedToUnchecked(target, f, zeros, existingData); + } + + internal virtual void MapIndexedToUnchecked(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + for (int j = 0; j < ColumnCount; j++) + { + for (int i = 0; i < RowCount; i++) + { + target.At(i, j, f(i, j, At(i, j))); + } + } + } + + public void MapSubMatrixIndexedTo(MatrixStorage target, Func f, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (rowCount == 0 || columnCount == 0) + { + return; + } + + if (ReferenceEquals(this, target)) + { + throw new NotSupportedException(); + } + + ValidateSubMatrixRange(target, + sourceRowIndex, targetRowIndex, rowCount, + sourceColumnIndex, targetColumnIndex, columnCount); + + MapSubMatrixIndexedToUnchecked(target, f, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount, zeros, existingData); + } + + internal virtual void MapSubMatrixIndexedToUnchecked(MatrixStorage target, Func f, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) + { + target.At(ii, jj, f(ii, jj, At(i, j))); + } + } + } + + public void Map2To(MatrixStorage target, MatrixStorage other, Func f, Zeros zeros, ExistingData existingData) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, RowCount + "x" + ColumnCount, target.RowCount + "x" + target.ColumnCount); + throw new ArgumentException(message, nameof(target)); + } + + if (RowCount != other.RowCount || ColumnCount != other.ColumnCount) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, RowCount + "x" + ColumnCount, other.RowCount + "x" + other.ColumnCount); + throw new ArgumentException(message, nameof(other)); + } + + Map2ToUnchecked(target, other, f, zeros, existingData); + } + + internal virtual void Map2ToUnchecked(MatrixStorage target, MatrixStorage other, Func f, Zeros zeros, ExistingData existingData) + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + target.At(i, j, f(At(i, j), other.At(i, j))); + } + } + } + + // FUNCTIONAL COMBINATORS: FOLD + + /// The state array will not be modified, unless it is the same instance as the target array (which is allowed). + public void FoldByRow(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (target.Length != RowCount) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(target)); + } + + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + if (state.Length != RowCount) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(state)); + } + + FoldByRowUnchecked(target, f, finalize, state, zeros); + } + + /// The state array will not be modified, unless it is the same instance as the target array (which is allowed). + internal virtual void FoldByRowUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + for (int i = 0; i < RowCount; i++) + { + TU s = state[i]; + for (int j = 0; j < ColumnCount; j++) + { + s = f(s, At(i, j)); + } + + target[i] = finalize(s, ColumnCount); + } + } + + /// The state array will not be modified, unless it is the same instance as the target array (which is allowed). + public void FoldByColumn(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (target.Length != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(target)); + } + + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + if (state.Length != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(state)); + } + + FoldByColumnUnchecked(target, f, finalize, state, zeros); + } + + /// The state array will not be modified, unless it is the same instance as the target array (which is allowed). + internal virtual void FoldByColumnUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + for (int j = 0; j < ColumnCount; j++) + { + TU s = state[j]; + for (int i = 0; i < RowCount; i++) + { + s = f(s, At(i, j)); + } + + target[j] = finalize(s, RowCount); + } + } + + public TState Fold2(MatrixStorage other, Func f, TState state, Zeros zeros) + where TOther : struct, IEquatable, IFormattable + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (RowCount != other.RowCount || ColumnCount != other.ColumnCount) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, RowCount + "x" + ColumnCount, other.RowCount + "x" + other.ColumnCount); + throw new ArgumentException(message, nameof(other)); + } + + return Fold2Unchecked(other, f, state, zeros); + } + + internal virtual TState Fold2Unchecked(MatrixStorage other, Func f, TState state, Zeros zeros) + where TOther : struct, IEquatable, IFormattable + { + for (int i = 0; i < RowCount; i++) + { + for (int j = 0; j < ColumnCount; j++) + { + state = f(state, At(i, j), other.At(i, j)); + } + } + + return state; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Storage/SparseCompressedRowMatrixStorage.cs b/MathNet.Numerics/LinearAlgebra/Storage/SparseCompressedRowMatrixStorage.cs new file mode 100644 index 0000000..65ce5cf --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/SparseCompressedRowMatrixStorage.cs @@ -0,0 +1,2378 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/LinearAlgebra")] +public class SparseCompressedRowMatrixStorage : MatrixStorage + where T : struct, IEquatable, IFormattable +{ + // [ruegg] public fields are OK here + + /// + /// The array containing the row indices of the existing rows. Element "i" of the array gives the index of the + /// element in the array that is first non-zero element in a row "i". + /// The last value is equal to ValueCount, so that the number of non-zero entries in row "i" is always + /// given by RowPointers[i+i] - RowPointers[i]. This array thus has length RowCount+1. + /// + [DataMember(Order = 1)] + public readonly int[] RowPointers; + + /// + /// An array containing the column indices of the non-zero values. Element "j" of the array + /// is the number of the column in matrix that contains the j-th value in the array. + /// + [DataMember(Order = 2)] + public int[] ColumnIndices; + + /// + /// Array that contains the non-zero elements of matrix. Values of the non-zero elements of matrix are mapped into the values + /// array using the row-major storage mapping described in a compressed sparse row (CSR) format. + /// + [DataMember(Order = 3)] + public T[] Values; + + /// + /// Gets the number of non zero elements in the matrix. + /// + /// The number of non zero elements. + public int ValueCount + { + get { return RowPointers[RowCount]; } + } + + internal SparseCompressedRowMatrixStorage(int rows, int columns) + : base(rows, columns) + { + RowPointers = new int[rows + 1]; + ColumnIndices = new int[0]; + Values = new T[0]; + } + + /// + /// True if the matrix storage format is dense. + /// + public override bool IsDense + { + get { return false; } + } + + /// + /// True if all fields of this matrix can be set to any value. + /// False if some fields are fixed, like on a diagonal matrix. + /// + public override bool IsFullyMutable + { + get { return true; } + } + + /// + /// True if the specified field can be set to any value. + /// False if the field is fixed, like an off-diagonal field on a diagonal matrix. + /// + public override bool IsMutableAt(int row, int column) + { + return true; + } + + /// + /// Retrieves the requested element without range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The requested element. + /// + /// Not range-checked. + public override T At(int row, int column) + { + var index = FindItem(row, column); + return index >= 0 ? Values[index] : Zero; + } + + /// + /// Sets the element without range checking. + /// + /// The row of the element. + /// The column of the element. + /// The value to set the element to. + /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks. + public override void At(int row, int column, T value) + { + var index = FindItem(row, column); + if (index >= 0) + { + // Non-zero item found in matrix + if (Zero.Equals(value)) + { + // Delete existing item + RemoveAtIndexUnchecked(index, row); + } + else + { + // Update item + Values[index] = value; + } + } + else + { + // Item not found. Add new value + if (Zero.Equals(value)) + { + return; + } + + index = ~index; + var valueCount = RowPointers[RowPointers.Length - 1]; + + // Check if the storage needs to be increased + if ((valueCount == Values.Length) && (valueCount < ((long)RowCount * ColumnCount))) + { + // Value array is completely full so we increase the size + // Determine the increase in size. We will not grow beyond the size of the matrix + var size = Math.Min(Values.Length + GrowthSize(), (long)RowCount * ColumnCount); + if (size > int.MaxValue) + { + throw new NotSupportedException(Resources.TooManyElements); + } + + Array.Resize(ref Values, (int)size); + Array.Resize(ref ColumnIndices, (int)size); + } + + // Move all values (with a position larger than index) in the value array to the next position + // move all values (with a position larger than index) in the columIndices array to the next position + Array.Copy(Values, index, Values, index + 1, valueCount - index); + Array.Copy(ColumnIndices, index, ColumnIndices, index + 1, valueCount - index); + + // Add the value and the column index + Values[index] = value; + ColumnIndices[index] = column; + + // add 1 to all the row indices for rows bigger than rowIndex + // so that they point to the correct part of the value array again. + for (var i = row + 1; i < RowPointers.Length; i++) + { + RowPointers[i] += 1; + } + } + } + + /// + /// Delete value from internal storage + /// + /// Index of value in nonZeroValues array + /// Row number of matrix + /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks + void RemoveAtIndexUnchecked(int itemIndex, int row) + { + var valueCount = RowPointers[RowPointers.Length - 1]; + + // Move all values (with a position larger than index) in the value array to the previous position + // move all values (with a position larger than index) in the columIndices array to the previous position + Array.Copy(Values, itemIndex + 1, Values, itemIndex, valueCount - itemIndex - 1); + Array.Copy(ColumnIndices, itemIndex + 1, ColumnIndices, itemIndex, valueCount - itemIndex - 1); + + // Decrease value in Row + for (var i = row + 1; i < RowPointers.Length; i++) + { + RowPointers[i] -= 1; + } + + valueCount -= 1; + + // Check whether we need to shrink the arrays. This is reasonable to do if + // there are a lot of non-zero elements and storage is two times bigger + if ((valueCount > 1024) && (valueCount < Values.Length / 2)) + { + Array.Resize(ref Values, valueCount); + Array.Resize(ref ColumnIndices, valueCount); + } + } + + /// + /// Find item Index in nonZeroValues array + /// + /// Matrix row index + /// Matrix column index + /// Item index + /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks + public int FindItem(int row, int column) + { + // Determine bounds in columnIndices array where this item should be searched (using rowIndex) + return Array.BinarySearch(ColumnIndices, RowPointers[row], RowPointers[row + 1] - RowPointers[row], column); + } + + /// + /// Calculates the amount with which to grow the storage array's if they need to be + /// increased in size. + /// + /// The amount grown. + int GrowthSize() + { + int delta; + if (Values.Length > 1024) + { + delta = Values.Length / 4; + } + else + { + if (Values.Length > 256) + { + delta = 512; + } + else + { + delta = Values.Length > 64 ? 128 : 32; + } + } + + return delta; + } + + public void Normalize() + { + NormalizeOrdering(); + NormalizeZeros(); + } + + public void NormalizeOrdering() + { + for (int i = 0; i < RowCount; i++) + { + int index = RowPointers[i]; + int count = RowPointers[i + 1] - index; + if (count > 1) + { + Sorting.Sort(ColumnIndices, Values, index, count); + } + } + } + + public void NormalizeZeros() + { + MapInplace(x => x, Zeros.AllowSkip); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + var values = Values; + var hashNum = Math.Min(ValueCount, 25); + int hash = 17; + unchecked + { + for (var i = 0; i < hashNum; i++) + { + hash = hash * 31 + values[i].GetHashCode(); + } + } + + return hash; + } + + // CLEARING + + public override void Clear() + { + Array.Clear(RowPointers, 0, RowPointers.Length); + } + + internal override void ClearUnchecked(int rowIndex, int rowCount, int columnIndex, int columnCount) + { + if (rowIndex == 0 && columnIndex == 0 && rowCount == RowCount && columnCount == ColumnCount) + { + Clear(); + return; + } + + var valueCount = RowPointers[RowPointers.Length - 1]; + + for (int row = rowIndex + rowCount - 1; row >= rowIndex; row--) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + + // empty row + if (startIndex == endIndex) + { + continue; + } + + // multiple entries in row + var first = Array.BinarySearch(ColumnIndices, startIndex, endIndex - startIndex, columnIndex); + var last = Array.BinarySearch(ColumnIndices, startIndex, endIndex - startIndex, columnIndex + columnCount - 1); + if (first < 0) + first = ~first; + if (last < 0) + last = ~last - 1; + int count = last - first + 1; + + if (count > 0) + { + // Move all values (with a position larger than index) in the value array to the previous position + // move all values (with a position larger than index) in the columIndices array to the previous position + Array.Copy(Values, first + count, Values, first, valueCount - first - count); + Array.Copy(ColumnIndices, first + count, ColumnIndices, first, valueCount - first - count); + + // Decrease value in Row + for (var k = row + 1; k < RowPointers.Length; k++) + { + RowPointers[k] -= count; + } + + valueCount -= count; + } + } + + // Check whether we need to shrink the arrays. This is reasonable to do if + // there are a lot of non-zero elements and storage is two times bigger + if ((valueCount > 1024) && (valueCount < Values.Length / 2)) + { + Array.Resize(ref Values, valueCount); + Array.Resize(ref ColumnIndices, valueCount); + } + } + + internal override void ClearRowsUnchecked(int[] rowIndices) + { + var rows = new bool[RowCount]; + for (int i = 0; i < rowIndices.Length; i++) + { + rows[rowIndices[i]] = true; + } + + MapIndexedInplace((i, j, x) => rows[i] ? Zero : x, Zeros.AllowSkip); + } + + internal override void ClearColumnsUnchecked(int[] columnIndices) + { + var columns = new bool[ColumnCount]; + for (int i = 0; i < columnIndices.Length; i++) + { + columns[columnIndices[i]] = true; + } + + MapIndexedInplace((i, j, x) => columns[j] ? Zero : x, Zeros.AllowSkip); + } + + // INITIALIZATION + + public static SparseCompressedRowMatrixStorage OfMatrix(MatrixStorage matrix) + { + var storage = new SparseCompressedRowMatrixStorage(matrix.RowCount, matrix.ColumnCount); + matrix.CopyToUnchecked(storage, ExistingData.AssumeZeros); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfValue(int rows, int columns, T value) + { + if (Zero.Equals(value)) + { + return new SparseCompressedRowMatrixStorage(rows, columns); + } + + var storage = new SparseCompressedRowMatrixStorage(rows, columns); + + var values = new T[rows * columns]; + for (int i = 0; i < values.Length; i++) + { + values[i] = value; + } + + var rowPointers = storage.RowPointers; + for (int i = 0; i <= rows; i++) + { + rowPointers[i] = i * columns; + } + + var columnIndices = new int[values.Length]; + for (int row = 0; row < rows; row++) + { + int offset = row * columns; + for (int col = 0; col < columns; col++) + { + columnIndices[offset + col] = col; + } + } + + rowPointers[rows] = values.Length; + storage.ColumnIndices = columnIndices; + storage.Values = values; + return storage; + } + + public static SparseCompressedRowMatrixStorage OfInit(int rows, int columns, Func init) + { + var storage = new SparseCompressedRowMatrixStorage(rows, columns); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + for (int row = 0; row < rows; row++) + { + rowPointers[row] = values.Count; + for (int col = 0; col < columns; col++) + { + var x = init(row, col); + if (!Zero.Equals(x)) + { + values.Add(x); + columnIndices.Add(col); + } + } + } + + rowPointers[rows] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfDiagonalInit(int rows, int columns, Func init) + { + int diagonalLength = Math.Min(rows, columns); + var storage = new SparseCompressedRowMatrixStorage(rows, columns); + var rowPointers = storage.RowPointers; + var columnIndices = new List(diagonalLength); + var values = new List(diagonalLength); + + for (int i = 0; i < diagonalLength; i++) + { + rowPointers[i] = values.Count; + var x = init(i); + if (!Zero.Equals(x)) + { + values.Add(x); + columnIndices.Add(i); + } + } + + rowPointers[rows] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfArray(T[,] array) + { + var storage = new SparseCompressedRowMatrixStorage(array.GetLength(0), array.GetLength(1)); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + for (int row = 0; row < storage.RowCount; row++) + { + rowPointers[row] = values.Count; + for (int col = 0; col < storage.ColumnCount; col++) + { + if (!Zero.Equals(array[row, col])) + { + values.Add(array[row, col]); + columnIndices.Add(col); + } + } + } + + rowPointers[storage.RowCount] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfRowArrays(T[][] data) + { + if (data.Length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(data), Resources.MatrixCanNotBeEmpty); + } + + var storage = new SparseCompressedRowMatrixStorage(data.Length, data[0].Length); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + for (int row = 0; row < storage.RowCount; row++) + { + rowPointers[row] = values.Count; + for (int col = 0; col < storage.ColumnCount; col++) + { + T x = data[row][col]; + if (!Zero.Equals(x)) + { + values.Add(x); + columnIndices.Add(col); + } + } + } + + rowPointers[storage.RowCount] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfColumnArrays(T[][] data) + { + if (data.Length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(data), Resources.MatrixCanNotBeEmpty); + } + + var storage = new SparseCompressedRowMatrixStorage(data[0].Length, data.Length); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + for (int row = 0; row < storage.RowCount; row++) + { + rowPointers[row] = values.Count; + for (int col = 0; col < storage.ColumnCount; col++) + { + T x = data[col][row]; + if (!Zero.Equals(x)) + { + values.Add(x); + columnIndices.Add(col); + } + } + } + + rowPointers[storage.RowCount] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfRowVectors(VectorStorage[] data) + { + if (data.Length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(data), Resources.MatrixCanNotBeEmpty); + } + + var storage = new SparseCompressedRowMatrixStorage(data.Length, data[0].Length); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + // TODO PERF: Optimize for sparse and dense cases + for (int row = 0; row < storage.RowCount; row++) + { + var vector = data[row]; + rowPointers[row] = values.Count; + for (int col = 0; col < storage.ColumnCount; col++) + { + var x = vector.At(col); + if (!Zero.Equals(x)) + { + values.Add(x); + columnIndices.Add(col); + } + } + } + + rowPointers[storage.RowCount] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfColumnVectors(VectorStorage[] data) + { + if (data.Length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(data), Resources.MatrixCanNotBeEmpty); + } + + var storage = new SparseCompressedRowMatrixStorage(data[0].Length, data.Length); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + // TODO PERF: Optimize for sparse and dense cases + for (int row = 0; row < storage.RowCount; row++) + { + rowPointers[row] = values.Count; + for (int col = 0; col < storage.ColumnCount; col++) + { + var x = data[col].At(row); + if (!Zero.Equals(x)) + { + values.Add(x); + columnIndices.Add(col); + } + } + } + + rowPointers[storage.RowCount] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfIndexedEnumerable(int rows, int columns, IEnumerable> data) + { + var trows = new List>[rows]; + foreach (var item in data) + { + if (!Zero.Equals(item.Item3)) + { + var row = trows[item.Item1] ?? (trows[item.Item1] = new List>()); + row.Add(new Tuple(item.Item2, item.Item3)); + } + } + + var storage = new SparseCompressedRowMatrixStorage(rows, columns); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + int index = 0; + for (int row = 0; row < rows; row++) + { + rowPointers[row] = index; + var trow = trows[row]; + if (trow != null) + { + trow.Sort(); + foreach (var item in trow) + { + values.Add(item.Item2); + columnIndices.Add(item.Item1); + index++; + } + } + } + + rowPointers[rows] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfRowEnumerables(int rows, int columns, IEnumerable> data) + { + var storage = new SparseCompressedRowMatrixStorage(rows, columns); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + using (var rowIterator = data.GetEnumerator()) + { + for (int row = 0; row < rows; row++) + { + if (!rowIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, rows)); + rowPointers[row] = values.Count; + using (var columnIterator = rowIterator.Current.GetEnumerator()) + { + for (int col = 0; col < columns; col++) + { + if (!columnIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, columns)); + if (!Zero.Equals(columnIterator.Current)) + { + values.Add(columnIterator.Current); + columnIndices.Add(col); + } + } + + if (columnIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, columns)); + } + } + + if (rowIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, rows)); + } + + rowPointers[rows] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfColumnEnumerables(int rows, int columns, IEnumerable> data) + { + var trows = new List>[rows]; + using (var columnIterator = data.GetEnumerator()) + { + for (int column = 0; column < columns; column++) + { + if (!columnIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, columns)); + using (var rowIterator = columnIterator.Current.GetEnumerator()) + { + for (int row = 0; row < rows; row++) + { + if (!rowIterator.MoveNext()) + throw new ArgumentOutOfRangeException(nameof(data), string.Format(Resources.ArgumentArrayWrongLength, rows)); + if (!Zero.Equals(rowIterator.Current)) + { + var trow = trows[row] ?? (trows[row] = new List>()); + trow.Add(new Tuple(column, rowIterator.Current)); + } + } + } + } + } + + var storage = new SparseCompressedRowMatrixStorage(rows, columns); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + int index = 0; + for (int row = 0; row < rows; row++) + { + rowPointers[row] = index; + var trow = trows[row]; + if (trow != null) + { + trow.Sort(); + foreach (var item in trow) + { + values.Add(item.Item2); + columnIndices.Add(item.Item1); + index++; + } + } + } + + rowPointers[rows] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfRowMajorEnumerable(int rows, int columns, IEnumerable data) + { + var storage = new SparseCompressedRowMatrixStorage(rows, columns); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + using (var iterator = data.GetEnumerator()) + { + for (int row = 0; row < rows; row++) + { + rowPointers[row] = values.Count; + for (int col = 0; col < columns; col++) + { + iterator.MoveNext(); + if (!Zero.Equals(iterator.Current)) + { + values.Add(iterator.Current); + columnIndices.Add(col); + } + } + } + } + + rowPointers[rows] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + public static SparseCompressedRowMatrixStorage OfColumnMajorList(int rows, int columns, IList data) + { + if (rows * columns != data.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var storage = new SparseCompressedRowMatrixStorage(rows, columns); + var rowPointers = storage.RowPointers; + var columnIndices = new List(); + var values = new List(); + + for (int row = 0; row < rows; row++) + { + rowPointers[row] = values.Count; + for (int col = 0; col < columns; col++) + { + var item = data[row + (col * rows)]; + if (!Zero.Equals(item)) + { + values.Add(item); + columnIndices.Add(col); + } + } + } + + rowPointers[rows] = values.Count; + storage.ColumnIndices = columnIndices.ToArray(); + storage.Values = values.ToArray(); + return storage; + } + + // MATRIX COPY + + internal override void CopyToUnchecked(MatrixStorage target, ExistingData existingData) + { + var sparseTarget = target as SparseCompressedRowMatrixStorage; + if (sparseTarget != null) + { + CopyToUnchecked(sparseTarget); + return; + } + + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + CopyToUnchecked(denseTarget, existingData); + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear) + { + target.Clear(); + } + + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + target.At(row, ColumnIndices[j], Values[j]); + } + } + } + } + + void CopyToUnchecked(SparseCompressedRowMatrixStorage target) + { + target.Values = new T[ValueCount]; + target.ColumnIndices = new int[ValueCount]; + + if (ValueCount != 0) + { + Array.Copy(Values, 0, target.Values, 0, ValueCount); + Buffer.BlockCopy(ColumnIndices, 0, target.ColumnIndices, 0, ValueCount * Constants.SizeOfInt); + Buffer.BlockCopy(RowPointers, 0, target.RowPointers, 0, (RowCount + 1) * Constants.SizeOfInt); + } + } + + void CopyToUnchecked(DenseColumnMajorMatrixStorage target, ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.Clear(); + } + + // TODO: proper implementation + + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + target.At(row, ColumnIndices[j], Values[j]); + } + } + } + } + + internal override void CopySubMatrixToUnchecked(MatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + var sparseTarget = target as SparseCompressedRowMatrixStorage; + if (sparseTarget != null) + { + CopySubMatrixToUnchecked(sparseTarget, + sourceRowIndex, targetRowIndex, rowCount, + sourceColumnIndex, targetColumnIndex, columnCount, + existingData); + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear) + { + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + } + + for (int i = sourceRowIndex, row = 0; i < sourceRowIndex + rowCount; i++, row++) + { + var startIndex = RowPointers[i]; + var endIndex = RowPointers[i + 1]; + + for (int j = startIndex; j < endIndex; j++) + { + // check if the column index is in the range + if ((ColumnIndices[j] >= sourceColumnIndex) && (ColumnIndices[j] < sourceColumnIndex + columnCount)) + { + var column = ColumnIndices[j] - sourceColumnIndex; + target.At(targetRowIndex + row, targetColumnIndex + column, Values[j]); + } + } + } + } + + void CopySubMatrixToUnchecked(SparseCompressedRowMatrixStorage target, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData) + { + var rowOffset = targetRowIndex - sourceRowIndex; + var columnOffset = targetColumnIndex - sourceColumnIndex; + + // special case for empty target - much faster + if (target.ValueCount == 0) + { + // note: ValueCount is maximum resulting ValueCount (just using max to avoid internal copying) + // resulting arrays will likely be smaller - unless all values fit in the chosen range. + var values = new List(ValueCount); + var columnIndices = new List(ValueCount); + var rowPointers = target.RowPointers; + + for (int i = sourceRowIndex; i < sourceRowIndex + rowCount; i++) + { + rowPointers[i + rowOffset] = values.Count; + + var startIndex = RowPointers[i]; + var endIndex = RowPointers[i + 1]; + + // note: we might be able to replace this loop with Array.Copy (perf) + for (int k = startIndex; k < endIndex; k++) + { + // check if the column index is in the range + if ((ColumnIndices[k] >= sourceColumnIndex) && (ColumnIndices[k] < sourceColumnIndex + columnCount)) + { + values.Add(Values[k]); + columnIndices.Add(ColumnIndices[k] + columnOffset); + } + } + } + + for (int i = targetRowIndex + rowCount; i < rowPointers.Length; i++) + { + rowPointers[i] = values.Count; + } + + target.RowPointers[target.RowCount] = values.Count; + target.Values = values.ToArray(); + target.ColumnIndices = columnIndices.ToArray(); + + return; + } + + if (existingData == ExistingData.Clear) + { + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + } + + // NOTE: potential for more efficient implementation + for (int i = sourceRowIndex, row = 0; row < rowCount; i++, row++) + { + var startIndex = RowPointers[i]; + var endIndex = RowPointers[i + 1]; + + for (int j = startIndex; j < endIndex; j++) + { + // check if the column index is in the range + if ((ColumnIndices[j] >= sourceColumnIndex) && (ColumnIndices[j] < sourceColumnIndex + columnCount)) + { + var column = ColumnIndices[j] - sourceColumnIndex; + target.At(targetRowIndex + row, targetColumnIndex + column, Values[j]); + } + } + } + } + + // ROW COPY + + internal override void CopySubRowToUnchecked(VectorStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData) + { + // Determine bounds in columnIndices array where this item should be searched (using rowIndex) + var startIndexOfRow = RowPointers[rowIndex]; + var endIndexOfRow = RowPointers[rowIndex + 1]; + + if (startIndexOfRow == endIndexOfRow) + { + if (existingData == ExistingData.Clear) + { + target.Clear(targetColumnIndex, columnCount); + } + + return; + } + + var targetSparse = target as SparseVectorStorage; + if (targetSparse != null) + { + if ((sourceColumnIndex == 0) && (targetColumnIndex == 0) && (columnCount == ColumnCount) && (ColumnCount == targetSparse.Length)) + { + // rebuild of the values, indices, no clean necessary + targetSparse.ValueCount = endIndexOfRow - startIndexOfRow; + targetSparse.Values = new T[targetSparse.ValueCount]; + targetSparse.Indices = new int[targetSparse.ValueCount]; + Array.Copy(ColumnIndices, startIndexOfRow, targetSparse.Indices, 0, targetSparse.ValueCount); + Array.Copy(Values, startIndexOfRow, targetSparse.Values, 0, targetSparse.ValueCount); + } + else + { + int sourceStartPos = Array.BinarySearch(ColumnIndices, startIndexOfRow, endIndexOfRow - startIndexOfRow, sourceColumnIndex); + if (sourceStartPos < 0) + { + sourceStartPos = ~sourceStartPos; + } + + int sourceEndPos = Array.BinarySearch(ColumnIndices, startIndexOfRow, endIndexOfRow - startIndexOfRow, sourceColumnIndex + columnCount); + if (sourceEndPos < 0) + { + sourceEndPos = ~sourceEndPos; + } + + int positionsToCopy = sourceEndPos - sourceStartPos; + if (positionsToCopy > 0) + { + // rebuild the target (no clean necessary) + int targetStartPos = Array.BinarySearch(targetSparse.Indices, 0, targetSparse.ValueCount, targetColumnIndex); + if (targetStartPos < 0) + { + targetStartPos = ~targetStartPos; + } + + int targetEndPos = Array.BinarySearch(targetSparse.Indices, 0, targetSparse.ValueCount, targetColumnIndex + columnCount); + if (targetEndPos < 0) + { + targetEndPos = Math.Max(~targetEndPos, targetStartPos); + } + + int newValueCount = targetSparse.ValueCount - (targetEndPos - targetStartPos) + positionsToCopy; + T[] newValues = new T[newValueCount]; + int[] newIndices = new int[newValueCount]; + // copy before + Array.Copy(targetSparse.Indices, 0, newIndices, 0, targetStartPos); + Array.Copy(targetSparse.Values, 0, newValues, 0, targetStartPos); + // copy values themselves, with new positions + int shiftRight = targetColumnIndex - sourceColumnIndex; + for (int i = 0; i < positionsToCopy; ++i) + { + newIndices[targetStartPos + i] = ColumnIndices[sourceStartPos + i] + shiftRight; + } + + Array.Copy(Values, sourceStartPos, newValues, targetStartPos, positionsToCopy); + // copy after + Array.Copy(targetSparse.Indices, targetEndPos, newIndices, positionsToCopy + targetStartPos, targetSparse.ValueCount - targetEndPos); + Array.Copy(targetSparse.Values, targetEndPos, newValues, positionsToCopy + targetStartPos, targetSparse.ValueCount - targetEndPos); + targetSparse.Values = newValues; + targetSparse.Indices = newIndices; + targetSparse.ValueCount = newValueCount; + } + else + { + // although there are no values to copy, we still need to clean the existing values (if necessary) + if (existingData == ExistingData.Clear) + { + target.Clear(targetColumnIndex, columnCount); + } + } + } + + return; + } + // FALLBACK + if (existingData == ExistingData.Clear) + { + target.Clear(targetColumnIndex, columnCount); + } + // If there are non-zero elements use base class implementation + for (int i = sourceColumnIndex, j = 0; i < sourceColumnIndex + columnCount; i++, j++) + { + var index = FindItem(rowIndex, i); + target.At(j, index >= 0 ? Values[index] : Zero); + } + } + + // TRANSPOSE + + internal override void TransposeToUnchecked(MatrixStorage target, ExistingData existingData) + { + var sparseTarget = target as SparseCompressedRowMatrixStorage; + if (sparseTarget != null) + { + TransposeToUnchecked(sparseTarget); + return; + } + + var denseTarget = target as DenseColumnMajorMatrixStorage; + if (denseTarget != null) + { + TransposeToUnchecked(denseTarget, existingData); + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear) + { + target.Clear(); + } + + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + target.At(ColumnIndices[j], row, Values[j]); + } + } + } + } + + void TransposeToUnchecked(SparseCompressedRowMatrixStorage target) + { + target.Values = new T[ValueCount]; + target.ColumnIndices = new int[ValueCount]; + var cx = target.Values; + var cp = target.RowPointers; + var ci = target.ColumnIndices; + + // Column counts + int[] w = new int[ColumnCount]; + for (int p = 0; p < RowPointers[RowCount]; p++) + { + w[ColumnIndices[p]]++; + } + + // Column pointers + int nz = 0; + for (int i = 0; i < ColumnCount; i++) + { + cp[i] = nz; + nz += w[i]; + w[i] = cp[i]; + } + + cp[ColumnCount] = nz; + + for (int i = 0; i < RowCount; i++) + { + for (int p = RowPointers[i]; p < RowPointers[i + 1]; p++) + { + int j = w[ColumnIndices[p]]++; + + // Place A(i,j) as entry C(j,i) + ci[j] = i; + cx[j] = Values[p]; + } + } + } + + void TransposeToUnchecked(DenseColumnMajorMatrixStorage target, ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.Clear(); + } + + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var targetIndex = row * ColumnCount; + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + target.Data[targetIndex + ColumnIndices[j]] = Values[j]; + } + } + } + } + + internal override void TransposeSquareInplaceUnchecked() + { + var cx = new T[ValueCount]; //target.Values; + var cp = new int[RowCount + 1]; + var ci = new int[ValueCount]; //target.ColumnIndices; + + // Column counts + int[] w = new int[ColumnCount]; + for (int p = 0; p < RowPointers[RowCount]; p++) + { + w[ColumnIndices[p]]++; + } + + // Column pointers + int nz = 0; + for (int i = 0; i < ColumnCount; i++) + { + cp[i] = nz; + nz += w[i]; + w[i] = cp[i]; + } + + cp[ColumnCount] = nz; + + for (int i = 0; i < RowCount; i++) + { + for (int p = RowPointers[i]; p < RowPointers[i + 1]; p++) + { + int j = w[ColumnIndices[p]]++; + + // Place A(i,j) as entry C(j,i) + ci[j] = i; + cx[j] = Values[p]; + } + } + + Array.Copy(cx, 0, Values, 0, ValueCount); + Buffer.BlockCopy(ci, 0, ColumnIndices, 0, ValueCount * Constants.SizeOfInt); + Buffer.BlockCopy(cp, 0, RowPointers, 0, (RowCount + 1) * Constants.SizeOfInt); + } + + // EXTRACT + + public override T[] ToRowMajorArray() + { + var ret = new T[RowCount * ColumnCount]; + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var offset = row * ColumnCount; + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + ret[offset + ColumnIndices[j]] = Values[j]; + } + } + } + + return ret; + } + + public override T[] ToColumnMajorArray() + { + var ret = new T[RowCount * ColumnCount]; + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + ret[(ColumnIndices[j]) * RowCount + row] = Values[j]; + } + } + } + + return ret; + } + + public override T[][] ToRowArrays() + { + var ret = new T[RowCount][]; + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var array = new T[ColumnCount]; + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + array[ColumnIndices[j]] = Values[j]; + } + + ret[row] = array; + } + } + + return ret; + } + + public override T[][] ToColumnArrays() + { + var ret = new T[ColumnCount][]; + for (int j = 0; j < ColumnCount; j++) + { + ret[j] = new T[RowCount]; + } + + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + ret[ColumnIndices[j]][row] = Values[j]; + } + } + } + + return ret; + } + + public override T[,] ToArray() + { + var ret = new T[RowCount, ColumnCount]; + if (ValueCount != 0) + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + ret[row, ColumnIndices[j]] = Values[j]; + } + } + } + + return ret; + } + + // ENUMERATION + + public override IEnumerable Enumerate() + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + yield return k < RowPointers[row + 1] && ColumnIndices[k] == col + ? Values[k++] + : Zero; + } + } + } + + public override IEnumerable> EnumerateIndexed() + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + yield return k < RowPointers[row + 1] && ColumnIndices[k] == col + ? new Tuple(row, col, Values[k++]) + : new Tuple(row, col, Zero); + } + } + } + + public override IEnumerable EnumerateNonZero() + { + return Values.Take(ValueCount).Where(x => !Zero.Equals(x)); + } + + public override IEnumerable> EnumerateNonZeroIndexed() + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + if (!Zero.Equals(Values[j])) + { + yield return new Tuple(row, ColumnIndices[j], Values[j]); + } + } + } + } + + // FIND + + public override Tuple Find(Func predicate, Zeros zeros) + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + if (predicate(Values[j])) + { + return new Tuple(row, ColumnIndices[j], Values[j]); + } + } + } + + if (zeros == Zeros.Include && ValueCount < (RowCount * ColumnCount)) + { + if (predicate(Zero)) + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + if (k < RowPointers[row + 1] && ColumnIndices[k] == col) + { + k++; + } + else + { + return new Tuple(row, col, Zero); + } + } + } + } + } + + return null; + } + + internal override Tuple Find2Unchecked(MatrixStorage other, Func predicate, Zeros zeros) + { + var denseOther = other as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col; + if (predicate(available ? Values[k++] : Zero, otherData[col * RowCount + row])) + { + return new Tuple(row, col, available ? Values[k - 1] : Zero, otherData[col * RowCount + row]); + } + } + } + + return null; + } + + var diagonalOther = other as DiagonalMatrixStorage; + if (diagonalOther != null) + { + TOther[] otherData = diagonalOther.Data; + TOther otherZero = BuilderInstance.Matrix.Zero; + + // Full Scan + if (zeros == Zeros.Include && predicate(Zero, otherZero)) + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col; + if (predicate(available ? Values[k++] : Zero, row == col ? otherData[row] : otherZero)) + { + return new Tuple(row, col, available ? Values[k - 1] : Zero, row == col ? otherData[row] : otherZero); + } + } + } + + return null; + } + + // Sparse Scan + for (int row = 0; row < RowCount; row++) + { + bool diagonal = false; + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + if (ColumnIndices[j] == row) + { + diagonal = true; + if (predicate(Values[j], otherData[row])) + { + return new Tuple(row, row, Values[j], otherData[row]); + } + } + else + { + if (predicate(Values[j], otherZero)) + { + return new Tuple(row, ColumnIndices[j], Values[j], otherZero); + } + } + } + + if (!diagonal && row < ColumnCount) + { + if (predicate(Zero, otherData[row])) + { + return new Tuple(row, row, Zero, otherData[row]); + } + } + } + + return null; + } + + var sparseOther = other as SparseCompressedRowMatrixStorage; + if (sparseOther != null) + { + int[] otherRowPointers = sparseOther.RowPointers; + int[] otherColumnIndices = sparseOther.ColumnIndices; + TOther[] otherValues = sparseOther.Values; + TOther otherZero = BuilderInstance.Matrix.Zero; + + if (zeros == Zeros.Include) + { + int k = 0, otherk = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col; + bool otherAvailable = otherk < otherRowPointers[row + 1] && otherColumnIndices[otherk] == col; + if (predicate(available ? Values[k++] : Zero, otherAvailable ? otherValues[otherk++] : otherZero)) + { + return new Tuple(row, col, available ? Values[k - 1] : Zero, otherAvailable ? otherValues[otherk - 1] : otherZero); + } + } + } + + return null; + } + + for (int row = 0; row < RowCount; row++) + { + var endIndex = RowPointers[row + 1]; + var otherEndIndex = otherRowPointers[row + 1]; + var k = RowPointers[row]; + var otherk = otherRowPointers[row]; + while (k < endIndex || otherk < otherEndIndex) + { + if (k == endIndex || otherk < otherEndIndex && ColumnIndices[k] > otherColumnIndices[otherk]) + { + if (predicate(Zero, otherValues[otherk++])) + { + return new Tuple(row, otherColumnIndices[otherk - 1], Zero, otherValues[otherk - 1]); + } + } + else if (otherk == otherEndIndex || ColumnIndices[k] < otherColumnIndices[otherk]) + { + if (predicate(Values[k++], otherZero)) + { + return new Tuple(row, ColumnIndices[k - 1], Values[k - 1], otherZero); + } + } + else + { + if (predicate(Values[k++], otherValues[otherk++])) + { + return new Tuple(row, ColumnIndices[k - 1], Values[k - 1], otherValues[otherk - 1]); + } + } + } + } + + return null; + } + + // FALL BACK + + return base.Find2Unchecked(other, predicate, zeros); + } + + // FUNCTIONAL COMBINATORS: MAP + + public override void MapInplace(Func f, Zeros zeros) + { + if (zeros == Zeros.Include || !Zero.Equals(f(Zero))) + { + var newRowPointers = RowPointers; + var newColumnIndices = new List(ColumnIndices.Length); + var newValues = new List(Values.Length); + + int k = 0; + for (int row = 0; row < RowCount; row++) + { + newRowPointers[row] = newValues.Count; + for (int col = 0; col < ColumnCount; col++) + { + var item = k < RowPointers[row + 1] && ColumnIndices[k] == col ? f(Values[k++]) : f(Zero); + if (!Zero.Equals(item)) + { + newValues.Add(item); + newColumnIndices.Add(col); + } + } + } + + ColumnIndices = newColumnIndices.ToArray(); + Values = newValues.ToArray(); + newRowPointers[RowCount] = newValues.Count; + } + else + { + // we can safely do this in-place: + int nonZero = 0; + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + RowPointers[row] = nonZero; + for (var j = startIndex; j < endIndex; j++) + { + var item = f(Values[j]); + if (!Zero.Equals(item)) + { + Values[nonZero] = item; + ColumnIndices[nonZero] = ColumnIndices[j]; + nonZero++; + } + } + } + + Array.Resize(ref ColumnIndices, nonZero); + Array.Resize(ref Values, nonZero); + RowPointers[RowCount] = nonZero; + } + } + + public override void MapIndexedInplace(Func f, Zeros zeros) + { + if (zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero))) + { + var newRowPointers = RowPointers; + var newColumnIndices = new List(ColumnIndices.Length); + var newValues = new List(Values.Length); + + int k = 0; + for (int row = 0; row < RowCount; row++) + { + newRowPointers[row] = newValues.Count; + for (int col = 0; col < ColumnCount; col++) + { + var item = k < RowPointers[row + 1] && ColumnIndices[k] == col ? f(row, col, Values[k++]) : f(row, col, Zero); + if (!Zero.Equals(item)) + { + newValues.Add(item); + newColumnIndices.Add(col); + } + } + } + + ColumnIndices = newColumnIndices.ToArray(); + Values = newValues.ToArray(); + newRowPointers[RowCount] = newValues.Count; + } + else + { + // we can safely do this in-place: + int nonZero = 0; + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + RowPointers[row] = nonZero; + for (var j = startIndex; j < endIndex; j++) + { + var item = f(row, ColumnIndices[j], Values[j]); + if (!Zero.Equals(item)) + { + Values[nonZero] = item; + ColumnIndices[nonZero] = ColumnIndices[j]; + nonZero++; + } + } + } + + Array.Resize(ref ColumnIndices, nonZero); + Array.Resize(ref Values, nonZero); + RowPointers[RowCount] = nonZero; + } + } + + internal override void MapToUnchecked(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) + { + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(Zero)); + + var sparseTarget = target as SparseCompressedRowMatrixStorage; + if (sparseTarget != null) + { + var newRowPointers = sparseTarget.RowPointers; + var newColumnIndices = new List(ColumnIndices.Length); + var newValues = new List(Values.Length); + + if (processZeros) + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + newRowPointers[row] = newValues.Count; + for (int col = 0; col < ColumnCount; col++) + { + var item = k < RowPointers[row + 1] && ColumnIndices[k] == col ? f(Values[k++]) : f(Zero); + if (!Zero.Equals(item)) + { + newValues.Add(item); + newColumnIndices.Add(col); + } + } + } + } + else + { + for (int row = 0; row < RowCount; row++) + { + newRowPointers[row] = newValues.Count; + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + var item = f(Values[j]); + if (!Zero.Equals(item)) + { + newValues.Add(item); + newColumnIndices.Add(ColumnIndices[j]); + } + } + } + } + + sparseTarget.ColumnIndices = newColumnIndices.ToArray(); + sparseTarget.Values = newValues.ToArray(); + newRowPointers[RowCount] = newValues.Count; + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear && !processZeros) + { + target.Clear(); + } + + if (processZeros) + { + for (int row = 0; row < RowCount; row++) + { + var index = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (int j = 0; j < ColumnCount; j++) + { + if (index < endIndex && j == ColumnIndices[index]) + { + target.At(row, j, f(Values[index])); + index = Math.Min(index + 1, endIndex); + } + else + { + target.At(row, j, f(Zero)); + } + } + } + } + else + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + target.At(row, ColumnIndices[j], f(Values[j])); + } + } + } + } + + internal override void MapIndexedToUnchecked(MatrixStorage target, Func f, Zeros zeros, ExistingData existingData) + { + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero)); + + var sparseTarget = target as SparseCompressedRowMatrixStorage; + if (sparseTarget != null) + { + var newRowPointers = sparseTarget.RowPointers; + var newColumnIndices = new List(ColumnIndices.Length); + var newValues = new List(Values.Length); + + if (processZeros) + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + newRowPointers[row] = newValues.Count; + for (int col = 0; col < ColumnCount; col++) + { + var item = k < RowPointers[row + 1] && ColumnIndices[k] == col ? f(row, col, Values[k++]) : f(row, col, Zero); + if (!Zero.Equals(item)) + { + newValues.Add(item); + newColumnIndices.Add(col); + } + } + } + } + else + { + for (int row = 0; row < RowCount; row++) + { + newRowPointers[row] = newValues.Count; + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + var item = f(row, ColumnIndices[j], Values[j]); + if (!Zero.Equals(item)) + { + newValues.Add(item); + newColumnIndices.Add(ColumnIndices[j]); + } + } + } + } + + sparseTarget.ColumnIndices = newColumnIndices.ToArray(); + sparseTarget.Values = newValues.ToArray(); + newRowPointers[RowCount] = newValues.Count; + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear && !processZeros) + { + target.Clear(); + } + + if (processZeros) + { + for (int row = 0; row < RowCount; row++) + { + var index = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (int j = 0; j < ColumnCount; j++) + { + if (index < endIndex && j == ColumnIndices[index]) + { + target.At(row, j, f(row, j, Values[index])); + index = Math.Min(index + 1, endIndex); + } + else + { + target.At(row, j, f(row, j, Zero)); + } + } + } + } + else + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + target.At(row, ColumnIndices[j], f(row, ColumnIndices[j], Values[j])); + } + } + } + } + + internal override void MapSubMatrixIndexedToUnchecked(MatrixStorage target, Func f, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + Zeros zeros, ExistingData existingData) + { + var sparseTarget = target as SparseCompressedRowMatrixStorage; + if (sparseTarget != null) + { + MapSubMatrixIndexedToUnchecked(sparseTarget, f, sourceRowIndex, targetRowIndex, rowCount, sourceColumnIndex, targetColumnIndex, columnCount, zeros, existingData); + return; + } + + // FALL BACK + + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero)); + if (existingData == ExistingData.Clear && !processZeros) + { + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + } + + if (processZeros) + { + for (int sr = sourceRowIndex, tr = targetRowIndex; sr < sourceRowIndex + rowCount; sr++, tr++) + { + var index = RowPointers[sr]; + var endIndex = RowPointers[sr + 1]; + + // move forward to our sub-range + for (; ColumnIndices[index] < sourceColumnIndex && index < endIndex; index++) + { + } + + for (int sc = sourceColumnIndex, tc = targetColumnIndex; sc < sourceColumnIndex + columnCount; sc++, tc++) + { + if (index < endIndex && sc == ColumnIndices[index]) + { + target.At(tr, tc, f(tr, tc, Values[index])); + index = Math.Min(index + 1, endIndex); + } + else + { + target.At(tr, tc, f(tr, tc, Zero)); + } + } + } + } + else + { + int columnOffset = targetColumnIndex - sourceColumnIndex; + for (int sr = sourceRowIndex, tr = targetRowIndex; sr < sourceRowIndex + rowCount; sr++, tr++) + { + var startIndex = RowPointers[sr]; + var endIndex = RowPointers[sr + 1]; + for (int k = startIndex; k < endIndex; k++) + { + // check if the column index is in the range + if ((ColumnIndices[k] >= sourceColumnIndex) && (ColumnIndices[k] < sourceColumnIndex + columnCount)) + { + int tc = ColumnIndices[k] + columnOffset; + target.At(tr, tc, f(tr, tc, Values[k])); + } + } + } + } + } + + void MapSubMatrixIndexedToUnchecked(SparseCompressedRowMatrixStorage target, Func f, + int sourceRowIndex, int targetRowIndex, int rowCount, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(0, 1, Zero)); + if (existingData == ExistingData.Clear && !processZeros) + { + target.ClearUnchecked(targetRowIndex, rowCount, targetColumnIndex, columnCount); + } + + var rowOffset = targetRowIndex - sourceRowIndex; + var columnOffset = targetColumnIndex - sourceColumnIndex; + var zero = Matrix.Zero; + + // special case for empty target - much faster + if (target.ValueCount == 0) + { + var values = new List(ValueCount); + var columnIndices = new List(ValueCount); + var rowPointers = target.RowPointers; + + if (processZeros) + { + for (int sr = sourceRowIndex; sr < sourceRowIndex + rowCount; sr++) + { + int tr = sr + rowOffset; + rowPointers[tr] = values.Count; + + var index = RowPointers[sr]; + var endIndex = RowPointers[sr + 1]; + + // move forward to our sub-range + for (; ColumnIndices[index] < sourceColumnIndex && index < endIndex; index++) + { + } + + for (int sc = sourceColumnIndex, tc = targetColumnIndex; sc < sourceColumnIndex + columnCount; sc++, tc++) + { + if (index < endIndex && sc == ColumnIndices[index]) + { + TU item = f(tr, tc, Values[index]); + if (!zero.Equals(item)) + { + values.Add(item); + columnIndices.Add(tc); + } + + index = Math.Min(index + 1, endIndex); + } + else + { + TU item = f(tr, tc, Zero); + if (!zero.Equals(item)) + { + values.Add(item); + columnIndices.Add(tc); + } + } + } + } + } + else + { + for (int sr = sourceRowIndex; sr < sourceRowIndex + rowCount; sr++) + { + int tr = sr + rowOffset; + rowPointers[tr] = values.Count; + + var startIndex = RowPointers[sr]; + var endIndex = RowPointers[sr + 1]; + + for (int k = startIndex; k < endIndex; k++) + { + // check if the column index is in the range + if ((ColumnIndices[k] >= sourceColumnIndex) && (ColumnIndices[k] < sourceColumnIndex + columnCount)) + { + int tc = ColumnIndices[k] + columnOffset; + TU item = f(tr, tc, Values[k]); + if (!zero.Equals(item)) + { + values.Add(item); + columnIndices.Add(tc); + } + } + } + } + } + + for (int i = targetRowIndex + rowCount; i < rowPointers.Length; i++) + { + rowPointers[i] = values.Count; + } + + target.RowPointers[target.RowCount] = values.Count; + target.Values = values.ToArray(); + target.ColumnIndices = columnIndices.ToArray(); + return; + } + + // TODO: proper general sparse case - the following is essentially a fall back, not leveraging the target data structure + + if (processZeros) + { + for (int sr = sourceRowIndex, tr = targetRowIndex; sr < sourceRowIndex + rowCount; sr++, tr++) + { + var index = RowPointers[sr]; + var endIndex = RowPointers[sr + 1]; + + // move forward to our sub-range + for (; ColumnIndices[index] < sourceColumnIndex && index < endIndex; index++) + { + } + + for (int sc = sourceColumnIndex, tc = targetColumnIndex; sc < sourceColumnIndex + columnCount; sc++, tc++) + { + if (index < endIndex && sc == ColumnIndices[index]) + { + target.At(tr, tc, f(tr, tc, Values[index])); + index = Math.Min(index + 1, endIndex); + } + else + { + target.At(tr, tc, f(tr, tc, Zero)); + } + } + } + } + else + { + for (int sr = sourceRowIndex, tr = targetRowIndex; sr < sourceRowIndex + rowCount; sr++, tr++) + { + var startIndex = RowPointers[sr]; + var endIndex = RowPointers[sr + 1]; + for (int k = startIndex; k < endIndex; k++) + { + // check if the column index is in the range + if ((ColumnIndices[k] >= sourceColumnIndex) && (ColumnIndices[k] < sourceColumnIndex + columnCount)) + { + int tc = ColumnIndices[k] + columnOffset; + target.At(tr, tc, f(tr, tc, Values[k])); + } + } + } + } + } + + // FUNCTIONAL COMBINATORS: FOLD + + internal override void FoldByRowUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + if (zeros == Zeros.AllowSkip) + { + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + TU s = state[row]; + for (var j = startIndex; j < endIndex; j++) + { + s = f(s, Values[j]); + } + + target[row] = finalize(s, endIndex - startIndex); + } + } + else + { + for (int row = 0; row < RowCount; row++) + { + var index = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + TU s = state[row]; + for (int j = 0; j < ColumnCount; j++) + { + if (index < endIndex && j == ColumnIndices[index]) + { + s = f(s, Values[index]); + index = Math.Min(index + 1, endIndex); + } + else + { + s = f(s, Zero); + } + } + + target[row] = finalize(s, ColumnCount); + } + } + } + + internal override void FoldByColumnUnchecked(TU[] target, Func f, Func finalize, TU[] state, Zeros zeros) + { + if (!ReferenceEquals(state, target)) + { + Array.Copy(state, 0, target, 0, state.Length); + } + + if (zeros == Zeros.AllowSkip) + { + int[] count = new int[ColumnCount]; + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + var column = ColumnIndices[j]; + target[column] = f(target[column], Values[j]); + count[column]++; + } + } + + for (int j = 0; j < ColumnCount; j++) + { + target[j] = finalize(target[j], count[j]); + } + } + else + { + for (int row = 0; row < RowCount; row++) + { + var index = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (int j = 0; j < ColumnCount; j++) + { + if (index < endIndex && j == ColumnIndices[index]) + { + target[j] = f(target[j], Values[index]); + index = Math.Min(index + 1, endIndex); + } + else + { + target[j] = f(target[j], Zero); + } + } + } + + for (int j = 0; j < ColumnCount; j++) + { + target[j] = finalize(target[j], RowCount); + } + } + } + + internal override TState Fold2Unchecked(MatrixStorage other, Func f, TState state, Zeros zeros) + { + var denseOther = other as DenseColumnMajorMatrixStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col; + state = f(state, available ? Values[k++] : Zero, otherData[col * RowCount + row]); + } + } + + return state; + } + + var diagonalOther = other as DiagonalMatrixStorage; + if (diagonalOther != null) + { + TOther[] otherData = diagonalOther.Data; + TOther otherZero = BuilderInstance.Matrix.Zero; + + if (zeros == Zeros.Include) + { + int k = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col; + state = f(state, available ? Values[k++] : Zero, row == col ? otherData[row] : otherZero); + } + } + + return state; + } + + for (int row = 0; row < RowCount; row++) + { + bool diagonal = false; + + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + for (var j = startIndex; j < endIndex; j++) + { + if (ColumnIndices[j] == row) + { + diagonal = true; + state = f(state, Values[j], otherData[row]); + } + else + { + state = f(state, Values[j], otherZero); + } + } + + if (!diagonal && row < ColumnCount) + { + state = f(state, Zero, otherData[row]); + } + } + + return state; + } + + var sparseOther = other as SparseCompressedRowMatrixStorage; + if (sparseOther != null) + { + int[] otherRowPointers = sparseOther.RowPointers; + int[] otherColumnIndices = sparseOther.ColumnIndices; + TOther[] otherValues = sparseOther.Values; + TOther otherZero = BuilderInstance.Matrix.Zero; + + if (zeros == Zeros.Include) + { + int k = 0, otherk = 0; + for (int row = 0; row < RowCount; row++) + { + for (int col = 0; col < ColumnCount; col++) + { + bool available = k < RowPointers[row + 1] && ColumnIndices[k] == col; + bool otherAvailable = otherk < otherRowPointers[row + 1] && otherColumnIndices[otherk] == col; + state = f(state, available ? Values[k++] : Zero, otherAvailable ? otherValues[otherk++] : otherZero); + } + } + + return state; + } + + for (int row = 0; row < RowCount; row++) + { + var startIndex = RowPointers[row]; + var endIndex = RowPointers[row + 1]; + var otherStartIndex = otherRowPointers[row]; + var otherEndIndex = otherRowPointers[row + 1]; + + var j1 = startIndex; + var j2 = otherStartIndex; + + while (j1 < endIndex || j2 < otherEndIndex) + { + if (j1 == endIndex || j2 < otherEndIndex && ColumnIndices[j1] > otherColumnIndices[j2]) + { + state = f(state, Zero, otherValues[j2++]); + } + else if (j2 == otherEndIndex || ColumnIndices[j1] < otherColumnIndices[j2]) + { + state = f(state, Values[j1++], otherZero); + } + else + { + state = f(state, Values[j1++], otherValues[j2++]); + } + } + } + + return state; + } + + // FALL BACK + + return base.Fold2Unchecked(other, f, state, zeros); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Storage/SparseVectorStorage.cs b/MathNet.Numerics/LinearAlgebra/Storage/SparseVectorStorage.cs new file mode 100644 index 0000000..4ca265f --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/SparseVectorStorage.cs @@ -0,0 +1,1244 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/LinearAlgebra")] +public class SparseVectorStorage : VectorStorage + where T : struct, IEquatable, IFormattable +{ + // [ruegg] public fields are OK here + + /// + /// Array that contains the indices of the non-zero values. + /// + [DataMember(Order = 1)] + public int[] Indices; + + /// + /// Array that contains the non-zero elements of the vector. + /// + [DataMember(Order = 2)] + public T[] Values; + + /// + /// Gets the number of non-zero elements in the vector. + /// + [DataMember(Order = 3)] + public int ValueCount; + + internal SparseVectorStorage(int length) + : base(length) + { + Indices = new int[0]; + Values = new T[0]; + ValueCount = 0; + } + + /// + /// True if the vector storage format is dense. + /// + public override bool IsDense + { + get { return false; } + } + + /// + /// Retrieves the requested element without range checking. + /// + public override T At(int index) + { + // Search if item index exists in NonZeroIndices array in range "0 - nonzero values count" + var itemIndex = Array.BinarySearch(Indices, 0, ValueCount, index); + return itemIndex >= 0 ? Values[itemIndex] : Zero; + } + + /// + /// Sets the element without range checking. + /// + public override void At(int index, T value) + { + // Search if "index" already exists in range "0 - nonzero values count" + var itemIndex = Array.BinarySearch(Indices, 0, ValueCount, index); + if (itemIndex >= 0) + { + // Non-zero item found in matrix + if (Zero.Equals(value)) + { + // Delete existing item + RemoveAtIndexUnchecked(itemIndex); + } + else + { + // Update item + Values[itemIndex] = value; + } + } + else + { + // Item not found. Add new value + if (!Zero.Equals(value)) + { + InsertAtIndexUnchecked(~itemIndex, index, value); + } + } + } + + internal void InsertAtIndexUnchecked(int itemIndex, int index, T value) + { + // Check if the storage needs to be increased + if ((ValueCount == Values.Length) && (ValueCount < Length)) + { + // Value and Indices arrays are completely full so we increase the size + var size = Math.Min(Values.Length + GrowthSize(), Length); + Array.Resize(ref Values, size); + Array.Resize(ref Indices, size); + } + + // Move all values (with a position larger than index) in the value array to the next position + // Move all values (with a position larger than index) in the columIndices array to the next position + Array.Copy(Values, itemIndex, Values, itemIndex + 1, ValueCount - itemIndex); + Array.Copy(Indices, itemIndex, Indices, itemIndex + 1, ValueCount - itemIndex); + + // Add the value and the column index + Values[itemIndex] = value; + Indices[itemIndex] = index; + + // increase the number of non-zero numbers by one + ValueCount += 1; + } + + internal void RemoveAtIndexUnchecked(int itemIndex) + { + // Value is zero. Let's delete it from Values and Indices array + Array.Copy(Values, itemIndex + 1, Values, itemIndex, ValueCount - itemIndex - 1); + Array.Copy(Indices, itemIndex + 1, Indices, itemIndex, ValueCount - itemIndex - 1); + + ValueCount -= 1; + + // Check whether we need to shrink the arrays. This is reasonable to do if + // there are a lot of non-zero elements and storage is two times bigger + if ((ValueCount > 1024) && (ValueCount < Indices.Length / 2)) + { + Array.Resize(ref Values, ValueCount); + Array.Resize(ref Indices, ValueCount); + } + } + + /// + /// Calculates the amount with which to grow the storage array's if they need to be + /// increased in size. + /// + /// The amount grown. + int GrowthSize() + { + int delta; + if (Values.Length > 1024) + { + delta = Values.Length / 4; + } + else + { + if (Values.Length > 256) + { + delta = 512; + } + else + { + delta = Values.Length > 64 ? 128 : 32; + } + } + + return delta; + } + + public override bool Equals(VectorStorage other) + { + // Reject equality when the argument is null or has a different shape. + if (other == null || Length != other.Length) + { + return false; + } + + // Accept if the argument is the same object as this. + if (ReferenceEquals(this, other)) + { + return true; + } + + var otherSparse = other as SparseVectorStorage; + if (otherSparse == null) + { + return base.Equals(other); + } + + int i = 0, j = 0; + while (i < ValueCount || j < otherSparse.ValueCount) + { + if (j >= otherSparse.ValueCount || i < ValueCount && Indices[i] < otherSparse.Indices[j]) + { + if (!Zero.Equals(Values[i++])) + { + return false; + } + + continue; + } + + if (i >= ValueCount || j < otherSparse.ValueCount && otherSparse.Indices[j] < Indices[i]) + { + if (!Zero.Equals(otherSparse.Values[j++])) + { + return false; + } + + continue; + } + + if (!Values[i].Equals(otherSparse.Values[j])) + { + return false; + } + + i++; + j++; + } + + return true; + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + var values = Values; + var hashNum = Math.Min(ValueCount, 25); + int hash = 17; + unchecked + { + for (var i = 0; i < hashNum; i++) + { + hash = hash * 31 + values[i].GetHashCode(); + } + } + + return hash; + } + + // CLEARING + + public override void Clear() + { + ValueCount = 0; + } + + public override void Clear(int index, int count) + { + if (index == 0 && count == Length) + { + Clear(); + return; + } + + var first = Array.BinarySearch(Indices, 0, ValueCount, index); + var last = Array.BinarySearch(Indices, 0, ValueCount, index + count - 1); + if (first < 0) + first = ~first; + if (last < 0) + last = ~last - 1; + int itemCount = last - first + 1; + + if (itemCount > 0) + { + Array.Copy(Values, first + itemCount, Values, first, ValueCount - first - itemCount); + Array.Copy(Indices, first + itemCount, Indices, first, ValueCount - first - itemCount); + + ValueCount -= itemCount; + } + + // Check whether we need to shrink the arrays. This is reasonable to do if + // there are a lot of non-zero elements and storage is two times bigger + if ((ValueCount > 1024) && (ValueCount < Indices.Length / 2)) + { + Array.Resize(ref Values, ValueCount); + Array.Resize(ref Indices, ValueCount); + } + } + + // INITIALIZATION + + public static SparseVectorStorage OfVector(VectorStorage vector) + { + var storage = new SparseVectorStorage(vector.Length); + vector.CopyToUnchecked(storage, ExistingData.AssumeZeros); + return storage; + } + + public static SparseVectorStorage OfValue(int length, T value) + { + if (Zero.Equals(value)) + { + return new SparseVectorStorage(length); + } + + if (length < 1) + { + throw new ArgumentOutOfRangeException(nameof(length), string.Format(Resources.ArgumentLessThanOne, length)); + } + + var indices = new int[length]; + var values = new T[length]; + for (int i = 0; i < indices.Length; i++) + { + indices[i] = i; + values[i] = value; + } + + return new SparseVectorStorage(length) + { + Indices = indices, + Values = values, + ValueCount = length + }; + } + + public static SparseVectorStorage OfInit(int length, Func init) + { + if (length < 1) + { + throw new ArgumentOutOfRangeException(nameof(length), string.Format(Resources.ArgumentLessThanOne, length)); + } + + var indices = new List(); + var values = new List(); + for (int i = 0; i < length; i++) + { + var item = init(i); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(i); + } + } + + return new SparseVectorStorage(length) + { + Indices = indices.ToArray(), + Values = values.ToArray(), + ValueCount = values.Count + }; + } + + public static SparseVectorStorage OfEnumerable(IEnumerable data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var indices = new List(); + var values = new List(); + int index = 0; + + foreach (T item in data) + { + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(index); + } + + index++; + } + + return new SparseVectorStorage(index) + { + Indices = indices.ToArray(), + Values = values.ToArray(), + ValueCount = values.Count + }; + } + + public static SparseVectorStorage OfIndexedEnumerable(int length, IEnumerable> data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var indices = new List(); + var values = new List(); + foreach (var item in data) + { + if (!Zero.Equals(item.Item2)) + { + values.Add(item.Item2); + indices.Add(item.Item1); + } + } + + var indicesArray = indices.ToArray(); + var valuesArray = values.ToArray(); + Sorting.Sort(indicesArray, valuesArray); + + return new SparseVectorStorage(length) + { + Indices = indicesArray, + Values = valuesArray, + ValueCount = values.Count + }; + } + + // VECTOR COPY + + internal override void CopyToUnchecked(VectorStorage target, ExistingData existingData) + { + var sparseTarget = target as SparseVectorStorage; + if (sparseTarget != null) + { + CopyToUnchecked(sparseTarget); + return; + } + + // FALL BACK + + if (existingData == ExistingData.Clear) + { + target.Clear(); + } + + if (ValueCount != 0) + { + for (int i = 0; i < ValueCount; i++) + { + target.At(Indices[i], Values[i]); + } + } + } + + void CopyToUnchecked(SparseVectorStorage target) + { + if (ReferenceEquals(this, target)) + { + return; + } + + if (Length != target.Length) + { + var message = string.Format(Resources.ArgumentMatrixDimensions2, Length, target.Length); + throw new ArgumentException(message, nameof(target)); + } + + target.ValueCount = ValueCount; + target.Values = new T[ValueCount]; + target.Indices = new int[ValueCount]; + + if (ValueCount != 0) + { + Array.Copy(Values, 0, target.Values, 0, ValueCount); + Buffer.BlockCopy(Indices, 0, target.Indices, 0, ValueCount * Constants.SizeOfInt); + } + } + + // Row COPY + + internal override void CopyToRowUnchecked(MatrixStorage target, int rowIndex, ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.ClearUnchecked(rowIndex, 1, 0, Length); + } + + if (ValueCount == 0) + { + return; + } + + for (int i = 0; i < ValueCount; i++) + { + target.At(rowIndex, Indices[i], Values[i]); + } + } + + // COLUMN COPY + + internal override void CopyToColumnUnchecked(MatrixStorage target, int columnIndex, ExistingData existingData) + { + if (existingData == ExistingData.Clear) + { + target.ClearUnchecked(0, Length, columnIndex, 1); + } + + if (ValueCount == 0) + { + return; + } + + for (int i = 0; i < ValueCount; i++) + { + target.At(Indices[i], columnIndex, Values[i]); + } + } + + // SUB-VECTOR COPY + + internal override void CopySubVectorToUnchecked(VectorStorage target, + int sourceIndex, int targetIndex, int count, ExistingData existingData) + { + var sparseTarget = target as SparseVectorStorage; + if (sparseTarget != null) + { + CopySubVectorToUnchecked(sparseTarget, sourceIndex, targetIndex, count, existingData); + return; + } + + // FALL BACK + + var offset = targetIndex - sourceIndex; + + var sourceFirst = Array.BinarySearch(Indices, 0, ValueCount, sourceIndex); + var sourceLast = Array.BinarySearch(Indices, 0, ValueCount, sourceIndex + count - 1); + if (sourceFirst < 0) + sourceFirst = ~sourceFirst; + if (sourceLast < 0) + sourceLast = ~sourceLast - 1; + + if (existingData == ExistingData.Clear) + { + target.Clear(targetIndex, count); + } + + for (int i = sourceFirst; i <= sourceLast; i++) + { + target.At(Indices[i] + offset, Values[i]); + } + } + + void CopySubVectorToUnchecked(SparseVectorStorage target, + int sourceIndex, int targetIndex, int count, + ExistingData existingData) + { + var offset = targetIndex - sourceIndex; + + var sourceFirst = Array.BinarySearch(Indices, 0, ValueCount, sourceIndex); + var sourceLast = Array.BinarySearch(Indices, 0, ValueCount, sourceIndex + count - 1); + if (sourceFirst < 0) + sourceFirst = ~sourceFirst; + if (sourceLast < 0) + sourceLast = ~sourceLast - 1; + int sourceCount = sourceLast - sourceFirst + 1; + + // special case when copying to itself + if (ReferenceEquals(this, target)) + { + var values = new T[sourceCount]; + var indices = new int[sourceCount]; + + Array.Copy(Values, sourceFirst, values, 0, sourceCount); + for (int i = 0; i < indices.Length; i++) + { + indices[i] = Indices[i + sourceFirst]; + } + + if (existingData == ExistingData.Clear) + { + Clear(targetIndex, count); + } + + for (int i = sourceFirst; i <= sourceLast; i++) + { + At(indices[i] + offset, values[i]); + } + + return; + } + + // special case for empty target - much faster + if (target.ValueCount == 0) + { + var values = new T[sourceCount]; + var indices = new int[sourceCount]; + + Array.Copy(Values, sourceFirst, values, 0, sourceCount); + for (int i = 0; i < indices.Length; i++) + { + indices[i] = Indices[i + sourceFirst] + offset; + } + + target.ValueCount = sourceCount; + target.Values = values; + target.Indices = indices; + + return; + } + + if (existingData == ExistingData.Clear) + { + target.Clear(targetIndex, count); + } + + for (int i = sourceFirst; i <= sourceLast; i++) + { + target.At(Indices[i] + offset, Values[i]); + } + } + + // EXTRACT + + public override T[] ToArray() + { + var ret = new T[Length]; + for (int i = 0; i < ValueCount; i++) + { + ret[Indices[i]] = Values[i]; + } + + return ret; + } + + // ENUMERATION + + public override IEnumerable Enumerate() + { + int k = 0; + for (int i = 0; i < Length; i++) + { + yield return k < ValueCount && Indices[k] == i + ? Values[k++] + : Zero; + } + } + + public override IEnumerable> EnumerateIndexed() + { + int k = 0; + for (int i = 0; i < Length; i++) + { + yield return k < ValueCount && Indices[k] == i + ? new Tuple(i, Values[k++]) + : new Tuple(i, Zero); + } + } + + public override IEnumerable EnumerateNonZero() + { + return Values.Take(ValueCount).Where(x => !Zero.Equals(x)); + } + + public override IEnumerable> EnumerateNonZeroIndexed() + { + for (var i = 0; i < ValueCount; i++) + { + if (!Zero.Equals(Values[i])) + { + yield return new Tuple(Indices[i], Values[i]); + } + } + } + + // FIND + + public override Tuple Find(Func predicate, Zeros zeros) + { + for (int i = 0; i < ValueCount; i++) + { + if (predicate(Values[i])) + { + return new Tuple(Indices[i], Values[i]); + } + } + + if (zeros == Zeros.Include && ValueCount < Length && predicate(Zero)) + { + for (int i = 0; i < Length; i++) + { + if (i >= ValueCount || Indices[i] != i) + { + return new Tuple(i, Zero); + } + } + } + + return null; + } + + internal override Tuple Find2Unchecked(VectorStorage other, Func predicate, Zeros zeros) + { + var denseOther = other as DenseVectorStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + int k = 0; + for (int i = 0; i < otherData.Length; i++) + { + if (k < ValueCount && Indices[k] == i) + { + if (predicate(Values[k], otherData[i])) + { + return new Tuple(i, Values[k], otherData[i]); + } + + k++; + } + else + { + if (predicate(Zero, otherData[i])) + { + return new Tuple(i, Zero, otherData[i]); + } + } + } + + return null; + } + + var sparseOther = other as SparseVectorStorage; + if (sparseOther != null) + { + int[] otherIndices = sparseOther.Indices; + TOther[] otherValues = sparseOther.Values; + int otherValueCount = sparseOther.ValueCount; + TOther otherZero = BuilderInstance.Matrix.Zero; + + // Full Scan + int k = 0, otherk = 0; + if (zeros == Zeros.Include && ValueCount < Length && sparseOther.ValueCount < Length && predicate(Zero, otherZero)) + { + for (int i = 0; i < Length; i++) + { + var left = k < ValueCount && Indices[k] == i ? Values[k++] : Zero; + var right = otherk < otherValueCount && otherIndices[otherk] == i ? otherValues[otherk++] : otherZero; + if (predicate(left, right)) + { + return new Tuple(i, left, right); + } + } + + return null; + } + + // Sparse Scan + k = 0; + otherk = 0; + while (k < ValueCount || otherk < otherValueCount) + { + if (k == ValueCount || otherk < otherValueCount && Indices[k] > otherIndices[otherk]) + { + if (predicate(Zero, otherValues[otherk++])) + { + return new Tuple(otherIndices[otherk - 1], Zero, otherValues[otherk - 1]); + } + } + else if (otherk == otherValueCount || Indices[k] < otherIndices[otherk]) + { + if (predicate(Values[k++], otherZero)) + { + return new Tuple(Indices[k - 1], Values[k - 1], otherZero); + } + } + else + { + if (predicate(Values[k++], otherValues[otherk++])) + { + return new Tuple(Indices[k - 1], Values[k - 1], otherValues[otherk - 1]); + } + } + } + + return null; + } + + // FALL BACK + + return base.Find2Unchecked(other, predicate, zeros); + } + + // FUNCTIONAL COMBINATORS: MAP + + public override void MapInplace(Func f, Zeros zeros) + { + var indices = new List(); + var values = new List(ValueCount); + if (zeros == Zeros.Include || !Zero.Equals(f(Zero))) + { + int k = 0; + for (int i = 0; i < Length; i++) + { + var item = k < ValueCount && (Indices[k]) == i ? f(Values[k++]) : f(Zero); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(i); + } + } + } + else + { + for (int i = 0; i < ValueCount; i++) + { + var item = f(Values[i]); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(Indices[i]); + } + } + } + + Indices = indices.ToArray(); + Values = values.ToArray(); + ValueCount = values.Count; + } + + public override void MapIndexedInplace(Func f, Zeros zeros) + { + var indices = new List(); + var values = new List(ValueCount); + if (zeros == Zeros.Include) + { + int k = 0; + for (int i = 0; i < Length; i++) + { + var item = k < ValueCount && (Indices[k]) == i ? f(i, Values[k++]) : f(i, Zero); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(i); + } + } + } + else + { + for (int i = 0; i < ValueCount; i++) + { + var item = f(Indices[i], Values[i]); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(Indices[i]); + } + } + } + + Indices = indices.ToArray(); + Values = values.ToArray(); + ValueCount = values.Count; + } + + internal override void MapToUnchecked(VectorStorage target, Func f, Zeros zeros, ExistingData existingData) + { + var sparseTarget = target as SparseVectorStorage; + if (sparseTarget != null) + { + var indices = new List(); + var values = new List(); + if (zeros == Zeros.Include || !Zero.Equals(f(Zero))) + { + int k = 0; + for (int i = 0; i < Length; i++) + { + var item = k < ValueCount && (Indices[k]) == i ? f(Values[k++]) : f(Zero); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(i); + } + } + } + else + { + for (int i = 0; i < ValueCount; i++) + { + var item = f(Values[i]); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(Indices[i]); + } + } + } + + sparseTarget.Indices = indices.ToArray(); + sparseTarget.Values = values.ToArray(); + sparseTarget.ValueCount = values.Count; + return; + } + + var denseTarget = target as DenseVectorStorage; + if (denseTarget != null) + { + if (existingData == ExistingData.Clear) + { + denseTarget.Clear(); + } + + if (zeros == Zeros.Include || !Zero.Equals(f(Zero))) + { + int k = 0; + for (int i = 0; i < Length; i++) + { + denseTarget.Data[i] = k < ValueCount && (Indices[k]) == i + ? f(Values[k++]) + : f(Zero); + } + } + else + { + CommonParallel.For(0, ValueCount, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + denseTarget.Data[Indices[i]] = f(Values[i]); + } + }); + } + + return; + } + + // FALL BACK + + base.MapToUnchecked(target, f, zeros, existingData); + } + + internal override void MapIndexedToUnchecked(VectorStorage target, Func f, Zeros zeros, ExistingData existingData) + { + var sparseTarget = target as SparseVectorStorage; + if (sparseTarget != null) + { + var indices = new List(); + var values = new List(); + if (zeros == Zeros.Include || !Zero.Equals(f(0, Zero))) + { + int k = 0; + for (int i = 0; i < Length; i++) + { + var item = k < ValueCount && (Indices[k]) == i ? f(i, Values[k++]) : f(i, Zero); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(i); + } + } + } + else + { + for (int i = 0; i < ValueCount; i++) + { + var item = f(Indices[i], Values[i]); + if (!Zero.Equals(item)) + { + values.Add(item); + indices.Add(Indices[i]); + } + } + } + + sparseTarget.Indices = indices.ToArray(); + sparseTarget.Values = values.ToArray(); + sparseTarget.ValueCount = values.Count; + return; + } + + var denseTarget = target as DenseVectorStorage; + if (denseTarget != null) + { + if (existingData == ExistingData.Clear) + { + denseTarget.Clear(); + } + + if (zeros == Zeros.Include || !Zero.Equals(f(0, Zero))) + { + int k = 0; + for (int i = 0; i < Length; i++) + { + denseTarget.Data[i] = k < ValueCount && (Indices[k]) == i + ? f(i, Values[k++]) + : f(i, Zero); + } + } + else + { + CommonParallel.For(0, ValueCount, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + denseTarget.Data[Indices[i]] = f(Indices[i], Values[i]); + } + }); + } + + return; + } + + // FALL BACK + + base.MapIndexedToUnchecked(target, f, zeros, existingData); + } + + internal override void Map2ToUnchecked(VectorStorage target, VectorStorage other, Func f, Zeros zeros, ExistingData existingData) + { + var processZeros = zeros == Zeros.Include || !Zero.Equals(f(Zero, Zero)); + + var denseTarget = target as DenseVectorStorage; + var denseOther = other as DenseVectorStorage; + + if (denseTarget == null && (denseOther != null || processZeros)) + { + // The handling is effectively dense but we're supposed to push + // to a sparse target. Let's use a dense target instead, + // then copy it normalized back to the sparse target. + var intermediate = new DenseVectorStorage(target.Length); + Map2ToUnchecked(intermediate, other, f, zeros, ExistingData.AssumeZeros); + intermediate.CopyTo(target, existingData); + return; + } + + if (denseOther != null) + { + T[] targetData = denseTarget.Data; + T[] otherData = denseOther.Data; + + int k = 0; + for (int i = 0; i < otherData.Length; i++) + { + if (k < ValueCount && Indices[k] == i) + { + targetData[i] = f(Values[k], otherData[i]); + k++; + } + else + { + targetData[i] = f(Zero, otherData[i]); + } + } + + return; + } + + var sparseOther = other as SparseVectorStorage; + if (sparseOther != null && denseTarget != null) + { + T[] targetData = denseTarget.Data; + int[] otherIndices = sparseOther.Indices; + T[] otherValues = sparseOther.Values; + int otherValueCount = sparseOther.ValueCount; + + if (processZeros) + { + int p = 0, q = 0; + for (int i = 0; i < targetData.Length; i++) + { + var left = p < ValueCount && Indices[p] == i ? Values[p++] : Zero; + var right = q < otherValueCount && otherIndices[q] == i ? otherValues[q++] : Zero; + targetData[i] = f(left, right); + } + } + else + { + if (existingData == ExistingData.Clear) + { + denseTarget.Clear(); + } + + int p = 0, q = 0; + while (p < ValueCount || q < otherValueCount) + { + if (q >= otherValueCount || p < ValueCount && Indices[p] < otherIndices[q]) + { + targetData[Indices[p]] = f(Values[p], Zero); + p++; + } + else if (p >= ValueCount || q < otherValueCount && Indices[p] > otherIndices[q]) + { + targetData[otherIndices[q]] = f(Zero, otherValues[q]); + q++; + } + else + { + Debug.Assert(Indices[p] == otherIndices[q]); + targetData[Indices[p]] = f(Values[p], otherValues[q]); + p++; + q++; + } + } + } + + return; + } + + var sparseTarget = target as SparseVectorStorage; + if (sparseOther != null && sparseTarget != null) + { + var indices = new List(); + var values = new List(); + int[] otherIndices = sparseOther.Indices; + T[] otherValues = sparseOther.Values; + int otherValueCount = sparseOther.ValueCount; + + int p = 0, q = 0; + while (p < ValueCount || q < otherValueCount) + { + if (q >= otherValueCount || p < ValueCount && Indices[p] < otherIndices[q]) + { + var value = f(Values[p], Zero); + if (!Zero.Equals(value)) + { + indices.Add(Indices[p]); + values.Add(value); + } + + p++; + } + else if (p >= ValueCount || q < otherValueCount && Indices[p] > otherIndices[q]) + { + var value = f(Zero, otherValues[q]); + if (!Zero.Equals(value)) + { + indices.Add(otherIndices[q]); + values.Add(value); + } + + q++; + } + else + { + var value = f(Values[p], otherValues[q]); + if (!Zero.Equals(value)) + { + indices.Add(Indices[p]); + values.Add(value); + } + + p++; + q++; + } + } + + sparseTarget.Indices = indices.ToArray(); + sparseTarget.Values = values.ToArray(); + sparseTarget.ValueCount = values.Count; + return; + } + + // FALL BACK + + base.Map2ToUnchecked(target, other, f, zeros, existingData); + } + + // FUNCTIONAL COMBINATORS: MAP + + internal override TState Fold2Unchecked(VectorStorage other, Func f, TState state, Zeros zeros) + { + var sparseOther = other as SparseVectorStorage; + if (sparseOther != null) + { + int[] otherIndices = sparseOther.Indices; + TOther[] otherValues = sparseOther.Values; + int otherValueCount = sparseOther.ValueCount; + TOther otherZero = BuilderInstance.Vector.Zero; + + if (zeros == Zeros.Include) + { + int p = 0, q = 0; + for (int i = 0; i < Length; i++) + { + var left = p < ValueCount && Indices[p] == i ? Values[p++] : Zero; + var right = q < otherValueCount && otherIndices[q] == i ? otherValues[q++] : otherZero; + state = f(state, left, right); + } + } + else + { + int p = 0, q = 0; + while (p < ValueCount || q < otherValueCount) + { + if (q >= otherValueCount || p < ValueCount && Indices[p] < otherIndices[q]) + { + state = f(state, Values[p], otherZero); + p++; + } + else if (p >= ValueCount || q < otherValueCount && Indices[p] > otherIndices[q]) + { + state = f(state, Zero, otherValues[q]); + q++; + } + else + { + Debug.Assert(Indices[p] == otherIndices[q]); + state = f(state, Values[p], otherValues[q]); + p++; + q++; + } + } + } + + return state; + } + + var denseOther = other as DenseVectorStorage; + if (denseOther != null) + { + TOther[] otherData = denseOther.Data; + + int k = 0; + for (int i = 0; i < otherData.Length; i++) + { + if (k < ValueCount && Indices[k] == i) + { + state = f(state, Values[k], otherData[i]); + k++; + } + else + { + state = f(state, Zero, otherData[i]); + } + } + + return state; + } + + return base.Fold2Unchecked(other, f, state, zeros); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Storage/VectorStorage.Validation.cs b/MathNet.Numerics/LinearAlgebra/Storage/VectorStorage.Validation.cs new file mode 100644 index 0000000..d7572e2 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/VectorStorage.Validation.cs @@ -0,0 +1,186 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +// ReSharper disable UnusedParameter.Global +public partial class VectorStorage +{ + void ValidateRange(int index) + { + if ((uint)index >= (uint)Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + + void ValidateSubVectorRange(VectorStorage target, + int sourceIndex, int targetIndex, int count) + { + if (count < 1) + { + throw new ArgumentOutOfRangeException(nameof(count), Resources.ArgumentMustBePositive); + } + + // Verify Source + + if ((uint)sourceIndex >= (uint)Length) + { + throw new ArgumentOutOfRangeException(nameof(sourceIndex)); + } + + var sourceMax = sourceIndex + count; + + if (sourceMax > Length) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + // Verify Target + + if ((uint)targetIndex >= (uint)target.Length) + { + throw new ArgumentOutOfRangeException(nameof(targetIndex)); + } + + var targetMax = targetIndex + count; + + if (targetMax > target.Length) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + void ValidateRowRange(MatrixStorage target, int rowIndex) + { + if ((uint)rowIndex >= (uint)target.RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowIndex)); + } + + if (target.ColumnCount != Length) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, nameof(target)); + } + } + + void ValidateColumnRange(MatrixStorage target, int columnIndex) + { + if ((uint)columnIndex >= (uint)target.ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + if (target.RowCount != Length) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, nameof(target)); + } + } + + void ValidateSubRowRange(MatrixStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount) + { + if (columnCount < 1) + { + throw new ArgumentOutOfRangeException(nameof(columnCount), Resources.ArgumentMustBePositive); + } + + // Verify Source + + if ((uint)sourceColumnIndex >= (uint)Length) + { + throw new ArgumentOutOfRangeException(nameof(sourceColumnIndex)); + } + + if (sourceColumnIndex + columnCount > Length) + { + throw new ArgumentOutOfRangeException(nameof(columnCount)); + } + + // Verify Target + + if ((uint)rowIndex >= (uint)target.RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowIndex)); + } + + if ((uint)targetColumnIndex >= (uint)target.ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(targetColumnIndex)); + } + + if (targetColumnIndex + columnCount > target.ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnCount)); + } + } + + void ValidateSubColumnRange(MatrixStorage target, int columnIndex, + int sourceRowIndex, int targetRowIndex, int rowCount) + { + if (rowCount < 1) + { + throw new ArgumentOutOfRangeException(nameof(rowCount), Resources.ArgumentMustBePositive); + } + + // Verify Source + + if ((uint)sourceRowIndex >= (uint)Length) + { + throw new ArgumentOutOfRangeException(nameof(sourceRowIndex)); + } + + if (sourceRowIndex + rowCount > Length) + { + throw new ArgumentOutOfRangeException(nameof(rowCount)); + } + + // Verify Target + + if ((uint)columnIndex >= (uint)target.ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(columnIndex)); + } + + if ((uint)targetRowIndex >= (uint)target.RowCount) + { + throw new ArgumentOutOfRangeException(nameof(targetRowIndex)); + } + + if (targetRowIndex + rowCount > target.RowCount) + { + throw new ArgumentOutOfRangeException(nameof(rowCount)); + } + } +} +// ReSharper restore UnusedParameter.Global diff --git a/MathNet.Numerics/LinearAlgebra/Storage/VectorStorage.cs b/MathNet.Numerics/LinearAlgebra/Storage/VectorStorage.cs new file mode 100644 index 0000000..2c53271 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Storage/VectorStorage.cs @@ -0,0 +1,618 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.LinearAlgebra.Storage; + +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/LinearAlgebra")] +public abstract partial class VectorStorage : IEquatable> + where T : struct, IEquatable, IFormattable +{ + // [ruegg] public fields are OK here + + protected static readonly T Zero = BuilderInstance.Vector.Zero; + + [DataMember(Order = 1)] + public readonly int Length; + + protected VectorStorage(int length) + { + if (length <= 0) + { + throw new ArgumentOutOfRangeException(nameof(length), Resources.ArgumentMustBePositive); + } + + Length = length; + } + + /// + /// True if the vector storage format is dense. + /// + public abstract bool IsDense { get; } + + /// + /// Gets or sets the value at the given index, with range checking. + /// + /// + /// The index of the element. + /// + /// The value to get or set. + /// This method is ranged checked. and + /// to get and set values without range checking. + public T this[int index] + { + get + { + ValidateRange(index); + return At(index); + } + + set + { + ValidateRange(index); + At(index, value); + } + } + + /// + /// Retrieves the requested element without range checking. + /// + /// The index of the element. + /// The requested element. + /// Not range-checked. + public abstract T At(int index); + + /// + /// Sets the element without range checking. + /// + /// The index of the element. + /// The value to set the element to. + /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks. + public abstract void At(int index, T value); + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// An object to compare with this object. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public virtual bool Equals(VectorStorage other) + { + // Reject equality when the argument is null or has a different shape. + if (other == null) + { + return false; + } + + if (Length != other.Length) + { + return false; + } + + // Accept if the argument is the same object as this. + if (ReferenceEquals(this, other)) + { + return true; + } + + // If all else fails, perform element wise comparison. + for (var index = 0; index < Length; index++) + { + if (!At(index).Equals(other.At(index))) + { + return false; + } + } + + return true; + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// true if the specified is equal to the current ; otherwise, false. + /// + /// The to compare with the current . + public sealed override bool Equals(object obj) + { + return Equals(obj as VectorStorage); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + public override int GetHashCode() + { + var hashNum = Math.Min(Length, 25); + int hash = 17; + unchecked + { + for (var i = 0; i < hashNum; i++) + { + hash = hash * 31 + At(i).GetHashCode(); + } + } + + return hash; + } + + // CLEARING + + public virtual void Clear() + { + for (var i = 0; i < Length; i++) + { + At(i, Zero); + } + } + + public virtual void Clear(int index, int count) + { + for (var i = index; i < index + count; i++) + { + At(i, Zero); + } + } + + // VECTOR COPY + + public void CopyTo(VectorStorage target, ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (ReferenceEquals(this, target)) + { + return; + } + + if (Length != target.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(target)); + } + + CopyToUnchecked(target, existingData); + } + + internal virtual void CopyToUnchecked(VectorStorage target, ExistingData existingData) + { + for (int i = 0; i < Length; i++) + { + target.At(i, At(i)); + } + } + + // ROW COPY + + public void CopyToRow(MatrixStorage target, int rowIndex, ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (Length != target.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(target)); + } + + ValidateRowRange(target, rowIndex); + CopyToRowUnchecked(target, rowIndex, existingData); + } + + internal virtual void CopyToRowUnchecked(MatrixStorage target, int rowIndex, ExistingData existingData) + { + for (int j = 0; j < Length; j++) + { + target.At(rowIndex, j, At(j)); + } + } + + // COLUMN COPY + + public void CopyToColumn(MatrixStorage target, int columnIndex, ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (Length != target.RowCount) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(target)); + } + + ValidateColumnRange(target, columnIndex); + CopyToColumnUnchecked(target, columnIndex, existingData); + } + + internal virtual void CopyToColumnUnchecked(MatrixStorage target, int columnIndex, ExistingData existingData) + { + for (int i = 0; i < Length; i++) + { + target.At(i, columnIndex, At(i)); + } + } + + // SUB-VECTOR COPY + + public void CopySubVectorTo(VectorStorage target, + int sourceIndex, int targetIndex, int count, + ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (count == 0) + { + return; + } + + ValidateSubVectorRange(target, sourceIndex, targetIndex, count); + CopySubVectorToUnchecked(target, sourceIndex, targetIndex, count, existingData); + } + + internal virtual void CopySubVectorToUnchecked(VectorStorage target, + int sourceIndex, int targetIndex, int count, ExistingData existingData) + { + if (ReferenceEquals(this, target)) + { + var tmp = new T[count]; + for (int i = 0; i < tmp.Length; i++) + { + tmp[i] = At(i + sourceIndex); + } + + for (int i = 0; i < tmp.Length; i++) + { + At(i + targetIndex, tmp[i]); + } + + return; + } + + for (int i = sourceIndex, ii = targetIndex; i < sourceIndex + count; i++, ii++) + { + target.At(ii, At(i)); + } + } + + // SUB-ROW COPY + + public void CopyToSubRow(MatrixStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount, + ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (columnCount == 0) + { + return; + } + + ValidateSubRowRange(target, rowIndex, sourceColumnIndex, targetColumnIndex, columnCount); + CopyToSubRowUnchecked(target, rowIndex, sourceColumnIndex, targetColumnIndex, columnCount, existingData); + } + + internal virtual void CopyToSubRowUnchecked(MatrixStorage target, int rowIndex, + int sourceColumnIndex, int targetColumnIndex, int columnCount, ExistingData existingData) + { + for (int j = sourceColumnIndex, jj = targetColumnIndex; j < sourceColumnIndex + columnCount; j++, jj++) + { + target.At(rowIndex, jj, At(j)); + } + } + + // SUB-COLUMN COPY + + public void CopyToSubColumn(MatrixStorage target, int columnIndex, + int sourceRowIndex, int targetRowIndex, int rowCount, + ExistingData existingData = ExistingData.Clear) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (rowCount == 0) + { + return; + } + + ValidateSubColumnRange(target, columnIndex, sourceRowIndex, targetRowIndex, rowCount); + CopyToSubColumnUnchecked(target, columnIndex, sourceRowIndex, targetRowIndex, rowCount, existingData); + } + + internal virtual void CopyToSubColumnUnchecked(MatrixStorage target, int columnIndex, + int sourceRowIndex, int targetRowIndex, int rowCount, ExistingData existingData) + { + for (int i = sourceRowIndex, ii = targetRowIndex; i < sourceRowIndex + rowCount; i++, ii++) + { + target.At(ii, columnIndex, At(i)); + } + } + + // EXTRACT + + public virtual T[] ToArray() + { + var ret = new T[Length]; + for (int i = 0; i < ret.Length; i++) + { + ret[i] = At(i); + } + + return ret; + } + + public virtual T[] AsArray() + { + return null; + } + + // ENUMERATION + + public virtual IEnumerable Enumerate() + { + for (var i = 0; i < Length; i++) + { + yield return At(i); + } + } + + public virtual IEnumerable> EnumerateIndexed() + { + for (var i = 0; i < Length; i++) + { + yield return new Tuple(i, At(i)); + } + } + + public virtual IEnumerable EnumerateNonZero() + { + for (var i = 0; i < Length; i++) + { + var x = At(i); + if (!Zero.Equals(x)) + { + yield return x; + } + } + } + + public virtual IEnumerable> EnumerateNonZeroIndexed() + { + for (var i = 0; i < Length; i++) + { + var x = At(i); + if (!Zero.Equals(x)) + { + yield return new Tuple(i, x); + } + } + } + + // FIND + + public virtual Tuple Find(Func predicate, Zeros zeros) + { + for (int i = 0; i < Length; i++) + { + var item = At(i); + if (predicate(item)) + { + return new Tuple(i, item); + } + } + + return null; + } + + public Tuple Find2(VectorStorage other, Func predicate, Zeros zeros) + where TOther : struct, IEquatable, IFormattable + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (Length != other.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + return Find2Unchecked(other, predicate, zeros); + } + + internal virtual Tuple Find2Unchecked(VectorStorage other, Func predicate, Zeros zeros) + where TOther : struct, IEquatable, IFormattable + { + for (int i = 0; i < Length; i++) + { + var item = At(i); + var otherItem = other.At(i); + if (predicate(item, otherItem)) + { + return new Tuple(i, item, otherItem); + } + } + + return null; + } + + // FUNCTIONAL COMBINATORS: MAP + + public virtual void MapInplace(Func f, Zeros zeros) + { + for (int i = 0; i < Length; i++) + { + At(i, f(At(i))); + } + } + + public virtual void MapIndexedInplace(Func f, Zeros zeros) + { + for (int i = 0; i < Length; i++) + { + At(i, f(i, At(i))); + } + } + + public void MapTo(VectorStorage target, Func f, Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (Length != target.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(target)); + } + + MapToUnchecked(target, f, zeros, existingData); + } + + internal virtual void MapToUnchecked(VectorStorage target, Func f, Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + for (int i = 0; i < Length; i++) + { + target.At(i, f(At(i))); + } + } + + public void MapIndexedTo(VectorStorage target, Func f, Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (Length != target.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(target)); + } + + MapIndexedToUnchecked(target, f, zeros, existingData); + } + + internal virtual void MapIndexedToUnchecked(VectorStorage target, Func f, Zeros zeros, ExistingData existingData) + where TU : struct, IEquatable, IFormattable + { + for (int i = 0; i < Length; i++) + { + target.At(i, f(i, At(i))); + } + } + + public void Map2To(VectorStorage target, VectorStorage other, Func f, Zeros zeros, ExistingData existingData) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (Length != target.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(target)); + } + + if (Length != other.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + Map2ToUnchecked(target, other, f, zeros, existingData); + } + + internal virtual void Map2ToUnchecked(VectorStorage target, VectorStorage other, Func f, Zeros zeros, ExistingData existingData) + { + for (int i = 0; i < Length; i++) + { + target.At(i, f(At(i), other.At(i))); + } + } + + // FUNCTIONAL COMBINATORS: FOLD + + public TState Fold2(VectorStorage other, Func f, TState state, Zeros zeros) + where TOther : struct, IEquatable, IFormattable + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + if (Length != other.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + return Fold2Unchecked(other, f, state, zeros); + } + + internal virtual TState Fold2Unchecked(VectorStorage other, Func f, TState state, Zeros zeros) + where TOther : struct, IEquatable, IFormattable + { + for (int i = 0; i < Length; i++) + { + state = f(state, At(i), other.At(i)); + } + + return state; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Vector.Arithmetic.cs b/MathNet.Numerics/LinearAlgebra/Vector.Arithmetic.cs new file mode 100644 index 0000000..0dfd938 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Vector.Arithmetic.cs @@ -0,0 +1,1769 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.LinearAlgebra; + +public abstract partial class Vector +{ + /// + /// The zero value for type T. + /// + public static readonly T Zero = BuilderInstance.Vector.Zero; + + /// + /// The value of 1.0 for type T. + /// + public static readonly T One = BuilderInstance.Vector.One; + + /// + /// Negates vector and save result to + /// + /// Target vector + protected abstract void DoNegate(Vector result); + + /// + /// Complex conjugates vector and save result to + /// + /// Target vector + protected abstract void DoConjugate(Vector result); + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to add. + /// The vector to store the result of the addition. + protected abstract void DoAdd(T scalar, Vector result); + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + protected abstract void DoAdd(Vector other, Vector result); + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The vector to store the result of the subtraction. + protected abstract void DoSubtract(T scalar, Vector result); + + /// + /// Subtracts each element of the vector from a scalar and stores the result in the result vector. + /// + /// The scalar to subtract from. + /// The vector to store the result of the subtraction. + protected void DoSubtractFrom(T scalar, Vector result) + { + DoNegate(result); + result.DoAdd(scalar, result); + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + protected abstract void DoSubtract(Vector other, Vector result); + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to multiply. + /// The vector to store the result of the multiplication. + protected abstract void DoMultiply(T scalar, Vector result); + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + protected abstract T DoDotProduct(Vector other); + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + protected abstract T DoConjugateDotProduct(Vector other); + + /// + /// Computes the outer product M[i,j] = u[i]*v[j] of this and another vector and stores the result in the result matrix. + /// + /// The other vector + /// The matrix to store the result of the product. + protected void DoOuterProduct(Vector other, Matrix result) + { + var work = Build.Dense(Count); + for (var i = 0; i < other.Count; i++) + { + DoMultiply(other.At(i), work); + result.SetColumn(i, work); + } + } + + /// + /// Divides each element of the vector by a scalar and stores the result in the result vector. + /// + /// The scalar denominator to use. + /// The vector to store the result of the division. + protected abstract void DoDivide(T divisor, Vector result); + + /// + /// Divides a scalar by each element of the vector and stores the result in the result vector. + /// + /// The scalar numerator to use. + /// The vector to store the result of the division. + protected abstract void DoDivideByThis(T dividend, Vector result); + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected abstract void DoModulus(T divisor, Vector result); + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected abstract void DoModulusByThis(T dividend, Vector result); + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + protected abstract void DoRemainder(T divisor, Vector result); + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + protected abstract void DoRemainderByThis(T dividend, Vector result); + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + protected abstract void DoPointwiseMultiply(Vector other, Vector result); + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the division. + protected abstract void DoPointwiseDivide(Vector divisor, Vector result); + + /// + /// Pointwise raise this vector to an exponent and store the result into the result vector. + /// + /// The exponent to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected abstract void DoPointwisePower(T exponent, Vector result); + + /// + /// Pointwise raise this vector to an exponent vector and store the result into the result vector. + /// + /// The exponent vector to raise this vector values to. + /// The vector to store the result of the pointwise power. + protected abstract void DoPointwisePower(Vector exponent, Vector result); + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected abstract void DoPointwiseModulus(Vector divisor, Vector result); + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The result of the modulus. + protected abstract void DoPointwiseRemainder(Vector divisor, Vector result); + + /// + /// Pointwise applies the exponential function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected abstract void DoPointwiseExp(Vector result); + + /// + /// Pointwise applies the natural logarithm function to each value and stores the result into the result vector. + /// + /// The vector to store the result. + protected abstract void DoPointwiseLog(Vector result); + + protected abstract void DoPointwiseAbs(Vector result); + protected abstract void DoPointwiseAcos(Vector result); + protected abstract void DoPointwiseAsin(Vector result); + protected abstract void DoPointwiseAtan(Vector result); + protected abstract void DoPointwiseCeiling(Vector result); + protected abstract void DoPointwiseCos(Vector result); + protected abstract void DoPointwiseCosh(Vector result); + protected abstract void DoPointwiseFloor(Vector result); + protected abstract void DoPointwiseLog10(Vector result); + protected abstract void DoPointwiseRound(Vector result); + protected abstract void DoPointwiseSign(Vector result); + protected abstract void DoPointwiseSin(Vector result); + protected abstract void DoPointwiseSinh(Vector result); + protected abstract void DoPointwiseSqrt(Vector result); + protected abstract void DoPointwiseTan(Vector result); + protected abstract void DoPointwiseTanh(Vector result); + protected abstract void DoPointwiseAtan2(Vector other, Vector result); + protected abstract void DoPointwiseAtan2(T scalar, Vector result); + protected abstract void DoPointwiseMinimum(T scalar, Vector result); + protected abstract void DoPointwiseMinimum(Vector other, Vector result); + protected abstract void DoPointwiseMaximum(T scalar, Vector result); + protected abstract void DoPointwiseMaximum(Vector other, Vector result); + protected abstract void DoPointwiseAbsoluteMinimum(T scalar, Vector result); + protected abstract void DoPointwiseAbsoluteMinimum(Vector other, Vector result); + protected abstract void DoPointwiseAbsoluteMaximum(T scalar, Vector result); + protected abstract void DoPointwiseAbsoluteMaximum(Vector other, Vector result); + + /// + /// Adds a scalar to each element of the vector. + /// + /// The scalar to add. + /// A copy of the vector with the scalar added. + public Vector Add(T scalar) + { + if (scalar.Equals(Zero)) + { + return Clone(); + } + + var result = Build.SameAs(this); + DoAdd(scalar, result); + return result; + } + + /// + /// Adds a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to add. + /// The vector to store the result of the addition. + /// If this vector and are not the same size. + public void Add(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + if (scalar.Equals(Zero)) + { + CopyTo(result); + return; + } + + DoAdd(scalar, result); + } + + /// + /// Adds another vector to this vector. + /// + /// The vector to add to this one. + /// A new vector containing the sum of both vectors. + /// If this vector and are not the same size. + public Vector Add(Vector other) + { + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + var result = Build.SameAs(this, other); + DoAdd(other, result); + return result; + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public void Add(Vector other, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoAdd(other, result); + } + + /// + /// Subtracts a scalar from each element of the vector. + /// + /// The scalar to subtract. + /// A new vector containing the subtraction of this vector and the scalar. + public Vector Subtract(T scalar) + { + if (scalar.Equals(Zero)) + { + return Clone(); + } + + var result = Build.SameAs(this); + DoSubtract(scalar, result); + return result; + } + + /// + /// Subtracts a scalar from each element of the vector and stores the result in the result vector. + /// + /// The scalar to subtract. + /// The vector to store the result of the subtraction. + /// If this vector and are not the same size. + public void Subtract(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + if (scalar.Equals(Zero)) + { + CopyTo(result); + return; + } + + DoSubtract(scalar, result); + } + + /// + /// Subtracts each element of the vector from a scalar. + /// + /// The scalar to subtract from. + /// A new vector containing the subtraction of the scalar and this vector. + public Vector SubtractFrom(T scalar) + { + var result = Build.SameAs(this); + DoSubtractFrom(scalar, result); + return result; + } + + /// + /// Subtracts each element of the vector from a scalar and stores the result in the result vector. + /// + /// The scalar to subtract from. + /// The vector to store the result of the subtraction. + /// If this vector and are not the same size. + public void SubtractFrom(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoSubtractFrom(scalar, result); + } + + /// + /// Returns a negated vector. + /// + /// The negated vector. + /// Added as an alternative to the unary negation operator. + public Vector Negate() + { + var retrunVector = Build.SameAs(this); + DoNegate(retrunVector); + return retrunVector; + } + + /// + /// Negates vector and save result to + /// + /// Target vector + public void Negate(Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoNegate(result); + } + + /// + /// Subtracts another vector from this vector. + /// + /// The vector to subtract from this one. + /// A new vector containing the subtraction of the two vectors. + /// If this vector and are not the same size. + public Vector Subtract(Vector other) + { + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + var result = Build.SameAs(this, other); + DoSubtract(other, result); + return result; + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public void Subtract(Vector other, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoSubtract(other, result); + } + + /// + /// Return vector with complex conjugate values of the source vector + /// + /// Conjugated vector + public Vector Conjugate() + { + var retrunVector = Build.SameAs(this); + DoConjugate(retrunVector); + return retrunVector; + } + + /// + /// Complex conjugates vector and save result to + /// + /// Target vector + public void Conjugate(Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoConjugate(result); + } + + /// + /// Multiplies a scalar to each element of the vector. + /// + /// The scalar to multiply. + /// A new vector that is the multiplication of the vector and the scalar. + public Vector Multiply(T scalar) + { + if (scalar.Equals(One)) + { + return Clone(); + } + + if (scalar.Equals(Zero)) + { + return Build.SameAs(this); + } + + var result = Build.SameAs(this); + DoMultiply(scalar, result); + return result; + } + + /// + /// Multiplies a scalar to each element of the vector and stores the result in the result vector. + /// + /// The scalar to multiply. + /// The vector to store the result of the multiplication. + /// If this vector and are not the same size. + public void Multiply(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + if (scalar.Equals(One)) + { + CopyTo(result); + return; + } + + if (scalar.Equals(Zero)) + { + result.Clear(); + return; + } + + DoMultiply(scalar, result); + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector. + /// The sum of a[i]*b[i] for all i. + /// If is not of the same size. + /// + public T DotProduct(Vector other) + { + if (Count != other.Count) + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + + return DoDotProduct(other); + } + + /// + /// Computes the dot product between the conjugate of this vector and another vector. + /// + /// The other vector. + /// The sum of conj(a[i])*b[i] for all i. + /// If is not of the same size. + /// If is . + /// + public T ConjugateDotProduct(Vector other) + { + if (Count != other.Count) + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + + return DoConjugateDotProduct(other); + } + + /// + /// Divides each element of the vector by a scalar. + /// + /// The scalar to divide with. + /// A new vector that is the division of the vector and the scalar. + public Vector Divide(T scalar) + { + if (scalar.Equals(One)) + { + return Clone(); + } + + var result = Build.SameAs(this); + DoDivide(scalar, result); + return result; + } + + /// + /// Divides each element of the vector by a scalar and stores the result in the result vector. + /// + /// The scalar to divide with. + /// The vector to store the result of the division. + /// If this vector and are not the same size. + public void Divide(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + if (scalar.Equals(One)) + { + CopyTo(result); + return; + } + + DoDivide(scalar, result); + } + + /// + /// Divides a scalar by each element of the vector. + /// + /// The scalar to divide. + /// A new vector that is the division of the vector and the scalar. + public Vector DivideByThis(T scalar) + { + var result = Build.SameAs(this); + DoDivideByThis(scalar, result); + return result; + } + + /// + /// Divides a scalar by each element of the vector and stores the result in the result vector. + /// + /// The scalar to divide. + /// The vector to store the result of the division. + /// If this vector and are not the same size. + public void DivideByThis(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoDivideByThis(scalar, result); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector containing the result. + public Vector Modulus(T divisor) + { + var result = Build.SameAs(this); + DoModulus(divisor, result); + return result; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + public void Modulus(T divisor, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoModulus(divisor, result); + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector containing the result. + public Vector ModulusByThis(T dividend) + { + var result = Build.SameAs(this); + DoModulusByThis(dividend, result); + return result; + } + + /// + /// Computes the canonical modulus, where the result has the sign of the divisor, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + public void ModulusByThis(T dividend, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoModulusByThis(dividend, result); + } + + /// + /// Computes the remainder (vector % divisor), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector containing the result. + public Vector Remainder(T divisor) + { + var result = Build.SameAs(this); + DoRemainder(divisor, result); + return result; + } + + /// + /// Computes the remainder (vector % divisor), where the result has the sign of the dividend, + /// for each element of the vector for the given divisor. + /// + /// The scalar denominator to use. + /// A vector to store the results in. + public void Remainder(T divisor, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoRemainder(divisor, result); + } + + /// + /// Computes the remainder (dividend % vector), where the result has the sign of the dividend, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector containing the result. + public Vector RemainderByThis(T dividend) + { + var result = Build.SameAs(this); + DoRemainderByThis(dividend, result); + return result; + } + + /// + /// Computes the remainder (dividend % vector), where the result has the sign of the dividend, + /// for the given dividend for each element of the vector. + /// + /// The scalar numerator to use. + /// A vector to store the results in. + public void RemainderByThis(T dividend, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoRemainderByThis(dividend, result); + } + + /// + /// Pointwise multiplies this vector with another vector. + /// + /// The vector to pointwise multiply with this one. + /// A new vector which is the pointwise multiplication of the two vectors. + /// If this vector and are not the same size. + public Vector PointwiseMultiply(Vector other) + { + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + var result = Build.SameAs(this, other); + DoPointwiseMultiply(other, result); + return result; + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public void PointwiseMultiply(Vector other, Vector result) + { + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseMultiply(other, result); + } + + /// + /// Pointwise divide this vector with another vector. + /// + /// The pointwise denominator vector to use. + /// A new vector which is the pointwise division of the two vectors. + /// If this vector and are not the same size. + public Vector PointwiseDivide(Vector divisor) + { + if (Count != divisor.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(divisor)); + } + + var result = Build.SameAs(this, divisor); + DoPointwiseDivide(divisor, result); + return result; + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The vector to store the result of the pointwise division. + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public void PointwiseDivide(Vector divisor, Vector result) + { + if (Count != divisor.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(divisor)); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseDivide(divisor, result); + } + + /// + /// Pointwise raise this vector to an exponent. + /// + /// The exponent to raise this vector values to. + public Vector PointwisePower(T exponent) + { + var result = Build.SameAs(this); + DoPointwisePower(exponent, result); + return result; + } + + /// + /// Pointwise raise this vector to an exponent and store the result into the result vector. + /// + /// The exponent to raise this vector values to. + /// The matrix to store the result into. + /// If this vector and are not the same size. + public void PointwisePower(T exponent, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwisePower(exponent, result); + } + + /// + /// Pointwise raise this vector to an exponent and store the result into the result vector. + /// + /// The exponent to raise this vector values to. + public Vector PointwisePower(Vector exponent) + { + if (Count != exponent.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(exponent)); + } + + var result = Build.SameAs(this); + DoPointwisePower(exponent, result); + return result; + } + + /// + /// Pointwise raise this vector to an exponent. + /// + /// The exponent to raise this vector values to. + /// The vector to store the result into. + /// If this vector and are not the same size. + public void PointwisePower(Vector exponent, Vector result) + { + if (Count != exponent.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(exponent)); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwisePower(exponent, result); + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this vector with another vector. + /// + /// The pointwise denominator vector to use. + /// If this vector and are not the same size. + public Vector PointwiseModulus(Vector divisor) + { + if (Count != divisor.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(divisor)); + } + + var result = Build.SameAs(this, divisor); + DoPointwiseModulus(divisor, result); + return result; + } + + /// + /// Pointwise canonical modulus, where the result has the sign of the divisor, + /// of this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The vector to store the result of the pointwise modulus. + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public void PointwiseModulus(Vector divisor, Vector result) + { + if (Count != divisor.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(divisor)); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseModulus(divisor, result); + } + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// of this vector with another vector. + /// + /// The pointwise denominator vector to use. + /// If this vector and are not the same size. + public Vector PointwiseRemainder(Vector divisor) + { + if (Count != divisor.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(divisor)); + } + + var result = Build.SameAs(this, divisor); + DoPointwiseRemainder(divisor, result); + return result; + } + + /// + /// Pointwise remainder (% operator), where the result has the sign of the dividend, + /// this vector with another vector and stores the result into the result vector. + /// + /// The pointwise denominator vector to use. + /// The vector to store the result of the pointwise remainder. + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public void PointwiseRemainder(Vector divisor, Vector result) + { + if (Count != divisor.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(divisor)); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseRemainder(divisor, result); + } + + /// + /// Helper function to apply a unary function to a vector. The function + /// f modifies the vector given to it in place. Before its + /// called, a copy of the 'this' vector with the same dimension is + /// first created, then passed to f. The copy is returned as the result + /// + /// Function which takes a vector, modifies it in place and returns void + /// New instance of vector which is the result + protected Vector PointwiseUnary(Action> f) + { + var result = Build.SameAs(this); + f(result); + return result; + } + + /// + /// Helper function to apply a unary function which modifies a vector + /// in place. + /// + /// Function which takes a vector, modifies it in place and returns void + /// The vector where the result is to be stored + /// If this vector and are not the same size. + protected void PointwiseUnary(Action> f, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + f(result); + } + + /// + /// Helper function to apply a binary function which takes a scalar and + /// a vector and modifies the latter in place. A copy of the "this" + /// vector is therefore first made and then passed to f together with + /// the scalar argument. The copy is then returned as the result + /// + /// Function which takes a scalar and a vector, modifies the vector in place and returns void + /// The scalar to be passed to the function + /// The resulting vector + protected Vector PointwiseBinary(Action> f, T other) + { + var result = Build.SameAs(this); + f(other, result); + return result; + } + + /// + /// Helper function to apply a binary function which takes a scalar and + /// a vector, modifies the latter in place and returns void. + /// + /// Function which takes a scalar and a vector, modifies the vector in place and returns void + /// The scalar to be passed to the function + /// The vector where the result will be placed + /// If this vector and are not the same size. + protected void PointwiseBinary(Action> f, T x, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + f(x, result); + } + + /// + /// Helper function to apply a binary function which takes two vectors + /// and modifies the latter in place. A copy of the "this" vector is + /// first made and then passed to f together with the other vector. The + /// copy is then returned as the result + /// + /// Function which takes two vectors, modifies the second in place and returns void + /// The other vector to be passed to the function as argument. It is not modified + /// The resulting vector + /// If this vector and are not the same size. + protected Vector PointwiseBinary(Action, Vector> f, Vector other) + { + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + var result = Build.SameAs(this, other); + f(other, result); + return result; + } + + /// + /// Helper function to apply a binary function which takes two vectors + /// and modifies the second one in place + /// + /// Function which takes two vectors, modifies the second in place and returns void + /// The other vector to be passed to the function as argument. It is not modified + /// The resulting vector + /// If this vector and are not the same size. + protected void PointwiseBinary(Action, Vector> f, Vector other, Vector result) + { + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(other)); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + f(other, result); + } + + /// + /// Pointwise applies the exponent function to each value. + /// + public Vector PointwiseExp() + { + return PointwiseUnary(DoPointwiseExp); + } + + /// + /// Pointwise applies the exponent function to each value. + /// + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseExp(Vector result) + { + PointwiseUnary(DoPointwiseExp, result); + } + + /// + /// Pointwise applies the natural logarithm function to each value. + /// + public Vector PointwiseLog() + { + return PointwiseUnary(DoPointwiseLog); + } + + /// + /// Pointwise applies the natural logarithm function to each value. + /// + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseLog(Vector result) + { + PointwiseUnary(DoPointwiseLog, result); + } + + /// + /// Pointwise applies the abs function to each value + /// + public Vector PointwiseAbs() + { + return PointwiseUnary(DoPointwiseAbs); + } + + /// + /// Pointwise applies the abs function to each value + /// + /// The vector to store the result + public void PointwiseAbs(Vector result) + { + PointwiseUnary(DoPointwiseAbs, result); + } + + /// + /// Pointwise applies the acos function to each value + /// + public Vector PointwiseAcos() + { + return PointwiseUnary(DoPointwiseAcos); + } + + /// + /// Pointwise applies the acos function to each value + /// + /// The vector to store the result + public void PointwiseAcos(Vector result) + { + PointwiseUnary(DoPointwiseAcos, result); + } + + /// + /// Pointwise applies the asin function to each value + /// + public Vector PointwiseAsin() + { + return PointwiseUnary(DoPointwiseAsin); + } + + /// + /// Pointwise applies the asin function to each value + /// + /// The vector to store the result + public void PointwiseAsin(Vector result) + { + PointwiseUnary(DoPointwiseAsin, result); + } + + /// + /// Pointwise applies the atan function to each value + /// + public Vector PointwiseAtan() + { + return PointwiseUnary(DoPointwiseAtan); + } + + /// + /// Pointwise applies the atan function to each value + /// + /// The vector to store the result + public void PointwiseAtan(Vector result) + { + PointwiseUnary(DoPointwiseAtan, result); + } + + /// + /// Pointwise applies the atan2 function to each value of the current + /// vector and a given other vector being the 'x' of atan2 and the + /// 'this' vector being the 'y' + /// + /// + public Vector PointwiseAtan2(Vector other) + { + return PointwiseBinary(DoPointwiseAtan2, other); + } + + /// + /// Pointwise applies the atan2 function to each value of the current + /// vector and a given other vector being the 'x' of atan2 and the + /// 'this' vector being the 'y' + /// + /// + /// The vector to store the result + public void PointwiseAtan2(Vector other, Vector result) + { + PointwiseBinary(DoPointwiseAtan2, other, result); + } + + /// + /// Pointwise applies the ceiling function to each value + /// + public Vector PointwiseCeiling() + { + return PointwiseUnary(DoPointwiseCeiling); + } + + /// + /// Pointwise applies the ceiling function to each value + /// + /// The vector to store the result + public void PointwiseCeiling(Vector result) + { + PointwiseUnary(DoPointwiseCeiling, result); + } + + /// + /// Pointwise applies the cos function to each value + /// + public Vector PointwiseCos() + { + return PointwiseUnary(DoPointwiseCos); + } + + /// + /// Pointwise applies the cos function to each value + /// + /// The vector to store the result + public void PointwiseCos(Vector result) + { + PointwiseUnary(DoPointwiseCos, result); + } + + /// + /// Pointwise applies the cosh function to each value + /// + public Vector PointwiseCosh() + { + return PointwiseUnary(DoPointwiseCosh); + } + + /// + /// Pointwise applies the cosh function to each value + /// + /// The vector to store the result + public void PointwiseCosh(Vector result) + { + PointwiseUnary(DoPointwiseCosh, result); + } + + /// + /// Pointwise applies the floor function to each value + /// + public Vector PointwiseFloor() + { + return PointwiseUnary(DoPointwiseFloor); + } + + /// + /// Pointwise applies the floor function to each value + /// + /// The vector to store the result + public void PointwiseFloor(Vector result) + { + PointwiseUnary(DoPointwiseFloor, result); + } + + /// + /// Pointwise applies the log10 function to each value + /// + public Vector PointwiseLog10() + { + return PointwiseUnary(DoPointwiseLog10); + } + + /// + /// Pointwise applies the log10 function to each value + /// + /// The vector to store the result + public void PointwiseLog10(Vector result) + { + PointwiseUnary(DoPointwiseLog10, result); + } + + /// + /// Pointwise applies the round function to each value + /// + public Vector PointwiseRound() + { + return PointwiseUnary(DoPointwiseRound); + } + + /// + /// Pointwise applies the round function to each value + /// + /// The vector to store the result + public void PointwiseRound(Vector result) + { + PointwiseUnary(DoPointwiseRound, result); + } + + /// + /// Pointwise applies the sign function to each value + /// + public Vector PointwiseSign() + { + return PointwiseUnary(DoPointwiseSign); + } + + /// + /// Pointwise applies the sign function to each value + /// + /// The vector to store the result + public void PointwiseSign(Vector result) + { + PointwiseUnary(DoPointwiseSign, result); + } + + /// + /// Pointwise applies the sin function to each value + /// + public Vector PointwiseSin() + { + return PointwiseUnary(DoPointwiseSin); + } + + /// + /// Pointwise applies the sin function to each value + /// + /// The vector to store the result + public void PointwiseSin(Vector result) + { + PointwiseUnary(DoPointwiseSin, result); + } + + /// + /// Pointwise applies the sinh function to each value + /// + public Vector PointwiseSinh() + { + return PointwiseUnary(DoPointwiseSinh); + } + + /// + /// Pointwise applies the sinh function to each value + /// + /// The vector to store the result + public void PointwiseSinh(Vector result) + { + PointwiseUnary(DoPointwiseSinh, result); + } + + /// + /// Pointwise applies the sqrt function to each value + /// + public Vector PointwiseSqrt() + { + return PointwiseUnary(DoPointwiseSqrt); + } + + /// + /// Pointwise applies the sqrt function to each value + /// + /// The vector to store the result + public void PointwiseSqrt(Vector result) + { + PointwiseUnary(DoPointwiseSqrt, result); + } + + /// + /// Pointwise applies the tan function to each value + /// + public Vector PointwiseTan() + { + return PointwiseUnary(DoPointwiseTan); + } + + /// + /// Pointwise applies the tan function to each value + /// + /// The vector to store the result + public void PointwiseTan(Vector result) + { + PointwiseUnary(DoPointwiseTan, result); + } + + /// + /// Pointwise applies the tanh function to each value + /// + public Vector PointwiseTanh() + { + return PointwiseUnary(DoPointwiseTanh); + } + + /// + /// Pointwise applies the tanh function to each value + /// + /// The vector to store the result + public void PointwiseTanh(Vector result) + { + PointwiseUnary(DoPointwiseTanh, result); + } + + /// + /// Computes the outer product M[i,j] = u[i]*v[j] of this and another vector. + /// + /// The other vector + public Matrix OuterProduct(Vector other) + { + var matrix = Matrix.Build.SameAs(this, Count, other.Count); + DoOuterProduct(other, matrix); + return matrix; + } + + /// + /// Computes the outer product M[i,j] = u[i]*v[j] of this and another vector and stores the result in the result matrix. + /// + /// The other vector + /// The matrix to store the result of the product. + public void OuterProduct(Vector other, Matrix result) + { + if (Count != result.RowCount || other.Count != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, nameof(result)); + } + + DoOuterProduct(other, result); + } + + public static Matrix OuterProduct(Vector u, Vector v) + { + return u.OuterProduct(v); + } + + /// + /// Pointwise applies the minimum with a scalar to each value. + /// + /// The scalar value to compare to. + public Vector PointwiseMinimum(T scalar) + { + var result = Build.SameAs(this); + DoPointwiseMinimum(scalar, result); + return result; + } + + /// + /// Pointwise applies the minimum with a scalar to each value. + /// + /// The scalar value to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseMinimum(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseMinimum(scalar, result); + } + + /// + /// Pointwise applies the maximum with a scalar to each value. + /// + /// The scalar value to compare to. + public Vector PointwiseMaximum(T scalar) + { + var result = Build.SameAs(this); + DoPointwiseMaximum(scalar, result); + return result; + } + + /// + /// Pointwise applies the maximum with a scalar to each value. + /// + /// The scalar value to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseMaximum(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseMaximum(scalar, result); + } + + /// + /// Pointwise applies the absolute minimum with a scalar to each value. + /// + /// The scalar value to compare to. + public Vector PointwiseAbsoluteMinimum(T scalar) + { + var result = Build.SameAs(this); + DoPointwiseAbsoluteMinimum(scalar, result); + return result; + } + + /// + /// Pointwise applies the absolute minimum with a scalar to each value. + /// + /// The scalar value to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseAbsoluteMinimum(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseAbsoluteMinimum(scalar, result); + } + + /// + /// Pointwise applies the absolute maximum with a scalar to each value. + /// + /// The scalar value to compare to. + public Vector PointwiseAbsoluteMaximum(T scalar) + { + var result = Build.SameAs(this); + DoPointwiseAbsoluteMaximum(scalar, result); + return result; + } + + /// + /// Pointwise applies the absolute maximum with a scalar to each value. + /// + /// The scalar value to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseAbsoluteMaximum(T scalar, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseAbsoluteMaximum(scalar, result); + } + + /// + /// Pointwise applies the minimum with the values of another vector to each value. + /// + /// The vector with the values to compare to. + public Vector PointwiseMinimum(Vector other) + { + var result = Build.SameAs(this); + DoPointwiseMinimum(other, result); + return result; + } + + /// + /// Pointwise applies the minimum with the values of another vector to each value. + /// + /// The vector with the values to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseMinimum(Vector other, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseMinimum(other, result); + } + + /// + /// Pointwise applies the maximum with the values of another vector to each value. + /// + /// The vector with the values to compare to. + public Vector PointwiseMaximum(Vector other) + { + var result = Build.SameAs(this); + DoPointwiseMaximum(other, result); + return result; + } + + /// + /// Pointwise applies the maximum with the values of another vector to each value. + /// + /// The vector with the values to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseMaximum(Vector other, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseMaximum(other, result); + } + + /// + /// Pointwise applies the absolute minimum with the values of another vector to each value. + /// + /// The vector with the values to compare to. + public Vector PointwiseAbsoluteMinimum(Vector other) + { + var result = Build.SameAs(this); + DoPointwiseAbsoluteMinimum(other, result); + return result; + } + + /// + /// Pointwise applies the absolute minimum with the values of another vector to each value. + /// + /// The vector with the values to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseAbsoluteMinimum(Vector other, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseAbsoluteMinimum(other, result); + } + + /// + /// Pointwise applies the absolute maximum with the values of another vector to each value. + /// + /// The vector with the values to compare to. + public Vector PointwiseAbsoluteMaximum(Vector other) + { + var result = Build.SameAs(this); + DoPointwiseAbsoluteMaximum(other, result); + return result; + } + + /// + /// Pointwise applies the absolute maximum with the values of another vector to each value. + /// + /// The vector with the values to compare to. + /// The vector to store the result. + /// If this vector and are not the same size. + public void PointwiseAbsoluteMaximum(Vector other, Vector result) + { + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, nameof(result)); + } + + DoPointwiseAbsoluteMaximum(other, result); + } + + /// + /// Calculates the L1 norm of the vector, also known as Manhattan norm. + /// + /// The sum of the absolute values. + public abstract double L1Norm(); + + /// + /// Calculates the L2 norm of the vector, also known as Euclidean norm. + /// + /// The square root of the sum of the squared values. + public abstract double L2Norm(); + + /// + /// Calculates the infinity norm of the vector. + /// + /// The maximum absolute value. + public abstract double InfinityNorm(); + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = (sum(abs(this[i])^p))^(1/p) + public abstract double Norm(double p); + + /// + /// Normalizes this vector to a unit vector with respect to the p-norm. + /// + /// The p value. + /// This vector normalized to a unit vector with respect to the p-norm. + public abstract Vector Normalize(double p); + + /// + /// Returns the value of the absolute minimum element. + /// + /// The value of the absolute minimum element. + public abstract T AbsoluteMinimum(); + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public abstract int AbsoluteMinimumIndex(); + + /// + /// Returns the value of the absolute maximum element. + /// + /// The value of the absolute maximum element. + public abstract T AbsoluteMaximum(); + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public abstract int AbsoluteMaximumIndex(); + + /// + /// Returns the value of maximum element. + /// + /// The value of maximum element. + public T Maximum() + { + return At(MaximumIndex()); + } + + /// + /// Returns the index of the maximum element. + /// + /// The index of maximum element. + public abstract int MaximumIndex(); + + /// + /// Returns the value of the minimum element. + /// + /// The value of the minimum element. + public T Minimum() + { + return At(MinimumIndex()); + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public abstract int MinimumIndex(); + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public abstract T Sum(); + + /// + /// Computes the sum of the absolute value of the vector's elements. + /// + /// The sum of the absolute value of the vector's elements. + public double SumMagnitudes() + { + return L1Norm(); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Vector.BCL.cs b/MathNet.Numerics/LinearAlgebra/Vector.BCL.cs new file mode 100644 index 0000000..bf38449 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Vector.BCL.cs @@ -0,0 +1,442 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace MathNet.Numerics.LinearAlgebra; + +[DebuggerDisplay("Vector {" + nameof(Count) + "}")] +public abstract partial class Vector +{ + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(Vector other) + { + return other != null && Storage.Equals(other.Storage); + } + + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public sealed override bool Equals(object obj) + { + var other = obj as Vector; + return other != null && Storage.Equals(other.Storage); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public sealed override int GetHashCode() + { + return Storage.GetHashCode(); + } + +#if !NETSTANDARD1_3 + + /// + /// Creates a new object that is a copy of the current instance. + /// + /// + /// A new object that is a copy of this instance. + /// + object ICloneable.Clone() + { + return Clone(); + } + +#endif + + int IList.IndexOf(T item) + { + for (int i = 0; i < Count; ++i) + { + if (At(i).Equals(item)) + return i; + } + + return -1; + } + + void IList.Insert(int index, T item) + { + throw new NotSupportedException(); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + bool ICollection.IsReadOnly + { + get { return false; } + } + + void ICollection.Add(T item) + { + throw new NotSupportedException(); + } + + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } + + bool ICollection.Contains(T item) + { + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var x in this) + { + if (x.Equals(item)) + return true; + } + + return false; + } + + void ICollection.CopyTo(T[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + Storage.CopySubVectorTo(new DenseVectorStorage(array.Length, array), 0, arrayIndex, Count); + } + + bool IList.IsReadOnly + { + get { return false; } + } + + bool IList.IsFixedSize + { + get { return true; } + } + + object IList.this[int index] + { + get { return Storage[index]; } + set { Storage[index] = (T)value; } + } + + int IList.IndexOf(object value) + { + if (!(value is T)) + { + return -1; + } + + return ((IList)this).IndexOf((T)value); + } + + bool IList.Contains(object value) + { + if (!(value is T)) + { + return false; + } + + return ((ICollection)this).Contains((T)value); + } + + void IList.Insert(int index, object value) + { + throw new NotSupportedException(); + } + + int IList.Add(object value) + { + throw new NotSupportedException(); + } + + void IList.Remove(object value) + { + throw new NotSupportedException(); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + bool ICollection.IsSynchronized + { + get { return false; } + } + + object ICollection.SyncRoot + { + get { return Storage; } + } + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (array.Rank != 1) + { + throw new ArgumentException(Resources.ArgumentSingleDimensionArray, nameof(array)); + } + + Storage.CopySubVectorTo(new DenseVectorStorage(array.Length, (T[])array), 0, index, Count); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return Enumerate().GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return Enumerate().GetEnumerator(); + } + + /// + /// Returns a string that describes the type, dimensions and shape of this vector. + /// + public virtual string ToTypeString() + { + return string.Format("{0} {1}-{2}", GetType().Name, Count, typeof(T).Name); + } + + public string[,] ToVectorStringArray(int maxPerColumn, int maxCharactersWidth, int padding, string ellipsis, Func formatValue) + { + // enforce minima to avoid pathetic cases + maxPerColumn = Math.Max(maxPerColumn, 3); + maxCharactersWidth = Math.Max(maxCharactersWidth, 16); + + var columns = new List>(); + int chars = 0; + int offset = 0; + while (offset < Count) + { + // full column + int height = Math.Min(maxPerColumn, Count - offset); + var candidate = FormatCompleteColumn(offset, height, formatValue); + chars += candidate.Item1 + padding; + if (chars > maxCharactersWidth && offset > 0) + { + break; + } + + columns.Add(candidate); + offset += height; + } + + if (offset < Count) + { + // we're not done yet, but adding the last column has failed + // --> make the last column partial + var last = columns[columns.Count - 1]; + var c = last.Item2; + c[c.Length - 2] = ellipsis; + c[c.Length - 1] = formatValue(At(Count - 1)); + } + + int rows = columns[0].Item2.Length; + int cols = columns.Count; + var array = new string[rows, cols]; + int colIndex = 0; + foreach (var column in columns) + { + for (int k = 0; k < column.Item2.Length; k++) + { + array[k, colIndex] = column.Item2[k]; + } + + for (int k = column.Item2.Length; k < rows; k++) + { + array[k, colIndex] = ""; + } + + colIndex++; + } + + return array; + } + + static string FormatStringArrayToString(string[,] array, string columnSeparator, string rowSeparator) + { + var rows = array.GetLength(0); + var cols = array.GetLength(1); + + var widths = new int[cols]; + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + widths[j] = Math.Max(widths[j], array[i, j].Length); + } + } + + var sb = new StringBuilder(); + for (int i = 0; i < rows; i++) + { + sb.Append(array[i, 0].PadLeft(widths[0])); + for (int j = 1; j < cols; j++) + { + sb.Append(columnSeparator); + sb.Append(array[i, j].PadLeft(widths[j])); + } + + sb.Append(rowSeparator); + } + + return sb.ToString(); + } + + Tuple FormatCompleteColumn(int offset, int height, Func formatValue) + { + var c = new string[height]; + int index = 0; + for (var k = 0; k < height; k++) + { + c[index++] = formatValue(At(offset + k)); + } + + int w = c.Max(x => x.Length); + return new Tuple(w, c); + } + + /// + /// Returns a string that represents the content of this vector, column by column. + /// + /// Maximum number of entries and thus lines per column. Typical value: 12; Minimum: 3. + /// Maximum number of characters per line over all columns. Typical value: 80; Minimum: 16. + /// Character to use to print if there is not enough space to print all entries. Typical value: "..". + /// Character to use to separate two columns on a line. Typical value: " " (2 spaces). + /// Character to use to separate two rows/lines. Typical value: Environment.NewLine. + /// Function to provide a string for any given entry value. + public string ToVectorString(int maxPerColumn, int maxCharactersWidth, string ellipsis, string columnSeparator, string rowSeparator, Func formatValue) + { + return FormatStringArrayToString( + ToVectorStringArray(maxPerColumn, maxCharactersWidth, columnSeparator.Length, ellipsis, formatValue), + columnSeparator, rowSeparator); + } + + /// + /// Returns a string that represents the content of this vector, column by column. + /// + /// Maximum number of entries and thus lines per column. Typical value: 12; Minimum: 3. + /// Maximum number of characters per line over all columns. Typical value: 80; Minimum: 16. + /// Floating point format string. Can be null. Default value: G6. + /// Format provider or culture. Can be null. + public string ToVectorString(int maxPerColumn, int maxCharactersWidth, string format = null, IFormatProvider provider = null) + { + if (format == null) + { + format = "G6"; + } + + return ToVectorString(maxPerColumn, maxCharactersWidth, "..", " ", Environment.NewLine, x => x.ToString(format, provider)); + } + + /// + /// Returns a string that represents the content of this vector, column by column. + /// + /// Floating point format string. Can be null. Default value: G6. + /// Format provider or culture. Can be null. + public string ToVectorString(string format = null, IFormatProvider provider = null) + { + if (format == null) + { + format = "G6"; + } + + return ToVectorString(12, 80, "..", " ", Environment.NewLine, x => x.ToString(format, provider)); + } + + /// + /// Returns a string that summarizes this vector, column by column and with a type header. + /// + /// Maximum number of entries and thus lines per column. Typical value: 12; Minimum: 3. + /// Maximum number of characters per line over all columns. Typical value: 80; Minimum: 16. + /// Floating point format string. Can be null. Default value: G6. + /// Format provider or culture. Can be null. + public string ToString(int maxPerColumn, int maxCharactersWidth, string format = null, IFormatProvider provider = null) + { + return string.Concat(ToTypeString(), Environment.NewLine, ToVectorString(maxPerColumn, maxCharactersWidth, format, provider)); + } + + /// + /// Returns a string that summarizes this vector. + /// The maximum number of cells can be configured in the class. + /// + public sealed override string ToString() + { + return string.Concat(ToTypeString(), Environment.NewLine, ToVectorString()); + } + + /// + /// Returns a string that summarizes this vector. + /// The maximum number of cells can be configured in the class. + /// The format string is ignored. + /// + public string ToString(string format = null, IFormatProvider formatProvider = null) + { + return string.Concat(ToTypeString(), Environment.NewLine, ToVectorString(format, formatProvider)); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Vector.Operators.cs b/MathNet.Numerics/LinearAlgebra/Vector.Operators.cs new file mode 100644 index 0000000..c31c949 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Vector.Operators.cs @@ -0,0 +1,443 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Runtime.CompilerServices; + +namespace MathNet.Numerics.LinearAlgebra; + +public abstract partial class Vector +{ + /// + /// Returns a Vector containing the same values of . + /// + /// This method is included for completeness. + /// The vector to get the values from. + /// A vector containing the same values as . + /// If is . + public static Vector operator +(Vector rightSide) + { + return rightSide.Clone(); + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static Vector operator -(Vector rightSide) + { + return rightSide.Negate(); + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static Vector operator +(Vector leftSide, Vector rightSide) + { + return leftSide.Add(rightSide); + } + + /// + /// Adds a scalar to each element of a vector. + /// + /// The vector to add to. + /// The scalar value to add. + /// The result of the addition. + /// If is . + public static Vector operator +(Vector leftSide, T rightSide) + { + return leftSide.Add(rightSide); + } + + /// + /// Adds a scalar to each element of a vector. + /// + /// The scalar value to add. + /// The vector to add to. + /// The result of the addition. + /// If is . + public static Vector operator +(T leftSide, Vector rightSide) + { + return rightSide.Add(leftSide); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static Vector operator -(Vector leftSide, Vector rightSide) + { + return leftSide.Subtract(rightSide); + } + + /// + /// Subtracts a scalar from each element of a vector. + /// + /// The vector to subtract from. + /// The scalar value to subtract. + /// The result of the subtraction. + /// If is . + public static Vector operator -(Vector leftSide, T rightSide) + { + return leftSide.Subtract(rightSide); + } + + /// + /// Subtracts each element of a vector from a scalar. + /// + /// The scalar value to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If is . + public static Vector operator -(T leftSide, Vector rightSide) + { + return rightSide.SubtractFrom(leftSide); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The vector to scale. + /// The scalar value. + /// The result of the multiplication. + /// If is . + public static Vector operator *(Vector leftSide, T rightSide) + { + return leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a scalar. + /// + /// The scalar value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static Vector operator *(T leftSide, Vector rightSide) + { + return rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static T operator *(Vector leftSide, Vector rightSide) + { + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a scalar with a vector. + /// + /// The scalar to divide. + /// The vector. + /// The result of the division. + /// If is . + public static Vector operator /(T dividend, Vector divisor) + { + return divisor.DivideByThis(dividend); + } + + /// + /// Divides a vector with a scalar. + /// + /// The vector to divide. + /// The scalar value. + /// The result of the division. + /// If is . + public static Vector operator /(Vector dividend, T divisor) + { + return dividend.Divide(divisor); + } + + /// + /// Pointwise divides two Vectors. + /// + /// The vector to divide. + /// The other vector. + /// The result of the division. + /// If and are not the same size. + /// If is . + public static Vector operator /(Vector dividend, Vector divisor) + { + return dividend.PointwiseDivide(divisor); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// of each element of the vector of the given divisor. + /// + /// The vector whose elements we want to compute the remainder of. + /// The divisor to use. + /// If is . + public static Vector operator %(Vector dividend, T divisor) + { + return dividend.Remainder(divisor); + } + + /// + /// Computes the remainder (% operator), where the result has the sign of the dividend, + /// of the given dividend of each element of the vector. + /// + /// The dividend we want to compute the remainder of. + /// The vector whose elements we want to use as divisor. + /// If is . + public static Vector operator %(T dividend, Vector divisor) + { + return divisor.RemainderByThis(dividend); + } + + /// + /// Computes the pointwise remainder (% operator), where the result has the sign of the dividend, + /// of each element of two vectors. + /// + /// The vector whose elements we want to compute the remainder of. + /// The divisor to use. + /// If and are not the same size. + /// If is . + public static Vector operator %(Vector dividend, Vector divisor) + { + return dividend.PointwiseRemainder(divisor); + } + + [SpecialName] + public static Vector op_DotMultiply(Vector x, Vector y) + { + return x.PointwiseMultiply(y); + } + + [SpecialName] + public static Vector op_DotDivide(Vector dividend, Vector divisor) + { + return dividend.PointwiseDivide(divisor); + } + + [SpecialName] + public static Vector op_DotPercent(Vector dividend, Vector divisor) + { + return dividend.PointwiseRemainder(divisor); + } + + [SpecialName] + public static Vector op_DotHat(Vector vector, Vector exponent) + { + return vector.PointwisePower(exponent); + } + + [SpecialName] + public static Vector op_DotHat(Vector vector, T exponent) + { + return vector.PointwisePower(exponent); + } + + /// + /// Computes the sqrt of a vector pointwise + /// + /// The input vector + /// + public static Vector Sqrt(Vector x) + { + return x.PointwiseSqrt(); + } + + /// + /// Computes the exponential of a vector pointwise + /// + /// The input vector + /// + public static Vector Exp(Vector x) + { + return x.PointwiseUnary(x.DoPointwiseExp); + } + + /// + /// Computes the log of a vector pointwise + /// + /// The input vector + /// + public static Vector Log(Vector x) + { + return x.PointwiseUnary(x.PointwiseLog); + } + /// + /// Computes the log10 of a vector pointwise + /// + /// The input vector + /// + public static Vector Log10(Vector x) + { + return x.PointwiseLog10(); + } + + /// + /// Computes the sin of a vector pointwise + /// + /// The input vector + /// + public static Vector Sin(Vector x) + { + return x.PointwiseSin(); + } + + /// + /// Computes the cos of a vector pointwise + /// + /// The input vector + /// + public static Vector Cos(Vector x) + { + return x.PointwiseCos(); + } + + /// + /// Computes the tan of a vector pointwise + /// + /// The input vector + /// + public static Vector Tan(Vector x) + { + return x.PointwiseTan(); + } + + /// + /// Computes the asin of a vector pointwise + /// + /// The input vector + /// + public static Vector Asin(Vector x) + { + return x.PointwiseAsin(); + } + + /// + /// Computes the acos of a vector pointwise + /// + /// The input vector + /// + public static Vector Acos(Vector x) + { + return x.PointwiseAcos(); + } + + /// + /// Computes the atan of a vector pointwise + /// + /// The input vector + /// + public static Vector Atan(Vector x) + { + return x.PointwiseAtan(); + } + + /// + /// Computes the sinh of a vector pointwise + /// + /// The input vector + /// + public static Vector Sinh(Vector x) + { + return x.PointwiseSinh(); + } + + /// + /// Computes the cosh of a vector pointwise + /// + /// The input vector + /// + public static Vector Cosh(Vector x) + { + return x.PointwiseCosh(); + } + + /// + /// Computes the tanh of a vector pointwise + /// + /// The input vector + /// + public static Vector Tanh(Vector x) + { + return x.PointwiseTanh(); + } + + /// + /// Computes the absolute value of a vector pointwise + /// + /// The input vector + /// + public static Vector Abs(Vector x) + { + return x.PointwiseAbs(); + } + + /// + /// Computes the floor of a vector pointwise + /// + /// The input vector + /// + public static Vector Floor(Vector x) + { + return x.PointwiseFloor(); + } + + /// + /// Computes the ceiling of a vector pointwise + /// + /// The input vector + /// + public static Vector Ceiling(Vector x) + { + return x.PointwiseCeiling(); + } + + /// + /// Computes the rounded value of a vector pointwise + /// + /// The input vector + /// + public static Vector Round(Vector x) + { + return x.PointwiseRound(); + } +} diff --git a/MathNet.Numerics/LinearAlgebra/Vector.cs b/MathNet.Numerics/LinearAlgebra/Vector.cs new file mode 100644 index 0000000..5822fb0 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/Vector.cs @@ -0,0 +1,545 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Storage; +using MathNet.Numerics.Properties; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime; +using System.Runtime.CompilerServices; + +namespace MathNet.Numerics.LinearAlgebra; + +/// +/// Defines the generic class for Vector classes. +/// +/// Supported data types are double, single, , and . +[Serializable] +public abstract partial class Vector : + IFormattable, IEquatable>, IList, IList +#if !NETSTANDARD1_3 + , ICloneable +#endif + where T : struct, IEquatable, IFormattable +{ + /// + /// Initializes a new instance of the Vector class. + /// + protected Vector(VectorStorage storage) + { + Storage = storage; + Count = storage.Length; + } + + public static readonly VectorBuilder Build = BuilderInstance.Vector; + + /// + /// Gets the raw vector data storage. + /// + public VectorStorage Storage { get; private set; } + + /// + /// Gets the length or number of dimensions of this vector. + /// + public int Count { get; private set; } + + /// Gets or sets the value at the given . + /// The index of the value to get or set. + /// The value of the vector at the given . + /// If is negative or + /// greater than the size of the vector. + public T this[int index] + { +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + get { return Storage[index]; } + +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + set { Storage[index] = value; } + } + + /// Gets the value at the given without range checking.. + /// The index of the value to get or set. + /// The value of the vector at the given . +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + public T At(int index) + { + return Storage.At(index); + } + + /// Sets the at the given without range checking.. + /// The index of the value to get or set. + /// The value to set. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + public void At(int index, T value) + { + Storage.At(index, value); + } + + /// + /// Resets all values to zero. + /// + public void Clear() + { + Storage.Clear(); + } + + /// + /// Sets all values of a subvector to zero. + /// + public void ClearSubVector(int index, int count) + { + if (count < 1) + { + throw new ArgumentOutOfRangeException(nameof(count), Resources.ArgumentMustBePositive); + } + + if (index + count > Count || index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + Storage.Clear(index, count); + } + + /// + /// Set all values whose absolute value is smaller than the threshold to zero, in-place. + /// + public abstract void CoerceZero(double threshold); + + /// + /// Set all values that meet the predicate to zero, in-place. + /// + public void CoerceZero(Func zeroPredicate) + { + MapInplace(x => zeroPredicate(x) ? Zero : x, Zeros.AllowSkip); + } + + /// + /// Returns a deep-copy clone of the vector. + /// + /// A deep-copy clone of the vector. + public Vector Clone() + { + var result = Build.SameAs(this); + Storage.CopyToUnchecked(result.Storage, ExistingData.AssumeZeros); + return result; + } + + /// + /// Set the values of this vector to the given values. + /// + /// The array containing the values to use. + /// If is . + /// If is not the same size as this vector. + public void SetValues(T[] values) + { + var source = new DenseVectorStorage(Count, values); + source.CopyTo(Storage); + } + + /// + /// Copies the values of this vector into the target vector. + /// + /// The vector to copy elements into. + /// If is . + /// If is not the same size as this vector. + public void CopyTo(Vector target) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + Storage.CopyTo(target.Storage); + } + + /// + /// Creates a vector containing specified elements. + /// + /// The first element to begin copying from. + /// The number of elements to copy. + /// A vector containing a copy of the specified elements. + /// If is not positive or + /// greater than or equal to the size of the vector. + /// If + is greater than or equal to the size of the vector. + /// + /// If is not positive. + public Vector SubVector(int index, int count) + { + var target = Build.SameAs(this, count); + Storage.CopySubVectorTo(target.Storage, index, 0, count, ExistingData.AssumeZeros); + return target; + } + + /// + /// Copies the values of a given vector into a region in this vector. + /// + /// The field to start copying to + /// The number of fields to copy. Must be positive. + /// The sub-vector to copy from. + /// If is + public void SetSubVector(int index, int count, Vector subVector) + { + if (subVector == null) + { + throw new ArgumentNullException(nameof(subVector)); + } + + subVector.Storage.CopySubVectorTo(Storage, 0, index, count); + } + + /// + /// Copies the requested elements from this vector to another. + /// + /// The vector to copy the elements to. + /// The element to start copying from. + /// The element to start copying to. + /// The number of elements to copy. + public void CopySubVectorTo(Vector destination, int sourceIndex, int targetIndex, int count) + { + if (destination == null) + { + throw new ArgumentNullException(nameof(destination)); + } + + // TODO: refactor range checks + Storage.CopySubVectorTo(destination.Storage, sourceIndex, targetIndex, count); + } + + /// + /// Returns the data contained in the vector as an array. + /// The returned array will be independent from this vector. + /// A new memory block will be allocated for the array. + /// + /// The vector's data as an array. + public T[] ToArray() + { + return Storage.ToArray(); + } + + /// + /// Returns the internal array of this vector if, and only if, this vector is stored by such an array internally. + /// Otherwise returns null. Changes to the returned array and the vector will affect each other. + /// Use ToArray instead if you always need an independent array. + /// + public T[] AsArray() + { + return Storage.AsArray(); + } + + /// + /// Create a matrix based on this vector in column form (one single column). + /// + /// + /// This vector as a column matrix. + /// + public Matrix ToColumnMatrix() + { + var result = Matrix.Build.SameAs(this, Count, 1); + Storage.CopyToColumnUnchecked(result.Storage, 0, ExistingData.AssumeZeros); + return result; + } + + /// + /// Create a matrix based on this vector in row form (one single row). + /// + /// + /// This vector as a row matrix. + /// + public Matrix ToRowMatrix() + { + var result = Matrix.Build.SameAs(this, 1, Count); + Storage.CopyToRowUnchecked(result.Storage, 0, ExistingData.AssumeZeros); + return result; + } + + /// + /// Returns an IEnumerable that can be used to iterate through all values of the vector. + /// + /// + /// The enumerator will include all values, even if they are zero. + /// + public IEnumerable Enumerate() + { + return Storage.Enumerate(); + } + + /// + /// Returns an IEnumerable that can be used to iterate through all values of the vector. + /// + /// + /// The enumerator will include all values, even if they are zero. + /// + public IEnumerable Enumerate(Zeros zeros = Zeros.Include) + { + switch (zeros) + { + case Zeros.AllowSkip: + return Storage.EnumerateNonZero(); + default: + return Storage.Enumerate(); + } + } + + /// + /// Returns an IEnumerable that can be used to iterate through all values of the vector and their index. + /// + /// + /// The enumerator returns a Tuple with the first value being the element index + /// and the second value being the value of the element at that index. + /// The enumerator will include all values, even if they are zero. + /// + public IEnumerable> EnumerateIndexed() + { + return Storage.EnumerateIndexed(); + } + + /// + /// Returns an IEnumerable that can be used to iterate through all values of the vector and their index. + /// + /// + /// The enumerator returns a Tuple with the first value being the element index + /// and the second value being the value of the element at that index. + /// The enumerator will include all values, even if they are zero. + /// + public IEnumerable> EnumerateIndexed(Zeros zeros = Zeros.Include) + { + switch (zeros) + { + case Zeros.AllowSkip: + return Storage.EnumerateNonZeroIndexed(); + default: + return Storage.EnumerateIndexed(); + } + } + + /// + /// Applies a function to each value of this vector and replaces the value with its result. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse vectors). + /// + public void MapInplace(Func f, Zeros zeros = Zeros.AllowSkip) + { + Storage.MapInplace(f, zeros); + } + + /// + /// Applies a function to each value of this vector and replaces the value with its result. + /// The index of each value (zero-based) is passed as first argument to the function. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse vectors). + /// + public void MapIndexedInplace(Func f, Zeros zeros = Zeros.AllowSkip) + { + Storage.MapIndexedInplace(f, zeros); + } + + /// + /// Applies a function to each value of this vector and replaces the value in the result vector. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse vectors). + /// + public void Map(Func f, Vector result, Zeros zeros = Zeros.AllowSkip) + { + if (ReferenceEquals(this, result)) + { + Storage.MapInplace(f, zeros); + } + else + { + Storage.MapTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear); + } + } + + /// + /// Applies a function to each value of this vector and replaces the value in the result vector. + /// The index of each value (zero-based) is passed as first argument to the function. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse vectors). + /// + public void MapIndexed(Func f, Vector result, Zeros zeros = Zeros.AllowSkip) + { + if (ReferenceEquals(this, result)) + { + Storage.MapIndexedInplace(f, zeros); + } + else + { + Storage.MapIndexedTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear); + } + } + + /// + /// Applies a function to each value of this vector and replaces the value in the result vector. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse vectors). + /// + public void MapConvert(Func f, Vector result, Zeros zeros = Zeros.AllowSkip) + where TU : struct, IEquatable, IFormattable + { + Storage.MapTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear); + } + + /// + /// Applies a function to each value of this vector and replaces the value in the result vector. + /// The index of each value (zero-based) is passed as first argument to the function. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse vectors). + /// + public void MapIndexedConvert(Func f, Vector result, Zeros zeros = Zeros.AllowSkip) + where TU : struct, IEquatable, IFormattable + { + Storage.MapIndexedTo(result.Storage, f, zeros, zeros == Zeros.Include ? ExistingData.AssumeZeros : ExistingData.Clear); + } + + /// + /// Applies a function to each value of this vector and returns the results as a new vector. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse vectors). + /// + public Vector Map(Func f, Zeros zeros = Zeros.AllowSkip) + where TU : struct, IEquatable, IFormattable + { + var result = Vector.Build.SameAs(this); + Storage.MapToUnchecked(result.Storage, f, zeros, ExistingData.AssumeZeros); + return result; + } + + /// + /// Applies a function to each value of this vector and returns the results as a new vector. + /// The index of each value (zero-based) is passed as first argument to the function. + /// If forceMapZero is not set to true, zero values may or may not be skipped depending + /// on the actual data storage implementation (relevant mostly for sparse vectors). + /// + public Vector MapIndexed(Func f, Zeros zeros = Zeros.AllowSkip) + where TU : struct, IEquatable, IFormattable + { + var result = Vector.Build.SameAs(this); + Storage.MapIndexedToUnchecked(result.Storage, f, zeros, ExistingData.AssumeZeros); + return result; + } + + /// + /// Applies a function to each value pair of two vectors and replaces the value in the result vector. + /// + public void Map2(Func f, Vector other, Vector result, Zeros zeros = Zeros.AllowSkip) + { + Storage.Map2To(result.Storage, other.Storage, f, zeros, ExistingData.Clear); + } + + /// + /// Applies a function to each value pair of two vectors and returns the results as a new vector. + /// + public Vector Map2(Func f, Vector other, Zeros zeros = Zeros.AllowSkip) + { + var result = Build.SameAs(this); + Storage.Map2To(result.Storage, other.Storage, f, zeros, ExistingData.AssumeZeros); + return result; + } + + /// + /// Applies a function to update the status with each value pair of two vectors and returns the resulting status. + /// + public TState Fold2(Func f, TState state, Vector other, Zeros zeros = Zeros.AllowSkip) + where TOther : struct, IEquatable, IFormattable + { + return Storage.Fold2(other.Storage, f, state, zeros); + } + + /// + /// Returns a tuple with the index and value of the first element satisfying a predicate, or null if none is found. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public Tuple Find(Func predicate, Zeros zeros = Zeros.AllowSkip) + { + return Storage.Find(predicate, zeros); + } + + /// + /// Returns a tuple with the index and values of the first element pair of two vectors of the same size satisfying a predicate, or null if none is found. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public Tuple Find2(Func predicate, Vector other, Zeros zeros = Zeros.AllowSkip) + where TOther : struct, IEquatable, IFormattable + { + return Storage.Find2(other.Storage, predicate, zeros); + } + + /// + /// Returns true if at least one element satisfies a predicate. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public bool Exists(Func predicate, Zeros zeros = Zeros.AllowSkip) + { + return Storage.Find(predicate, zeros) != null; + } + + /// + /// Returns true if at least one element pairs of two vectors of the same size satisfies a predicate. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public bool Exists2(Func predicate, Vector other, Zeros zeros = Zeros.AllowSkip) + where TOther : struct, IEquatable, IFormattable + { + return Storage.Find2(other.Storage, predicate, zeros) != null; + } + + /// + /// Returns true if all elements satisfy a predicate. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public bool ForAll(Func predicate, Zeros zeros = Zeros.AllowSkip) + { + return Storage.Find(x => !predicate(x), zeros) == null; + } + + /// + /// Returns true if all element pairs of two vectors of the same size satisfy a predicate. + /// Zero elements may be skipped on sparse data structures if allowed (default). + /// + public bool ForAll2(Func predicate, Vector other, Zeros zeros = Zeros.AllowSkip) + where TOther : struct, IEquatable, IFormattable + { + return Storage.Find2(other.Storage, (x, y) => !predicate(x, y), zeros) == null; + } +} diff --git a/MathNet.Numerics/LinearAlgebra/VectorExtensions.cs b/MathNet.Numerics/LinearAlgebra/VectorExtensions.cs new file mode 100644 index 0000000..d51b566 --- /dev/null +++ b/MathNet.Numerics/LinearAlgebra/VectorExtensions.cs @@ -0,0 +1,115 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.LinearAlgebra; + +using Complex64 = System.Numerics.Complex; + +public static class VectorExtensions +{ + /// + /// Converts a vector to single precision. + /// + public static Vector ToSingle(this Vector vector) + { + return vector.Map(x => (float)x, Zeros.AllowSkip); + } + + /// + /// Converts a vector to double precision. + /// + public static Vector ToDouble(this Vector vector) + { + return vector.Map(x => (double)x, Zeros.AllowSkip); + } + + /// + /// Converts a vector to single precision complex numbers. + /// + public static Vector ToComplex32(this Vector vector) + { + return vector.Map(x => new Numerics.Complex32((float)x.Real, (float)x.Imaginary), Zeros.AllowSkip); + } + + /// + /// Converts a vector to double precision complex numbers. + /// + public static Vector ToComplex(this Vector vector) + { + return vector.Map(x => new Complex64(x.Real, x.Imaginary), Zeros.AllowSkip); + } + + /// + /// Gets a single precision complex vector with the real parts from the given vector. + /// + public static Vector ToComplex32(this Vector vector) + { + return vector.Map(x => new Numerics.Complex32(x, 0f), Zeros.AllowSkip); + } + + /// + /// Gets a double precision complex vector with the real parts from the given vector. + /// + public static Vector ToComplex(this Vector vector) + { + return vector.Map(x => new Complex64(x, 0d), Zeros.AllowSkip); + } + + /// + /// Gets a real vector representing the real parts of a complex vector. + /// + public static Vector Real(this Vector vector) + { + return vector.Map(x => x.Real, Zeros.AllowSkip); + } + + /// + /// Gets a real vector representing the real parts of a complex vector. + /// + public static Vector Real(this Vector vector) + { + return vector.Map(x => x.Real, Zeros.AllowSkip); + } + + /// + /// Gets a real vector representing the imaginary parts of a complex vector. + /// + public static Vector Imaginary(this Vector vector) + { + return vector.Map(x => x.Imaginary, Zeros.AllowSkip); + } + + /// + /// Gets a real vector representing the imaginary parts of a complex vector. + /// + public static Vector Imaginary(this Vector vector) + { + return vector.Map(x => x.Imaginary, Zeros.AllowSkip); + } +} diff --git a/MathNet.Numerics/LinearRegression/MultipleRegression.cs b/MathNet.Numerics/LinearRegression/MultipleRegression.cs new file mode 100644 index 0000000..20d117f --- /dev/null +++ b/MathNet.Numerics/LinearRegression/MultipleRegression.cs @@ -0,0 +1,388 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearRegression; + +public static class MultipleRegression +{ + /// + /// Find the model parameters β such that X*β with predictor X becomes as close to response Y as possible, with least squares residuals. + /// + /// Predictor matrix X + /// Response vector Y + /// The direct method to be used to compute the regression. + /// Best fitting vector for model parameters β + public static Vector DirectMethod(Matrix x, Vector y, DirectRegressionMethod method = DirectRegressionMethod.NormalEquations) where T : struct, IEquatable, IFormattable + { + switch (method) + { + case DirectRegressionMethod.NormalEquations: + return NormalEquations(x, y); + case DirectRegressionMethod.QR: + return QR(x, y); + case DirectRegressionMethod.Svd: + return Svd(x, y); + default: + throw new NotSupportedException(method.ToString()); + } + } + + /// + /// Find the model parameters β such that X*β with predictor X becomes as close to response Y as possible, with least squares residuals. + /// + /// Predictor matrix X + /// Response matrix Y + /// The direct method to be used to compute the regression. + /// Best fitting vector for model parameters β + public static Matrix DirectMethod(Matrix x, Matrix y, DirectRegressionMethod method = DirectRegressionMethod.NormalEquations) where T : struct, IEquatable, IFormattable + { + switch (method) + { + case DirectRegressionMethod.NormalEquations: + return NormalEquations(x, y); + case DirectRegressionMethod.QR: + return QR(x, y); + case DirectRegressionMethod.Svd: + return Svd(x, y); + default: + throw new NotSupportedException(method.ToString()); + } + } + + /// + /// Find the model parameters β such that their linear combination with all predictor-arrays in X become as close to their response in Y as possible, with least squares residuals. + /// + /// List of predictor-arrays. + /// List of responses + /// True if an intercept should be added as first artificial predictor value. Default = false. + /// The direct method to be used to compute the regression. + /// Best fitting list of model parameters β for each element in the predictor-arrays. + public static T[] DirectMethod(T[][] x, T[] y, bool intercept = false, DirectRegressionMethod method = DirectRegressionMethod.NormalEquations) where T : struct, IEquatable, IFormattable + { + switch (method) + { + case DirectRegressionMethod.NormalEquations: + return NormalEquations(x, y, intercept); + case DirectRegressionMethod.QR: + return QR(x, y, intercept); + case DirectRegressionMethod.Svd: + return Svd(x, y, intercept); + default: + throw new NotSupportedException(method.ToString()); + } + } + + /// + /// Find the model parameters β such that their linear combination with all predictor-arrays in X become as close to their response in Y as possible, with least squares residuals. + /// Uses the cholesky decomposition of the normal equations. + /// + /// Sequence of predictor-arrays and their response. + /// True if an intercept should be added as first artificial predictor value. Default = false. + /// The direct method to be used to compute the regression. + /// Best fitting list of model parameters β for each element in the predictor-arrays. + public static T[] DirectMethod(IEnumerable> samples, bool intercept = false, DirectRegressionMethod method = DirectRegressionMethod.NormalEquations) where T : struct, IEquatable, IFormattable + { + switch (method) + { + case DirectRegressionMethod.NormalEquations: + return NormalEquations(samples, intercept); + case DirectRegressionMethod.QR: + return QR(samples, intercept); + case DirectRegressionMethod.Svd: + return Svd(samples, intercept); + default: + throw new NotSupportedException(method.ToString()); + } + } + + /// + /// Find the model parameters β such that X*β with predictor X becomes as close to response Y as possible, with least squares residuals. + /// Uses the cholesky decomposition of the normal equations. + /// + /// Predictor matrix X + /// Response vector Y + /// Best fitting vector for model parameters β + public static Vector NormalEquations(Matrix x, Vector y) where T : struct, IEquatable, IFormattable + { + if (x.RowCount != y.Count) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, x.RowCount, y.Count)); + } + + if (x.ColumnCount > y.Count) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, x.ColumnCount, y.Count)); + } + + return x.TransposeThisAndMultiply(x).Cholesky().Solve(x.TransposeThisAndMultiply(y)); + } + + /// + /// Find the model parameters β such that X*β with predictor X becomes as close to response Y as possible, with least squares residuals. + /// Uses the cholesky decomposition of the normal equations. + /// + /// Predictor matrix X + /// Response matrix Y + /// Best fitting vector for model parameters β + public static Matrix NormalEquations(Matrix x, Matrix y) where T : struct, IEquatable, IFormattable + { + if (x.RowCount != y.RowCount) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, x.RowCount, y.RowCount)); + } + + if (x.ColumnCount > y.RowCount) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, x.ColumnCount, y.RowCount)); + } + + return x.TransposeThisAndMultiply(x).Cholesky().Solve(x.TransposeThisAndMultiply(y)); + } + + /// + /// Find the model parameters β such that their linear combination with all predictor-arrays in X become as close to their response in Y as possible, with least squares residuals. + /// Uses the cholesky decomposition of the normal equations. + /// + /// List of predictor-arrays. + /// List of responses + /// True if an intercept should be added as first artificial predictor value. Default = false. + /// Best fitting list of model parameters β for each element in the predictor-arrays. + public static T[] NormalEquations(T[][] x, T[] y, bool intercept = false) where T : struct, IEquatable, IFormattable + { + var predictor = Matrix.Build.DenseOfRowArrays(x); + if (intercept) + { + predictor = predictor.InsertColumn(0, Vector.Build.Dense(predictor.RowCount, Vector.One)); + } + + if (predictor.RowCount != y.Length) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, predictor.RowCount, y.Length)); + } + + if (predictor.ColumnCount > y.Length) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, predictor.ColumnCount, y.Length)); + } + + var response = Vector.Build.Dense(y); + return predictor.TransposeThisAndMultiply(predictor).Cholesky().Solve(predictor.TransposeThisAndMultiply(response)).ToArray(); + } + + /// + /// Find the model parameters β such that their linear combination with all predictor-arrays in X become as close to their response in Y as possible, with least squares residuals. + /// Uses the cholesky decomposition of the normal equations. + /// + /// Sequence of predictor-arrays and their response. + /// True if an intercept should be added as first artificial predictor value. Default = false. + /// Best fitting list of model parameters β for each element in the predictor-arrays. + public static T[] NormalEquations(IEnumerable> samples, bool intercept = false) where T : struct, IEquatable, IFormattable + { + var xy = samples.UnpackSinglePass(); + return NormalEquations(xy.Item1, xy.Item2, intercept); + } + + /// + /// Find the model parameters β such that X*β with predictor X becomes as close to response Y as possible, with least squares residuals. + /// Uses an orthogonal decomposition and is therefore more numerically stable than the normal equations but also slower. + /// + /// Predictor matrix X + /// Response vector Y + /// Best fitting vector for model parameters β + public static Vector QR(Matrix x, Vector y) where T : struct, IEquatable, IFormattable + { + if (x.RowCount != y.Count) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, x.RowCount, y.Count)); + } + + if (x.ColumnCount > y.Count) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, x.ColumnCount, y.Count)); + } + + return x.QR().Solve(y); + } + + /// + /// Find the model parameters β such that X*β with predictor X becomes as close to response Y as possible, with least squares residuals. + /// Uses an orthogonal decomposition and is therefore more numerically stable than the normal equations but also slower. + /// + /// Predictor matrix X + /// Response matrix Y + /// Best fitting vector for model parameters β + public static Matrix QR(Matrix x, Matrix y) where T : struct, IEquatable, IFormattable + { + if (x.RowCount != y.RowCount) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, x.RowCount, y.RowCount)); + } + + if (x.ColumnCount > y.RowCount) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, x.ColumnCount, y.RowCount)); + } + + return x.QR().Solve(y); + } + + /// + /// Find the model parameters β such that their linear combination with all predictor-arrays in X become as close to their response in Y as possible, with least squares residuals. + /// Uses an orthogonal decomposition and is therefore more numerically stable than the normal equations but also slower. + /// + /// List of predictor-arrays. + /// List of responses + /// True if an intercept should be added as first artificial predictor value. Default = false. + /// Best fitting list of model parameters β for each element in the predictor-arrays. + public static T[] QR(T[][] x, T[] y, bool intercept = false) where T : struct, IEquatable, IFormattable + { + var predictor = Matrix.Build.DenseOfRowArrays(x); + if (intercept) + { + predictor = predictor.InsertColumn(0, Vector.Build.Dense(predictor.RowCount, Vector.One)); + } + + if (predictor.RowCount != y.Length) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, predictor.RowCount, y.Length)); + } + + if (predictor.ColumnCount > y.Length) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, predictor.ColumnCount, y.Length)); + } + + return predictor.QR().Solve(Vector.Build.Dense(y)).ToArray(); + } + + /// + /// Find the model parameters β such that their linear combination with all predictor-arrays in X become as close to their response in Y as possible, with least squares residuals. + /// Uses an orthogonal decomposition and is therefore more numerically stable than the normal equations but also slower. + /// + /// Sequence of predictor-arrays and their response. + /// True if an intercept should be added as first artificial predictor value. Default = false. + /// Best fitting list of model parameters β for each element in the predictor-arrays. + public static T[] QR(IEnumerable> samples, bool intercept = false) where T : struct, IEquatable, IFormattable + { + var xy = samples.UnpackSinglePass(); + return QR(xy.Item1, xy.Item2, intercept); + } + + /// + /// Find the model parameters β such that X*β with predictor X becomes as close to response Y as possible, with least squares residuals. + /// Uses a singular value decomposition and is therefore more numerically stable (especially if ill-conditioned) than the normal equations or QR but also slower. + /// + /// Predictor matrix X + /// Response vector Y + /// Best fitting vector for model parameters β + public static Vector Svd(Matrix x, Vector y) where T : struct, IEquatable, IFormattable + { + if (x.RowCount != y.Count) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, x.RowCount, y.Count)); + } + + if (x.ColumnCount > y.Count) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, x.ColumnCount, y.Count)); + } + + return x.Svd().Solve(y); + } + + /// + /// Find the model parameters β such that X*β with predictor X becomes as close to response Y as possible, with least squares residuals. + /// Uses a singular value decomposition and is therefore more numerically stable (especially if ill-conditioned) than the normal equations or QR but also slower. + /// + /// Predictor matrix X + /// Response matrix Y + /// Best fitting vector for model parameters β + public static Matrix Svd(Matrix x, Matrix y) where T : struct, IEquatable, IFormattable + { + if (x.RowCount != y.RowCount) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, x.RowCount, y.RowCount)); + } + + if (x.ColumnCount > y.RowCount) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, x.ColumnCount, y.RowCount)); + } + + return x.Svd().Solve(y); + } + + /// + /// Find the model parameters β such that their linear combination with all predictor-arrays in X become as close to their response in Y as possible, with least squares residuals. + /// Uses a singular value decomposition and is therefore more numerically stable (especially if ill-conditioned) than the normal equations or QR but also slower. + /// + /// List of predictor-arrays. + /// List of responses + /// True if an intercept should be added as first artificial predictor value. Default = false. + /// Best fitting list of model parameters β for each element in the predictor-arrays. + public static T[] Svd(T[][] x, T[] y, bool intercept = false) where T : struct, IEquatable, IFormattable + { + var predictor = Matrix.Build.DenseOfRowArrays(x); + if (intercept) + { + predictor = predictor.InsertColumn(0, Vector.Build.Dense(predictor.RowCount, Vector.One)); + } + + if (predictor.RowCount != y.Length) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, predictor.RowCount, y.Length)); + } + + if (predictor.ColumnCount > y.Length) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, predictor.ColumnCount, y.Length)); + } + + return predictor.Svd().Solve(Vector.Build.Dense(y)).ToArray(); + } + + /// + /// Find the model parameters β such that their linear combination with all predictor-arrays in X become as close to their response in Y as possible, with least squares residuals. + /// Uses a singular value decomposition and is therefore more numerically stable (especially if ill-conditioned) than the normal equations or QR but also slower. + /// + /// Sequence of predictor-arrays and their response. + /// True if an intercept should be added as first artificial predictor value. Default = false. + /// Best fitting list of model parameters β for each element in the predictor-arrays. + public static T[] Svd(IEnumerable> samples, bool intercept = false) where T : struct, IEquatable, IFormattable + { + var xy = samples.UnpackSinglePass(); + return Svd(xy.Item1, xy.Item2, intercept); + } +} diff --git a/MathNet.Numerics/LinearRegression/Options.cs b/MathNet.Numerics/LinearRegression/Options.cs new file mode 100644 index 0000000..ae826fd --- /dev/null +++ b/MathNet.Numerics/LinearRegression/Options.cs @@ -0,0 +1,37 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.LinearRegression; + +public enum DirectRegressionMethod +{ + NormalEquations = 0, + QR, + Svd +} diff --git a/MathNet.Numerics/LinearRegression/SimpleRegression.cs b/MathNet.Numerics/LinearRegression/SimpleRegression.cs new file mode 100644 index 0000000..7ab5ace --- /dev/null +++ b/MathNet.Numerics/LinearRegression/SimpleRegression.cs @@ -0,0 +1,145 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearRegression; + +public static class SimpleRegression +{ + /// + /// Least-Squares fitting the points (x,y) to a line y : x -> a+b*x, + /// returning its best fitting parameters as (a, b) tuple, + /// where a is the intercept and b the slope. + /// + /// Predictor (independent) + /// Response (dependent) + public static Tuple Fit(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, x.Length, y.Length)); + } + + if (x.Length <= 1) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, 2, x.Length)); + } + + // First Pass: Mean (Less robust but faster than ArrayStatistics.Mean) + double mx = 0.0; + double my = 0.0; + for (int i = 0; i < x.Length; i++) + { + mx += x[i]; + my += y[i]; + } + + mx /= x.Length; + my /= y.Length; + + // Second Pass: Covariance/Variance + double covariance = 0.0; + double variance = 0.0; + for (int i = 0; i < x.Length; i++) + { + double diff = x[i] - mx; + covariance += diff * (y[i] - my); + variance += diff * diff; + } + + var b = covariance / variance; + return new Tuple(my - b * mx, b); + } + + /// + /// Least-Squares fitting the points (x,y) to a line y : x -> a+b*x, + /// returning its best fitting parameters as (a, b) tuple, + /// where a is the intercept and b the slope. + /// + /// Predictor-Response samples as tuples + public static Tuple Fit(IEnumerable> samples) + { + var xy = samples.UnpackSinglePass(); + return Fit(xy.Item1, xy.Item2); + } + + /// + /// Least-Squares fitting the points (x,y) to a line y : x -> b*x, + /// returning its best fitting parameter b, + /// where the intercept is zero and b the slope. + /// + /// Predictor (independent) + /// Response (dependent) + public static double FitThroughOrigin(double[] x, double[] y) + { + if (x.Length != y.Length) + { + throw new ArgumentException(string.Format(Resources.SampleVectorsSameLength, x.Length, y.Length)); + } + + if (x.Length <= 1) + { + throw new ArgumentException(string.Format(Resources.RegressionNotEnoughSamples, 2, x.Length)); + } + + double mxy = 0.0; + double mxx = 0.0; + for (int i = 0; i < x.Length; i++) + { + mxx += x[i] * x[i]; + mxy += x[i] * y[i]; + } + + return mxy / mxx; + } + + /// + /// Least-Squares fitting the points (x,y) to a line y : x -> b*x, + /// returning its best fitting parameter b, + /// where the intercept is zero and b the slope. + /// + /// Predictor-Response samples as tuples + public static double FitThroughOrigin(IEnumerable> samples) + { + double mxy = 0.0; + double mxx = 0.0; + + foreach (var sample in samples) + { + mxx += sample.Item1 * sample.Item1; + mxy += sample.Item1 * sample.Item2; + } + + return mxy / mxx; + } +} diff --git a/MathNet.Numerics/LinearRegression/Util.cs b/MathNet.Numerics/LinearRegression/Util.cs new file mode 100644 index 0000000..4bc6df3 --- /dev/null +++ b/MathNet.Numerics/LinearRegression/Util.cs @@ -0,0 +1,50 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearRegression; + +internal static class Util +{ + public static Tuple UnpackSinglePass(this IEnumerable> samples) + { + var u = new List(); + var v = new List(); + + foreach (var tuple in samples) + { + u.Add(tuple.Item1); + v.Add(tuple.Item2); + } + + return new Tuple(u.ToArray(), v.ToArray()); + } +} diff --git a/MathNet.Numerics/LinearRegression/WeightedRegression.cs b/MathNet.Numerics/LinearRegression/WeightedRegression.cs new file mode 100644 index 0000000..c64f5f2 --- /dev/null +++ b/MathNet.Numerics/LinearRegression/WeightedRegression.cs @@ -0,0 +1,130 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.LinearRegression; + +public static class WeightedRegression +{ + /// + /// Weighted Linear Regression using normal equations. + /// + /// Predictor matrix X + /// Response vector Y + /// Weight matrix W, usually diagonal with an entry for each predictor (row). + public static Vector Weighted(Matrix x, Vector y, Matrix w) where T : struct, IEquatable, IFormattable + { + return x.TransposeThisAndMultiply(w * x).Cholesky().Solve(x.TransposeThisAndMultiply(w * y)); + } + + /// + /// Weighted Linear Regression using normal equations. + /// + /// Predictor matrix X + /// Response matrix Y + /// Weight matrix W, usually diagonal with an entry for each predictor (row). + public static Matrix Weighted(Matrix x, Matrix y, Matrix w) where T : struct, IEquatable, IFormattable + { + return x.TransposeThisAndMultiply(w * x).Cholesky().Solve(x.TransposeThisAndMultiply(w * y)); + } + + /// + /// Weighted Linear Regression using normal equations. + /// + /// Predictor matrix X + /// Response vector Y + /// Weight matrix W, usually diagonal with an entry for each predictor (row). + /// True if an intercept should be added as first artificial predictor value. Default = false. + public static T[] Weighted(T[][] x, T[] y, T[] w, bool intercept = false) where T : struct, IEquatable, IFormattable + { + var predictor = Matrix.Build.DenseOfRowArrays(x); + if (intercept) + { + predictor = predictor.InsertColumn(0, Vector.Build.Dense(predictor.RowCount, Vector.One)); + } + + var response = Vector.Build.Dense(y); + var weights = Matrix.Build.Diagonal(w); + return predictor.TransposeThisAndMultiply(weights * predictor).Cholesky().Solve(predictor.TransposeThisAndMultiply(weights * response)).ToArray(); + } + + /// + /// Weighted Linear Regression using normal equations. + /// + /// List of sample vectors (predictor) together with their response. + /// List of weights, one for each sample. + /// True if an intercept should be added as first artificial predictor value. Default = false. + public static T[] Weighted(IEnumerable> samples, T[] weights, bool intercept = false) where T : struct, IEquatable, IFormattable + { + var xy = samples.UnpackSinglePass(); + return Weighted(xy.Item1, xy.Item2, weights, intercept); + } + + /// + /// Locally-Weighted Linear Regression using normal equations. + /// + [Obsolete("Warning: This function is here to stay but its signature will likely change. Opting out from semantic versioning.")] + public static Vector Local(Matrix x, Vector y, Vector t, double radius, Func kernel) where T : struct, IEquatable, IFormattable + { + // TODO: Weird kernel definition + var w = Matrix.Build.Dense(x.RowCount, x.RowCount); + for (int i = 0; i < x.RowCount; i++) + { + w.At(i, i, kernel(Distance.Euclidean(t, x.Row(i)) / radius)); + } + + return Weighted(x, y, w); + } + + /// + /// Locally-Weighted Linear Regression using normal equations. + /// + [Obsolete("Warning: This function is here to stay but its signature will likely change. Opting out from semantic versioning.")] + public static Matrix Local(Matrix x, Matrix y, Vector t, double radius, Func kernel) where T : struct, IEquatable, IFormattable + { + // TODO: Weird kernel definition + var w = Matrix.Build.Dense(x.RowCount, x.RowCount); + for (int i = 0; i < x.RowCount; i++) + { + w.At(i, i, kernel(Distance.Euclidean(t, x.Row(i)) / radius)); + } + + return Weighted(x, y, w); + } + + [Obsolete("Warning: This function is here to stay but will likely be refactored and/or moved to another place. Opting out from semantic versioning.")] + public static double GaussianKernel(double normalizedDistance) + { + return Math.Exp(-0.5 * normalizedDistance * normalizedDistance); + } +} diff --git a/MathNet.Numerics/MathNet.Numerics.csproj b/MathNet.Numerics/MathNet.Numerics.csproj new file mode 100644 index 0000000..9bda912 --- /dev/null +++ b/MathNet.Numerics/MathNet.Numerics.csproj @@ -0,0 +1,44 @@ + + + + netstandard2.1 + disable + latest + Library + MathNet.Numerics + MathNet.Numerics + latest + false + true + false + true + false + true + false + true + true + 1701;1702;1705;1591;1573 + true + AnyCPU;x64 + + + + + bin\ + false + false + false + + + + + en-US + + false + + + + en-US + + + diff --git a/MathNet.Numerics/OdeSolvers/AdamsBashforth.cs b/MathNet.Numerics/OdeSolvers/AdamsBashforth.cs new file mode 100644 index 0000000..a1198b0 --- /dev/null +++ b/MathNet.Numerics/OdeSolvers/AdamsBashforth.cs @@ -0,0 +1,171 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +using System; + +namespace MathNet.Numerics.OdeSolvers; + +public static class AdamsBashforth +{ + /// + /// First Order AB method(same as Forward Euler) + /// + /// Initial value + /// Start Time + /// End Time + /// Size of output array(the larger, the finer) + /// ode model + /// approximation with size N + public static double[] FirstOrder(double y0, double start, double end, int N, Func f) + { + double dt = (end - start) / (N - 1); + double t = start; + double[] y = new double[N]; + y[0] = y0; + for (int i = 1; i < N; i++) + { + y[i] = y0 + dt * f(t, y0); + t += dt; + y0 = y[i]; + } + + return y; + } + + /// + /// Second Order AB Method + /// + /// Initial value 1 + /// Start Time + /// End Time + /// Size of output array(the larger, the finer) + /// ode model + /// approximation with size N + public static double[] SecondOrder(double y0, double start, double end, int N, Func f) + { + double dt = (end - start) / (N - 1); + double t = start; + double[] y = new double[N]; + + double k1 = f(t, y0); + double k2 = f(t + dt, y0 + dt * k1); + double y1 = y0 + 0.5 * dt * (k1 + k2); + + y[0] = y0; + y[1] = y1; + for (int i = 2; i < N; i++) + { + y[i] = y1 + dt * (1.5 * f(t + dt, y1) - 0.5 * f(t, y0)); + t += dt; + y0 = y[i - 1]; + y1 = y[i]; + } + + return y; + } + + /// + /// Third Order AB Method + /// + /// Initial value 1 + /// Start Time + /// End Time + /// Size of output array(the larger, the finer) + /// ode model + /// approximation with size N + public static double[] ThirdOrder(double y0, double start, double end, int N, Func f) + { + double dt = (end - start) / (N - 1); + double t = start; + double[] y = new double[N]; + + double k1 = 0; + double k2 = 0; + double k3 = 0; + double k4 = 0; + y[0] = y0; + for (int i = 1; i < 3; i++) + { + k1 = dt * f(t, y0); + k2 = dt * f(t + dt / 2, y0 + k1 / 2); + k3 = dt * f(t + dt / 2, y0 + k2 / 2); + k4 = dt * f(t + dt, y0 + k3); + y[i] = y0 + (k1 + 2 * k2 + 2 * k3 + k4) / 6; + t += dt; + y0 = y[i]; + } + + for (int i = 3; i < N; i++) + { + y[i] = y[i - 1] + dt * (23 * f(t, y[i - 1]) - 16 * f(t - dt, y[i - 2]) + 5 * f(t - 2 * dt, y[i - 3])) / 12.0; + t += dt; + } + + return y; + } + + /// + /// Fourth Order AB Method + /// + /// Initial value 1 + /// Start Time + /// End Time + /// Size of output array(the larger, the finer) + /// ode model + /// approximation with size N + public static double[] FourthOrder(double y0, double start, double end, int N, Func f) + { + double dt = (end - start) / (N - 1); + double t = start; + double[] y = new double[N]; + + double k1 = 0; + double k2 = 0; + double k3 = 0; + double k4 = 0; + y[0] = y0; + for (int i = 1; i < 4; i++) + { + k1 = dt * f(t, y0); + k2 = dt * f(t + dt / 2, y0 + k1 / 2); + k3 = dt * f(t + dt / 2, y0 + k2 / 2); + k4 = dt * f(t + dt, y0 + k3); + y[i] = y0 + (k1 + 2 * k2 + 2 * k3 + k4) / 6; + t += dt; + y0 = y[i]; + } + + for (int i = 4; i < N; i++) + { + y[i] = y[i - 1] + dt * (55 * f(t, y[i - 1]) - 59 * f(t - dt, y[i - 2]) + 37 * f(t - 2 * dt, y[i - 3]) - 9 * f(t - 3 * dt, y[i - 4])) / 24.0; + t += dt; + } + + return y; + } +} \ No newline at end of file diff --git a/MathNet.Numerics/OdeSolvers/RungeKutta.cs b/MathNet.Numerics/OdeSolvers/RungeKutta.cs new file mode 100644 index 0000000..4555741 --- /dev/null +++ b/MathNet.Numerics/OdeSolvers/RungeKutta.cs @@ -0,0 +1,160 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.OdeSolvers; + +/// +/// ODE Solver Algorithms +/// +public static class RungeKutta +{ + /// + /// Second Order Runge-Kutta method + /// + /// initial value + /// start time + /// end time + /// Size of output array(the larger, the finer) + /// ode function + /// approximations + public static double[] SecondOrder(double y0, double start, double end, int N, Func f) + { + double dt = (end - start) / (N - 1); + double k1 = 0; + double k2 = 0; + double t = start; + double[] y = new double[N]; + y[0] = y0; + for (int i = 1; i < N; i++) + { + k1 = f(t, y0); + k2 = f(t + dt, y0 + k1 * dt); + y[i] = y0 + dt * 0.5 * (k1 + k2); + t += dt; + y0 = y[i]; + } + + return y; + } + + /// + /// Fourth Order Runge-Kutta method + /// + /// initial value + /// start time + /// end time + /// Size of output array(the larger, the finer) + /// ode function + /// approximations + public static double[] FourthOrder(double y0, double start, double end, int N, Func f) + { + double dt = (end - start) / (N - 1); + double k1 = 0; + double k2 = 0; + double k3 = 0; + double k4 = 0; + double t = start; + double[] y = new double[N]; + y[0] = y0; + for (int i = 1; i < N; i++) + { + k1 = f(t, y0); + k2 = f(t + dt / 2, y0 + k1 * dt / 2); + k3 = f(t + dt / 2, y0 + k2 * dt / 2); + k4 = f(t + dt, y0 + k3 * dt); + y[i] = y0 + dt / 6 * (k1 + 2 * k2 + 2 * k3 + k4); + t += dt; + y0 = y[i]; + } + + return y; + } + + /// + /// Second Order Runge-Kutta to solve ODE SYSTEM + /// + /// initial vector + /// start time + /// end time + /// Size of output array(the larger, the finer) + /// ode function + /// approximations + public static Vector[] SecondOrder(Vector y0, double start, double end, int N, Func, Vector> f) + { + double dt = (end - start) / (N - 1); + Vector k1, k2; + Vector[] y = new Vector[N]; + double t = start; + y[0] = y0; + for (int i = 1; i < N; i++) + { + k1 = f(t, y0); + k2 = f(t, y0 + k1 * dt); + y[i] = y0 + dt * 0.5 * (k1 + k2); + t += dt; + y0 = y[i]; + } + + return y; + } + + /// + /// Fourth Order Runge-Kutta to solve ODE SYSTEM + /// + /// initial vector + /// start time + /// end time + /// Size of output array(the larger, the finer) + /// ode function + /// approximations + public static Vector[] FourthOrder(Vector y0, double start, double end, int N, Func, Vector> f) + { + double dt = (end - start) / (N - 1); + Vector k1, k2, k3, k4; + Vector[] y = new Vector[N]; + double t = start; + y[0] = y0; + for (int i = 1; i < N; i++) + { + k1 = f(t, y0); + k2 = f(t + dt / 2, y0 + k1 * dt / 2); + k3 = f(t + dt / 2, y0 + k2 * dt / 2); + k4 = f(t + dt, y0 + k3 * dt); + y[i] = y0 + dt / 6 * (k1 + 2 * k2 + 2 * k3 + k4); + t += dt; + y0 = y[i]; + } + + return y; + } +} diff --git a/MathNet.Numerics/Optimization/BfgsBMinimizer.cs b/MathNet.Numerics/Optimization/BfgsBMinimizer.cs new file mode 100644 index 0000000..d4b2c38 --- /dev/null +++ b/MathNet.Numerics/Optimization/BfgsBMinimizer.cs @@ -0,0 +1,312 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Double; +using MathNet.Numerics.Optimization.LineSearch; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Optimization; + +/// +/// Broyden–Fletcher–Goldfarb–Shanno Bounded (BFGS-B) algorithm is an iterative method for solving box-constrained nonlinear optimization problems +/// http://www.ece.northwestern.edu/~nocedal/PSfiles/limited.ps.gz +/// +public class BfgsBMinimizer : BfgsMinimizerBase +{ + public BfgsBMinimizer(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int maximumIterations = 1000) + : base(gradientTolerance, parameterTolerance, functionProgressTolerance, maximumIterations) + { + } + + /// + /// Find the minimum of the objective function given lower and upper bounds + /// + /// The objective function, must support a gradient + /// The lower bound + /// The upper bound + /// The initial guess + /// The MinimizationResult which contains the minimum and the ExitCondition + public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector lowerBound, Vector upperBound, Vector initialGuess) + { + _lowerBound = lowerBound; + _upperBound = upperBound; + if (!objective.IsGradientSupported) + throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for BFGS minimization."); + + // Check that dimensions match + if (lowerBound.Count != upperBound.Count || lowerBound.Count != initialGuess.Count) + throw new ArgumentException("Dimensions of bounds and/or initial guess do not match."); + + // Check that initial guess is feasible + for (int ii = 0; ii < initialGuess.Count; ++ii) + if (initialGuess[ii] < lowerBound[ii] || initialGuess[ii] > upperBound[ii]) + throw new ArgumentException("Initial guess is not in the feasible region"); + + objective.EvaluateAt(initialGuess); + ValidateGradientAndObjective(objective); + + // Check that we're not already done + var currentExitCondition = ExitCriteriaSatisfied(objective, null, 0); + if (currentExitCondition != ExitCondition.None) + return new MinimizationResult(objective, 0, currentExitCondition); + + // Set up line search algorithm + var lineSearcher = new StrongWolfeLineSearch(1e-4, 0.9, Math.Max(ParameterTolerance, 1e-5), maxIterations: 1000); + + // Declare state variables + Vector reducedSolution1, reducedGradient, reducedInitialPoint, reducedCauchyPoint, solution1; + Matrix reducedHessian; + List reducedMap; + + // First step + var pseudoHessian = CreateMatrix.DiagonalIdentity(initialGuess.Count); + + // Determine active set + var gradientProjectionResult = QuadraticGradientProjectionSearch.Search(objective.Point, objective.Gradient, pseudoHessian, lowerBound, upperBound); + var cauchyPoint = gradientProjectionResult.CauchyPoint; + var fixedCount = gradientProjectionResult.FixedCount; + var isFixed = gradientProjectionResult.IsFixed; + var freeCount = lowerBound.Count - fixedCount; + + if (freeCount > 0) + { + reducedGradient = new DenseVector(freeCount); + reducedHessian = new DenseMatrix(freeCount, freeCount); + reducedMap = new List(freeCount); + reducedInitialPoint = new DenseVector(freeCount); + reducedCauchyPoint = new DenseVector(freeCount); + + CreateReducedData(objective.Point, cauchyPoint, isFixed, lowerBound, upperBound, objective.Gradient, pseudoHessian, reducedInitialPoint, reducedCauchyPoint, reducedGradient, reducedHessian, reducedMap); + + // Determine search direction and maximum step size + reducedSolution1 = reducedInitialPoint + reducedHessian.Cholesky().Solve(-reducedGradient); + + solution1 = ReducedToFull(reducedMap, reducedSolution1, cauchyPoint); + } + else + { + solution1 = cauchyPoint; + } + + var directionFromCauchy = solution1 - cauchyPoint; + var maxStepFromCauchyPoint = FindMaxStep(cauchyPoint, directionFromCauchy, lowerBound, upperBound); + + var solution2 = cauchyPoint + Math.Min(maxStepFromCauchyPoint, 1.0) * directionFromCauchy; + + var lineSearchDirection = solution2 - objective.Point; + var maxLineSearchStep = FindMaxStep(objective.Point, lineSearchDirection, lowerBound, upperBound); + var estStepSize = -objective.Gradient * lineSearchDirection / (lineSearchDirection * pseudoHessian * lineSearchDirection); + + var startingStepSize = Math.Min(Math.Max(estStepSize, 1.0), maxLineSearchStep); + + // Line search + LineSearchResult lineSearchResult; + try + { + lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, startingStepSize, upperBound: maxLineSearchStep); + } + catch (Exception e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + var previousPoint = objective.Fork(); + var candidatePoint = lineSearchResult.FunctionInfoAtMinimum; + ValidateGradientAndObjective(candidatePoint); + + // Check that we're not done + currentExitCondition = ExitCriteriaSatisfied(candidatePoint, previousPoint, 0); + if (currentExitCondition != ExitCondition.None) + return new MinimizationResult(candidatePoint, 0, currentExitCondition); + + var gradient = candidatePoint.Gradient; + var step = candidatePoint.Point - initialGuess; + + // Subsequent steps + int totalLineSearchSteps = lineSearchResult.Iterations; + int iterationsWithNontrivialLineSearch = lineSearchResult.Iterations > 0 ? 0 : 1; + + int iterations = DoBfgsUpdate(ref currentExitCondition, lineSearcher, ref pseudoHessian, ref lineSearchDirection, ref previousPoint, ref lineSearchResult, ref candidatePoint, ref step, ref totalLineSearchSteps, ref iterationsWithNontrivialLineSearch); + + if (iterations == MaximumIterations && currentExitCondition == ExitCondition.None) + throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", MaximumIterations)); + + return new MinimizationWithLineSearchResult(candidatePoint, iterations, currentExitCondition, totalLineSearchSteps, iterationsWithNontrivialLineSearch); + } + + protected override Vector CalculateSearchDirection(ref Matrix pseudoHessian, + out double maxLineSearchStep, + out double startingStepSize, + IObjectiveFunction previousPoint, + IObjectiveFunction candidatePoint, + Vector step) + { + Vector lineSearchDirection; + var y = candidatePoint.Gradient - previousPoint.Gradient; + + double sy = step * y; + if (sy > 0.0) // only do update if it will create a positive definite matrix + { + double sts = step * step; + + var Hs = pseudoHessian * step; + var sHs = step * pseudoHessian * step; + pseudoHessian = pseudoHessian + y.OuterProduct(y) * (1.0 / sy) - Hs.OuterProduct(Hs) * (1.0 / sHs); + } + else + { + //pseudo_hessian = LinearAlgebra.Double.DiagonalMatrix.Identity(initial_guess.Count); + } + + // Determine active set + var gradientProjectionResult = QuadraticGradientProjectionSearch.Search(candidatePoint.Point, candidatePoint.Gradient, pseudoHessian, _lowerBound, _upperBound); + var cauchyPoint = gradientProjectionResult.CauchyPoint; + var fixedCount = gradientProjectionResult.FixedCount; + var isFixed = gradientProjectionResult.IsFixed; + var freeCount = _lowerBound.Count - fixedCount; + Vector solution1; + if (freeCount > 0) + { + var reducedGradient = new DenseVector(freeCount); + var reducedHessian = new DenseMatrix(freeCount, freeCount); + var reducedMap = new List(freeCount); + var reducedInitialPoint = new DenseVector(freeCount); + var reducedCauchyPoint = new DenseVector(freeCount); + + CreateReducedData(candidatePoint.Point, cauchyPoint, isFixed, _lowerBound, _upperBound, candidatePoint.Gradient, pseudoHessian, reducedInitialPoint, reducedCauchyPoint, reducedGradient, reducedHessian, reducedMap); + + // Determine search direction and maximum step size + Vector reducedSolution1 = reducedInitialPoint + reducedHessian.Cholesky().Solve(-reducedGradient); + + solution1 = ReducedToFull(reducedMap, reducedSolution1, cauchyPoint); + } + else + { + solution1 = cauchyPoint; + } + + var directionFromCauchy = solution1 - cauchyPoint; + var maxStepFromCauchyPoint = FindMaxStep(cauchyPoint, directionFromCauchy, _lowerBound, _upperBound); + + var solution2 = cauchyPoint + Math.Min(maxStepFromCauchyPoint, 1.0) * directionFromCauchy; + + lineSearchDirection = solution2 - candidatePoint.Point; + maxLineSearchStep = FindMaxStep(candidatePoint.Point, lineSearchDirection, _lowerBound, _upperBound); + + if (maxLineSearchStep == 0.0) + { + lineSearchDirection = cauchyPoint - candidatePoint.Point; + maxLineSearchStep = FindMaxStep(candidatePoint.Point, lineSearchDirection, _lowerBound, _upperBound); + } + + double estStepSize = -candidatePoint.Gradient * lineSearchDirection / (lineSearchDirection * pseudoHessian * lineSearchDirection); + + startingStepSize = Math.Min(Math.Max(estStepSize, 1.0), maxLineSearchStep); + return lineSearchDirection; + } + + static Vector ReducedToFull(List reducedMap, Vector reducedVector, Vector fullVector) + { + var output = fullVector.Clone(); + for (int ii = 0; ii < reducedMap.Count; ++ii) + output[reducedMap[ii]] = reducedVector[ii]; + return output; + } + + Vector _lowerBound; + Vector _upperBound; + + static double FindMaxStep(Vector startingPoint, Vector searchDirection, Vector lowerBound, Vector upperBound) + { + double maxStep = Double.PositiveInfinity; + for (int ii = 0; ii < startingPoint.Count; ++ii) + { + double paramMaxStep; + if (searchDirection[ii] > 0) + paramMaxStep = (upperBound[ii] - startingPoint[ii]) / searchDirection[ii]; + else if (searchDirection[ii] < 0) + paramMaxStep = (startingPoint[ii] - lowerBound[ii]) / -searchDirection[ii]; + else + paramMaxStep = Double.PositiveInfinity; + + if (paramMaxStep < maxStep) + maxStep = paramMaxStep; + } + + return maxStep; + } + + static void CreateReducedData(Vector initialPoint, Vector cauchyPoint, List isFixed, Vector lowerBound, Vector upperBound, Vector gradient, Matrix pseudoHessian, Vector reducedInitialPoint, Vector reducedCauchyPoint, Vector reducedGradient, Matrix reducedHessian, List reducedMap) + { + int ll = 0; + for (int ii = 0; ii < lowerBound.Count; ++ii) + { + if (!isFixed[ii]) + { + // hessian + int mm = 0; + for (int jj = 0; jj < lowerBound.Count; ++jj) + { + if (!isFixed[jj]) + { + reducedHessian[ll, mm++] = pseudoHessian[ii, jj]; + } + } + + // gradient + reducedInitialPoint[ll] = initialPoint[ii]; + reducedCauchyPoint[ll] = cauchyPoint[ii]; + reducedGradient[ll] = gradient[ii]; + ll += 1; + reducedMap.Add(ii); + + } + } + } + + protected override double GetProjectedGradient(IObjectiveFunctionEvaluation candidatePoint, int ii) + { + double projectedGradient; + bool atLowerBound = candidatePoint.Point[ii] - _lowerBound[ii] < VerySmall; + bool atUpperBound = _upperBound[ii] - candidatePoint.Point[ii] < VerySmall; + + if (atLowerBound && atUpperBound) + projectedGradient = 0.0; + else if (atLowerBound) + projectedGradient = Math.Min(candidatePoint.Gradient[ii], 0.0); + else if (atUpperBound) + projectedGradient = Math.Max(candidatePoint.Gradient[ii], 0.0); + else + projectedGradient = base.GetProjectedGradient(candidatePoint, ii); + return projectedGradient; + } +} diff --git a/MathNet.Numerics/Optimization/BfgsMinimizer.cs b/MathNet.Numerics/Optimization/BfgsMinimizer.cs new file mode 100644 index 0000000..c8fdcb3 --- /dev/null +++ b/MathNet.Numerics/Optimization/BfgsMinimizer.cs @@ -0,0 +1,141 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization.LineSearch; + +using System; + +namespace MathNet.Numerics.Optimization; + +/// +/// Broyden–Fletcher–Goldfarb–Shanno (BFGS) algorithm is an iterative method for solving unconstrained nonlinear optimization problems +/// +public class BfgsMinimizer : BfgsMinimizerBase, IUnconstrainedMinimizer +{ + /// + /// Creates BFGS minimizer + /// + /// The gradient tolerance + /// The parameter tolerance + /// The function progress tolerance + /// The maximum number of iterations + public BfgsMinimizer(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int maximumIterations = 1000) + : base(gradientTolerance, parameterTolerance, functionProgressTolerance, maximumIterations) + { + } + + /// + /// Find the minimum of the objective function given lower and upper bounds + /// + /// The objective function, must support a gradient + /// The initial guess + /// The MinimizationResult which contains the minimum and the ExitCondition + public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector initialGuess) + { + if (!objective.IsGradientSupported) + throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for BFGS minimization."); + + objective.EvaluateAt(initialGuess); + ValidateGradientAndObjective(objective); + + // Check that we're not already done + ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, 0); + if (currentExitCondition != ExitCondition.None) + return new MinimizationResult(objective, 0, currentExitCondition); + + // Set up line search algorithm + var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.9, Math.Max(ParameterTolerance, 1e-10), 1000); + + // First step + var inversePseudoHessian = CreateMatrix.DenseIdentity(initialGuess.Count); + var lineSearchDirection = -objective.Gradient; + var stepSize = 100 * GradientTolerance / (lineSearchDirection * lineSearchDirection); + + var previousPoint = objective; + + LineSearchResult lineSearchResult; + try + { + lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, stepSize); + } + catch (OptimizationException e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + catch (ArgumentException e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + var candidate = lineSearchResult.FunctionInfoAtMinimum; + ValidateGradientAndObjective(candidate); + + var gradient = candidate.Gradient; + var step = candidate.Point - initialGuess; + + // Subsequent steps + Matrix I = CreateMatrix.DiagonalIdentity(initialGuess.Count); + int iterations; + int totalLineSearchSteps = lineSearchResult.Iterations; + int iterationsWithNontrivialLineSearch = lineSearchResult.Iterations > 0 ? 0 : 1; + iterations = DoBfgsUpdate(ref currentExitCondition, lineSearcher, ref inversePseudoHessian, ref lineSearchDirection, ref previousPoint, ref lineSearchResult, ref candidate, ref step, ref totalLineSearchSteps, ref iterationsWithNontrivialLineSearch); + + if (iterations == MaximumIterations && currentExitCondition == ExitCondition.None) + throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", MaximumIterations)); + + return new MinimizationWithLineSearchResult(candidate, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch); + } + + protected override Vector CalculateSearchDirection(ref Matrix inversePseudoHessian, + out double maxLineSearchStep, + out double startingStepSize, + IObjectiveFunction previousPoint, + IObjectiveFunction candidate, + Vector step) + { + startingStepSize = 1.0; + maxLineSearchStep = double.PositiveInfinity; + + Vector lineSearchDirection; + var y = candidate.Gradient - previousPoint.Gradient; + + double sy = step * y; + inversePseudoHessian = inversePseudoHessian + ((sy + y * inversePseudoHessian * y) / Math.Pow(sy, 2.0)) * step.OuterProduct(step) - ((inversePseudoHessian * y.ToColumnMatrix()) * step.ToRowMatrix() + step.ToColumnMatrix() * (y.ToRowMatrix() * inversePseudoHessian)) * (1.0 / sy); + lineSearchDirection = -inversePseudoHessian * candidate.Gradient; + + if (lineSearchDirection * candidate.Gradient >= 0.0) + { + lineSearchDirection = -candidate.Gradient; + inversePseudoHessian = CreateMatrix.DenseIdentity(candidate.Point.Count); + } + + return lineSearchDirection; + } +} diff --git a/MathNet.Numerics/Optimization/BfgsMinimizerBase.cs b/MathNet.Numerics/Optimization/BfgsMinimizerBase.cs new file mode 100644 index 0000000..7c075cb --- /dev/null +++ b/MathNet.Numerics/Optimization/BfgsMinimizerBase.cs @@ -0,0 +1,86 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization.LineSearch; + +using System; + +namespace MathNet.Numerics.Optimization; + +public abstract class BfgsMinimizerBase : MinimizerBase +{ + /// + /// + /// Creates a base class for BFGS minimization + /// + protected BfgsMinimizerBase(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int maximumIterations) : base(gradientTolerance, parameterTolerance, functionProgressTolerance, maximumIterations) + { + } + + protected int DoBfgsUpdate(ref ExitCondition currentExitCondition, WolfeLineSearch lineSearcher, ref Matrix inversePseudoHessian, ref Vector lineSearchDirection, ref IObjectiveFunction previousPoint, ref LineSearchResult lineSearchResult, ref IObjectiveFunction candidate, ref Vector step, ref int totalLineSearchSteps, ref int iterationsWithNontrivialLineSearch) + { + int iterations; + for (iterations = 1; iterations < MaximumIterations; ++iterations) + { + double startingStepSize; + double maxLineSearchStep; + lineSearchDirection = CalculateSearchDirection(ref inversePseudoHessian, out maxLineSearchStep, out startingStepSize, previousPoint, candidate, step); + + try + { + lineSearchResult = lineSearcher.FindConformingStep(candidate, lineSearchDirection, startingStepSize, maxLineSearchStep); + } + catch (Exception e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + iterationsWithNontrivialLineSearch += lineSearchResult.Iterations > 0 ? 1 : 0; + totalLineSearchSteps += lineSearchResult.Iterations; + + step = lineSearchResult.FunctionInfoAtMinimum.Point - candidate.Point; + previousPoint = candidate; + candidate = lineSearchResult.FunctionInfoAtMinimum; + + currentExitCondition = ExitCriteriaSatisfied(candidate, previousPoint, iterations); + if (currentExitCondition != ExitCondition.None) + break; + } + + return iterations; + } + + protected abstract Vector CalculateSearchDirection(ref Matrix inversePseudoHessian, + out double maxLineSearchStep, + out double startingStepSize, + IObjectiveFunction previousPoint, + IObjectiveFunction candidate, + Vector step); +} diff --git a/MathNet.Numerics/Optimization/BfgsSolver.cs b/MathNet.Numerics/Optimization/BfgsSolver.cs new file mode 100644 index 0000000..c58184e --- /dev/null +++ b/MathNet.Numerics/Optimization/BfgsSolver.cs @@ -0,0 +1,109 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Double; +using MathNet.Numerics.Optimization.LineSearch; + +using System; + +namespace MathNet.Numerics.Optimization; + +/// +/// Broyden-Fletcher-Goldfarb-Shanno solver for finding function minima +/// See http://en.wikipedia.org/wiki/Broyden%E2%80%93Fletcher%E2%80%93Goldfarb%E2%80%93Shanno_algorithm +/// Inspired by implementation: https://github.com/PatWie/CppNumericalSolvers/blob/master/src/BfgsSolver.cpp +/// +public static class BfgsSolver +{ + private const double GradientTolerance = 1e-5; + private const int MaxIterations = 100000; + + /// + /// Finds a minimum of a function by the BFGS quasi-Newton method + /// This uses the function and it's gradient (partial derivatives in each direction) and approximates the Hessian + /// + /// An initial guess + /// Evaluates the function at a point + /// Evaluates the gradient of the function at a point + /// The minimum found + public static Vector Solve(Vector initialGuess, Func, double> functionValue, Func, Vector> functionGradient) + { + var objectiveFunction = ObjectiveFunction.Gradient(functionValue, functionGradient); + objectiveFunction.EvaluateAt(initialGuess); + + int dim = initialGuess.Count; + int iter = 0; + // H represents the approximation of the inverse hessian matrix + // it is updated via the Sherman–Morrison formula (http://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula) + Matrix H = DenseMatrix.CreateIdentity(dim); + + Vector x = initialGuess; + Vector x_old = x; + Vector grad; + WolfeLineSearch wolfeLineSearch = new WeakWolfeLineSearch(1e-4, 0.9, 1e-5, 200); + do + { + // search along the direction of the gradient + grad = objectiveFunction.Gradient; + Vector p = -1 * H * grad; + var lineSearchResult = wolfeLineSearch.FindConformingStep(objectiveFunction, p, 1.0); + double rate = lineSearchResult.FinalStep; + x = x + rate * p; + Vector grad_old = grad; + + // update the gradient + objectiveFunction.EvaluateAt(x); + grad = objectiveFunction.Gradient;// functionGradient(x); + + Vector s = x - x_old; + Vector y = grad - grad_old; + + double rho = 1.0 / (y * s); + if (iter == 0) + { + // set up an initial hessian + H = (y * s) / (y * y) * DenseMatrix.CreateIdentity(dim); + } + + var sM = s.ToColumnMatrix(); + var yM = y.ToColumnMatrix(); + + // Update the estimate of the hessian + H = H + - rho * (sM * (yM.TransposeThisAndMultiply(H)) + (H * yM).TransposeAndMultiply(sM)) + + rho * rho * (y.DotProduct(H * y) + 1.0 / rho) * (sM.TransposeAndMultiply(sM)); + x_old = x; + iter++; + } + while ((grad.InfinityNorm() > GradientTolerance) && (iter < MaxIterations)); + + return x; + } +} diff --git a/MathNet.Numerics/Optimization/ConjugateGradientMinimizer.cs b/MathNet.Numerics/Optimization/ConjugateGradientMinimizer.cs new file mode 100644 index 0000000..0b14747 --- /dev/null +++ b/MathNet.Numerics/Optimization/ConjugateGradientMinimizer.cs @@ -0,0 +1,152 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization.LineSearch; + +using System; + +namespace MathNet.Numerics.Optimization; + +public class ConjugateGradientMinimizer : IUnconstrainedMinimizer +{ + public double GradientTolerance { get; set; } + public int MaximumIterations { get; set; } + + public ConjugateGradientMinimizer(double gradientTolerance, int maximumIterations) + { + GradientTolerance = gradientTolerance; + MaximumIterations = maximumIterations; + } + + public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector initialGuess) + { + return Minimum(objective, initialGuess, GradientTolerance, MaximumIterations); + } + + public static MinimizationResult Minimum(IObjectiveFunction objective, Vector initialGuess, double gradientTolerance = 1e-8, int maxIterations = 1000) + { + if (!objective.IsGradientSupported) + { + throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for ConjugateGradient minimization."); + } + + objective.EvaluateAt(initialGuess); + var gradient = objective.Gradient; + ValidateGradient(objective); + + // Check that we're not already done + if (gradient.Norm(2.0) < gradientTolerance) + { + return new MinimizationResult(objective, 0, ExitCondition.AbsoluteGradient); + } + + // Set up line search algorithm + var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.1, 1e-4, 1000); + + // First step + var steepestDirection = -gradient; + var searchDirection = steepestDirection; + double initialStepSize = 100 * gradientTolerance / (gradient * gradient); + + LineSearchResult result; + try + { + result = lineSearcher.FindConformingStep(objective, searchDirection, initialStepSize); + } + catch (Exception e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + objective = result.FunctionInfoAtMinimum; + ValidateGradient(objective); + + double stepSize = result.FinalStep; + + // Subsequent steps + int iterations = 1; + int totalLineSearchSteps = result.Iterations; + int iterationsWithNontrivialLineSearch = result.Iterations > 0 ? 0 : 1; + int steepestDescentResets = 0; + while (objective.Gradient.Norm(2.0) >= gradientTolerance && iterations < maxIterations) + { + var previousSteepestDirection = steepestDirection; + steepestDirection = -objective.Gradient; + var searchDirectionAdjuster = Math.Max(0, steepestDirection * (steepestDirection - previousSteepestDirection) / (previousSteepestDirection * previousSteepestDirection)); + searchDirection = steepestDirection + searchDirectionAdjuster * searchDirection; + if (searchDirection * objective.Gradient >= 0) + { + searchDirection = steepestDirection; + steepestDescentResets += 1; + } + + try + { + result = lineSearcher.FindConformingStep(objective, searchDirection, stepSize); + } + catch (Exception e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + iterationsWithNontrivialLineSearch += result.Iterations == 0 ? 1 : 0; + totalLineSearchSteps += result.Iterations; + stepSize = result.FinalStep; + objective = result.FunctionInfoAtMinimum; + iterations += 1; + } + + if (iterations == maxIterations) + { + throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", maxIterations)); + } + + return new MinimizationWithLineSearchResult(objective, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch); + } + + static void ValidateGradient(IObjectiveFunctionEvaluation objective) + { + foreach (var x in objective.Gradient) + { + if (Double.IsNaN(x) || Double.IsInfinity(x)) + { + throw new EvaluationException("Non-finite gradient returned.", objective); + } + } + } + + static void ValidateObjective(IObjectiveFunctionEvaluation objective) + { + if (Double.IsNaN(objective.Value) || Double.IsInfinity(objective.Value)) + { + throw new EvaluationException("Non-finite objective function returned.", objective); + } + } +} diff --git a/MathNet.Numerics/Optimization/Exceptions.cs b/MathNet.Numerics/Optimization/Exceptions.cs new file mode 100644 index 0000000..f56d2aa --- /dev/null +++ b/MathNet.Numerics/Optimization/Exceptions.cs @@ -0,0 +1,79 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Optimization; + +public class OptimizationException : Exception +{ + public OptimizationException(string message) + : base(message) { } + + public OptimizationException(string message, Exception innerException) + : base(message, innerException) { } +} + +public class MaximumIterationsException : OptimizationException +{ + public MaximumIterationsException(string message) + : base(message) { } +} + +public class EvaluationException : OptimizationException +{ + public IObjectiveFunctionEvaluation ObjectiveFunction { get; private set; } + + public EvaluationException(string message, IObjectiveFunctionEvaluation eval) + : base(message) + { + ObjectiveFunction = eval; + } + + public EvaluationException(string message, IObjectiveFunctionEvaluation eval, Exception innerException) + : base(message, innerException) + { + ObjectiveFunction = eval; + } +} + +public class InnerOptimizationException : OptimizationException +{ + public InnerOptimizationException(string message) + : base(message) { } + + public InnerOptimizationException(string message, Exception inner_exception) + : base(message, inner_exception) { } +} + +public class IncompatibleObjectiveException : OptimizationException +{ + public IncompatibleObjectiveException(string message) + : base(message) { } +} diff --git a/MathNet.Numerics/Optimization/ExitCondition.cs b/MathNet.Numerics/Optimization/ExitCondition.cs new file mode 100644 index 0000000..251dc00 --- /dev/null +++ b/MathNet.Numerics/Optimization/ExitCondition.cs @@ -0,0 +1,46 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Optimization; + +public enum ExitCondition +{ + None, + InvalidValues, + ExceedIterations, + RelativePoints, + RelativeGradient, + LackOfProgress, + AbsoluteGradient, + WeakWolfeCriteria, + BoundTolerance, + StrongWolfeCriteria, + Converged, + ManuallyStopped +} diff --git a/MathNet.Numerics/Optimization/GoldenSectionMinimizer.cs b/MathNet.Numerics/Optimization/GoldenSectionMinimizer.cs new file mode 100644 index 0000000..235980e --- /dev/null +++ b/MathNet.Numerics/Optimization/GoldenSectionMinimizer.cs @@ -0,0 +1,148 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Optimization; + +public class GoldenSectionMinimizer +{ + public double XTolerance { get; set; } + public int MaximumIterations { get; set; } + public int MaximumExpansionSteps { get; set; } + public double LowerExpansionFactor { get; set; } + public double UpperExpansionFactor { get; set; } + + public GoldenSectionMinimizer(double xTolerance = 1e-5, int maxIterations = 1000, int maxExpansionSteps = 10, double lowerExpansionFactor = 2.0, double upperExpansionFactor = 2.0) + { + XTolerance = xTolerance; + MaximumIterations = maxIterations; + MaximumExpansionSteps = maxExpansionSteps; + LowerExpansionFactor = lowerExpansionFactor; + UpperExpansionFactor = upperExpansionFactor; + } + + public ScalarMinimizationResult FindMinimum(IScalarObjectiveFunction objective, double lowerBound, double upperBound) + { + return Minimum(objective, lowerBound, upperBound, XTolerance, MaximumIterations, MaximumExpansionSteps, LowerExpansionFactor, UpperExpansionFactor); + } + + public static ScalarMinimizationResult Minimum(IScalarObjectiveFunction objective, double lowerBound, double upperBound, double xTolerance = 1e-5, int maxIterations = 1000, int maxExpansionSteps = 10, double lowerExpansionFactor = 2.0, double upperExpansionFactor = 2.0) + { + if (upperBound <= lowerBound) + { + throw new OptimizationException("Lower bound must be lower than upper bound."); + } + + double middlePointX = lowerBound + (upperBound - lowerBound) / (1 + Constants.GoldenRatio); + IScalarObjectiveFunctionEvaluation lower = objective.Evaluate(lowerBound); + IScalarObjectiveFunctionEvaluation middle = objective.Evaluate(middlePointX); + IScalarObjectiveFunctionEvaluation upper = objective.Evaluate(upperBound); + + ValueChecker(lower.Value, lowerBound); + ValueChecker(middle.Value, middlePointX); + ValueChecker(upper.Value, upperBound); + + int expansion_steps = 0; + while ((expansion_steps < maxExpansionSteps) && (upper.Value < middle.Value || lower.Value < middle.Value)) + { + if (lower.Value < middle.Value) + { + lowerBound = 0.5 * (upperBound + lowerBound) - lowerExpansionFactor * 0.5 * (upperBound - lowerBound); + lower = objective.Evaluate(lowerBound); + } + + if (upper.Value < middle.Value) + { + upperBound = 0.5 * (upperBound + lowerBound) + upperExpansionFactor * 0.5 * (upperBound - lowerBound); + upper = objective.Evaluate(upperBound); + } + + middlePointX = lowerBound + (upperBound - lowerBound) / (1 + Constants.GoldenRatio); + middle = objective.Evaluate(middlePointX); + + expansion_steps += 1; + } + + if (upper.Value < middle.Value || lower.Value < middle.Value) + { + throw new OptimizationException("Lower and upper bounds do not necessarily bound a minimum."); + } + + int iterations = 0; + while (Math.Abs(upper.Point - lower.Point) > xTolerance && iterations < maxIterations) + { + double testX = lower.Point + (upper.Point - middle.Point); + var test = objective.Evaluate(testX); + ValueChecker(test.Value, testX); + + if (test.Point < middle.Point) + { + if (test.Value > middle.Value) + { + lower = test; + } + else + { + upper = middle; + middle = test; + } + } + else + { + if (test.Value > middle.Value) + { + upper = test; + } + else + { + lower = middle; + middle = test; + } + } + + iterations += 1; + } + + if (iterations == maxIterations) + { + throw new MaximumIterationsException("Max iterations reached."); + } + + return new ScalarMinimizationResult(middle, iterations, ExitCondition.BoundTolerance); + } + + static void ValueChecker(double value, double point) + { + if (Double.IsNaN(value) || Double.IsInfinity(value)) + { + throw new Exception("Objective function returned non-finite value."); + } + } +} diff --git a/MathNet.Numerics/Optimization/IObjectiveFunction.cs b/MathNet.Numerics/Optimization/IObjectiveFunction.cs new file mode 100644 index 0000000..abd30cc --- /dev/null +++ b/MathNet.Numerics/Optimization/IObjectiveFunction.cs @@ -0,0 +1,76 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization; + +/// +/// Objective function with a frozen evaluation that must not be changed from the outside. +/// +public interface IObjectiveFunctionEvaluation +{ + /// Create a new unevaluated and independent copy of this objective function + IObjectiveFunction CreateNew(); + + Vector Point { get; } + double Value { get; } + + bool IsGradientSupported { get; } + Vector Gradient { get; } + + bool IsHessianSupported { get; } + Matrix Hessian { get; } +} + +/// +/// Objective function with a mutable evaluation. +/// +public interface IObjectiveFunction : IObjectiveFunctionEvaluation +{ + void EvaluateAt(Vector point); + + /// Create a new independent copy of this objective function, evaluated at the same point. + IObjectiveFunction Fork(); +} + +public interface IScalarObjectiveFunctionEvaluation +{ + double Point { get; } + double Value { get; } + double Derivative { get; } + double SecondDerivative { get; } +} + +public interface IScalarObjectiveFunction +{ + bool IsDerivativeSupported { get; } + bool IsSecondDerivativeSupported { get; } + IScalarObjectiveFunctionEvaluation Evaluate(double point); +} diff --git a/MathNet.Numerics/Optimization/IObjectiveModel.cs b/MathNet.Numerics/Optimization/IObjectiveModel.cs new file mode 100644 index 0000000..a4f885b --- /dev/null +++ b/MathNet.Numerics/Optimization/IObjectiveModel.cs @@ -0,0 +1,74 @@ +using MathNet.Numerics.LinearAlgebra; + +using System.Collections.Generic; + +namespace MathNet.Numerics.Optimization; + +public interface IObjectiveModelEvaluation +{ + IObjectiveModel CreateNew(); + + /// + /// Get the y-values of the observations. + /// + Vector ObservedY { get; } + + /// + /// Get the values of the weights for the observations. + /// + Matrix Weights { get; } + + /// + /// Get the y-values of the fitted model that correspond to the independent values. + /// + Vector ModelValues { get; } + + /// + /// Get the values of the parameters. + /// + Vector Point { get; } + + /// + /// Get the residual sum of squares. + /// + double Value { get; } + + /// + /// Get the Gradient vector. G = J'(y - f(x; p)) + /// + Vector Gradient { get; } + + /// + /// Get the approximated Hessian matrix. H = J'J + /// + Matrix Hessian { get; } + + /// + /// Get the number of calls to function. + /// + int FunctionEvaluations { get; set; } + + /// + /// Get the number of calls to jacobian. + /// + int JacobianEvaluations { get; set; } + + /// + /// Get the degree of freedom. + /// + int DegreeOfFreedom { get; } + + bool IsGradientSupported { get; } + bool IsHessianSupported { get; } +} + +public interface IObjectiveModel : IObjectiveModelEvaluation +{ + void SetParameters(Vector initialGuess, List isFixed = null); + + void EvaluateAt(Vector parameters); + + IObjectiveModel Fork(); + + IObjectiveFunction ToObjectiveFunction(); +} diff --git a/MathNet.Numerics/Optimization/IUnconstrainedMinimizer.cs b/MathNet.Numerics/Optimization/IUnconstrainedMinimizer.cs new file mode 100644 index 0000000..1f4a40c --- /dev/null +++ b/MathNet.Numerics/Optimization/IUnconstrainedMinimizer.cs @@ -0,0 +1,37 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization; + +public interface IUnconstrainedMinimizer +{ + MinimizationResult FindMinimum(IObjectiveFunction objective, Vector initialGuess); +} diff --git a/MathNet.Numerics/Optimization/LevenbergMarquardtMinimizer.cs b/MathNet.Numerics/Optimization/LevenbergMarquardtMinimizer.cs new file mode 100644 index 0000000..4882daa --- /dev/null +++ b/MathNet.Numerics/Optimization/LevenbergMarquardtMinimizer.cs @@ -0,0 +1,234 @@ +using MathNet.Numerics.LinearAlgebra; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Optimization; + +public class LevenbergMarquardtMinimizer : NonlinearMinimizerBase +{ + /// + /// The scale factor for initial mu + /// + public static double InitialMu { get; set; } + + public LevenbergMarquardtMinimizer(double initialMu = 1E-3, double gradientTolerance = 1E-15, double stepTolerance = 1E-15, double functionTolerance = 1E-15, int maximumIterations = -1) + : base(gradientTolerance, stepTolerance, functionTolerance, maximumIterations) + { + InitialMu = initialMu; + } + + public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, Vector initialGuess, + Vector lowerBound = null, Vector upperBound = null, Vector scales = null, List isFixed = null) + { + return Minimum(objective, initialGuess, lowerBound, upperBound, scales, isFixed, InitialMu, GradientTolerance, StepTolerance, FunctionTolerance, MaximumIterations); + } + + public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, double[] initialGuess, + double[] lowerBound = null, double[] upperBound = null, double[] scales = null, bool[] isFixed = null) + { + if (objective == null) + throw new ArgumentNullException("objective"); + if (initialGuess == null) + throw new ArgumentNullException("initialGuess"); + + var lb = (lowerBound == null) ? null : CreateVector.Dense(lowerBound); + var ub = (upperBound == null) ? null : CreateVector.Dense(upperBound); + var sc = (scales == null) ? null : CreateVector.Dense(scales); + var fx = (isFixed == null) ? null : isFixed.ToList(); + + return Minimum(objective, CreateVector.DenseOfArray(initialGuess), lb, ub, sc, fx, InitialMu, GradientTolerance, StepTolerance, FunctionTolerance, MaximumIterations); + } + + /// + /// Non-linear least square fitting by the Levenberg-Marduardt algorithm. + /// + /// The objective function, including model, observations, and parameter bounds. + /// The initial guess values. + /// The initial damping parameter of mu. + /// The stopping threshold for infinity norm of the gradient vector. + /// The stopping threshold for L2 norm of the change of parameters. + /// The stopping threshold for L2 norm of the residuals. + /// The max iterations. + /// The result of the Levenberg-Marquardt minimization + public static NonlinearMinimizationResult Minimum(IObjectiveModel objective, Vector initialGuess, + Vector lowerBound = null, Vector upperBound = null, Vector scales = null, List isFixed = null, + double initialMu = 1E-3, double gradientTolerance = 1E-15, double stepTolerance = 1E-15, double functionTolerance = 1E-15, int maximumIterations = -1) + { + // Non-linear least square fitting by the Levenberg-Marduardt algorithm. + // + // Levenberg-Marquardt is finding the minimum of a function F(p) that is a sum of squares of nonlinear functions. + // + // For given datum pair (x, y), uncertainties σ (or weighting W = 1 / σ^2) and model function f = f(x; p), + // let's find the parameters of the model so that the sum of the quares of the deviations is minimized. + // + // F(p) = 1/2 * ∑{ Wi * (yi - f(xi; p))^2 } + // pbest = argmin F(p) + // + // We will use the following terms: + // Weighting W is the diagonal matrix and can be decomposed as LL', so L = 1/σ + // Residuals, R = L(y - f(x; p)) + // Residual sum of squares, RSS = ||R||^2 = R.DotProduct(R) + // Jacobian J = df(x; p)/dp + // Gradient g = -J'W(y − f(x; p)) = -J'LR + // Approximated Hessian H = J'WJ + // + // The Levenberg-Marquardt algorithm is summarized as follows: + // initially let μ = τ * max(diag(H)). + // repeat + // solve linear equations: (H + μI)ΔP = -g + // let ρ = (||R||^2 - ||Rnew||^2) / (Δp'(μΔp - g)). + // if ρ > ε, P = P + ΔP; μ = μ * max(1/3, 1 - (2ρ - 1)^3); ν = 2; + // otherwise μ = μ*ν; ν = 2*ν; + // + // References: + // [1]. Madsen, K., H. B. Nielsen, and O. Tingleff. + // "Methods for Non-Linear Least Squares Problems. Technical University of Denmark, 2004. Lecture notes." (2004). + // Available Online from: http://orbit.dtu.dk/files/2721358/imm3215.pdf + // [2]. Gavin, Henri. + // "The Levenberg-Marquardt method for nonlinear least squares curve-fitting problems." + // Department of Civil and Environmental Engineering, Duke University (2017): 1-19. + // Availble Online from: http://people.duke.edu/~hpgavin/ce281/lm.pdf + + if (objective == null) + throw new ArgumentNullException("objective"); + + ValidateBounds(initialGuess, lowerBound, upperBound, scales); + + objective.SetParameters(initialGuess, isFixed); + + ExitCondition exitCondition = ExitCondition.None; + + // First, calculate function values and setup variables + var P = ProjectToInternalParameters(initialGuess); // current internal parameters + var Pstep = Vector.Build.Dense(P.Count); // the change of parameters + var RSS = EvaluateFunction(objective, P); // Residual Sum of Squares = R'R + + if (maximumIterations < 0) + { + maximumIterations = 200 * (initialGuess.Count + 1); + } + + // if RSS == NaN, stop + if (double.IsNaN(RSS)) + { + exitCondition = ExitCondition.InvalidValues; + return new NonlinearMinimizationResult(objective, -1, exitCondition); + } + + // When only function evaluation is needed, set maximumIterations to zero, + if (maximumIterations == 0) + { + exitCondition = ExitCondition.ManuallyStopped; + } + + // if RSS <= fTol, stop + if (RSS <= functionTolerance) + { + exitCondition = ExitCondition.Converged; // SmallRSS + } + + // Evaluate gradient and Hessian + var jac = EvaluateJacobian(objective, P); + var Gradient = jac.Item1; // objective.Gradient; + var Hessian = jac.Item2; // objective.Hessian; + var diagonalOfHessian = Hessian.Diagonal(); // diag(H) + + // if ||g||oo <= gtol, found and stop + if (Gradient.InfinityNorm() <= gradientTolerance) + { + exitCondition = ExitCondition.RelativeGradient; + } + + if (exitCondition != ExitCondition.None) + { + return new NonlinearMinimizationResult(objective, -1, exitCondition); + } + + double mu = initialMu * diagonalOfHessian.Max(); // μ + double nu = 2; // ν + int iterations = 0; + while (iterations < maximumIterations && exitCondition == ExitCondition.None) + { + iterations++; + + while (true) + { + Hessian.SetDiagonal(Hessian.Diagonal() + mu); // hessian[i, i] = hessian[i, i] + mu; + + // solve normal equations + Pstep = Hessian.Solve(-Gradient); + + // if ||ΔP|| <= xTol * (||P|| + xTol), found and stop + if (Pstep.L2Norm() <= stepTolerance * (stepTolerance + P.DotProduct(P))) + { + exitCondition = ExitCondition.RelativePoints; + break; + } + + var Pnew = P + Pstep; // new parameters to test + // evaluate function at Pnew + var RSSnew = EvaluateFunction(objective, Pnew); + + if (double.IsNaN(RSSnew)) + { + exitCondition = ExitCondition.InvalidValues; + break; + } + + // calculate the ratio of the actual to the predicted reduction. + // ρ = (RSS - RSSnew) / (Δp'(μΔp - g)) + var predictedReduction = Pstep.DotProduct(mu * Pstep - Gradient); + var rho = (predictedReduction != 0) + ? (RSS - RSSnew) / predictedReduction + : 0; + + if (rho > 0.0) + { + // accepted + Pnew.CopyTo(P); + RSS = RSSnew; + + // update gradient and Hessian + jac = EvaluateJacobian(objective, P); + Gradient = jac.Item1; // objective.Gradient; + Hessian = jac.Item2; // objective.Hessian; + diagonalOfHessian = Hessian.Diagonal(); + + // if ||g||_oo <= gtol, found and stop + if (Gradient.InfinityNorm() <= gradientTolerance) + { + exitCondition = ExitCondition.RelativeGradient; + } + + // if ||R||^2 < fTol, found and stop + if (RSS <= functionTolerance) + { + exitCondition = ExitCondition.Converged; // SmallRSS + } + + mu = mu * Math.Max(1.0 / 3.0, 1.0 - Math.Pow(2.0 * rho - 1.0, 3)); + nu = 2; + + break; + } + else + { + // rejected, increased μ + mu = mu * nu; + nu = 2 * nu; + + Hessian.SetDiagonal(diagonalOfHessian); + } + } + } + + if (iterations >= maximumIterations) + { + exitCondition = ExitCondition.ExceedIterations; + } + + return new NonlinearMinimizationResult(objective, iterations, exitCondition); + } +} diff --git a/MathNet.Numerics/Optimization/LimitedMemoryBfgsMinimizer.cs b/MathNet.Numerics/Optimization/LimitedMemoryBfgsMinimizer.cs new file mode 100644 index 0000000..71fa375 --- /dev/null +++ b/MathNet.Numerics/Optimization/LimitedMemoryBfgsMinimizer.cs @@ -0,0 +1,182 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization.LineSearch; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Optimization; + +/// +/// Limited Memory version of Broyden–Fletcher–Goldfarb–Shanno (BFGS) algorithm +/// +public class LimitedMemoryBfgsMinimizer : MinimizerBase, IUnconstrainedMinimizer +{ + public int Memory { get; set; } + + /// + /// + /// Creates L-BFGS minimizer + /// + /// Numbers of gradients and steps to store. + public LimitedMemoryBfgsMinimizer(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int memory, int maximumIterations = 1000) : base(gradientTolerance, parameterTolerance, functionProgressTolerance, maximumIterations) + { + Memory = memory; + } + + /// + /// Find the minimum of the objective function given lower and upper bounds + /// + /// The objective function, must support a gradient + /// The initial guess + /// The MinimizationResult which contains the minimum and the ExitCondition + public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector initialGuess) + { + if (!objective.IsGradientSupported) + throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for L-BFGS minimization."); + + objective.EvaluateAt(initialGuess); + ValidateGradientAndObjective(objective); + + // Check that we're not already done + ExitCondition currentExitCondition = ExitCriteriaSatisfied(objective, null, 0); + if (currentExitCondition != ExitCondition.None) + return new MinimizationResult(objective, 0, currentExitCondition); + + // Set up line search algorithm + var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.9, Math.Max(ParameterTolerance, 1e-10), 1000); + + // First step + + var lineSearchDirection = -objective.Gradient; + var stepSize = 100 * GradientTolerance / (lineSearchDirection * lineSearchDirection); + + var previousPoint = objective; + + LineSearchResult lineSearchResult; + try + { + lineSearchResult = lineSearcher.FindConformingStep(objective, lineSearchDirection, stepSize); + } + catch (OptimizationException e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + catch (ArgumentException e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + var candidate = lineSearchResult.FunctionInfoAtMinimum; + ValidateGradientAndObjective(candidate); + + var gradient = candidate.Gradient; + var step = candidate.Point - initialGuess; + var yk = candidate.Gradient - previousPoint.Gradient; + var ykhistory = new List>() { yk }; + var skhistory = new List>() { step }; + var rhokhistory = new List() { 1.0 / yk.DotProduct(step) }; + + // Subsequent steps + int iterations = 1; + int totalLineSearchSteps = lineSearchResult.Iterations; + int iterationsWithNontrivialLineSearch = lineSearchResult.Iterations > 0 ? 0 : 1; + previousPoint = candidate; + while (iterations++ < MaximumIterations && previousPoint.Gradient.Norm(2) >= GradientTolerance) + { + lineSearchDirection = -ApplyLbfgsUpdate(previousPoint, ykhistory, skhistory, rhokhistory); + var directionalDerivative = previousPoint.Gradient.DotProduct(lineSearchDirection); + if (directionalDerivative > 0) + throw new InnerOptimizationException("Direction is not a descent direction."); + try + { + lineSearchResult = lineSearcher.FindConformingStep(previousPoint, lineSearchDirection, 1.0); + } + catch (OptimizationException e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + catch (ArgumentException e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + iterationsWithNontrivialLineSearch += lineSearchResult.Iterations > 0 ? 1 : 0; + totalLineSearchSteps += lineSearchResult.Iterations; + + candidate = lineSearchResult.FunctionInfoAtMinimum; + currentExitCondition = ExitCriteriaSatisfied(candidate, previousPoint, iterations); + if (currentExitCondition != ExitCondition.None) + break; + step = candidate.Point - previousPoint.Point; + yk = candidate.Gradient - previousPoint.Gradient; + ykhistory.Add(yk); + skhistory.Add(step); + rhokhistory.Add(1.0 / yk.DotProduct(step)); + previousPoint = candidate; + if (ykhistory.Count > Memory) + { + ykhistory.RemoveAt(0); + skhistory.RemoveAt(0); + rhokhistory.RemoveAt(0); + } + } + + if (iterations == MaximumIterations && currentExitCondition == ExitCondition.None) + throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", MaximumIterations)); + + return new MinimizationWithLineSearchResult(candidate, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch); + } + + private Vector ApplyLbfgsUpdate(IObjectiveFunction previousPoint, List> ykhistory, List> skhistory, List rhokhistory) + { + var q = previousPoint.Gradient.Clone(); + var alphas = new Stack(); + for (int k = ykhistory.Count - 1; k >= 0; k--) + { + var alpha = rhokhistory[k] * q.DotProduct(skhistory[k]); + alphas.Push(alpha); + q -= alpha * ykhistory[k]; + } + + var yk = ykhistory.Last(); + var sk = skhistory.Last(); + q *= yk.DotProduct(sk) / yk.DotProduct(yk); + for (int k = 0; k < ykhistory.Count; k++) + { + var beta = rhokhistory[k] * ykhistory[k].DotProduct(q); + q += skhistory[k] * (alphas.Pop() - beta); + } + + return q; + } +} diff --git a/MathNet.Numerics/Optimization/LineSearch/LineSearchResult.cs b/MathNet.Numerics/Optimization/LineSearch/LineSearchResult.cs new file mode 100644 index 0000000..9841d2d --- /dev/null +++ b/MathNet.Numerics/Optimization/LineSearch/LineSearchResult.cs @@ -0,0 +1,41 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Optimization.LineSearch; + +public class LineSearchResult : MinimizationResult +{ + public double FinalStep { get; private set; } + + public LineSearchResult(IObjectiveFunction functionInfo, int iterations, double finalStep, ExitCondition reasonForExit) + : base(functionInfo, iterations, reasonForExit) + { + FinalStep = finalStep; + } +} diff --git a/MathNet.Numerics/Optimization/LineSearch/StrongWolfeLineSearch.cs b/MathNet.Numerics/Optimization/LineSearch/StrongWolfeLineSearch.cs new file mode 100644 index 0000000..dfeafac --- /dev/null +++ b/MathNet.Numerics/Optimization/LineSearch/StrongWolfeLineSearch.cs @@ -0,0 +1,51 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Optimization.LineSearch; + +public class StrongWolfeLineSearch : WolfeLineSearch +{ + public StrongWolfeLineSearch(double c1, double c2, double parameterTolerance, int maxIterations = 10) + : base(c1, c2, parameterTolerance, maxIterations) + { + // Argument validation in base class + } + + protected override ExitCondition WolfeExitCondition + { + get { return ExitCondition.StrongWolfeCriteria; } + } + + protected override bool WolfeCondition(double stepDd, double initialDd) + { + return Math.Abs(stepDd) > C2 * Math.Abs(initialDd); + } +} diff --git a/MathNet.Numerics/Optimization/LineSearch/WeakWolfeLineSearch.cs b/MathNet.Numerics/Optimization/LineSearch/WeakWolfeLineSearch.cs new file mode 100644 index 0000000..2ebc596 --- /dev/null +++ b/MathNet.Numerics/Optimization/LineSearch/WeakWolfeLineSearch.cs @@ -0,0 +1,96 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.LineSearch; + +/// +/// Search for a step size alpha that satisfies the weak Wolfe conditions. The weak Wolfe +/// Conditions are +/// i) Armijo Rule: f(x_k + alpha_k p_k) <= f(x_k) + c1 alpha_k p_k^T g(x_k) +/// ii) Curvature Condition: p_k^T g(x_k + alpha_k p_k) >= c2 p_k^T g(x_k) +/// where g(x) is the gradient of f(x), 0 < c1 < c2 < 1. +/// +/// Implementation is based on http://www.math.washington.edu/~burke/crs/408/lectures/L9-weak-Wolfe.pdf +/// +/// references: +/// http://en.wikipedia.org/wiki/Wolfe_conditions +/// http://www.math.washington.edu/~burke/crs/408/lectures/L9-weak-Wolfe.pdf +/// +public class WeakWolfeLineSearch : WolfeLineSearch +{ + public WeakWolfeLineSearch(double c1, double c2, double parameterTolerance, int maxIterations = 10) + : base(c1, c2, parameterTolerance, maxIterations) + { + // Validation in base class + } + + protected override ExitCondition WolfeExitCondition + { + get { return ExitCondition.WeakWolfeCriteria; } + } + + protected override bool WolfeCondition(double stepDd, double initialDd) + { + return stepDd < C2 * initialDd; + } + + protected override void ValidateValue(IObjectiveFunctionEvaluation eval) + { + if (!IsFinite(eval.Value)) + { + throw new EvaluationException(string.Format("Non-finite value returned by objective function: {0}", eval.Value), eval); + } + } + + protected override void ValidateInputArguments(IObjectiveFunctionEvaluation startingPoint, Vector searchDirection, double initialStep, double upperBound) + { + if (!startingPoint.IsGradientSupported) + throw new ArgumentException("objective function does not support gradient"); + } + + protected override void ValidateGradient(IObjectiveFunctionEvaluation eval) + { + foreach (double x in eval.Gradient) + { + if (!IsFinite(x)) + { + throw new EvaluationException(string.Format("Non-finite value returned by gradient: {0}", x), eval); + } + } + } + + static bool IsFinite(double x) + { + return !(double.IsNaN(x) || double.IsInfinity(x)); + } +} diff --git a/MathNet.Numerics/Optimization/LineSearch/WolfeLineSearch.cs b/MathNet.Numerics/Optimization/LineSearch/WolfeLineSearch.cs new file mode 100644 index 0000000..4178a67 --- /dev/null +++ b/MathNet.Numerics/Optimization/LineSearch/WolfeLineSearch.cs @@ -0,0 +1,156 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.LineSearch; + +public abstract class WolfeLineSearch +{ + protected double C1 { get; } + protected double C2 { get; } + protected double ParameterTolerance { get; } + protected int MaximumIterations { get; } + + public WolfeLineSearch(double c1, double c2, double parameterTolerance, int maxIterations = 10) + { + if (c1 <= 0) + throw new ArgumentException(string.Format("c1 {0} should be greater than 0", c1)); + if (c2 <= c1) + throw new ArgumentException(string.Format("c1 {0} should be less than c2 {1}", c1, c2)); + if (c2 >= 1) + throw new ArgumentException(string.Format("c2 {0} should be less than 1", c2)); + + C1 = c1; + C2 = c2; + ParameterTolerance = parameterTolerance; + MaximumIterations = maxIterations; + } + + /// Implemented following http://www.math.washington.edu/~burke/crs/408/lectures/L9-weak-Wolfe.pdf + /// The objective function being optimized, evaluated at the starting point of the search + /// Search direction + /// Initial size of the step in the search direction + public LineSearchResult FindConformingStep(IObjectiveFunctionEvaluation startingPoint, Vector searchDirection, double initialStep) + { + return FindConformingStep(startingPoint, searchDirection, initialStep, double.PositiveInfinity); + } + + /// + /// The objective function being optimized, evaluated at the starting point of the search + /// Search direction + /// Initial size of the step in the search direction + /// The upper bound + public LineSearchResult FindConformingStep(IObjectiveFunctionEvaluation startingPoint, Vector searchDirection, double initialStep, double upperBound) + { + ValidateInputArguments(startingPoint, searchDirection, initialStep, upperBound); + + double lowerBound = 0.0; + double step = initialStep; + + double initialValue = startingPoint.Value; + Vector initialGradient = startingPoint.Gradient; + + double initialDd = searchDirection * initialGradient; + + IObjectiveFunction objective = startingPoint.CreateNew(); + int ii; + ExitCondition reasonForExit = ExitCondition.None; + for (ii = 0; ii < MaximumIterations; ++ii) + { + objective.EvaluateAt(startingPoint.Point + searchDirection * step); + ValidateGradient(objective); + ValidateValue(objective); + + double stepDd = searchDirection * objective.Gradient; + + if (objective.Value > initialValue + C1 * step * initialDd) + { + upperBound = step; + step = 0.5 * (lowerBound + upperBound); + } + else if (WolfeCondition(stepDd, initialDd)) + { + lowerBound = step; + step = double.IsPositiveInfinity(upperBound) ? 2 * lowerBound : 0.5 * (lowerBound + upperBound); + } + else + { + reasonForExit = WolfeExitCondition; + break; + } + + if (!double.IsInfinity(upperBound)) + { + double maxRelChange = 0.0; + for (int jj = 0; jj < objective.Point.Count; ++jj) + { + double tmp = Math.Abs(searchDirection[jj] * (upperBound - lowerBound)) / Math.Max(Math.Abs(objective.Point[jj]), 1.0); + maxRelChange = Math.Max(maxRelChange, tmp); + } + + if (maxRelChange < ParameterTolerance) + { + reasonForExit = ExitCondition.LackOfProgress; + break; + } + } + } + + if (ii == MaximumIterations && Double.IsPositiveInfinity(upperBound)) + { + throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached. Function appears to be unbounded in search direction.", MaximumIterations)); + } + + if (ii == MaximumIterations) + { + throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", MaximumIterations)); + } + + return new LineSearchResult(objective, ii, step, reasonForExit); + } + + protected abstract ExitCondition WolfeExitCondition { get; } + + protected abstract bool WolfeCondition(double stepDd, double initialDd); + + protected virtual void ValidateGradient(IObjectiveFunctionEvaluation objective) + { + } + + protected virtual void ValidateValue(IObjectiveFunctionEvaluation objective) + { + } + + protected virtual void ValidateInputArguments(IObjectiveFunctionEvaluation startingPoint, Vector searchDirection, double initialStep, double upperBound) + { + } +} diff --git a/MathNet.Numerics/Optimization/MinimizationResult.cs b/MathNet.Numerics/Optimization/MinimizationResult.cs new file mode 100644 index 0000000..bfd58c2 --- /dev/null +++ b/MathNet.Numerics/Optimization/MinimizationResult.cs @@ -0,0 +1,47 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization; + +public class MinimizationResult +{ + public Vector MinimizingPoint { get { return FunctionInfoAtMinimum.Point; } } + public IObjectiveFunction FunctionInfoAtMinimum { get; private set; } + public int Iterations { get; private set; } + public ExitCondition ReasonForExit { get; private set; } + + public MinimizationResult(IObjectiveFunction functionInfo, int iterations, ExitCondition reasonForExit) + { + FunctionInfoAtMinimum = functionInfo; + Iterations = iterations; + ReasonForExit = reasonForExit; + } +} diff --git a/MathNet.Numerics/Optimization/MinimizationWithLineSearchResult.cs b/MathNet.Numerics/Optimization/MinimizationWithLineSearchResult.cs new file mode 100644 index 0000000..8e7121a --- /dev/null +++ b/MathNet.Numerics/Optimization/MinimizationWithLineSearchResult.cs @@ -0,0 +1,43 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Optimization; + +public class MinimizationWithLineSearchResult : MinimizationResult +{ + public int TotalLineSearchIterations { get; private set; } + public int IterationsWithNonTrivialLineSearch { get; private set; } + + public MinimizationWithLineSearchResult(IObjectiveFunction functionInfo, int iterations, ExitCondition reasonForExit, int totalLineSearchIterations, int iterationsWithNonTrivialLineSearch) + : base(functionInfo, iterations, reasonForExit) + { + TotalLineSearchIterations = totalLineSearchIterations; + IterationsWithNonTrivialLineSearch = iterationsWithNonTrivialLineSearch; + } +} diff --git a/MathNet.Numerics/Optimization/MinimizerBase.cs b/MathNet.Numerics/Optimization/MinimizerBase.cs new file mode 100644 index 0000000..f4178be --- /dev/null +++ b/MathNet.Numerics/Optimization/MinimizerBase.cs @@ -0,0 +1,119 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Double; + +using System; + +namespace MathNet.Numerics.Optimization; + +public abstract class MinimizerBase +{ + public double GradientTolerance { get; set; } + public double ParameterTolerance { get; set; } + public double FunctionProgressTolerance { get; set; } + public int MaximumIterations { get; set; } + + protected const double VerySmall = 1e-15; + + /// + /// Creates a base class for minimization + /// + /// The gradient tolerance + /// The parameter tolerance + /// The function progress tolerance + /// The maximum number of iterations + protected MinimizerBase(double gradientTolerance, double parameterTolerance, double functionProgressTolerance, int maximumIterations) + { + GradientTolerance = gradientTolerance; + ParameterTolerance = parameterTolerance; + FunctionProgressTolerance = functionProgressTolerance; + MaximumIterations = maximumIterations; + } + + protected ExitCondition ExitCriteriaSatisfied(IObjectiveFunctionEvaluation candidatePoint, IObjectiveFunctionEvaluation lastPoint, int iterations) + { + Vector relGrad = new DenseVector(candidatePoint.Point.Count); + double relativeGradient = 0.0; + double normalizer = Math.Max(Math.Abs(candidatePoint.Value), 1.0); + for (int ii = 0; ii < relGrad.Count; ++ii) + { + double projectedGradient = GetProjectedGradient(candidatePoint, ii); + + double tmp = projectedGradient * + Math.Max(Math.Abs(candidatePoint.Point[ii]), 1.0) / normalizer; + relativeGradient = Math.Max(relativeGradient, Math.Abs(tmp)); + } + + if (relativeGradient < GradientTolerance) + { + return ExitCondition.RelativeGradient; + } + + if (lastPoint != null) + { + double mostProgress = 0.0; + for (int ii = 0; ii < candidatePoint.Point.Count; ++ii) + { + var tmp = Math.Abs(candidatePoint.Point[ii] - lastPoint.Point[ii]) / + Math.Max(Math.Abs(lastPoint.Point[ii]), 1.0); + mostProgress = Math.Max(mostProgress, tmp); + } + + if (mostProgress < ParameterTolerance) + { + return ExitCondition.LackOfProgress; + } + + double functionChange = candidatePoint.Value - lastPoint.Value; + if (iterations > 500 && functionChange < 0 && Math.Abs(functionChange) < FunctionProgressTolerance) + return ExitCondition.LackOfProgress; + } + + return ExitCondition.None; + } + + protected virtual double GetProjectedGradient(IObjectiveFunctionEvaluation candidatePoint, int ii) + { + return candidatePoint.Gradient[ii]; + } + + protected void ValidateGradientAndObjective(IObjectiveFunctionEvaluation eval) + { + foreach (var x in eval.Gradient) + { + if (Double.IsNaN(x) || Double.IsInfinity(x)) + throw new EvaluationException("Non-finite gradient returned.", eval); + } + + if (Double.IsNaN(eval.Value) || Double.IsInfinity(eval.Value)) + throw new EvaluationException("Non-finite objective function returned.", eval); + } +} diff --git a/MathNet.Numerics/Optimization/NelderMeadSimplex.cs b/MathNet.Numerics/Optimization/NelderMeadSimplex.cs new file mode 100644 index 0000000..b8a7283 --- /dev/null +++ b/MathNet.Numerics/Optimization/NelderMeadSimplex.cs @@ -0,0 +1,423 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// Converted from code released with a MIT license available at https://code.google.com/p/nelder-mead-simplex/ + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization; + +/// +/// Class implementing the Nelder-Mead simplex algorithm, used to find a minima when no gradient is available. +/// Called fminsearch() in Matlab. A description of the algorithm can be found at +/// http://se.mathworks.com/help/matlab/math/optimizing-nonlinear-functions.html#bsgpq6p-11 +/// or +/// https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method +/// +public sealed class NelderMeadSimplex : IUnconstrainedMinimizer +{ + static readonly double JITTER = 1e-10d; // a small value used to protect against floating point noise + + public double ConvergenceTolerance { get; set; } + public int MaximumIterations { get; set; } + + public NelderMeadSimplex(double convergenceTolerance, int maximumIterations) + { + ConvergenceTolerance = convergenceTolerance; + MaximumIterations = maximumIterations; + } + + /// + /// Finds the minimum of the objective function without an initial perturbation, the default values used + /// by fminsearch() in Matlab are used instead + /// http://se.mathworks.com/help/matlab/math/optimizing-nonlinear-functions.html#bsgpq6p-11 + /// + /// The objective function, no gradient or hessian needed + /// The initial guess + /// The minimum point + public MinimizationResult FindMinimum(IObjectiveFunction objectiveFunction, Vector initialGuess) + { + return Minimum(objectiveFunction, initialGuess, ConvergenceTolerance, MaximumIterations); + } + + /// + /// Finds the minimum of the objective function with an initial perturbation + /// + /// The objective function, no gradient or hessian needed + /// The initial guess + /// The initial perturbation + /// The minimum point + public MinimizationResult FindMinimum(IObjectiveFunction objectiveFunction, Vector initialGuess, Vector initalPertubation) + { + return Minimum(objectiveFunction, initialGuess, initalPertubation, ConvergenceTolerance, MaximumIterations); + } + + /// + /// Finds the minimum of the objective function without an initial perturbation, the default values used + /// by fminsearch() in Matlab are used instead + /// http://se.mathworks.com/help/matlab/math/optimizing-nonlinear-functions.html#bsgpq6p-11 + /// + /// The objective function, no gradient or hessian needed + /// The initial guess + /// The minimum point + public static MinimizationResult Minimum(IObjectiveFunction objectiveFunction, Vector initialGuess, double convergenceTolerance = 1e-8, int maximumIterations = 1000) + { + var initalPertubation = new LinearAlgebra.Double.DenseVector(initialGuess.Count); + for (int i = 0; i < initialGuess.Count; i++) + { + initalPertubation[i] = initialGuess[i] == 0.0 ? 0.00025 : initialGuess[i] * 0.05; + } + + return Minimum(objectiveFunction, initialGuess, initalPertubation, convergenceTolerance, maximumIterations); + } + + /// + /// Finds the minimum of the objective function with an initial perturbation + /// + /// The objective function, no gradient or hessian needed + /// The initial guess + /// The initial perturbation + /// The minimum point + public static MinimizationResult Minimum(IObjectiveFunction objectiveFunction, Vector initialGuess, Vector initalPertubation, double convergenceTolerance = 1e-8, int maximumIterations = 1000) + { + // confirm that we are in a position to commence + if (objectiveFunction == null) + throw new ArgumentNullException(nameof(objectiveFunction), "ObjectiveFunction must be set to a valid ObjectiveFunctionDelegate"); + + if (initialGuess == null) + throw new ArgumentNullException(nameof(initialGuess), "initialGuess must be initialized"); + + if (initalPertubation == null) + throw new ArgumentNullException(nameof(initalPertubation), "initalPertubation must be initialized, if unknown use overloaded version of FindMinimum()"); + + SimplexConstant[] simplexConstants = SimplexConstant.CreateSimplexConstantsFromVectors(initialGuess, initalPertubation); + + // create the initial simplex + int numDimensions = simplexConstants.Length; + int numVertices = numDimensions + 1; + Vector[] vertices = InitializeVertices(simplexConstants); + double[] errorValues = new double[numVertices]; + + int evaluationCount = 0; + ExitCondition exitCondition = ExitCondition.None; + ErrorProfile errorProfile; + + errorValues = InitializeErrorValues(vertices, objectiveFunction); + int numTimesHasConverged = 0; + + // iterate until we converge, or complete our permitted number of iterations + while (true) + { + errorProfile = EvaluateSimplex(errorValues); + + // see if the range in point heights is small enough to exit + // to handle the case when the function is symmetrical and extra iteration is performed + if (HasConverged(convergenceTolerance, errorProfile, errorValues)) + { + numTimesHasConverged++; + } + else + { + numTimesHasConverged = 0; + } + + if (numTimesHasConverged == 2) + { + exitCondition = ExitCondition.Converged; + break; + } + + // attempt a reflection of the simplex + double reflectionPointValue = TryToScaleSimplex(-1.0, ref errorProfile, vertices, errorValues, objectiveFunction); + ++evaluationCount; + if (reflectionPointValue <= errorValues[errorProfile.LowestIndex]) + { + // it's better than the best point, so attempt an expansion of the simplex + double expansionPointValue = TryToScaleSimplex(2.0, ref errorProfile, vertices, errorValues, objectiveFunction); + ++evaluationCount; + } + else if (reflectionPointValue >= errorValues[errorProfile.NextHighestIndex]) + { + // it would be worse than the second best point, so attempt a contraction to look + // for an intermediate point + double currentWorst = errorValues[errorProfile.HighestIndex]; + double contractionPointValue = TryToScaleSimplex(0.5, ref errorProfile, vertices, errorValues, objectiveFunction); + ++evaluationCount; + if (contractionPointValue >= currentWorst) + { + // that would be even worse, so let's try to contract uniformly towards the low point; + // don't bother to update the error profile, we'll do it at the start of the + // next iteration + ShrinkSimplex(errorProfile, vertices, errorValues, objectiveFunction); + evaluationCount += numVertices; // that required one function evaluation for each vertex; keep track + } + } + // check to see if we have exceeded our alloted number of evaluations + if (evaluationCount >= maximumIterations) + { + throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", maximumIterations)); + } + } + + objectiveFunction.EvaluateAt(vertices[errorProfile.LowestIndex]); + var regressionResult = new MinimizationResult(objectiveFunction, evaluationCount, exitCondition); + return regressionResult; + } + + /// + /// Evaluate the objective function at each vertex to create a corresponding + /// list of error values for each vertex + /// + /// + /// + /// + static double[] InitializeErrorValues(Vector[] vertices, IObjectiveFunction objectiveFunction) + { + double[] errorValues = new double[vertices.Length]; + for (int i = 0; i < vertices.Length; i++) + { + objectiveFunction.EvaluateAt(vertices[i]); + errorValues[i] = objectiveFunction.Value; + } + + return errorValues; + } + + /// + /// Check whether the points in the error profile have so little range that we + /// consider ourselves to have converged + /// + /// + /// + /// + /// + static bool HasConverged(double convergenceTolerance, ErrorProfile errorProfile, double[] errorValues) + { + double range = 2 * Math.Abs(errorValues[errorProfile.HighestIndex] - errorValues[errorProfile.LowestIndex]) / + (Math.Abs(errorValues[errorProfile.HighestIndex]) + Math.Abs(errorValues[errorProfile.LowestIndex]) + JITTER); + + if (range < convergenceTolerance) + { + return true; + } + else + { + return false; + } + } + + /// + /// Examine all error values to determine the ErrorProfile + /// + /// + /// + static ErrorProfile EvaluateSimplex(double[] errorValues) + { + ErrorProfile errorProfile = new ErrorProfile(); + if (errorValues[0] > errorValues[1]) + { + errorProfile.HighestIndex = 0; + errorProfile.NextHighestIndex = 1; + } + else + { + errorProfile.HighestIndex = 1; + errorProfile.NextHighestIndex = 0; + } + + for (int index = 0; index < errorValues.Length; index++) + { + double errorValue = errorValues[index]; + if (errorValue <= errorValues[errorProfile.LowestIndex]) + { + errorProfile.LowestIndex = index; + } + + if (errorValue > errorValues[errorProfile.HighestIndex]) + { + errorProfile.NextHighestIndex = errorProfile.HighestIndex; // downgrade the current highest to next highest + errorProfile.HighestIndex = index; + } + else if (errorValue > errorValues[errorProfile.NextHighestIndex] && index != errorProfile.HighestIndex) + { + errorProfile.NextHighestIndex = index; + } + } + + return errorProfile; + } + + /// + /// Construct an initial simplex, given starting guesses for the constants, and + /// initial step sizes for each dimension + /// + /// + /// + static Vector[] InitializeVertices(SimplexConstant[] simplexConstants) + { + int numDimensions = simplexConstants.Length; + Vector[] vertices = new Vector[numDimensions + 1]; + + // define one point of the simplex as the given initial guesses + var p0 = new LinearAlgebra.Double.DenseVector(numDimensions); + for (int i = 0; i < numDimensions; i++) + { + p0[i] = simplexConstants[i].Value; + } + + // now fill in the vertices, creating the additional points as: + // P(i) = P(0) + Scale(i) * UnitVector(i) + vertices[0] = p0; + for (int i = 0; i < numDimensions; i++) + { + double scale = simplexConstants[i].InitialPerturbation; + Vector unitVector = new LinearAlgebra.Double.DenseVector(numDimensions); + unitVector[i] = 1; + vertices[i + 1] = p0.Add(unitVector.Multiply(scale)); + } + + return vertices; + } + + /// + /// Test a scaling operation of the high point, and replace it if it is an improvement + /// + /// + /// + /// + /// + /// + /// + static double TryToScaleSimplex(double scaleFactor, ref ErrorProfile errorProfile, Vector[] vertices, + double[] errorValues, IObjectiveFunction objectiveFunction) + { + // find the centroid through which we will reflect + Vector centroid = ComputeCentroid(vertices, errorProfile); + + // define the vector from the centroid to the high point + Vector centroidToHighPoint = vertices[errorProfile.HighestIndex].Subtract(centroid); + + // scale and position the vector to determine the new trial point + Vector newPoint = centroidToHighPoint.Multiply(scaleFactor).Add(centroid); + + // evaluate the new point + objectiveFunction.EvaluateAt(newPoint); + double newErrorValue = objectiveFunction.Value; + + // if it's better, replace the old high point + if (newErrorValue < errorValues[errorProfile.HighestIndex]) + { + vertices[errorProfile.HighestIndex] = newPoint; + errorValues[errorProfile.HighestIndex] = newErrorValue; + } + + return newErrorValue; + } + + /// + /// Contract the simplex uniformly around the lowest point + /// + /// + /// + /// + /// + static void ShrinkSimplex(ErrorProfile errorProfile, Vector[] vertices, double[] errorValues, + IObjectiveFunction objectiveFunction) + { + Vector lowestVertex = vertices[errorProfile.LowestIndex]; + for (int i = 0; i < vertices.Length; i++) + { + if (i != errorProfile.LowestIndex) + { + vertices[i] = (vertices[i].Add(lowestVertex)).Multiply(0.5); + objectiveFunction.EvaluateAt(vertices[i]); + errorValues[i] = objectiveFunction.Value; + } + } + } + + /// + /// Compute the centroid of all points except the worst + /// + /// + /// + /// + static Vector ComputeCentroid(Vector[] vertices, ErrorProfile errorProfile) + { + int numVertices = vertices.Length; + // find the centroid of all points except the worst one + Vector centroid = new LinearAlgebra.Double.DenseVector(numVertices - 1); + for (int i = 0; i < numVertices; i++) + { + if (i != errorProfile.HighestIndex) + { + centroid = centroid.Add(vertices[i]); + } + } + + return centroid.Multiply(1.0d / (numVertices - 1)); + } + + sealed class SimplexConstant + { + public SimplexConstant(double value, double initialPerturbation) + { + Value = value; + InitialPerturbation = initialPerturbation; + } + + /// + /// The value of the constant + /// + public double Value { get; } + + // The size of the initial perturbation + public double InitialPerturbation { get; } + + public static SimplexConstant[] CreateSimplexConstantsFromVectors(Vector initialGuess, Vector initialPertubation) + { + var constants = new SimplexConstant[initialGuess.Count]; + for (int i = 0; i < constants.Length; i++) + { + constants[i] = new SimplexConstant(initialGuess[i], initialPertubation[i]); + } + + return constants; + } + } + + sealed class ErrorProfile + { + public int HighestIndex { get; set; } + public int NextHighestIndex { get; set; } + public int LowestIndex { get; set; } + } +} diff --git a/MathNet.Numerics/Optimization/NewtonMinimizer.cs b/MathNet.Numerics/Optimization/NewtonMinimizer.cs new file mode 100644 index 0000000..206557b --- /dev/null +++ b/MathNet.Numerics/Optimization/NewtonMinimizer.cs @@ -0,0 +1,153 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization.LineSearch; + +using System; + +namespace MathNet.Numerics.Optimization; + +public sealed class NewtonMinimizer : IUnconstrainedMinimizer +{ + public double GradientTolerance { get; set; } + public int MaximumIterations { get; set; } + public bool UseLineSearch { get; set; } + + public NewtonMinimizer(double gradientTolerance, int maximumIterations, bool useLineSearch = false) + { + GradientTolerance = gradientTolerance; + MaximumIterations = maximumIterations; + UseLineSearch = useLineSearch; + } + + public MinimizationResult FindMinimum(IObjectiveFunction objective, Vector initialGuess) + { + return Minimum(objective, initialGuess, GradientTolerance, MaximumIterations, UseLineSearch); + } + + public static MinimizationResult Minimum(IObjectiveFunction objective, Vector initialGuess, double gradientTolerance = 1e-8, int maxIterations = 1000, bool useLineSearch = false) + { + if (!objective.IsGradientSupported) + { + throw new IncompatibleObjectiveException("Gradient not supported in objective function, but required for Newton minimization."); + } + + if (!objective.IsHessianSupported) + { + throw new IncompatibleObjectiveException("Hessian not supported in objective function, but required for Newton minimization."); + } + + // Check that we're not already done + objective.EvaluateAt(initialGuess); + ValidateGradient(objective); + if (objective.Gradient.Norm(2.0) < gradientTolerance) + { + return new MinimizationResult(objective, 0, ExitCondition.AbsoluteGradient); + } + + // Set up line search algorithm + var lineSearcher = new WeakWolfeLineSearch(1e-4, 0.9, 1e-4, maxIterations: 1000); + + // Subsequent steps + int iterations = 0; + int totalLineSearchSteps = 0; + int iterationsWithNontrivialLineSearch = 0; + bool tmpLineSearch = false; + while (objective.Gradient.Norm(2.0) >= gradientTolerance && iterations < maxIterations) + { + ValidateHessian(objective); + + var searchDirection = objective.Hessian.LU().Solve(-objective.Gradient); + if (searchDirection * objective.Gradient >= 0) + { + searchDirection = -objective.Gradient; + tmpLineSearch = true; + } + + if (useLineSearch || tmpLineSearch) + { + LineSearchResult result; + try + { + result = lineSearcher.FindConformingStep(objective, searchDirection, 1.0); + } + catch (Exception e) + { + throw new InnerOptimizationException("Line search failed.", e); + } + + iterationsWithNontrivialLineSearch += result.Iterations > 0 ? 1 : 0; + totalLineSearchSteps += result.Iterations; + objective = result.FunctionInfoAtMinimum; + } + else + { + objective.EvaluateAt(objective.Point + searchDirection); + } + + ValidateGradient(objective); + + tmpLineSearch = false; + iterations += 1; + } + + if (iterations == maxIterations) + { + throw new MaximumIterationsException(string.Format("Maximum iterations ({0}) reached.", maxIterations)); + } + + return new MinimizationWithLineSearchResult(objective, iterations, ExitCondition.AbsoluteGradient, totalLineSearchSteps, iterationsWithNontrivialLineSearch); + } + + static void ValidateGradient(IObjectiveFunctionEvaluation eval) + { + foreach (var x in eval.Gradient) + { + if (Double.IsNaN(x) || Double.IsInfinity(x)) + { + throw new EvaluationException("Non-finite gradient returned.", eval); + } + } + } + + static void ValidateHessian(IObjectiveFunctionEvaluation eval) + { + for (int ii = 0; ii < eval.Hessian.RowCount; ++ii) + { + for (int jj = 0; jj < eval.Hessian.ColumnCount; ++jj) + { + if (Double.IsNaN(eval.Hessian[ii, jj]) || Double.IsInfinity(eval.Hessian[ii, jj])) + { + throw new EvaluationException("Non-finite Hessian returned.", eval); + } + } + } + } +} diff --git a/MathNet.Numerics/Optimization/NonlinearMinimizationResult.cs b/MathNet.Numerics/Optimization/NonlinearMinimizationResult.cs new file mode 100644 index 0000000..efbb958 --- /dev/null +++ b/MathNet.Numerics/Optimization/NonlinearMinimizationResult.cs @@ -0,0 +1,77 @@ +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization; + +public class NonlinearMinimizationResult +{ + public IObjectiveModel ModelInfoAtMinimum { get; private set; } + + /// + /// Returns the best fit parameters. + /// + public Vector MinimizingPoint { get { return ModelInfoAtMinimum.Point; } } + + /// + /// Returns the standard errors of the corresponding parameters + /// + public Vector StandardErrors { get; private set; } + + /// + /// Returns the y-values of the fitted model that correspond to the independent values. + /// + public Vector MinimizedValues { get { return ModelInfoAtMinimum.ModelValues; } } + + /// + /// Returns the covariance matrix at minimizing point. + /// + public Matrix Covariance { get; private set; } + + /// + /// Returns the correlation matrix at minimizing point. + /// + public Matrix Correlation { get; private set; } + + public int Iterations { get; private set; } + + public ExitCondition ReasonForExit { get; private set; } + + public NonlinearMinimizationResult(IObjectiveModel modelInfo, int iterations, ExitCondition reasonForExit) + { + ModelInfoAtMinimum = modelInfo; + Iterations = iterations; + ReasonForExit = reasonForExit; + + EvaluateCovariance(modelInfo); + } + + private void EvaluateCovariance(IObjectiveModel objective) + { + objective.EvaluateAt(objective.Point); // Hessian may be not yet updated. + + var Hessian = objective.Hessian; + if (Hessian == null || objective.DegreeOfFreedom < 1) + { + Covariance = null; + Correlation = null; + StandardErrors = null; + return; + } + + Covariance = Hessian.PseudoInverse() * objective.Value / objective.DegreeOfFreedom; + + if (Covariance != null) + { + StandardErrors = Covariance.Diagonal().PointwiseSqrt(); + + var correlation = Covariance.Clone(); + var d = correlation.Diagonal().PointwiseSqrt(); + var dd = d.OuterProduct(d); + Correlation = correlation.PointwiseDivide(dd); + } + else + { + StandardErrors = null; + Correlation = null; + } + } +} diff --git a/MathNet.Numerics/Optimization/NonlinearMinimizerBase.cs b/MathNet.Numerics/Optimization/NonlinearMinimizerBase.cs new file mode 100644 index 0000000..c2446ca --- /dev/null +++ b/MathNet.Numerics/Optimization/NonlinearMinimizerBase.cs @@ -0,0 +1,312 @@ +using MathNet.Numerics.LinearAlgebra; + +using System; +using System.Linq; + +namespace MathNet.Numerics.Optimization; + +public abstract class NonlinearMinimizerBase +{ + /// + /// The stopping threshold for the function value or L2 norm of the residuals. + /// + public static double FunctionTolerance { get; set; } + + /// + /// The stopping threshold for L2 norm of the change of the parameters. + /// + public static double StepTolerance { get; set; } + + /// + /// The stopping threshold for infinity norm of the gradient. + /// + public static double GradientTolerance { get; set; } + + /// + /// The maximum number of iterations. + /// + public static int MaximumIterations { get; set; } + + /// + /// The lower bound of the parameters. + /// + public static Vector LowerBound { get; private set; } + + /// + /// The upper bound of the parameters. + /// + public static Vector UpperBound { get; private set; } + + /// + /// The scale factors for the parameters. + /// + public static Vector Scales { get; private set; } + + private static bool IsBounded { get { return LowerBound != null || UpperBound != null || Scales != null; } } + + protected NonlinearMinimizerBase(double gradientTolerance = 1E-18, double stepTolerance = 1E-18, double functionTolerance = 1E-18, int maximumIterations = -1) + { + GradientTolerance = gradientTolerance; + StepTolerance = stepTolerance; + FunctionTolerance = functionTolerance; + MaximumIterations = maximumIterations; + } + + protected static void ValidateBounds(Vector parameters, Vector lowerBound = null, Vector upperBound = null, Vector scales = null) + { + if (parameters == null) + { + throw new ArgumentNullException("parameters"); + } + + if (lowerBound != null && lowerBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0) + { + throw new ArgumentException("The lower bounds must be finite."); + } + + if (lowerBound != null && lowerBound.Count != parameters.Count) + { + throw new ArgumentException("The lower bounds can't have different size from the parameters."); + } + + LowerBound = lowerBound; + + if (upperBound != null && upperBound.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0) + { + throw new ArgumentException("The upper bounds must be finite."); + } + + if (upperBound != null && upperBound.Count != parameters.Count) + { + throw new ArgumentException("The upper bounds can't have different size from the parameetrs."); + } + + UpperBound = upperBound; + + if (scales != null && scales.Count(x => double.IsInfinity(x) || double.IsNaN(x) || x == 0) > 0) + { + throw new ArgumentException("The scales must be finite."); + } + + if (scales != null && scales.Count != parameters.Count) + { + throw new ArgumentException("The scales can't have different size from the parameters."); + } + + if (scales != null && scales.Count(x => x < 0) > 0) + { + scales.PointwiseAbs(); + } + + Scales = scales; + } + + protected static double EvaluateFunction(IObjectiveModel objective, Vector Pint) + { + var Pext = ProjectToExternalParameters(Pint); + objective.EvaluateAt(Pext); + return objective.Value; + } + + protected static Tuple, Matrix> EvaluateJacobian(IObjectiveModel objective, Vector Pint) + { + var gradient = objective.Gradient; + var hessian = objective.Hessian; + + if (IsBounded) + { + var scaleFactors = ScaleFactorsOfJacobian(Pint); // the parameters argument is always internal. + + for (int i = 0; i < gradient.Count; i++) + { + gradient[i] = gradient[i] * scaleFactors[i]; + } + + for (int i = 0; i < hessian.RowCount; i++) + { + for (int j = 0; j < hessian.ColumnCount; j++) + { + hessian[i, j] = hessian[i, j] * scaleFactors[i] * scaleFactors[j]; + } + } + } + + return new Tuple, Matrix>(gradient, hessian); + } + + #region Projection of Parameters + + // To handle the box constrained minimization as the unconstrained minimization, + // the parameters are mapping by the following rules, + // which are modified the rules shown in the ref[1] in order to introduce scales. + // + // 1. lower < Pext < upper + // Pint = asin(2 * (Pext - lower) / (upper - lower) - 1) + // Pext = lower + (sin(Pint) + 1) * (upper - lower) / 2 + // dPext/dPint = (upper - lower) / 2 * cos(Pint) + // + // 2. lower < Pext + // Pint = sqrt((Pext/scale - lower/scale + 1)^2 - 1) + // Pext = lower + scale * (sqrt(Pint^2 + 1) - 1) + // dPext/dPint = scale * Pint / sqrt(Pint^2 + 1) + // + // 3. Pext < upper + // Pint = sqrt((upper / scale - Pext / scale + 1)^2 - 1) + // Pext = upper + scale - scale * sqrt(Pint^2 + 1) + // dPext/dPint = - scale * Pint / sqrt(Pint^2 + 1) + // + // 4. no bounds, but scales + // Pint = Pext / scale + // Pext = Pint * scale + // dPext/dPint = scale + // + // The rules are applied in ProjectParametersToInternal, ProjectParametersToExternal, and ScaleFactorsOfJacobian methods. + // + // References: + // [1] https://lmfit.github.io/lmfit-py/bounds.html + // [2] MINUIT User's Guide, https://root.cern.ch/download/minuit.pdf + // + // Except when it is initial guess, the parameters argument is always internal parameter. + // So, first map the parameters argument to the external parameters in order to calculate function values. + + protected static Vector ProjectToInternalParameters(Vector Pext) + { + var Pint = Pext.Clone(); + + if (LowerBound != null && UpperBound != null) + { + for (int i = 0; i < Pext.Count; i++) + { + Pint[i] = Math.Asin((2.0 * (Pext[i] - LowerBound[i]) / (UpperBound[i] - LowerBound[i])) - 1.0); + } + + return Pint; + } + else if (LowerBound != null && UpperBound == null) + { + for (int i = 0; i < Pext.Count; i++) + { + Pint[i] = (Scales == null) + ? Math.Sqrt(Math.Pow(Pext[i] - LowerBound[i] + 1.0, 2) - 1.0) + : Math.Sqrt(Math.Pow((Pext[i] - LowerBound[i]) / Scales[i] + 1.0, 2) - 1.0); + } + + return Pint; + } + else if (LowerBound == null && UpperBound != null) + { + for (int i = 0; i < Pext.Count; i++) + { + Pint[i] = (Scales == null) + ? Math.Sqrt(Math.Pow(UpperBound[i] - Pext[i] + 1.0, 2) - 1.0) + : Math.Sqrt(Math.Pow((UpperBound[i] - Pext[i]) / Scales[i] + 1.0, 2) - 1.0); + } + + return Pint; + } + else if (Scales != null) + { + for (int i = 0; i < Pext.Count; i++) + { + Pint[i] = Pext[i] / Scales[i]; + } + + return Pint; + } + + return Pint; + } + + protected static Vector ProjectToExternalParameters(Vector Pint) + { + var Pext = Pint.Clone(); + + if (LowerBound != null && UpperBound != null) + { + for (int i = 0; i < Pint.Count; i++) + { + Pext[i] = LowerBound[i] + (UpperBound[i] / 2.0 - LowerBound[i] / 2.0) * (Math.Sin(Pint[i]) + 1.0); + } + + return Pext; + } + else if (LowerBound != null && UpperBound == null) + { + for (int i = 0; i < Pint.Count; i++) + { + Pext[i] = (Scales == null) + ? LowerBound[i] + Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0 + : LowerBound[i] + Scales[i] * (Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0); + } + + return Pext; + } + else if (LowerBound == null && UpperBound != null) + { + for (int i = 0; i < Pint.Count; i++) + { + Pext[i] = (Scales == null) + ? UpperBound[i] - Math.Sqrt(Pint[i] * Pint[i] + 1.0) + 1.0 + : UpperBound[i] - Scales[i] * (Math.Sqrt(Pint[i] * Pint[i] + 1.0) - 1.0); + } + + return Pext; + } + else if (Scales != null) + { + for (int i = 0; i < Pint.Count; i++) + { + Pext[i] = Pint[i] * Scales[i]; + } + + return Pext; + } + + return Pext; + } + + protected static Vector ScaleFactorsOfJacobian(Vector Pint) + { + var scale = Vector.Build.Dense(Pint.Count, 1.0); + + if (LowerBound != null && UpperBound != null) + { + for (int i = 0; i < Pint.Count; i++) + { + scale[i] = (UpperBound[i] - LowerBound[i]) / 2.0 * Math.Cos(Pint[i]); + } + + return scale; + } + else if (LowerBound != null && UpperBound == null) + { + for (int i = 0; i < Pint.Count; i++) + { + scale[i] = (Scales == null) + ? Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0) + : Scales[i] * Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0); + } + + return scale; + } + else if (LowerBound == null && UpperBound != null) + { + for (int i = 0; i < Pint.Count; i++) + { + scale[i] = (Scales == null) + ? -Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0) + : -Scales[i] * Pint[i] / Math.Sqrt(Pint[i] * Pint[i] + 1.0); + } + + return scale; + } + else if (Scales != null) + { + return Scales; + } + + return scale; + } + + #endregion Projection of Parameters +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunction.cs new file mode 100644 index 0000000..86bef5b --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunction.cs @@ -0,0 +1,224 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Optimization.ObjectiveFunctions; + +using System; + +namespace MathNet.Numerics.Optimization; + +public static class ObjectiveFunction +{ + /// + /// Objective function where neither Gradient nor Hessian is available. + /// + public static IObjectiveFunction Value(Func, double> function) + { + return new ValueObjectiveFunction(function); + } + + /// + /// Objective function where the Gradient is available. Greedy evaluation. + /// + public static IObjectiveFunction Gradient(Func, Tuple>> function) + { + return new GradientObjectiveFunction(function); + } + + /// + /// Objective function where the Gradient is available. Lazy evaluation. + /// + public static IObjectiveFunction Gradient(Func, double> function, Func, Vector> gradient) + { + return new LazyObjectiveFunction(function, gradient: gradient); + } + + /// + /// Objective function where the Hessian is available. Greedy evaluation. + /// + public static IObjectiveFunction Hessian(Func, Tuple>> function) + { + return new HessianObjectiveFunction(function); + } + + /// + /// Objective function where the Hessian is available. Lazy evaluation. + /// + public static IObjectiveFunction Hessian(Func, double> function, Func, Matrix> hessian) + { + return new LazyObjectiveFunction(function, hessian: hessian); + } + + /// + /// Objective function where both Gradient and Hessian are available. Greedy evaluation. + /// + public static IObjectiveFunction GradientHessian(Func, Tuple, Matrix>> function) + { + return new GradientHessianObjectiveFunction(function); + } + + /// + /// Objective function where both Gradient and Hessian are available. Lazy evaluation. + /// + public static IObjectiveFunction GradientHessian(Func, double> function, Func, Vector> gradient, Func, Matrix> hessian) + { + return new LazyObjectiveFunction(function, gradient: gradient, hessian: hessian); + } + + /// + /// Objective function where neither first nor second derivative is available. + /// + public static IScalarObjectiveFunction ScalarValue(Func function) + { + return new ScalarValueObjectiveFunction(function); + } + + /// + /// Objective function where the first derivative is available. + /// + public static IScalarObjectiveFunction ScalarDerivative(Func function, Func derivative) + { + return new ScalarObjectiveFunction(function, derivative); + } + + /// + /// Objective function where the first and second derivatives are available. + /// + public static IScalarObjectiveFunction ScalarSecondDerivative(Func function, Func derivative, Func secondDerivative) + { + return new ScalarObjectiveFunction(function, derivative, secondDerivative); + } + + /// + /// objective model with a user supplied jacobian for non-linear least squares regression. + /// + public static IObjectiveModel NonlinearModel(Func, Vector, Vector> function, + Func, Vector, Matrix> derivatives, + Vector observedX, Vector observedY, Vector weight = null) + { + var objective = new NonlinearObjectiveFunction(function, derivatives); + objective.SetObserved(observedX, observedY, weight); + return objective; + } + + /// + /// Objective model for non-linear least squares regression. + /// + public static IObjectiveModel NonlinearModel(Func, Vector, Vector> function, + Vector observedX, Vector observedY, Vector weight = null, + int accuracyOrder = 2) + { + var objective = new NonlinearObjectiveFunction(function, accuracyOrder: accuracyOrder); + objective.SetObserved(observedX, observedY, weight); + return objective; + } + + /// + /// Objective model with a user supplied jacobian for non-linear least squares regression. + /// + public static IObjectiveModel NonlinearModel(Func, double, double> function, + Func, double, Vector> derivatives, + Vector observedX, Vector observedY, Vector weight = null) + { + Vector func(Vector point, Vector x) + { + var functionValues = CreateVector.Dense(x.Count); + for (int i = 0; i < x.Count; i++) + { + functionValues[i] = function(point, x[i]); + } + + return functionValues; + } + + Matrix prime(Vector point, Vector x) + { + var derivativeValues = CreateMatrix.Dense(x.Count, point.Count); + for (int i = 0; i < x.Count; i++) + { + derivativeValues.SetRow(i, derivatives(point, x[i])); + } + + return derivativeValues; + } + + var objective = new NonlinearObjectiveFunction(func, prime); + objective.SetObserved(observedX, observedY, weight); + return objective; + } + + /// + /// Objective model for non-linear least squares regression. + /// + public static IObjectiveModel NonlinearModel(Func, double, double> function, + Vector observedX, Vector observedY, Vector weight = null, + int accuracyOrder = 2) + { + Vector func(Vector point, Vector x) + { + var functionValues = CreateVector.Dense(x.Count); + for (int i = 0; i < x.Count; i++) + { + functionValues[i] = function(point, x[i]); + } + + return functionValues; + } + + var objective = new NonlinearObjectiveFunction(func, accuracyOrder: accuracyOrder); + objective.SetObserved(observedX, observedY, weight); + return objective; + } + + /// + /// Objective function with a user supplied jacobian for nonlinear least squares regression. + /// + public static IObjectiveFunction NonlinearFunction(Func, Vector, Vector> function, + Func, Vector, Matrix> derivatives, + Vector observedX, Vector observedY, Vector weight = null) + { + var objective = new NonlinearObjectiveFunction(function, derivatives); + objective.SetObserved(observedX, observedY, weight); + return objective.ToObjectiveFunction(); + } + + /// + /// Objective function for nonlinear least squares regression. + /// The numerical jacobian with accuracy order is used. + /// + public static IObjectiveFunction NonlinearFunction(Func, Vector, Vector> function, + Vector observedX, Vector observedY, Vector weight = null, + int accuracyOrder = 2) + { + var objective = new NonlinearObjectiveFunction(function, null, accuracyOrder: accuracyOrder); + objective.SetObserved(observedX, observedY, weight); + return objective.ToObjectiveFunction(); + } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/ForwardDifferenceGradientObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/ForwardDifferenceGradientObjectiveFunction.cs new file mode 100644 index 0000000..dfa0851 --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/ForwardDifferenceGradientObjectiveFunction.cs @@ -0,0 +1,172 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +/// +/// Adapts an objective function with only value implemented +/// to provide a gradient as well. Gradient calculation is +/// done using the finite difference method, specifically +/// forward differences. +/// +/// For each gradient computed, the algorithm requires an +/// additional number of function evaluations equal to the +/// functions's number of input parameters. +/// +public class ForwardDifferenceGradientObjectiveFunction : IObjectiveFunction +{ + public IObjectiveFunction InnerObjectiveFunction { get; protected set; } + protected Vector LowerBound { get; set; } + protected Vector UpperBound { get; set; } + + protected bool ValueEvaluated { get; set; } = false; + protected bool GradientEvaluated { get; set; } = false; + private Vector _gradient; + + public double MinimumIncrement { get; set; } + public double RelativeIncrement { get; set; } + + public ForwardDifferenceGradientObjectiveFunction(IObjectiveFunction valueOnlyObj, Vector lowerBound, Vector upperBound, double relativeIncrement = 1e-5, double minimumIncrement = 1e-8) + { + InnerObjectiveFunction = valueOnlyObj; + LowerBound = lowerBound; + UpperBound = upperBound; + _gradient = new LinearAlgebra.Double.DenseVector(LowerBound.Count); + RelativeIncrement = relativeIncrement; + MinimumIncrement = minimumIncrement; + } + + protected void EvaluateValue() + { + ValueEvaluated = true; + } + + protected void EvaluateGradient() + { + if (!ValueEvaluated) + EvaluateValue(); + + var tmpPoint = Point.Clone(); + var tmpObj = InnerObjectiveFunction.CreateNew(); + for (int ii = 0; ii < _gradient.Count; ++ii) + { + var origPoint = tmpPoint[ii]; + var relIncr = origPoint * RelativeIncrement; + var h = Math.Max(relIncr, MinimumIncrement); + var mult = 1; + if (origPoint + h > UpperBound[ii]) + mult = -1; + + tmpPoint[ii] = origPoint + mult * h; + tmpObj.EvaluateAt(tmpPoint); + double bumpedValue = tmpObj.Value; + _gradient[ii] = (mult * bumpedValue - mult * InnerObjectiveFunction.Value) / h; + + tmpPoint[ii] = origPoint; + } + + GradientEvaluated = true; + } + + public Vector Gradient + { + get + { + if (!GradientEvaluated) + EvaluateGradient(); + return _gradient; + } + protected set { _gradient = value; } + } + + public Matrix Hessian + { + get + { + throw new NotImplementedException(); + } + } + + public bool IsGradientSupported + { + get + { + return true; + } + } + + public bool IsHessianSupported + { + get + { + return false; + } + } + + public Vector Point { get; protected set; } + + public double Value + { + get + { + if (!ValueEvaluated) + EvaluateValue(); + return this.InnerObjectiveFunction.Value; + } + } + + public IObjectiveFunction CreateNew() + { + var tmp = new ForwardDifferenceGradientObjectiveFunction(this.InnerObjectiveFunction.CreateNew(), LowerBound, UpperBound, this.RelativeIncrement, this.MinimumIncrement); + return tmp; + } + + public void EvaluateAt(Vector point) + { + Point = point; + ValueEvaluated = false; + GradientEvaluated = false; + InnerObjectiveFunction.EvaluateAt(point); + } + + public IObjectiveFunction Fork() + { + return new ForwardDifferenceGradientObjectiveFunction(this.InnerObjectiveFunction.Fork(), LowerBound, UpperBound, this.RelativeIncrement, this.MinimumIncrement) + { + Point = Point?.Clone(), + GradientEvaluated = GradientEvaluated, + ValueEvaluated = ValueEvaluated, + _gradient = _gradient?.Clone() + }; + } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/GradientHessianObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/GradientHessianObjectiveFunction.cs new file mode 100644 index 0000000..3cf022c --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/GradientHessianObjectiveFunction.cs @@ -0,0 +1,86 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +internal class GradientHessianObjectiveFunction : IObjectiveFunction +{ + readonly Func, Tuple, Matrix>> _function; + + public GradientHessianObjectiveFunction(Func, Tuple, Matrix>> function) + { + _function = function; + } + + public IObjectiveFunction CreateNew() + { + return new GradientHessianObjectiveFunction(_function); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new GradientHessianObjectiveFunction(_function) + { + Point = Point, + Value = Value, + Gradient = Gradient, + Hessian = Hessian + }; + } + + public bool IsGradientSupported + { + get { return true; } + } + + public bool IsHessianSupported + { + get { return true; } + } + + public void EvaluateAt(Vector point) + { + Point = point; + + var result = _function(point); + Value = result.Item1; + Gradient = result.Item2; + Hessian = result.Item3; + } + + public Vector Point { get; private set; } + public double Value { get; private set; } + public Vector Gradient { get; private set; } + public Matrix Hessian { get; private set; } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/GradientObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/GradientObjectiveFunction.cs new file mode 100644 index 0000000..0592d87 --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/GradientObjectiveFunction.cs @@ -0,0 +1,88 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +internal class GradientObjectiveFunction : IObjectiveFunction +{ + readonly Func, Tuple>> _function; + + public GradientObjectiveFunction(Func, Tuple>> function) + { + _function = function; + } + + public IObjectiveFunction CreateNew() + { + return new GradientObjectiveFunction(_function); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new GradientObjectiveFunction(_function) + { + Point = Point, + Value = Value, + Gradient = Gradient + }; + } + + public bool IsGradientSupported + { + get { return true; } + } + + public bool IsHessianSupported + { + get { return false; } + } + + public void EvaluateAt(Vector point) + { + Point = point; + + var result = _function(point); + Value = result.Item1; + Gradient = result.Item2; + } + + public Vector Point { get; private set; } + public double Value { get; private set; } + public Vector Gradient { get; private set; } + + public Matrix Hessian + { + get { throw new NotSupportedException(); } + } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/HessianObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/HessianObjectiveFunction.cs new file mode 100644 index 0000000..bff8504 --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/HessianObjectiveFunction.cs @@ -0,0 +1,88 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +internal class HessianObjectiveFunction : IObjectiveFunction +{ + readonly Func, Tuple>> _function; + + public HessianObjectiveFunction(Func, Tuple>> function) + { + _function = function; + } + + public IObjectiveFunction CreateNew() + { + return new HessianObjectiveFunction(_function); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new HessianObjectiveFunction(_function) + { + Point = Point, + Value = Value, + Hessian = Hessian + }; + } + + public bool IsGradientSupported + { + get { return false; } + } + + public bool IsHessianSupported + { + get { return true; } + } + + public void EvaluateAt(Vector point) + { + Point = point; + + var result = _function(point); + Value = result.Item1; + Hessian = result.Item2; + } + + public Vector Point { get; private set; } + public double Value { get; private set; } + public Matrix Hessian { get; private set; } + + public Vector Gradient + { + get { throw new NotSupportedException(); } + } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunction.cs new file mode 100644 index 0000000..0b25858 --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunction.cs @@ -0,0 +1,144 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +internal class LazyObjectiveFunction : IObjectiveFunction +{ + readonly Func, double> _function; + readonly Func, Vector> _gradient; + readonly Func, Matrix> _hessian; + + Vector _point; + + bool _hasFunctionValue; + double _functionValue; + + bool _hasGradientValue; + Vector _gradientValue; + + bool _hasHessianValue; + Matrix _hessianValue; + + public LazyObjectiveFunction(Func, double> function, Func, Vector> gradient = null, Func, Matrix> hessian = null) + { + _function = function; + _gradient = gradient; + _hessian = hessian; + + IsGradientSupported = gradient != null; + IsHessianSupported = hessian != null; + } + + public IObjectiveFunction CreateNew() + { + return new LazyObjectiveFunction(_function, _gradient, _hessian); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new LazyObjectiveFunction(_function, _gradient, _hessian) + { + _point = _point, + _hasFunctionValue = _hasFunctionValue, + _functionValue = _functionValue, + _hasGradientValue = _hasGradientValue, + _gradientValue = _gradientValue, + _hasHessianValue = _hasHessianValue, + _hessianValue = _hessianValue + }; + } + + public bool IsGradientSupported { get; private set; } + public bool IsHessianSupported { get; private set; } + + public void EvaluateAt(Vector point) + { + _point = point; + _hasFunctionValue = false; + _hasGradientValue = false; + _hasHessianValue = false; + + // don't keep references unnecessarily + _gradientValue = null; + _hessianValue = null; + } + + public Vector Point + { + get { return _point; } + } + + public double Value + { + get + { + if (!_hasFunctionValue) + { + _functionValue = _function(_point); + _hasFunctionValue = true; + } + + return _functionValue; + } + } + + public Vector Gradient + { + get + { + if (!_hasGradientValue) + { + _gradientValue = _gradient(_point); + _hasGradientValue = true; + } + + return _gradientValue; + } + } + + public Matrix Hessian + { + get + { + if (!_hasHessianValue) + { + _hessianValue = _hessian(_point); + _hasHessianValue = true; + } + + return _hessianValue; + } + } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunctionBase.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunctionBase.cs new file mode 100644 index 0000000..c8c999f --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/LazyObjectiveFunctionBase.cs @@ -0,0 +1,150 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +public abstract class LazyObjectiveFunctionBase : IObjectiveFunction +{ + Vector _point; + + protected bool HasFunctionValue { get; set; } + protected double FunctionValue { get; set; } + + protected bool HasGradientValue { get; set; } + protected Vector GradientValue { get; set; } + + protected bool HasHessianValue { get; set; } + protected Matrix HessianValue { get; set; } + + protected LazyObjectiveFunctionBase(bool gradientSupported, bool hessianSupported) + { + IsGradientSupported = gradientSupported; + IsHessianSupported = hessianSupported; + } + + public abstract IObjectiveFunction CreateNew(); + + public virtual IObjectiveFunction Fork() + { + // we need to deep-clone values since they may be updated inplace on evaluation + LazyObjectiveFunctionBase fork = (LazyObjectiveFunctionBase)CreateNew(); + fork._point = _point?.Clone(); + fork.HasFunctionValue = HasFunctionValue; + fork.FunctionValue = FunctionValue; + fork.HasGradientValue = HasGradientValue; + fork.GradientValue = GradientValue?.Clone(); + fork.HasHessianValue = HasHessianValue; + fork.HessianValue = HessianValue?.Clone(); + return fork; + } + + public bool IsGradientSupported { get; private set; } + public bool IsHessianSupported { get; private set; } + + public void EvaluateAt(Vector point) + { + _point = point; + HasFunctionValue = false; + HasGradientValue = false; + HasHessianValue = false; + } + + protected abstract void EvaluateValue(); + + protected virtual void EvaluateGradient() + { + Gradient = null; + } + + protected virtual void EvaluateHessian() + { + Hessian = null; + } + + public Vector Point + { + get { return _point; } + } + + public double Value + { + get + { + if (!HasFunctionValue) + { + EvaluateValue(); + } + + return FunctionValue; + } + protected set + { + FunctionValue = value; + HasFunctionValue = true; + } + } + + public Vector Gradient + { + get + { + if (!HasGradientValue) + { + EvaluateGradient(); + } + + return GradientValue; + } + protected set + { + GradientValue = value; + HasGradientValue = true; + } + } + + public Matrix Hessian + { + get + { + if (!HasHessianValue) + { + EvaluateHessian(); + } + + return HessianValue; + } + protected set + { + HessianValue = value; + HasHessianValue = true; + } + } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/NonlinearObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/NonlinearObjectiveFunction.cs new file mode 100644 index 0000000..d6284aa --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/NonlinearObjectiveFunction.cs @@ -0,0 +1,450 @@ +using MathNet.Numerics.LinearAlgebra; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +internal class NonlinearObjectiveFunction : IObjectiveModel +{ + #region Private Variables + + readonly Func, Vector, Vector> userFunction; // (p, x) => f(x; p) + readonly Func, Vector, Matrix> userDerivative; // (p, x) => df(x; p)/dp + readonly int accuracyOrder; // the desired accuracy order to evaluate the jacobian by numerical approximaiton. + + Vector coefficients; + + bool hasFunctionValue; + double functionValue; // the residual sum of squares, residuals * residuals. + Vector residuals; // the weighted error values + + bool hasJacobianValue; + Matrix jacobianValue; // the Jacobian matrix. + Vector gradientValue; // the Gradient vector. + Matrix hessianValue; // the Hessian matrix. + + #endregion Private Variables + + #region Public Variables + + /// + /// Set or get the values of the independent variable. + /// + public Vector ObservedX { get; private set; } + + /// + /// Set or get the values of the observations. + /// + public Vector ObservedY { get; private set; } + + /// + /// Set or get the values of the weights for the observations. + /// + public Matrix Weights { get; private set; } + private Vector L; // Weights = LL' + + /// + /// Get whether parameters are fixed or free. + /// + public List IsFixed { get; private set; } + + /// + /// Get the number of observations. + /// + public int NumberOfObservations { get { return (ObservedY == null) ? 0 : ObservedY.Count; } } + + /// + /// Get the number of unknown parameters. + /// + public int NumberOfParameters { get { return (Point == null) ? 0 : Point.Count; } } + + /// + /// Get the degree of freedom + /// + public int DegreeOfFreedom + { + get + { + var df = NumberOfObservations - NumberOfParameters; + if (IsFixed != null) + { + df = df + IsFixed.Count(p => p == true); + } + + return df; + } + } + + /// + /// Get the number of calls to function. + /// + public int FunctionEvaluations { get; set; } + + /// + /// Get the number of calls to jacobian. + /// + public int JacobianEvaluations { get; set; } + + #endregion Public Variables + + public NonlinearObjectiveFunction(Func, Vector, Vector> function, + Func, Vector, Matrix> derivative = null, int accuracyOrder = 2) + { + this.userFunction = function; + this.userDerivative = derivative; + this.accuracyOrder = Math.Min(6, Math.Max(1, accuracyOrder)); + } + + public IObjectiveModel Fork() + { + return new NonlinearObjectiveFunction(userFunction, userDerivative, accuracyOrder) + { + ObservedX = ObservedX, + ObservedY = ObservedY, + Weights = Weights, + + coefficients = coefficients, + + hasFunctionValue = hasFunctionValue, + functionValue = functionValue, + + hasJacobianValue = hasJacobianValue, + jacobianValue = jacobianValue, + gradientValue = gradientValue, + hessianValue = hessianValue + }; + } + + public IObjectiveModel CreateNew() + { + return new NonlinearObjectiveFunction(userFunction, userDerivative, accuracyOrder); + } + + /// + /// Set or get the values of the parameters. + /// + public Vector Point { get { return coefficients; } } + + /// + /// Get the y-values of the fitted model that correspond to the independent values. + /// + public Vector ModelValues { get; private set; } + + /// + /// Get the residual sum of squares. + /// + public double Value + { + get + { + if (!hasFunctionValue) + { + EvaluateFunction(); + hasFunctionValue = true; + } + + return functionValue; + } + } + + /// + /// Get the Gradient vector of x and p. + /// + public Vector Gradient + { + get + { + if (!hasJacobianValue) + { + EvaluateJacobian(); + hasJacobianValue = true; + } + + return gradientValue; + } + } + + /// + /// Get the Hessian matrix of x and p, J'WJ + /// + public Matrix Hessian + { + get + { + if (!hasJacobianValue) + { + EvaluateJacobian(); + hasJacobianValue = true; + } + + return hessianValue; + } + } + + public bool IsGradientSupported { get { return true; } } + public bool IsHessianSupported { get { return true; } } + + /// + /// Set observed data to fit. + /// + public void SetObserved(Vector observedX, Vector observedY, Vector weights = null) + { + if (observedX == null || observedY == null) + { + throw new ArgumentNullException("The data set can't be null."); + } + + if (observedX.Count != observedY.Count) + { + throw new ArgumentException("The observed x data can't have different from observed y data."); + } + + ObservedX = observedX; + ObservedY = observedY; + + if (weights != null && weights.Count != observedY.Count) + { + throw new ArgumentException("The weightings can't have different from observations."); + } + + if (weights != null && weights.Count(x => double.IsInfinity(x) || double.IsNaN(x)) > 0) + { + throw new ArgumentException("The weightings are not well-defined."); + } + + if (weights != null && weights.Count(x => x == 0) == weights.Count) + { + throw new ArgumentException("All the weightings can't be zero."); + } + + if (weights != null && weights.Count(x => x < 0) > 0) + { + weights = weights.PointwiseAbs(); + } + + Weights = (weights == null) + ? null + : Matrix.Build.DenseOfDiagonalVector(weights); + + L = (weights == null) + ? null + : Weights.Diagonal().PointwiseSqrt(); + } + + /// + /// Set parameters and bounds. + /// + /// The initial values of parameters. + /// The list to the parameters fix or free. + public void SetParameters(Vector initialGuess, List isFixed = null) + { + if (initialGuess == null) + { + throw new ArgumentNullException("initialGuess"); + } + + coefficients = initialGuess; + + if (isFixed != null && isFixed.Count != initialGuess.Count) + { + throw new ArgumentException("The isFixed can't have different size from the initial guess."); + } + + if (isFixed != null && isFixed.Count(p => p == true) == isFixed.Count) + { + throw new ArgumentException("All the parameters can't be fixed."); + } + + IsFixed = isFixed; + } + + public void EvaluateAt(Vector parameters) + { + if (parameters == null) + { + throw new ArgumentNullException("parameters"); + } + + if (parameters.Count(p => double.IsNaN(p) || double.IsInfinity(p)) > 0) + { + throw new ArgumentException("The parameters must be finite."); + } + + coefficients = parameters; + hasFunctionValue = false; + hasJacobianValue = false; + + jacobianValue = null; + gradientValue = null; + hessianValue = null; + } + + public IObjectiveFunction ToObjectiveFunction() + { + Tuple, Matrix> function(Vector point) + { + EvaluateAt(point); + + return new Tuple, Matrix>(Value, Gradient, Hessian); + } + + var objective = new GradientHessianObjectiveFunction(function); + return objective; + } + + #region Private Methods + + private void EvaluateFunction() + { + // Calculates the residuals, (y[i] - f(x[i]; p)) * L[i] + if (ModelValues == null) + { + ModelValues = Vector.Build.Dense(NumberOfObservations); + } + + ModelValues = userFunction(Point, ObservedX); + FunctionEvaluations++; + + // calculate the weighted residuals + residuals = (Weights == null) + ? ObservedY - ModelValues + : (ObservedY - ModelValues).PointwiseMultiply(L); + + // Calculate the residual sum of squares + functionValue = residuals.DotProduct(residuals); + + return; + } + + private void EvaluateJacobian() + { + // Calculates the jacobian of x and p. + if (userDerivative != null) + { + // analytical jacobian + jacobianValue = userDerivative(Point, ObservedX); + JacobianEvaluations++; + } + else + { + // numerical jacobian + jacobianValue = NumericalJacobian(Point, ModelValues, accuracyOrder); + FunctionEvaluations += accuracyOrder; + } + + // weighted jacobian + for (int i = 0; i < NumberOfObservations; i++) + { + for (int j = 0; j < NumberOfParameters; j++) + { + if (IsFixed != null && IsFixed[j]) + { + // if j-th parameter is fixed, set J[i, j] = 0 + jacobianValue[i, j] = 0.0; + } + else + { + jacobianValue[i, j] = (Weights == null) + ? jacobianValue[i, j] + : jacobianValue[i, j] * L[j]; + } + } + } + + // Gradient, g = -J'W(y − f(x; p)) = -J'L(L'E) = -J'LR + gradientValue = -jacobianValue.Transpose() * residuals; + + // approximated Hessian, H = J'WJ + ∑LRiHi ~ J'WJ near the minimum + hessianValue = jacobianValue.Transpose() * jacobianValue; + } + + private Matrix NumericalJacobian(Vector parameters, Vector currentValues, int accuracyOrder = 2) + { + const double sqrtEpsilon = 1.4901161193847656250E-8; // sqrt(machineEpsilon) + + Matrix derivertives = Matrix.Build.Dense(NumberOfObservations, NumberOfParameters); + + var d = 0.000003 * parameters.PointwiseAbs().PointwiseMaximum(sqrtEpsilon); + + var h = Vector.Build.Dense(NumberOfParameters); + for (int j = 0; j < NumberOfParameters; j++) + { + h[j] = d[j]; + + if (accuracyOrder >= 6) + { + // f'(x) = {- f(x - 3h) + 9f(x - 2h) - 45f(x - h) + 45f(x + h) - 9f(x + 2h) + f(x + 3h)} / 60h + O(h^6) + var f1 = userFunction(parameters - 3 * h, ObservedX); + var f2 = userFunction(parameters - 2 * h, ObservedX); + var f3 = userFunction(parameters - h, ObservedX); + var f4 = userFunction(parameters + h, ObservedX); + var f5 = userFunction(parameters + 2 * h, ObservedX); + var f6 = userFunction(parameters + 3 * h, ObservedX); + + var prime = (-f1 + 9 * f2 - 45 * f3 + 45 * f4 - 9 * f5 + f6) / (60 * h[j]); + derivertives.SetColumn(j, prime); + } + else if (accuracyOrder == 5) + { + // f'(x) = {-137f(x) + 300f(x + h) - 300f(x + 2h) + 200f(x + 3h) - 75f(x + 4h) + 12f(x + 5h)} / 60h + O(h^5) + var f1 = currentValues; + var f2 = userFunction(parameters + h, ObservedX); + var f3 = userFunction(parameters + 2 * h, ObservedX); + var f4 = userFunction(parameters + 3 * h, ObservedX); + var f5 = userFunction(parameters + 4 * h, ObservedX); + var f6 = userFunction(parameters + 5 * h, ObservedX); + + var prime = (-137 * f1 + 300 * f2 - 300 * f3 + 200 * f4 - 75 * f5 + 12 * f6) / (60 * h[j]); + derivertives.SetColumn(j, prime); + } + else if (accuracyOrder == 4) + { + // f'(x) = {f(x - 2h) - 8f(x - h) + 8f(x + h) - f(x + 2h)} / 12h + O(h^4) + var f1 = userFunction(parameters - 2 * h, ObservedX); + var f2 = userFunction(parameters - h, ObservedX); + var f3 = userFunction(parameters + h, ObservedX); + var f4 = userFunction(parameters + 2 * h, ObservedX); + + var prime = (f1 - 8 * f2 + 8 * f3 - f4) / (12 * h[j]); + derivertives.SetColumn(j, prime); + } + else if (accuracyOrder == 3) + { + // f'(x) = {-11f(x) + 18f(x + h) - 9f(x + 2h) + 2f(x + 3h)} / 6h + O(h^3) + var f1 = currentValues; + var f2 = userFunction(parameters + h, ObservedX); + var f3 = userFunction(parameters + 2 * h, ObservedX); + var f4 = userFunction(parameters + 3 * h, ObservedX); + + var prime = (-11 * f1 + 18 * f2 - 9 * f3 + 2 * f4) / (6 * h[j]); + derivertives.SetColumn(j, prime); + } + else if (accuracyOrder == 2) + { + // f'(x) = {f(x + h) - f(x - h)} / 2h + O(h^2) + var f1 = userFunction(parameters + h, ObservedX); + var f2 = userFunction(parameters - h, ObservedX); + + var prime = (f1 - f2) / (2 * h[j]); + derivertives.SetColumn(j, prime); + } + else + { + // f'(x) = {- f(x) + f(x + h)} / h + O(h) + var f1 = currentValues; + var f2 = userFunction(parameters + h, ObservedX); + + var prime = (-f1 + f2) / h[j]; + derivertives.SetColumn(j, prime); + } + + h[j] = 0; + } + + return derivertives; + } + + #endregion Private Methods +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/ObjectiveFunctionBase.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/ObjectiveFunctionBase.cs new file mode 100644 index 0000000..c00fd5f --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/ObjectiveFunctionBase.cs @@ -0,0 +1,70 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +public abstract class ObjectiveFunctionBase : IObjectiveFunction +{ + protected ObjectiveFunctionBase(bool isGradientSupported, bool isHessianSupported) + { + IsGradientSupported = isGradientSupported; + IsHessianSupported = isHessianSupported; + } + + public abstract IObjectiveFunction CreateNew(); + + public virtual IObjectiveFunction Fork() + { + // we need to deep-clone values since they may be updated inplace on evaluation + ObjectiveFunctionBase objective = (ObjectiveFunctionBase)CreateNew(); + objective.Point = Point == null ? null : Point.Clone(); + objective.Value = Value; + objective.Gradient = Gradient == null ? null : Gradient.Clone(); + objective.Hessian = Hessian == null ? null : Hessian.Clone(); + return objective; + } + + public bool IsGradientSupported { get; private set; } + public bool IsHessianSupported { get; private set; } + + public void EvaluateAt(Vector point) + { + Point = point; + Evaluate(); + } + + protected abstract void Evaluate(); + + public Vector Point { get; private set; } + public double Value { get; protected set; } + public Vector Gradient { get; protected set; } + public Matrix Hessian { get; protected set; } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/ScalarObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/ScalarObjectiveFunction.cs new file mode 100644 index 0000000..658b32c --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/ScalarObjectiveFunction.cs @@ -0,0 +1,113 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +internal class LazyScalarObjectiveFunctionEvaluation : IScalarObjectiveFunctionEvaluation +{ + double? _value; + double? _derivative; + double? _secondDerivative; + readonly ScalarObjectiveFunction _objectiveObject; + readonly double _point; + + public LazyScalarObjectiveFunctionEvaluation(ScalarObjectiveFunction f, double point) + { + _objectiveObject = f; + _point = point; + } + + double SetValue() + { + _value = _objectiveObject.Objective(_point); + return _value.Value; + } + + double SetDerivative() + { + _derivative = _objectiveObject.Derivative(_point); + return _derivative.Value; + } + + double SetSecondDerivative() + { + _secondDerivative = _objectiveObject.SecondDerivative(_point); + return _secondDerivative.Value; + } + + public double Point { get { return _point; } } + public double Value { get { return _value ?? SetValue(); } } + public double Derivative { get { return _derivative ?? SetDerivative(); } } + public double SecondDerivative { get { return _secondDerivative ?? SetSecondDerivative(); } } +} + +internal class ScalarObjectiveFunction : IScalarObjectiveFunction +{ + public Func Objective { get; private set; } + public Func Derivative { get; private set; } + public Func SecondDerivative { get; private set; } + + public ScalarObjectiveFunction(Func objective) + { + Objective = objective; + Derivative = null; + SecondDerivative = null; + } + + public ScalarObjectiveFunction(Func objective, Func derivative) + { + Objective = objective; + Derivative = derivative; + SecondDerivative = null; + } + + public ScalarObjectiveFunction(Func objective, Func derivative, Func secondDerivative) + { + Objective = objective; + Derivative = derivative; + SecondDerivative = secondDerivative; + } + + public bool IsDerivativeSupported + { + get { return Derivative != null; } + } + + public bool IsSecondDerivativeSupported + { + get { return SecondDerivative != null; } + } + + public IScalarObjectiveFunctionEvaluation Evaluate(double point) + { + return new LazyScalarObjectiveFunctionEvaluation(this, point); + } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/ScalarValueObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/ScalarValueObjectiveFunction.cs new file mode 100644 index 0000000..9fcd30a --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/ScalarValueObjectiveFunction.cs @@ -0,0 +1,79 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +internal class ScalarValueObjectiveFunctionEvaluation : IScalarObjectiveFunctionEvaluation +{ + public ScalarValueObjectiveFunctionEvaluation(double point, double value) + { + Point = point; + Value = value; + } + + public double Point { get; } + public double Value { get; } + + public double Derivative + { + get { throw new NotSupportedException(); } + } + + public double SecondDerivative + { + get { throw new NotSupportedException(); } + } +} + +internal class ScalarValueObjectiveFunction : IScalarObjectiveFunction +{ + public Func Objective { get; private set; } + + public ScalarValueObjectiveFunction(Func objective) + { + Objective = objective; + } + + public bool IsDerivativeSupported + { + get { return false; } + } + + public bool IsSecondDerivativeSupported + { + get { return false; } + } + + public IScalarObjectiveFunctionEvaluation Evaluate(double point) + { + return new ScalarValueObjectiveFunctionEvaluation(point, Objective(point)); + } +} diff --git a/MathNet.Numerics/Optimization/ObjectiveFunctions/ValueObjectiveFunction.cs b/MathNet.Numerics/Optimization/ObjectiveFunctions/ValueObjectiveFunction.cs new file mode 100644 index 0000000..e61e844 --- /dev/null +++ b/MathNet.Numerics/Optimization/ObjectiveFunctions/ValueObjectiveFunction.cs @@ -0,0 +1,88 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.ObjectiveFunctions; + +internal class ValueObjectiveFunction : IObjectiveFunction +{ + readonly Func, double> _function; + + public ValueObjectiveFunction(Func, double> function) + { + _function = function; + } + + public IObjectiveFunction CreateNew() + { + return new ValueObjectiveFunction(_function); + } + + public IObjectiveFunction Fork() + { + // no need to deep-clone values since they are replaced on evaluation + return new ValueObjectiveFunction(_function) + { + Point = Point, + Value = Value, + }; + } + + public bool IsGradientSupported + { + get { return false; } + } + + public bool IsHessianSupported + { + get { return false; } + } + + public void EvaluateAt(Vector point) + { + Point = point; + Value = _function(point); + } + + public Vector Point { get; private set; } + public double Value { get; private set; } + + public Matrix Hessian + { + get { throw new NotSupportedException(); } + } + + public Vector Gradient + { + get { throw new NotSupportedException(); } + } +} diff --git a/MathNet.Numerics/Optimization/QuadraticGradientProjectionSearch.cs b/MathNet.Numerics/Optimization/QuadraticGradientProjectionSearch.cs new file mode 100644 index 0000000..1627c3f --- /dev/null +++ b/MathNet.Numerics/Optimization/QuadraticGradientProjectionSearch.cs @@ -0,0 +1,127 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Optimization; + +public static class QuadraticGradientProjectionSearch +{ + public static GradientProjectionResult Search(Vector x0, Vector gradient, Matrix hessian, Vector lowerBound, Vector upperBound) + { + List isFixed = new List(x0.Count); + List breakpoint = new List(x0.Count); + for (int ii = 0; ii < x0.Count; ++ii) + { + breakpoint.Add(0.0); + isFixed.Add(false); + if (gradient[ii] < 0) + breakpoint[ii] = (x0[ii] - upperBound[ii]) / gradient[ii]; + else if (gradient[ii] > 0) + breakpoint[ii] = (x0[ii] - lowerBound[ii]) / gradient[ii]; + else + { + if (Math.Abs(x0[ii] - upperBound[ii]) < 100 * Double.Epsilon || Math.Abs(x0[ii] - lowerBound[ii]) < 100 * Double.Epsilon) + breakpoint[ii] = 0.0; + else + breakpoint[ii] = Double.PositiveInfinity; + } + } + + var orderedBreakpoint = new List(x0.Count); + orderedBreakpoint.AddRange(breakpoint); + orderedBreakpoint.Sort(); + + // Compute initial state variables + var d = -gradient; + for (int ii = 0; ii < d.Count; ++ii) + if (breakpoint[ii] <= 0.0) + d[ii] *= 0.0; + + int jj = -1; + var x = x0; + var f1 = gradient * d; + var f2 = 0.5 * d * hessian * d; + var sMin = -f1 / f2; + var maxS = orderedBreakpoint[0]; + + if (sMin < maxS) + return new GradientProjectionResult(x + sMin * d, 0, isFixed); + + // while minimum of the last quadratic piece observed is beyond the interval searched + while (true) + { + // update data to the beginning of the interval we're searching + jj += 1; + x = x + d * maxS; + maxS = orderedBreakpoint[jj + 1] - orderedBreakpoint[jj]; + + int fixedCount = 0; + for (int ii = 0; ii < d.Count; ++ii) + if (orderedBreakpoint[jj] >= breakpoint[ii]) + { + d[ii] *= 0.0; + isFixed[ii] = true; + fixedCount += 1; + } + + if (Double.IsPositiveInfinity(orderedBreakpoint[jj + 1])) + return new GradientProjectionResult(x, fixedCount, isFixed); + + f1 = gradient * d + (x - x0) * hessian * d; + f2 = d * hessian * d; + + sMin = -f1 / f2; + + if (sMin < maxS) + return new GradientProjectionResult(x + sMin * d, fixedCount, isFixed); + else if (jj + 1 >= orderedBreakpoint.Count - 1) + { + isFixed[isFixed.Count - 1] = true; + return new GradientProjectionResult(x + maxS * d, lowerBound.Count, isFixed); + } + } + } + + public struct GradientProjectionResult + { + public GradientProjectionResult(Vector cauchyPoint, int fixedCount, List isFixed) + { + CauchyPoint = cauchyPoint; + FixedCount = fixedCount; + IsFixed = isFixed; + } + public Vector CauchyPoint { get; } + public int FixedCount { get; } + public List IsFixed { get; } + } +} diff --git a/MathNet.Numerics/Optimization/ScalarMinimizationResult.cs b/MathNet.Numerics/Optimization/ScalarMinimizationResult.cs new file mode 100644 index 0000000..a4b32e7 --- /dev/null +++ b/MathNet.Numerics/Optimization/ScalarMinimizationResult.cs @@ -0,0 +1,45 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Optimization; + +public class ScalarMinimizationResult +{ + public double MinimizingPoint { get { return FunctionInfoAtMinimum.Point; } } + public IScalarObjectiveFunctionEvaluation FunctionInfoAtMinimum { get; private set; } + public int Iterations { get; private set; } + public ExitCondition ReasonForExit { get; private set; } + + public ScalarMinimizationResult(IScalarObjectiveFunctionEvaluation functionInfo, int iterations, ExitCondition reasonForExit) + { + FunctionInfoAtMinimum = functionInfo; + Iterations = iterations; + ReasonForExit = reasonForExit; + } +} diff --git a/MathNet.Numerics/Optimization/TrustRegion/ITrustRegionSubProblem.cs b/MathNet.Numerics/Optimization/TrustRegion/ITrustRegionSubProblem.cs new file mode 100644 index 0000000..84500ca --- /dev/null +++ b/MathNet.Numerics/Optimization/TrustRegion/ITrustRegionSubProblem.cs @@ -0,0 +1,11 @@ +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.TrustRegion; + +public interface ITrustRegionSubproblem +{ + Vector Pstep { get; } + bool HitBoundary { get; } + + void Solve(IObjectiveModel objective, double radius); +} diff --git a/MathNet.Numerics/Optimization/TrustRegion/Subproblems/DogLegSubproblem.cs b/MathNet.Numerics/Optimization/TrustRegion/Subproblems/DogLegSubproblem.cs new file mode 100644 index 0000000..406b026 --- /dev/null +++ b/MathNet.Numerics/Optimization/TrustRegion/Subproblems/DogLegSubproblem.cs @@ -0,0 +1,44 @@ +using MathNet.Numerics.LinearAlgebra; + +namespace MathNet.Numerics.Optimization.TrustRegion.Subproblems; + +internal class DogLegSubproblem : ITrustRegionSubproblem +{ + public Vector Pstep { get; private set; } + + public bool HitBoundary { get; private set; } + + public void Solve(IObjectiveModel objective, double delta) + { + var Gradient = objective.Gradient; + var Hessian = objective.Hessian; + + // newton point, the Gauss–Newton step by solving the normal equations + var Pgn = -Hessian.PseudoInverse() * Gradient; // Hessian.Solve(Gradient) fails so many times... + + // cauchy point, steepest descent direction is given by + var alpha = Gradient.DotProduct(Gradient) / (Hessian * Gradient).DotProduct(Gradient); + var Psd = -alpha * Gradient; + + // update step and prectted reduction + if (Pgn.L2Norm() <= delta) + { + // Pgn is inside trust region radius + HitBoundary = false; + Pstep = Pgn; + } + else if (alpha * Psd.L2Norm() >= delta) + { + // Psd is outside trust region radius + HitBoundary = true; + Pstep = delta / Psd.L2Norm() * Psd; + } + else + { + // Pstep is intersection of the trust region boundary + HitBoundary = true; + var beta = Util.FindBeta(alpha, Psd, Pgn, delta).Item2; + Pstep = alpha * Psd + beta * (Pgn - alpha * Psd); + } + } +} diff --git a/MathNet.Numerics/Optimization/TrustRegion/Subproblems/NewtonCGSubproblem.cs b/MathNet.Numerics/Optimization/TrustRegion/Subproblems/NewtonCGSubproblem.cs new file mode 100644 index 0000000..b94bc84 --- /dev/null +++ b/MathNet.Numerics/Optimization/TrustRegion/Subproblems/NewtonCGSubproblem.cs @@ -0,0 +1,65 @@ +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.TrustRegion.Subproblems; + +internal class NewtonCGSubproblem : ITrustRegionSubproblem +{ + public Vector Pstep { get; private set; } + + public bool HitBoundary { get; private set; } + + public void Solve(IObjectiveModel objective, double delta) + { + var Gradient = objective.Gradient; + var Hessian = objective.Hessian; + + // define tolerance + var gnorm = Gradient.L2Norm(); + var tolerance = Math.Min(0.5, Math.Sqrt(gnorm)) * gnorm; + + // initialize internal variables + var z = Vector.Build.Dense(Hessian.RowCount); + var r = Gradient; + var d = -r; + + while (true) + { + var Bd = Hessian * d; + var dBd = d.DotProduct(Bd); + + if (dBd <= 0) + { + var t = Util.FindBeta(1, z, d, delta); + Pstep = z + t.Item1 * d; + HitBoundary = true; + return; + } + + var r_sq = r.DotProduct(r); + var alpha = r_sq / dBd; + var znext = z + alpha * d; + if (znext.L2Norm() >= delta) + { + var t = Util.FindBeta(1, z, d, delta); + Pstep = z + t.Item2 * d; + HitBoundary = true; + return; + } + + var rnext = r + alpha * Bd; + var rnext_sq = rnext.DotProduct(rnext); + if (Math.Sqrt(rnext_sq) < tolerance) + { + Pstep = znext; + HitBoundary = false; + return; + } + + z = znext; + r = rnext; + d = -rnext + rnext_sq / r_sq * d; + } + } +} diff --git a/MathNet.Numerics/Optimization/TrustRegion/Subproblems/Util.cs b/MathNet.Numerics/Optimization/TrustRegion/Subproblems/Util.cs new file mode 100644 index 0000000..ffc2d0d --- /dev/null +++ b/MathNet.Numerics/Optimization/TrustRegion/Subproblems/Util.cs @@ -0,0 +1,35 @@ +using MathNet.Numerics.LinearAlgebra; + +using System; + +namespace MathNet.Numerics.Optimization.TrustRegion.Subproblems; + +internal static class Util +{ + public static Tuple FindBeta(double alpha, Vector sd, Vector gn, double delta) + { + // Pstep is intersection of the trust region boundary + // Pstep = α*Psd + β*(Pgn - α*Psd) + // find r so that ||Pstep|| = Δ + // z = α*Psd, d = (Pgn - z) + // (d^2)β^2 + (2*z*d)β + (z^2 - Δ^2) = 0 + // + // positive β is used for the quadratic formula + + var z = alpha * sd; + var d = gn - z; + + var a = d.DotProduct(d); + var b = 2.0 * z.DotProduct(d); + var c = z.DotProduct(z) - delta * delta; + + var aux = b + ((b >= 0) ? 1.0 : -1.0) * Math.Sqrt(b * b - 4.0 * a * c); + var beta1 = -aux / 2.0 / a; + var beta2 = -2.0 * c / aux; + + // return sorted beta + return (beta1 < beta2) + ? new Tuple(beta1, beta2) + : new Tuple(beta2, beta1); + } +} diff --git a/MathNet.Numerics/Optimization/TrustRegion/TrustRegionDogLegMinimizer.cs b/MathNet.Numerics/Optimization/TrustRegion/TrustRegionDogLegMinimizer.cs new file mode 100644 index 0000000..ac79035 --- /dev/null +++ b/MathNet.Numerics/Optimization/TrustRegion/TrustRegionDogLegMinimizer.cs @@ -0,0 +1,11 @@ +namespace MathNet.Numerics.Optimization.TrustRegion; + +public sealed class TrustRegionDogLegMinimizer : TrustRegionMinimizerBase +{ + /// + /// Non-linear least square fitting by the trust region dogleg algorithm. + /// + public TrustRegionDogLegMinimizer(double gradientTolerance = 1E-8, double stepTolerance = 1E-8, double functionTolerance = 1E-8, double radiusTolerance = 1E-8, int maximumIterations = -1) + : base(TrustRegionSubproblem.DogLeg(), gradientTolerance, stepTolerance, functionTolerance, radiusTolerance, maximumIterations) + { } +} diff --git a/MathNet.Numerics/Optimization/TrustRegion/TrustRegionMinimizerBase.cs b/MathNet.Numerics/Optimization/TrustRegion/TrustRegionMinimizerBase.cs new file mode 100644 index 0000000..a2d1746 --- /dev/null +++ b/MathNet.Numerics/Optimization/TrustRegion/TrustRegionMinimizerBase.cs @@ -0,0 +1,245 @@ +using MathNet.Numerics.LinearAlgebra; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Optimization.TrustRegion; + +public abstract class TrustRegionMinimizerBase : NonlinearMinimizerBase +{ + /// + /// The trust region subproblem. + /// + public static ITrustRegionSubproblem Subproblem; + + /// + /// The stopping threshold for the trust region radius. + /// + public static double RadiusTolerance { get; set; } + + public TrustRegionMinimizerBase(ITrustRegionSubproblem subproblem, + double gradientTolerance = 1E-8, double stepTolerance = 1E-8, double functionTolerance = 1E-8, double radiusTolerance = 1E-8, int maximumIterations = -1) + : base(gradientTolerance, stepTolerance, functionTolerance, maximumIterations) + { + if (subproblem == null) + throw new ArgumentNullException("subproblem"); + + Subproblem = subproblem; + RadiusTolerance = radiusTolerance; + } + + public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, Vector initialGuess, + Vector lowerBound = null, Vector upperBound = null, Vector scales = null, List isFixed = null) + { + return Minimum(Subproblem, objective, initialGuess, lowerBound, upperBound, scales, isFixed, + GradientTolerance, StepTolerance, FunctionTolerance, RadiusTolerance, MaximumIterations); + } + + public NonlinearMinimizationResult FindMinimum(IObjectiveModel objective, double[] initialGuess, + double[] lowerBound = null, double[] upperBound = null, double[] scales = null, bool[] isFixed = null) + { + var lb = (lowerBound == null) ? null : CreateVector.Dense(lowerBound); + var ub = (upperBound == null) ? null : CreateVector.Dense(upperBound); + var sc = (scales == null) ? null : CreateVector.Dense(scales); + var fx = (isFixed == null) ? null : isFixed.ToList(); + + return Minimum(Subproblem, objective, CreateVector.DenseOfArray(initialGuess), lb, ub, sc, fx, + GradientTolerance, StepTolerance, FunctionTolerance, RadiusTolerance, MaximumIterations); + } + + /// + /// Non-linear least square fitting by the trust-region algorithm. + /// + /// The objective model, including function, jacobian, observations, and parameter bounds. + /// The subproblem + /// The initial guess values. + /// The stopping threshold for L2 norm of the residuals. + /// The stopping threshold for infinity norm of the gradient vector. + /// The stopping threshold for L2 norm of the change of parameters. + /// The stopping threshold for trust region radius + /// The max iterations. + /// + public static NonlinearMinimizationResult Minimum(ITrustRegionSubproblem subproblem, IObjectiveModel objective, Vector initialGuess, + Vector lowerBound = null, Vector upperBound = null, Vector scales = null, List isFixed = null, + double gradientTolerance = 1E-8, double stepTolerance = 1E-8, double functionTolerance = 1E-8, double radiusTolerance = 1E-18, int maximumIterations = -1) + { + // Non-linear least square fitting by the trust-region algorithm. + // + // For given datum pair (x, y), uncertainties σ (or weighting W = 1 / σ^2) and model function f = f(x; p), + // let's find the parameters of the model so that the sum of the quares of the deviations is minimized. + // + // F(p) = 1/2 * ∑{ Wi * (yi - f(xi; p))^2 } + // pbest = argmin F(p) + // + // Here, we will use the following terms: + // Weighting W is the diagonal matrix and can be decomposed as LL', so L = 1/σ + // Residuals, R = L(y - f(x; p)) + // Residual sum of squares, RSS = ||R||^2 = R.DotProduct(R) + // Jacobian J = df(x; p)/dp + // Gradient g = -J'W(y − f(x; p)) = -J'LR + // Approximated Hessian H = J'WJ + // + // The trust region algorithm is summarized as follows: + // initially set trust-region radius, Δ + // repeat + // solve subproblem + // update Δ: + // let ρ = (RSS - RSSnew) / predRed + // if ρ > 0.75, Δ = 2Δ + // if ρ < 0.25, Δ = Δ/4 + // if ρ > eta, P = P + ΔP + // + // References: + // [1]. Madsen, K., H. B. Nielsen, and O. Tingleff. + // "Methods for Non-Linear Least Squares Problems. Technical University of Denmark, 2004. Lecture notes." (2004). + // Available Online from: http://orbit.dtu.dk/files/2721358/imm3215.pdf + // [2]. Nocedal, Jorge, and Stephen J. Wright. + // Numerical optimization (2006): 101-134. + // [3]. SciPy + // Available Online from: https://github.com/scipy/scipy/blob/master/scipy/optimize/_trustregion.py + + double maxDelta = 1000; + double eta = 0; + + if (objective == null) + throw new ArgumentNullException("objective"); + + ValidateBounds(initialGuess, lowerBound, upperBound, scales); + + objective.SetParameters(initialGuess, isFixed); + + ExitCondition exitCondition = ExitCondition.None; + + // First, calculate function values and setup variables + var P = ProjectToInternalParameters(initialGuess); // current internal parameters + var Pstep = Vector.Build.Dense(P.Count); // the change of parameters + var RSS = EvaluateFunction(objective, initialGuess); // Residual Sum of Squares + + if (maximumIterations < 0) + { + maximumIterations = 200 * (initialGuess.Count + 1); + } + + // if RSS == NaN, stop + if (double.IsNaN(RSS)) + { + exitCondition = ExitCondition.InvalidValues; + return new NonlinearMinimizationResult(objective, -1, exitCondition); + } + + // When only function evaluation is needed, set maximumIterations to zero, + if (maximumIterations == 0) + { + exitCondition = ExitCondition.ManuallyStopped; + } + + // if ||R||^2 <= fTol, stop + if (RSS <= functionTolerance) + { + exitCondition = ExitCondition.Converged; // SmallRSS + } + + // evaluate projected gradient and Hessian + var jac = EvaluateJacobian(objective, P); + var Gradient = jac.Item1; // objective.Gradient; + var Hessian = jac.Item2; // objective.Hessian; + + // if ||g||_oo <= gtol, found and stop + if (Gradient.InfinityNorm() <= gradientTolerance) + { + exitCondition = ExitCondition.RelativeGradient; // SmallGradient + } + + if (exitCondition != ExitCondition.None) + { + return new NonlinearMinimizationResult(objective, -1, exitCondition); + } + + // initialize trust-region radius, Δ + double delta = Gradient.DotProduct(Gradient) / (Hessian * Gradient).DotProduct(Gradient); + delta = Math.Max(1.0, Math.Min(delta, maxDelta)); + + int iterations = 0; + bool hitBoundary = false; + while (iterations < maximumIterations && exitCondition == ExitCondition.None) + { + iterations++; + + // solve the subproblem + subproblem.Solve(objective, delta); + Pstep = subproblem.Pstep; + hitBoundary = subproblem.HitBoundary; + + // predicted reduction = L(0) - L(Δp) = -Δp'g - 1/2 * Δp'HΔp + var predictedReduction = -Gradient.DotProduct(Pstep) - 0.5 * Pstep.DotProduct(Hessian * Pstep); + + if (Pstep.L2Norm() <= stepTolerance * (stepTolerance + P.L2Norm())) + { + exitCondition = ExitCondition.RelativePoints; // SmallRelativeParameters + break; + } + + var Pnew = P + Pstep; // parameters to test + // evaluate function at Pnew + var RSSnew = EvaluateFunction(objective, Pnew); + + // if RSS == NaN, stop + if (double.IsNaN(RSSnew)) + { + exitCondition = ExitCondition.InvalidValues; + break; + } + + // calculate the ratio of the actual to the predicted reduction. + double rho = (predictedReduction != 0) + ? (RSS - RSSnew) / predictedReduction + : 0.0; + + if (rho > 0.75 && hitBoundary) + { + delta = Math.Min(2.0 * delta, maxDelta); + } + else if (rho < 0.25) + { + delta = delta * 0.25; + if (delta <= radiusTolerance * (radiusTolerance + P.DotProduct(P))) + { + exitCondition = ExitCondition.LackOfProgress; + break; + } + } + + if (rho > eta) + { + // accepted + Pnew.CopyTo(P); + RSS = RSSnew; + + // evaluate projected gradient and Hessian + jac = EvaluateJacobian(objective, P); + Gradient = jac.Item1; // objective.Gradient; + Hessian = jac.Item2; // objective.Hessian; + + // if ||g||_oo <= gtol, found and stop + if (Gradient.InfinityNorm() <= gradientTolerance) + { + exitCondition = ExitCondition.RelativeGradient; + } + + // if ||R||^2 < fTol, found and stop + if (RSS <= functionTolerance) + { + exitCondition = ExitCondition.Converged; // SmallRSS + } + } + } + + if (iterations >= maximumIterations) + { + exitCondition = ExitCondition.ExceedIterations; + } + + return new NonlinearMinimizationResult(objective, iterations, exitCondition); + } +} diff --git a/MathNet.Numerics/Optimization/TrustRegion/TrustRegionNewtonCGMinimizer.cs b/MathNet.Numerics/Optimization/TrustRegion/TrustRegionNewtonCGMinimizer.cs new file mode 100644 index 0000000..6390a9d --- /dev/null +++ b/MathNet.Numerics/Optimization/TrustRegion/TrustRegionNewtonCGMinimizer.cs @@ -0,0 +1,11 @@ +namespace MathNet.Numerics.Optimization.TrustRegion; + +public sealed class TrustRegionNewtonCGMinimizer : TrustRegionMinimizerBase +{ + /// + /// Non-linear least square fitting by the trust region Newton-Conjugate-Gradient algorithm. + /// + public TrustRegionNewtonCGMinimizer(double gradientTolerance = 1E-8, double stepTolerance = 1E-8, double functionTolerance = 1E-8, double radiusTolerance = 1E-8, int maximumIterations = -1) + : base(TrustRegionSubproblem.NewtonCG(), gradientTolerance, stepTolerance, functionTolerance, radiusTolerance, maximumIterations) + { } +} diff --git a/MathNet.Numerics/Optimization/TrustRegion/TrustRegionSubProblem.cs b/MathNet.Numerics/Optimization/TrustRegion/TrustRegionSubProblem.cs new file mode 100644 index 0000000..7e1eec4 --- /dev/null +++ b/MathNet.Numerics/Optimization/TrustRegion/TrustRegionSubProblem.cs @@ -0,0 +1,16 @@ +using MathNet.Numerics.Optimization.TrustRegion.Subproblems; + +namespace MathNet.Numerics.Optimization.TrustRegion; + +public static class TrustRegionSubproblem +{ + public static ITrustRegionSubproblem DogLeg() + { + return new DogLegSubproblem(); + } + + public static ITrustRegionSubproblem NewtonCG() + { + return new NewtonCGSubproblem(); + } +} diff --git a/MathNet.Numerics/Permutation.cs b/MathNet.Numerics/Permutation.cs new file mode 100644 index 0000000..7fac021 --- /dev/null +++ b/MathNet.Numerics/Permutation.cs @@ -0,0 +1,192 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics; + +using Properties; + +using System; + +/// +/// Class to represent a permutation for a subset of the natural numbers. +/// +[Serializable] +public class Permutation +{ + #region fields + + /// + /// Entry _indices[i] represents the location to which i is permuted to. + /// + private readonly int[] _indices; + + #endregion fields + + #region Constructor + + /// + /// Initializes a new instance of the Permutation class. + /// + /// An array which represents where each integer is permuted too: indices[i] represents that integer i + /// is permuted to location indices[i]. + public Permutation(int[] indices) + { + if (!CheckForProperPermutation(indices)) + { + throw new ArgumentException(Resources.PermutationAsIntArrayInvalid, nameof(indices)); + } + + _indices = (int[])indices.Clone(); + } + + #endregion + + /// + /// Gets the number of elements this permutation is over. + /// + public int Dimension + { + get { return _indices.Length; } + } + + /// + /// Computes where permutes too. + /// + /// The index to permute from. + /// The index which is permuted to. + public int this[int idx] + { + get + { + return _indices[idx]; + } + } + + /// + /// Computes the inverse of the permutation. + /// + /// The inverse of the permutation. + public Permutation Inverse() + { + var invIdx = new int[Dimension]; + for (int i = 0; i < invIdx.Length; i++) + { + invIdx[_indices[i]] = i; + } + + return new Permutation(invIdx); + } + + /// + /// Construct an array from a sequence of inversions. + /// + /// + /// From wikipedia: the permutation 12043 has the inversions (0,2), (1,2) and (3,4). This would be + /// encoded using the array [22244]. + /// + /// The set of inversions to construct the permutation from. + /// A permutation generated from a sequence of inversions. + public static Permutation FromInversions(int[] inv) + { + var idx = new int[inv.Length]; + for (int i = 0; i < inv.Length; i++) + { + idx[i] = i; + } + + for (int i = inv.Length - 1; i >= 0; i--) + { + if (idx[i] != inv[i]) + { + int t = idx[i]; + idx[i] = idx[inv[i]]; + idx[inv[i]] = t; + } + } + + return new Permutation(idx); + } + + /// + /// Construct a sequence of inversions from the permutation. + /// + /// + /// From wikipedia: the permutation 12043 has the inversions (0,2), (1,2) and (3,4). This would be + /// encoded using the array [22244]. + /// + /// A sequence of inversions. + public int[] ToInversions() + { + var idx = (int[])_indices.Clone(); + + for (int i = 0; i < idx.Length; i++) + { + if (idx[i] != i) + { + int q = Array.FindIndex(idx, i + 1, x => x == i); + var t = idx[i]; + idx[i] = q; + idx[q] = t; + } + } + + return idx; + } + + /// + /// Checks whether the array represents a proper permutation. + /// + /// An array which represents where each integer is permuted too: indices[i] represents that integer i + /// is permuted to location indices[i]. + /// True if represents a proper permutation, false otherwise. + static bool CheckForProperPermutation(int[] indices) + { + var idxCheck = new bool[indices.Length]; + + for (int i = 0; i < indices.Length; i++) + { + if (indices[i] >= indices.Length || indices[i] < 0) + { + return false; + } + + idxCheck[indices[i]] = true; + } + + for (int i = 0; i < indices.Length; i++) + { + if (idxCheck[i] == false) + { + return false; + } + } + + return true; + } +} diff --git a/MathNet.Numerics/Polynomial.cs b/MathNet.Numerics/Polynomial.cs new file mode 100644 index 0000000..946e1e3 --- /dev/null +++ b/MathNet.Numerics/Polynomial.cs @@ -0,0 +1,1072 @@ +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Double; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.LinearRegression; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; + +using Complex = System.Numerics.Complex; + +#if !NETSTANDARD1_3 +#endif + +namespace MathNet.Numerics; + +/// +/// A single-variable polynomial with real-valued coefficients and non-negative exponents. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics")] +public class Polynomial : IFormattable, IEquatable +#if !NETSTANDARD1_3 + , ICloneable +#endif +{ + /// + /// The coefficients of the polynomial in a + /// + [DataMember(Order = 1)] + public double[] Coefficients { get; private set; } + + /// + /// Only needed for the ToString method + /// + [DataMember(Order = 2)] + public string VariableName = "x"; + + /// + /// Degree of the polynomial, i.e. the largest monomial exponent. For example, the degree of y=x^2+x^5 is 5, for y=3 it is 0. + /// The null-polynomial returns degree -1 because the correct degree, negative infinity, cannot be represented by integers. + /// + public int Degree => EvaluateDegree(Coefficients); + + /// + /// Create a zero-polynomial with a coefficient array of the given length. + /// An array of length N can support polynomials of a degree of at most N-1. + /// + /// Length of the coefficient array + public Polynomial(int n) + { + if (n < 0) + { + throw new ArgumentOutOfRangeException(nameof(n), "n must be non-negative"); + } + + Coefficients = new double[n]; + } + + /// + /// Create a zero-polynomial + /// + public Polynomial() + { +#if NET40 + Coefficients = new double[0]; +#else + Coefficients = Array.Empty(); +#endif + } + + /// + /// Create a constant polynomial. + /// Example: 3.0 -> "p : x -> 3.0" + /// + /// The coefficient of the "x^0" monomial. + public Polynomial(double coefficient) + { + if (coefficient == 0.0) + { +#if NET40 + Coefficients = new double[0]; +#else + Coefficients = Array.Empty(); +#endif + } + else + { + Coefficients = new[] { coefficient }; + } + } + + /// + /// Create a polynomial with the provided coefficients (in ascending order, where the index matches the exponent). + /// Example: {5, 0, 2} -> "p : x -> 5 + 0 x^1 + 2 x^2". + /// + /// Polynomial coefficients as array + public Polynomial(params double[] coefficients) + { + Coefficients = coefficients; + } + + /// + /// Create a polynomial with the provided coefficients (in ascending order, where the index matches the exponent). + /// Example: {5, 0, 2} -> "p : x -> 5 + 0 x^1 + 2 x^2". + /// + /// Polynomial coefficients as enumerable + public Polynomial(IEnumerable coefficients) : this(coefficients.ToArray()) + { + } + + public static Polynomial Zero => new Polynomial(); + + /// + /// Least-Squares fitting the points (x,y) to a k-order polynomial y : x -> p0 + p1*x + p2*x^2 + ... + pk*x^k + /// + public static Polynomial Fit(double[] x, double[] y, int order, DirectRegressionMethod method = DirectRegressionMethod.QR) + { + var coefficients = Numerics.Fit.Polynomial(x, y, order, method); + return new Polynomial(coefficients); + } + + static int EvaluateDegree(double[] coefficients) + { + for (int i = coefficients.Length - 1; i >= 0; i--) + { + if (coefficients[i] != 0.0) + { + return i; + } + } + + return -1; + } + + #region Evaluation + + /// + /// Evaluate a polynomial at point x. + /// Coefficients are ordered ascending by power with power k at index k. + /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. + /// + /// The location where to evaluate the polynomial at. + /// The coefficients of the polynomial, coefficient for power k at index k. + public static double Evaluate(double z, params double[] coefficients) + { + double sum = coefficients[coefficients.Length - 1]; + for (int i = coefficients.Length - 2; i >= 0; --i) + { + sum *= z; + sum += coefficients[i]; + } + + return sum; + } + + /// + /// Evaluate a polynomial at point x. + /// Coefficients are ordered ascending by power with power k at index k. + /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. + /// + /// The location where to evaluate the polynomial at. + /// The coefficients of the polynomial, coefficient for power k at index k. + public static Complex Evaluate(Complex z, params double[] coefficients) + { + Complex sum = coefficients[coefficients.Length - 1]; + for (int i = coefficients.Length - 2; i >= 0; --i) + { + sum *= z; + sum += coefficients[i]; + } + + return sum; + } + + /// + /// Evaluate a polynomial at point x. + /// Coefficients are ordered ascending by power with power k at index k. + /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. + /// + /// The location where to evaluate the polynomial at. + /// The coefficients of the polynomial, coefficient for power k at index k. + public static Complex Evaluate(Complex z, params Complex[] coefficients) + { + Complex sum = coefficients[coefficients.Length - 1]; + for (int i = coefficients.Length - 2; i >= 0; --i) + { + sum *= z; + sum += coefficients[i]; + } + + return sum; + } + + /// + /// Evaluate a polynomial at point x. + /// + /// The location where to evaluate the polynomial at. + public double Evaluate(double z) + { + return Evaluate(z, Coefficients); + } + + /// + /// Evaluate a polynomial at point x. + /// + /// The location where to evaluate the polynomial at. + public Complex Evaluate(Complex z) + { + return Evaluate(z, Coefficients); + } + + /// + /// Evaluate a polynomial at points z. + /// + /// The locations where to evaluate the polynomial at. + public IEnumerable Evaluate(IEnumerable z) + { + return z.Select(Evaluate); + } + + /// + /// Evaluate a polynomial at points z. + /// + /// The locations where to evaluate the polynomial at. + public IEnumerable Evaluate(IEnumerable z) + { + return z.Select(Evaluate); + } + + #endregion + + #region Calculus + + public Polynomial Differentiate() + { + int n = Degree; + if (n < 0) + { + return this; + } + + if (n == 0) + { + // Zero + return Zero; + } + + var c = new double[n]; + for (int i = 0; i < c.Length; i++) + { + c[i] = Coefficients[i + 1] * (i + 1); + } + + return new Polynomial(c); + } + + public Polynomial Integrate() + { + int n = Degree; + if (n < 0) + { + return this; + } + + var c = new double[n + 2]; + for (int i = 1; i < c.Length; i++) + { + c[i] = Coefficients[i - 1] / i; + } + + return new Polynomial(c); + } + + #endregion + + #region Linear Algebra + + /// + /// Calculates the complex roots of the Polynomial by eigenvalue decomposition + /// + /// a vector of complex numbers with the roots + public Complex[] Roots() + { + switch (Degree) + { + case -1: // Zero-polynomial + case 0: // Non-zero constant: y = a0 + return new Complex[0]; + case 1: // Linear: y = a0 + a1*x + return new[] { new Complex(-Coefficients[0] / Coefficients[1], 0) }; + } + + DenseMatrix A = EigenvalueMatrix(); + Evd eigen = A.Evd(Symmetricity.Asymmetric); + return eigen.EigenValues.AsArray(); + } + + /// + /// Get the eigenvalue matrix A of this polynomial such that eig(A) = roots of this polynomial. + /// + /// Eigenvalue matrix A + /// This matrix is similar to the companion matrix of this polynomial, in such a way, that it's transpose is the columnflip of the companion matrix + public DenseMatrix EigenvalueMatrix() + { + int n = Degree; + if (n < 2) + { + return null; + } + + // Negate, and normalize (scale such that the polynomial becomes monic) + double aN = Coefficients[n]; + double[] p = new double[n]; + for (int i = n - 1; i >= 0; i--) + { + p[i] = -Coefficients[i] / aN; + } + + DenseMatrix A0 = DenseMatrix.CreateDiagonal(n - 1, n - 1, 1.0); + DenseMatrix A = new DenseMatrix(n); + + A.SetSubMatrix(1, 0, A0); + A.SetRow(0, p.Reverse().ToArray()); + + return A; + } + + #endregion + + #region Arithmetic Operations + + /// + /// Addition of two Polynomials (point-wise). + /// + /// Left Polynomial + /// Right Polynomial + /// Resulting Polynomial + public static Polynomial Add(Polynomial a, Polynomial b) + { + var ac = a.Coefficients; + var bc = b.Coefficients; + + var degree = Math.Max(a.Degree, b.Degree); + var result = new double[degree + 1]; + + var commonLength = Math.Min(Math.Min(ac.Length, bc.Length), result.Length); + for (int i = 0; i < commonLength; i++) + { + result[i] = ac[i] + bc[i]; + } + + int acLength = Math.Min(ac.Length, result.Length); + for (int i = commonLength; i < acLength; i++) + { + // no need to add since only one of both applies + result[i] = ac[i]; + } + + int bcLength = Math.Min(bc.Length, result.Length); + for (int i = commonLength; i < bcLength; i++) + { + // no need to add since only one of both applies + result[i] = bc[i]; + } + + return new Polynomial(result); + } + + /// + /// Addition of a polynomial and a scalar. + /// + public static Polynomial Add(Polynomial a, double b) + { + var ac = a.Coefficients; + + var degree = Math.Max(a.Degree, 0); + var result = new double[degree + 1]; + + var commonLength = Math.Min(ac.Length, result.Length); + for (int i = 0; i < commonLength; i++) + { + result[i] = ac[i]; + } + + result[0] += b; + + return new Polynomial(result); + } + + /// + /// Subtraction of two Polynomials (point-wise). + /// + /// Left Polynomial + /// Right Polynomial + /// Resulting Polynomial + public static Polynomial Subtract(Polynomial a, Polynomial b) + { + var ac = a.Coefficients; + var bc = b.Coefficients; + + var degree = Math.Max(a.Degree, b.Degree); + var result = new double[degree + 1]; + + var commonLength = Math.Min(Math.Min(ac.Length, bc.Length), result.Length); + for (int i = 0; i < commonLength; i++) + { + result[i] = ac[i] - bc[i]; + } + + int acLength = Math.Min(ac.Length, result.Length); + for (int i = commonLength; i < acLength; i++) + { + // no need to add since only one of both applies + result[i] = ac[i]; + } + + int bcLength = Math.Min(bc.Length, result.Length); + for (int i = commonLength; i < bcLength; i++) + { + // no need to add since only one of both applies + result[i] = -bc[i]; + } + + return new Polynomial(result); + } + + /// + /// Addition of a scalar from a polynomial. + /// + public static Polynomial Subtract(Polynomial a, double b) + { + return Add(a, -b); + } + + /// + /// Addition of a polynomial from a scalar. + /// + public static Polynomial Subtract(double b, Polynomial a) + { + var ac = a.Coefficients; + + var degree = Math.Max(a.Degree, 0); + var result = new double[degree + 1]; + + var commonLength = Math.Min(ac.Length, result.Length); + for (int i = 0; i < commonLength; i++) + { + result[i] = -ac[i]; + } + + result[0] += b; + + return new Polynomial(result); + } + + /// + /// Negation of a polynomial. + /// + public static Polynomial Negate(Polynomial a) + { + var ac = a.Coefficients; + + var degree = a.Degree; + var result = new double[degree + 1]; + + for (int i = 0; i < result.Length; i++) + { + result[i] = -ac[i]; + } + + return new Polynomial(result); + } + + /// + /// Multiplies a polynomial by a polynomial (convolution) + /// + /// Left polynomial + /// Right polynomial + /// Resulting Polynomial + public static Polynomial Multiply(Polynomial a, Polynomial b) + { + var ad = a.Degree; + var bd = b.Degree; + double[] ac = a.Coefficients; + double[] bc = b.Coefficients; + + var degree = ad + bd; + double[] result = new double[degree + 1]; + + for (int i = 0; i <= ad; i++) + { + for (int j = 0; j <= bd; j++) + { + result[i + j] += ac[i] * bc[j]; + } + } + + return new Polynomial(result); + } + + /// + /// Scales a polynomial by a scalar + /// + /// Polynomial + /// Scalar value + /// Resulting Polynomial + public static Polynomial Multiply(Polynomial a, double k) + { + var ac = a.Coefficients; + + var result = new double[a.Degree + 1]; + for (int i = 0; i < result.Length; i++) + { + result[i] = ac[i] * k; + } + + return new Polynomial(result); + } + + /// + /// Scales a polynomial by division by a scalar + /// + /// Polynomial + /// Scalar value + /// Resulting Polynomial + public static Polynomial Divide(Polynomial a, double k) + { + var ac = a.Coefficients; + + var result = new double[a.Degree + 1]; + for (int i = 0; i < result.Length; i++) + { + result[i] = ac[i] / k; + } + + return new Polynomial(result); + } + + /// + /// Euclidean long division of two polynomials, returning the quotient q and remainder r of the two polynomials a and b such that a = q*b + r + /// + /// Left polynomial + /// Right polynomial + /// A tuple holding quotient in first and remainder in second + public static Tuple DivideRemainder(Polynomial a, Polynomial b) + { + var bDegree = b.Degree; + if (bDegree < 0) + { + throw new DivideByZeroException("b polynomial ends with zero"); + } + + var aDegree = a.Degree; + if (aDegree < 0) + { + // zero divided by non-zero is zero without remainder + return Tuple.Create(a, a); + } + + if (bDegree == 0) + { + // division by scalar + return Tuple.Create(Divide(a, b.Coefficients[0]), Zero); + } + + if (aDegree < bDegree) + { + // denominator degree higher than nominator degree + // quotient always be 0 and return c1 as remainder + return Tuple.Create(Zero, a); + } + + var c1 = a.Coefficients.ToArray(); + var c2 = b.Coefficients.ToArray(); + + var scl = c2[bDegree]; + var c22 = new double[bDegree]; + for (int ii = 0; ii < c22.Length; ii++) + { + c22[ii] = c2[ii] / scl; + } + + int i = aDegree - bDegree; + int j = aDegree; + while (i >= 0) + { + var v = c1[j]; + for (int k = i; k < j; k++) + { + c1[k] -= c22[k - i] * v; + } + + i--; + j--; + } + + var j1 = j + 1; + var l1 = aDegree - j; + + var quo = new double[l1]; + for (int k = 0; k < l1; k++) + { + quo[k] = c1[k + j1] / scl; + } + + var rem = new double[j1]; + for (int k = 0; k < j1; k++) + { + rem[k] = c1[k]; + } + + return Tuple.Create(new Polynomial(quo), new Polynomial(rem)); + } + + #endregion + + #region Arithmetic Pointwise Operations + + /// + /// Point-wise division of two Polynomials + /// + /// Left Polynomial + /// Right Polynomial + /// Resulting Polynomial + public static Polynomial PointwiseDivide(Polynomial a, Polynomial b) + { + var ac = a.Coefficients; + var bc = b.Coefficients; + + var degree = a.Degree; + var result = new double[degree + 1]; + + var commonLength = Math.Min(Math.Min(ac.Length, bc.Length), result.Length); + for (int i = 0; i < commonLength; i++) + { + result[i] = ac[i] / bc[i]; + } + + for (int i = commonLength; i < result.Length; i++) + { + result[i] = ac[i] / 0.0; + } + + return new Polynomial(result); + } + + /// + /// Point-wise multiplication of two Polynomials + /// + /// Left Polynomial + /// Right Polynomial + /// Resulting Polynomial + public static Polynomial PointwiseMultiply(Polynomial a, Polynomial b) + { + var ac = a.Coefficients; + var bc = b.Coefficients; + + var degree = Math.Min(a.Degree, b.Degree); + var result = new double[degree + 1]; + for (int i = 0; i < result.Length; i++) + { + result[i] = ac[i] * bc[i]; + } + + return new Polynomial(result); + } + + #endregion + + #region Arithmetic Instance Methods (forwarders) + + /// + /// Division of two polynomials returning the quotient-with-remainder of the two polynomials given + /// + /// Right polynomial + /// A tuple holding quotient in first and remainder in second + public Tuple DivideRemainder(Polynomial b) + { + return DivideRemainder(this, b); + } + + #endregion + + #region Arithmetic Operator Overloads (forwarders) + + /// + /// Addition of two Polynomials (piecewise) + /// + /// Left polynomial + /// Right polynomial + /// Resulting Polynomial + public static Polynomial operator +(Polynomial a, Polynomial b) + { + return Add(a, b); + } + + /// + /// adds a scalar to a polynomial. + /// + /// Polynomial + /// Scalar value + /// Resulting Polynomial + public static Polynomial operator +(Polynomial a, double k) + { + return Add(a, k); + } + + /// + /// adds a scalar to a polynomial. + /// + /// Scalar value + /// Polynomial + /// Resulting Polynomial + public static Polynomial operator +(double k, Polynomial a) + { + return Add(a, k); + } + + /// + /// Subtraction of two polynomial. + /// + /// Left polynomial + /// Right polynomial + /// Resulting Polynomial + public static Polynomial operator -(Polynomial a, Polynomial b) + { + return Subtract(a, b); + } + + /// + /// Subtracts a scalar from a polynomial. + /// + /// Polynomial + /// Scalar value + /// Resulting Polynomial + public static Polynomial operator -(Polynomial a, double k) + { + return Subtract(a, k); + } + + /// + /// Subtracts a polynomial from a scalar. + /// + /// Scalar value + /// Polynomial + /// Resulting Polynomial + public static Polynomial operator -(double k, Polynomial a) + { + return Subtract(k, a); + } + + /// + /// Negates a polynomial. + /// + /// Polynomial + /// Resulting Polynomial + public static Polynomial operator -(Polynomial a) + { + return Negate(a); + } + + /// + /// Multiplies a polynomial by a polynomial (convolution). + /// + /// Left polynomial + /// Right polynomial + /// resulting Polynomial + public static Polynomial operator *(Polynomial a, Polynomial b) + { + return Multiply(a, b); + } + + /// + /// Multiplies a polynomial by a scalar. + /// + /// Polynomial + /// Scalar value + /// Resulting Polynomial + public static Polynomial operator *(Polynomial a, double k) + { + return Multiply(a, k); + } + + /// + /// Multiplies a polynomial by a scalar. + /// + /// Scalar value + /// Polynomial + /// Resulting Polynomial + public static Polynomial operator *(double k, Polynomial a) + { + return Multiply(a, k); + } + + /// + /// Divides a polynomial by scalar value. + /// + /// Polynomial + /// Scalar value + /// Resulting Polynomial + public static Polynomial operator /(Polynomial a, double k) + { + return Divide(a, k); + } + + #endregion + + #region ToString + + /// + /// Format the polynomial in ascending order, e.g. "4.3 + 2.0x^2 - x^3". + /// + public override string ToString() + { + return ToString("G", CultureInfo.CurrentCulture); + } + + /// + /// Format the polynomial in descending order, e.g. "x^3 + 2.0x^2 - 4.3". + /// + public string ToStringDescending() + { + return ToStringDescending("G", CultureInfo.CurrentCulture); + } + + /// + /// Format the polynomial in ascending order, e.g. "4.3 + 2.0x^2 - x^3". + /// + public string ToString(string format) + { + return ToString(format, CultureInfo.CurrentCulture); + } + + /// + /// Format the polynomial in descending order, e.g. "x^3 + 2.0x^2 - 4.3". + /// + public string ToStringDescending(string format) + { + return ToStringDescending(format, CultureInfo.CurrentCulture); + } + + /// + /// Format the polynomial in ascending order, e.g. "4.3 + 2.0x^2 - x^3". + /// + public string ToString(IFormatProvider formatProvider) + { + return ToString("G", formatProvider); + } + + /// + /// Format the polynomial in descending order, e.g. "x^3 + 2.0x^2 - 4.3". + /// + public string ToStringDescending(IFormatProvider formatProvider) + { + return ToStringDescending("G", formatProvider); + } + + /// + /// Format the polynomial in ascending order, e.g. "4.3 + 2.0x^2 - x^3". + /// + public string ToString(string format, IFormatProvider formatProvider) + { + if (Degree < 0) + { + return "0"; + } + + var sb = new StringBuilder(); + bool first = true; + for (int i = 0; i < Coefficients.Length; i++) + { + double c = Coefficients[i]; + if (c == 0.0) + { + continue; + } + + if (first) + { + sb.Append(c.ToString(format, formatProvider)); + if (i > 0) + { + sb.Append(VariableName); + } + + if (i > 1) + { + sb.Append("^"); + sb.Append(i); + } + + first = false; + } + else + { + if (c < 0.0) + { + sb.Append(" - "); + sb.Append((-c).ToString(format, formatProvider)); + } + else + { + sb.Append(" + "); + sb.Append(c.ToString(format, formatProvider)); + } + + if (i > 0) + { + sb.Append(VariableName); + } + + if (i > 1) + { + sb.Append("^"); + sb.Append(i); + } + } + } + + return sb.ToString(); + } + + /// + /// Format the polynomial in descending order, e.g. "x^3 + 2.0x^2 - 4.3". + /// + public string ToStringDescending(string format, IFormatProvider formatProvider) + { + if (Degree < 0) + { + return "0"; + } + + var sb = new StringBuilder(); + bool first = true; + for (int i = Coefficients.Length - 1; i >= 0; i--) + { + double c = Coefficients[i]; + if (c == 0.0) + { + continue; + } + + if (first) + { + sb.Append(c.ToString(format, formatProvider)); + if (i > 0) + { + sb.Append(VariableName); + } + + if (i > 1) + { + sb.Append("^"); + sb.Append(i); + } + + first = false; + } + else + { + if (c < 0.0) + { + sb.Append(" - "); + sb.Append((-c).ToString(format, formatProvider)); + } + else + { + sb.Append(" + "); + sb.Append(c.ToString(format, formatProvider)); + } + + if (i > 0) + { + sb.Append(VariableName); + } + + if (i > 1) + { + sb.Append("^"); + sb.Append(i); + } + } + } + + return sb.ToString(); + } + + #endregion + + #region Equality + + public bool Equals(Polynomial other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + int n = Degree; + if (n != other.Degree) + { + return false; + } + + for (var i = 0; i <= n; i++) + { + if (!Coefficients[i].Equals(other.Coefficients[i])) + { + return false; + } + } + + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != typeof(Polynomial)) + return false; + return Equals((Polynomial)obj); + } + + public override int GetHashCode() + { + var hashNum = Math.Min(Degree + 1, 25); + int hash = 17; + unchecked + { + for (var i = 0; i < hashNum; i++) + { + hash = hash * 31 + Coefficients[i].GetHashCode(); + } + } + + return hash; + } + + #endregion + + #region Clone + + public Polynomial Clone() + { + int degree = EvaluateDegree(Coefficients); + var coefficients = new double[degree + 1]; + Array.Copy(Coefficients, coefficients, coefficients.Length); + return new Polynomial(coefficients); + } + +#if !NETSTANDARD1_3 + /// + /// Creates a new object that is a copy of the current instance. + /// + /// + /// A new object that is a copy of this instance. + /// + object ICloneable.Clone() + { + return Clone(); + } +#endif + + #endregion +} diff --git a/MathNet.Numerics/Precision.Comparison.cs b/MathNet.Numerics/Precision.Comparison.cs new file mode 100644 index 0000000..9c08fd6 --- /dev/null +++ b/MathNet.Numerics/Precision.Comparison.cs @@ -0,0 +1,686 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics; + +public static partial class Precision +{ + /// + /// Compares two doubles and determines which double is bigger. + /// a < b -> -1; a ~= b (almost equal according to parameter) -> 0; a > b -> +1. + /// + /// The first value. + /// The second value. + /// The absolute accuracy required for being almost equal. + public static int CompareTo(this double a, double b, double maximumAbsoluteError) + { + // NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return a.CompareTo(b); + } + + // If A or B are infinity (positive or negative) then + // only return true if first is smaller + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a.CompareTo(b); + } + + // If the numbers are equal to within the number of decimal places + // then there's technically no difference + if (AlmostEqual(a, b, maximumAbsoluteError)) + { + return 0; + } + + // The numbers differ by more than the decimal places, so + // we can check the normal way to see if the first is + // larger than the second. + return a.CompareTo(b); + } + + /// + /// Compares two doubles and determines which double is bigger. + /// a < b -> -1; a ~= b (almost equal according to parameter) -> 0; a > b -> +1. + /// + /// The first value. + /// The second value. + /// The number of decimal places on which the values must be compared. Must be 1 or larger. + public static int CompareTo(this double a, double b, int decimalPlaces) + { + // NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return a.CompareTo(b); + } + + // If A or B are infinity (positive or negative) then + // only return true if first is smaller + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a.CompareTo(b); + } + + // If the numbers are equal to within the number of decimal places + // then there's technically no difference + if (AlmostEqual(a, b, decimalPlaces)) + { + return 0; + } + + // The numbers differ by more than the decimal places, so + // we can check the normal way to see if the first is + // larger than the second. + return a.CompareTo(b); + } + + /// + /// Compares two doubles and determines which double is bigger. + /// a < b -> -1; a ~= b (almost equal according to parameter) -> 0; a > b -> +1. + /// + /// The first value. + /// The second value. + /// The relative accuracy required for being almost equal. + public static int CompareToRelative(this double a, double b, double maximumError) + { + // NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return a.CompareTo(b); + } + + // If A or B are infinity (positive or negative) then + // only return true if first is smaller + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a.CompareTo(b); + } + + // If the numbers are equal to within the number of decimal places + // then there's technically no difference + if (AlmostEqualRelative(a, b, maximumError)) + { + return 0; + } + + // The numbers differ by more than the decimal places, so + // we can check the normal way to see if the first is + // larger than the second. + return a.CompareTo(b); + } + + /// + /// Compares two doubles and determines which double is bigger. + /// a < b -> -1; a ~= b (almost equal according to parameter) -> 0; a > b -> +1. + /// + /// The first value. + /// The second value. + /// The number of decimal places on which the values must be compared. Must be 1 or larger. + public static int CompareToRelative(this double a, double b, int decimalPlaces) + { + // NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return a.CompareTo(b); + } + + // If A or B are infinity (positive or negative) then + // only return true if first is smaller + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a.CompareTo(b); + } + + // If the numbers are equal to within the number of decimal places + // then there's technically no difference + if (AlmostEqualRelative(a, b, decimalPlaces)) + { + return 0; + } + + // The numbers differ by more than the decimal places, so + // we can check the normal way to see if the first is + // larger than the second. + return a.CompareTo(b); + } + + /// + /// Compares two doubles and determines which double is bigger. + /// a < b -> -1; a ~= b (almost equal according to parameter) -> 0; a > b -> +1. + /// + /// The first value. + /// The second value. + /// The maximum error in terms of Units in Last Place (ulps), i.e. the maximum number of decimals that may be different. Must be 1 or larger. + public static int CompareToNumbersBetween(this double a, double b, long maxNumbersBetween) + { + // NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return a.CompareTo(b); + } + + // If A or B are infinity (positive or negative) then + // only return true if first is smaller + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a.CompareTo(b); + } + + // If the numbers are equal to within the tolerance then + // there's technically no difference + if (AlmostEqualNumbersBetween(a, b, maxNumbersBetween)) + { + return 0; + } + + return a.CompareTo(b); + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLarger(this double a, double b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareTo(a, b, decimalPlaces) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLarger(this float a, float b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareTo(a, b, decimalPlaces) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The absolute accuracy required for being almost equal. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLarger(this double a, double b, double maximumAbsoluteError) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareTo(a, b, maximumAbsoluteError) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The absolute accuracy required for being almost equal. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLarger(this float a, float b, double maximumAbsoluteError) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareTo(a, b, maximumAbsoluteError) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLargerRelative(this double a, double b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareToRelative(a, b, decimalPlaces) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLargerRelative(this float a, float b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareToRelative(a, b, decimalPlaces) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The relative accuracy required for being almost equal. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLargerRelative(this double a, double b, double maximumError) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareToRelative(a, b, maximumError) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The relative accuracy required for being almost equal. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLargerRelative(this float a, float b, double maximumError) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareToRelative(a, b, maximumError) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values for which the two values are considered equal. Must be 1 or larger. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLargerNumbersBetween(this double a, double b, long maxNumbersBetween) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareToNumbersBetween(a, b, maxNumbersBetween) > 0; + } + + /// + /// Compares two doubles and determines if the first value is larger than the second + /// value to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values for which the two values are considered equal. Must be 1 or larger. + /// true if the first value is larger than the second value; otherwise false. + public static bool IsLargerNumbersBetween(this float a, float b, long maxNumbersBetween) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareToNumbersBetween(a, b, maxNumbersBetween) > 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of thg. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmaller(this double a, double b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareTo(a, b, decimalPlaces) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of thg. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmaller(this float a, float b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareTo(a, b, decimalPlaces) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The absolute accuracy required for being almost equal. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmaller(this double a, double b, double maximumAbsoluteError) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareTo(a, b, maximumAbsoluteError) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The absolute accuracy required for being almost equal. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmaller(this float a, float b, double maximumAbsoluteError) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareTo(a, b, maximumAbsoluteError) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmallerRelative(this double a, double b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareToRelative(a, b, decimalPlaces) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmallerRelative(this float a, float b, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareToRelative(a, b, decimalPlaces) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The relative accuracy required for being almost equal. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmallerRelative(this double a, double b, double maximumError) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareToRelative(a, b, maximumError) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the specified number of decimal places or not. + /// + /// The first value. + /// The second value. + /// The relative accuracy required for being almost equal. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmallerRelative(this float a, float b, double maximumError) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareToRelative(a, b, maximumError) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values for which the two values are considered equal. Must be 1 or larger. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmallerNumbersBetween(this double a, double b, long maxNumbersBetween) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return CompareToNumbersBetween(a, b, maxNumbersBetween) < 0; + } + + /// + /// Compares two doubles and determines if the first value is smaller than the second + /// value to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values for which the two values are considered equal. Must be 1 or larger. + /// true if the first value is smaller than the second value; otherwise false. + public static bool IsSmallerNumbersBetween(this float a, float b, long maxNumbersBetween) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves, and thus they're not bigger or + // smaller than anything either + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + return CompareToNumbersBetween(a, b, maxNumbersBetween) < 0; + } + + /// + /// Checks if a given double values is finite, i.e. neither NaN nor inifnity + /// + /// The value to be checked fo finitenes. + public static bool IsFinite(this double value) + { + return !double.IsNaN(value) && !double.IsInfinity(value); + } +} diff --git a/MathNet.Numerics/Precision.Equality.cs b/MathNet.Numerics/Precision.Equality.cs new file mode 100644 index 0000000..6b6e18d --- /dev/null +++ b/MathNet.Numerics/Precision.Equality.cs @@ -0,0 +1,1046 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; + +using System; +using System.Collections.Generic; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +// TODO PERF: Cache/Precompute 10^x terms + +public static partial class Precision +{ + /// + /// Compares two doubles and determines if they are equal + /// within the specified maximum absolute error. + /// + /// The norm of the first value (can be negative). + /// The norm of the second value (can be negative). + /// The norm of the difference of the two values (can be negative). + /// The absolute accuracy required for being almost equal. + /// True if both doubles are almost equal up to the specified maximum absolute error, false otherwise. + public static bool AlmostEqualNorm(this double a, double b, double diff, double maximumAbsoluteError) + { + // If A or B are infinity (positive or negative) then + // only return true if they are exactly equal to each other - + // that is, if they are both infinities of the same sign. + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a == b; + } + + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + return Math.Abs(diff) < maximumAbsoluteError; + } + + /// + /// Compares two doubles and determines if they are equal + /// within the specified maximum absolute error. + /// + /// The first value. + /// The second value. + /// The absolute accuracy required for being almost equal. + /// True if both doubles are almost equal up to the specified maximum absolute error, false otherwise. + public static bool AlmostEqualNorm(this T a, T b, double maximumAbsoluteError) + where T : IPrecisionSupport + { + return AlmostEqualNorm(a.Norm(), b.Norm(), a.NormOfDifference(b), maximumAbsoluteError); + } + + /// + /// Compares two doubles and determines if they are equal + /// within the specified maximum error. + /// + /// The norm of the first value (can be negative). + /// The norm of the second value (can be negative). + /// The norm of the difference of the two values (can be negative). + /// The accuracy required for being almost equal. + /// True if both doubles are almost equal up to the specified maximum error, false otherwise. + public static bool AlmostEqualNormRelative(this double a, double b, double diff, double maximumError) + { + // If A or B are infinity (positive or negative) then + // only return true if they are exactly equal to each other - + // that is, if they are both infinities of the same sign. + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a == b; + } + + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + // If one is almost zero, fall back to absolute equality + if (Math.Abs(a) < DoublePrecision || Math.Abs(b) < DoublePrecision) + { + return Math.Abs(diff) < maximumError; + } + + if ((a == 0 && Math.Abs(b) < maximumError) || (b == 0 && Math.Abs(a) < maximumError)) + { + return true; + } + + return Math.Abs(diff) < maximumError * Math.Max(Math.Abs(a), Math.Abs(b)); + } + + /// + /// Compares two doubles and determines if they are equal + /// within the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + /// True if both doubles are almost equal up to the specified maximum error, false otherwise. + public static bool AlmostEqualNormRelative(this T a, T b, double maximumError) + where T : IPrecisionSupport + { + return AlmostEqualNormRelative(a.Norm(), b.Norm(), a.NormOfDifference(b), maximumError); + } + + /// + /// Compares two doubles and determines if they are equal within + /// the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqual(this double a, double b, double maximumAbsoluteError) + { + return AlmostEqualNorm(a, b, a - b, maximumAbsoluteError); + } + + /// + /// Compares two complex and determines if they are equal within + /// the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqual(this float a, float b, double maximumAbsoluteError) + { + return AlmostEqualNorm(a, b, a - b, maximumAbsoluteError); + } + + /// + /// Compares two complex and determines if they are equal within + /// the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqual(this Complex a, Complex b, double maximumAbsoluteError) + { + return AlmostEqualNorm(a.Norm(), b.Norm(), a.NormOfDifference(b), maximumAbsoluteError); + } + + /// + /// Compares two complex and determines if they are equal within + /// the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqual(this Complex32 a, Complex32 b, double maximumAbsoluteError) + { + return AlmostEqualNorm(a.Norm(), b.Norm(), a.NormOfDifference(b), maximumAbsoluteError); + } + + /// + /// Compares two doubles and determines if they are equal within + /// the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqualRelative(this double a, double b, double maximumError) + { + return AlmostEqualNormRelative(a, b, a - b, maximumError); + } + + /// + /// Compares two complex and determines if they are equal within + /// the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqualRelative(this float a, float b, double maximumError) + { + return AlmostEqualNormRelative(a, b, a - b, maximumError); + } + + /// + /// Compares two complex and determines if they are equal within + /// the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqualRelative(this Complex a, Complex b, double maximumError) + { + return AlmostEqualNormRelative(a.Norm(), b.Norm(), a.NormOfDifference(b), maximumError); + } + + /// + /// Compares two complex and determines if they are equal within + /// the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqualRelative(this Complex32 a, Complex32 b, double maximumError) + { + return AlmostEqualNormRelative(a.Norm(), b.Norm(), a.NormOfDifference(b), maximumError); + } + + /// + /// Checks whether two real numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqual(this double a, double b) + { + return AlmostEqualNorm(a, b, a - b, DefaultDoubleAccuracy); + } + + /// + /// Checks whether two real numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqual(this float a, float b) + { + return AlmostEqualNorm(a, b, a - b, DefaultSingleAccuracy); + } + + /// + /// Checks whether two Complex numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqual(this Complex a, Complex b) + { + return AlmostEqualNorm(a.Norm(), b.Norm(), a.NormOfDifference(b), DefaultDoubleAccuracy); + } + + /// + /// Checks whether two Complex numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqual(this Complex32 a, Complex32 b) + { + return AlmostEqualNorm(a.Norm(), b.Norm(), a.NormOfDifference(b), DefaultSingleAccuracy); + } + + /// + /// Checks whether two real numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqualRelative(this double a, double b) + { + return AlmostEqualNormRelative(a, b, a - b, DefaultDoubleAccuracy); + } + + /// + /// Checks whether two real numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqualRelative(this float a, float b) + { + return AlmostEqualNormRelative(a, b, a - b, DefaultSingleAccuracy); + } + + /// + /// Checks whether two Complex numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqualRelative(this Complex a, Complex b) + { + return AlmostEqualNormRelative(a.Norm(), b.Norm(), a.NormOfDifference(b), DefaultDoubleAccuracy); + } + + /// + /// Checks whether two Complex numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqualRelative(this Complex32 a, Complex32 b) + { + return AlmostEqualNormRelative(a.Norm(), b.Norm(), a.NormOfDifference(b), DefaultSingleAccuracy); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not, using the + /// number of decimal places as an absolute measure. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 0.5e-decimalPlaces. We divide by + /// two so that we have half the range on each side of the numbers, e.g. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The norm of the first value (can be negative). + /// The norm of the second value (can be negative). + /// The norm of the difference of the two values (can be negative). + /// The number of decimal places. + public static bool AlmostEqualNorm(this double a, double b, double diff, int decimalPlaces) + { + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + // If A or B are infinity (positive or negative) then + // only return true if they are exactly equal to each other - + // that is, if they are both infinities of the same sign. + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a == b; + } + + // The values are equal if the difference between the two numbers is smaller than + // 10^(-numberOfDecimalPlaces). We divide by two so that we have half the range + // on each side of the numbers, e.g. if decimalPlaces == 2, + // then 0.01 will equal between 0.005 and 0.015, but not 0.02 and not 0.00 + return Math.Abs(diff) < Math.Pow(10, -decimalPlaces) / 2d; + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not, using the + /// number of decimal places as an absolute measure. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 0.5e-decimalPlaces. We divide by + /// two so that we have half the range on each side of the numbers, e.g. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqualNorm(this T a, T b, int decimalPlaces) + where T : IPrecisionSupport + { + return AlmostEqualNorm(a.Norm(), b.Norm(), a.NormOfDifference(b), decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not. If the numbers + /// are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The norm of the first value (can be negative). + /// The norm of the second value (can be negative). + /// The norm of the difference of the two values (can be negative). + /// The number of decimal places. + /// Thrown if is smaller than zero. + public static bool AlmostEqualNormRelative(this double a, double b, double diff, int decimalPlaces) + { + if (decimalPlaces < 0) + { + // Can't have a negative number of decimal places + throw new ArgumentOutOfRangeException(nameof(decimalPlaces)); + } + + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + // If A or B are infinity (positive or negative) then + // only return true if they are exactly equal to each other - + // that is, if they are both infinities of the same sign. + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a == b; + } + + // If both numbers are equal, get out now. This should remove the possibility of both numbers being zero + // and any problems associated with that. + if (a.Equals(b)) + { + return true; + } + + // If one is almost zero, fall back to absolute equality + if (Math.Abs(a) < DoublePrecision || Math.Abs(b) < DoublePrecision) + { + // The values are equal if the difference between the two numbers is smaller than + // 10^(-numberOfDecimalPlaces). We divide by two so that we have half the range + // on each side of the numbers, e.g. if decimalPlaces == 2, + // then 0.01 will equal between 0.005 and 0.015, but not 0.02 and not 0.00 + return Math.Abs(diff) < Math.Pow(10, -decimalPlaces) / 2d; + } + + // If the magnitudes of the two numbers are equal to within one magnitude the numbers could potentially be equal + int magnitudeOfFirst = Magnitude(a); + int magnitudeOfSecond = Magnitude(b); + int magnitudeOfMax = Math.Max(magnitudeOfFirst, magnitudeOfSecond); + if (magnitudeOfMax > (Math.Min(magnitudeOfFirst, magnitudeOfSecond) + 1)) + { + return false; + } + + // The values are equal if the difference between the two numbers is smaller than + // 10^(-numberOfDecimalPlaces). We divide by two so that we have half the range + // on each side of the numbers, e.g. if decimalPlaces == 2, + // then 0.01 will equal between 0.00995 and 0.01005, but not 0.0015 and not 0.0095 + return Math.Abs(diff) < Math.Pow(10, magnitudeOfMax - decimalPlaces) / 2d; + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not. If the numbers + /// are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// + /// + /// The values are equal if the difference between the two numbers is smaller than 10^(-numberOfDecimalPlaces). We divide by + /// two so that we have half the range on each side of the numbers, e.g. if == 2, then 0.01 will equal between + /// 0.005 and 0.015, but not 0.02 and not 0.00 + /// + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqualNormRelative(this T a, T b, int decimalPlaces) + where T : IPrecisionSupport + { + return AlmostEqualNormRelative(a.Norm(), b.Norm(), a.NormOfDifference(b), decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not, using the + /// number of decimal places as an absolute measure. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqual(this double a, double b, int decimalPlaces) + { + return AlmostEqualNorm(a, b, a - b, decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not, using the + /// number of decimal places as an absolute measure. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqual(this float a, float b, int decimalPlaces) + { + return AlmostEqualNorm(a, b, a - b, decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not, using the + /// number of decimal places as an absolute measure. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqual(this Complex a, Complex b, int decimalPlaces) + { + return AlmostEqualNorm(a.Norm(), b.Norm(), a.NormOfDifference(b), decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not, using the + /// number of decimal places as an absolute measure. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqual(this Complex32 a, Complex32 b, int decimalPlaces) + { + return AlmostEqualNorm(a.Norm(), b.Norm(), a.NormOfDifference(b), decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not. If the numbers + /// are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqualRelative(this double a, double b, int decimalPlaces) + { + return AlmostEqualNormRelative(a, b, a - b, decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not. If the numbers + /// are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqualRelative(this float a, float b, int decimalPlaces) + { + return AlmostEqualNormRelative(a, b, a - b, decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not. If the numbers + /// are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqualRelative(this Complex a, Complex b, int decimalPlaces) + { + return AlmostEqualNormRelative(a.Norm(), b.Norm(), a.NormOfDifference(b), decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the specified number of decimal places or not. If the numbers + /// are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqualRelative(this Complex32 a, Complex32 b, int decimalPlaces) + { + return AlmostEqualNormRelative(a.Norm(), b.Norm(), a.NormOfDifference(b), decimalPlaces); + } + + /// + /// Compares two doubles and determines if they are equal to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// + /// + /// Determines the 'number' of floating point numbers between two values (i.e. the number of discrete steps + /// between the two numbers) and then checks if that is within the specified tolerance. So if a tolerance + /// of 1 is passed then the result will be true only if the two numbers have the same binary representation + /// OR if they are two adjacent numbers that only differ by one step. + /// + /// + /// The comparison method used is explained in http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm . The article + /// at http://www.extremeoptimization.com/resources/Articles/FPDotNetConceptsAndFormats.aspx explains how to transform the C code to + /// .NET enabled code without using pointers and unsafe code. + /// + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values between the two values. Must be 1 or larger. + /// Thrown if is smaller than one. + public static bool AlmostEqualNumbersBetween(this double a, double b, long maxNumbersBetween) + { + // Make sure maxNumbersBetween is non-negative and small enough that the + // default NAN won't compare as equal to anything. + if (maxNumbersBetween < 1) + { + throw new ArgumentOutOfRangeException(nameof(maxNumbersBetween)); + } + + // If A or B are infinity (positive or negative) then + // only return true if they are exactly equal to each other - + // that is, if they are both infinities of the same sign. + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a == b; + } + + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + // Get the first double and convert it to an integer value (by using the binary representation) + long firstUlong = AsDirectionalInt64(a); + + // Get the second double and convert it to an integer value (by using the binary representation) + long secondUlong = AsDirectionalInt64(b); + + // Now compare the values. + // Note that this comparison can overflow so we'll approach this differently + // Do note that we could overflow this way too. We should probably check that we don't. + return (a > b) ? (secondUlong + maxNumbersBetween >= firstUlong) : (firstUlong + maxNumbersBetween >= secondUlong); + } + + /// + /// Compares two floats and determines if they are equal to within the tolerance or not. Equality comparison is based on the binary representation. + /// + /// The first value. + /// The second value. + /// The maximum number of floating point values between the two values. Must be 1 or larger. + /// Thrown if is smaller than one. + public static bool AlmostEqualNumbersBetween(this float a, float b, int maxNumbersBetween) + { + // Make sure maxNumbersBetween is non-negative and small enough that the + // default NAN won't compare as equal to anything. + if (maxNumbersBetween < 1) + { + throw new ArgumentOutOfRangeException(nameof(maxNumbersBetween)); + } + + // If A or B are infinity (positive or negative) then + // only return true if they are exactly equal to each other - + // that is, if they are both infinities of the same sign. + if (float.IsInfinity(a) || float.IsInfinity(b)) + { + return a == b; + } + + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if (float.IsNaN(a) || float.IsNaN(b)) + { + return false; + } + + // Get the first float and convert it to an integer value (by using the binary representation) + int firstUlong = AsDirectionalInt32(a); + + // Get the second float and convert it to an integer value (by using the binary representation) + int secondUlong = AsDirectionalInt32(b); + + // Now compare the values. + // Note that this comparison can overflow so we'll approach this differently + // Do note that we could overflow this way too. We should probably check that we don't. + return (a > b) ? (secondUlong + maxNumbersBetween >= firstUlong) : (firstUlong + maxNumbersBetween >= secondUlong); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqual(this IList a, IList b, double maximumAbsoluteError) + { + return ListForAll(a, b, AlmostEqual, maximumAbsoluteError); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqual(this IList a, IList b, double maximumAbsoluteError) + { + return ListForAll(a, b, AlmostEqual, maximumAbsoluteError); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqual(this IList a, IList b, double maximumAbsoluteError) + { + return ListForAll(a, b, AlmostEqual, maximumAbsoluteError); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqual(this IList a, IList b, double maximumAbsoluteError) + { + return ListForAll(a, b, AlmostEqual, maximumAbsoluteError); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqualRelative(this IList a, IList b, double maximumError) + { + return ListForAll(a, b, AlmostEqualRelative, maximumError); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqualRelative(this IList a, IList b, double maximumError) + { + return ListForAll(a, b, AlmostEqualRelative, maximumError); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqualRelative(this IList a, IList b, double maximumError) + { + return ListForAll(a, b, AlmostEqualRelative, maximumError); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqualRelative(this IList a, IList b, double maximumError) + { + return ListForAll(a, b, AlmostEqualRelative, maximumError); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The number of decimal places. + public static bool ListAlmostEqual(this IList a, IList b, int decimalPlaces) + { + return ListForAll(a, b, AlmostEqual, decimalPlaces); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The number of decimal places. + public static bool ListAlmostEqual(this IList a, IList b, int decimalPlaces) + { + return ListForAll(a, b, AlmostEqual, decimalPlaces); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The number of decimal places. + public static bool ListAlmostEqual(this IList a, IList b, int decimalPlaces) + { + return ListForAll(a, b, AlmostEqual, decimalPlaces); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The number of decimal places. + public static bool ListAlmostEqual(this IList a, IList b, int decimalPlaces) + { + return ListForAll(a, b, AlmostEqual, decimalPlaces); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The number of decimal places. + public static bool ListAlmostEqualRelative(this IList a, IList b, int decimalPlaces) + { + return ListForAll(a, b, AlmostEqualRelative, decimalPlaces); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The number of decimal places. + public static bool ListAlmostEqualRelative(this IList a, IList b, int decimalPlaces) + { + return ListForAll(a, b, AlmostEqualRelative, decimalPlaces); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The number of decimal places. + public static bool ListAlmostEqualRelative(this IList a, IList b, int decimalPlaces) + { + return ListForAll(a, b, AlmostEqualRelative, decimalPlaces); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The number of decimal places. + public static bool ListAlmostEqualRelative(this IList a, IList b, int decimalPlaces) + { + return ListForAll(a, b, AlmostEqualRelative, decimalPlaces); + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqualNorm(this IList a, IList b, double maximumAbsoluteError) + where T : IPrecisionSupport + { + if (a == null && b == null) + { + return true; + } + + if (a == null || b == null || a.Count != b.Count) + { + return false; + } + + for (int i = 0; i < a.Count; i++) + { + if (!AlmostEqualNorm(a[i], b[i], maximumAbsoluteError)) + { + return false; + } + } + + return true; + } + + /// + /// Compares two lists of doubles and determines if they are equal within the + /// specified maximum error. + /// + /// The first value list. + /// The second value list. + /// The accuracy required for being almost equal. + public static bool ListAlmostEqualNormRelative(this IList a, IList b, double maximumError) + where T : IPrecisionSupport + { + if (a == null && b == null) + { + return true; + } + + if (a == null || b == null || a.Count != b.Count) + { + return false; + } + + for (int i = 0; i < a.Count; i++) + { + if (!AlmostEqualNormRelative(a[i], b[i], maximumError)) + { + return false; + } + } + + return true; + } + + private static bool ListForAll(IList a, IList b, Func predicate, TP parameter) + { + if (a == null && b == null) + { + return true; + } + + if (a == null || b == null || a.Count != b.Count) + { + return false; + } + + for (int i = 0; i < a.Count; i++) + { + if (!predicate(a[i], b[i], parameter)) + { + return false; + } + } + + return true; + } + + /// + /// Compares two vectors and determines if they are equal within the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqual(this Vector a, Vector b, double maximumAbsoluteError) + where T : struct, IEquatable, IFormattable + { + return AlmostEqualNorm(a.L2Norm(), b.L2Norm(), (a - b).L2Norm(), maximumAbsoluteError); + } + + /// + /// Compares two vectors and determines if they are equal within the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqualRelative(this Vector a, Vector b, double maximumError) + where T : struct, IEquatable, IFormattable + { + return AlmostEqualNormRelative(a.L2Norm(), b.L2Norm(), (a - b).L2Norm(), maximumError); + } + + /// + /// Compares two vectors and determines if they are equal to within the specified number + /// of decimal places or not, using the number of decimal places as an absolute measure. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqual(this Vector a, Vector b, int decimalPlaces) + where T : struct, IEquatable, IFormattable + { + return AlmostEqualNorm(a.L2Norm(), b.L2Norm(), (a - b).L2Norm(), decimalPlaces); + } + + /// + /// Compares two vectors and determines if they are equal to within the specified number of decimal places or not. + /// If the numbers are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqualRelative(this Vector a, Vector b, int decimalPlaces) + where T : struct, IEquatable, IFormattable + { + return AlmostEqualNormRelative(a.L2Norm(), b.L2Norm(), (a - b).L2Norm(), decimalPlaces); + } + + /// + /// Compares two matrices and determines if they are equal within the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqual(this Matrix a, Matrix b, double maximumAbsoluteError) + where T : struct, IEquatable, IFormattable + { + return AlmostEqualNorm(a.L2Norm(), b.L2Norm(), (a - b).L2Norm(), maximumAbsoluteError); + } + + /// + /// Compares two matrices and determines if they are equal within the specified maximum error. + /// + /// The first value. + /// The second value. + /// The accuracy required for being almost equal. + public static bool AlmostEqualRelative(this Matrix a, Matrix b, double maximumError) + where T : struct, IEquatable, IFormattable + { + return AlmostEqualNormRelative(a.L2Norm(), b.L2Norm(), (a - b).L2Norm(), maximumError); + } + + /// + /// Compares two matrices and determines if they are equal to within the specified number + /// of decimal places or not, using the number of decimal places as an absolute measure. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqual(this Matrix a, Matrix b, int decimalPlaces) + where T : struct, IEquatable, IFormattable + { + return AlmostEqualNorm(a.L2Norm(), b.L2Norm(), (a - b).L2Norm(), decimalPlaces); + } + + /// + /// Compares two matrices and determines if they are equal to within the specified number of decimal places or not. + /// If the numbers are very close to zero an absolute difference is compared, otherwise the relative difference is compared. + /// + /// The first value. + /// The second value. + /// The number of decimal places. + public static bool AlmostEqualRelative(this Matrix a, Matrix b, int decimalPlaces) + where T : struct, IEquatable, IFormattable + { + return AlmostEqualNormRelative(a.L2Norm(), b.L2Norm(), (a - b).L2Norm(), decimalPlaces); + } +} diff --git a/MathNet.Numerics/Precision.cs b/MathNet.Numerics/Precision.cs new file mode 100644 index 0000000..a2c0750 --- /dev/null +++ b/MathNet.Numerics/Precision.cs @@ -0,0 +1,792 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Runtime; +using System.Runtime.InteropServices; + +namespace MathNet.Numerics; + +/// +/// Support Interface for Precision Operations (like AlmostEquals). +/// +/// Type of the implementing class. +public interface IPrecisionSupport +{ + /// + /// Returns a Norm of a value of this type, which is appropriate for measuring how + /// close this value is to zero. + /// + /// A norm of this value. + double Norm(); + + /// + /// Returns a Norm of the difference of two values of this type, which is + /// appropriate for measuring how close together these two values are. + /// + /// The value to compare with. + /// A norm of the difference between this and the other value. + double NormOfDifference(T otherValue); +} + +/// +/// Utilities for working with floating point numbers. +/// +/// +/// +/// Useful links: +/// +/// +/// http://docs.sun.com/source/806-3568/ncg_goldberg.html#689 - What every computer scientist should know about floating-point arithmetic +/// +/// +/// http://en.wikipedia.org/wiki/Machine_epsilon - Gives the definition of machine epsilon +/// +/// +/// +/// +public static partial class Precision +{ + /// + /// The number of binary digits used to represent the binary number for a double precision floating + /// point value. i.e. there are this many digits used to represent the + /// actual number, where in a number as: 0.134556 * 10^5 the digits are 0.134556 and the exponent is 5. + /// + const int DoubleWidth = 53; + + /// + /// The number of binary digits used to represent the binary number for a single precision floating + /// point value. i.e. there are this many digits used to represent the + /// actual number, where in a number as: 0.134556 * 10^5 the digits are 0.134556 and the exponent is 5. + /// + const int SingleWidth = 24; + + /// + /// Standard epsilon, the maximum relative precision of IEEE 754 double-precision floating numbers (64 bit). + /// According to the definition of Prof. Demmel and used in LAPACK and Scilab. + /// + public static readonly double DoublePrecision = Math.Pow(2, -DoubleWidth); + + /// + /// Standard epsilon, the maximum relative precision of IEEE 754 double-precision floating numbers (64 bit). + /// According to the definition of Prof. Higham and used in the ISO C standard and MATLAB. + /// + public static readonly double PositiveDoublePrecision = 2 * DoublePrecision; + + /// + /// Standard epsilon, the maximum relative precision of IEEE 754 single-precision floating numbers (32 bit). + /// According to the definition of Prof. Demmel and used in LAPACK and Scilab. + /// + public static readonly double SinglePrecision = Math.Pow(2, -SingleWidth); + + /// + /// Standard epsilon, the maximum relative precision of IEEE 754 single-precision floating numbers (32 bit). + /// According to the definition of Prof. Higham and used in the ISO C standard and MATLAB. + /// + public static readonly double PositiveSinglePrecision = 2 * SinglePrecision; + + /// + /// Actual double precision machine epsilon, the smallest number that can be subtracted from 1, yielding a results different than 1. + /// This is also known as unit roundoff error. According to the definition of Prof. Demmel. + /// On a standard machine this is equivalent to `DoublePrecision`. + /// + public static readonly double MachineEpsilon = MeasureMachineEpsilon(); + + /// + /// Actual double precision machine epsilon, the smallest number that can be added to 1, yielding a results different than 1. + /// This is also known as unit roundoff error. According to the definition of Prof. Higham. + /// On a standard machine this is equivalent to `PositiveDoublePrecision`. + /// + public static readonly double PositiveMachineEpsilon = MeasurePositiveMachineEpsilon(); + + /// + /// The number of significant decimal places of double-precision floating numbers (64 bit). + /// + public static readonly int DoubleDecimalPlaces = (int)Math.Floor(Math.Abs(Math.Log10(DoublePrecision))); + + /// + /// The number of significant decimal places of single-precision floating numbers (32 bit). + /// + public static readonly int SingleDecimalPlaces = (int)Math.Floor(Math.Abs(Math.Log10(SinglePrecision))); + + /// + /// Value representing 10 * 2^(-53) = 1.11022302462516E-15 + /// + static readonly double DefaultDoubleAccuracy = DoublePrecision * 10; + + /// + /// Value representing 10 * 2^(-24) = 5.96046447753906E-07 + /// + static readonly float DefaultSingleAccuracy = (float)(SinglePrecision * 10); + + /// + /// Returns the magnitude of the number. + /// + /// The value. + /// The magnitude of the number. + public static int Magnitude(this double value) + { + // Can't do this with zero because the 10-log of zero doesn't exist. + if (value.Equals(0.0)) + { + return 0; + } + + // Note that we need the absolute value of the input because Log10 doesn't + // work for negative numbers (obviously). + double magnitude = Math.Log10(Math.Abs(value)); + var truncated = (int)Truncate(magnitude); + + // To get the right number we need to know if the value is negative or positive + // truncating a positive number will always give use the correct magnitude + // truncating a negative number will give us a magnitude that is off by 1 (unless integer) + return magnitude < 0d && truncated != magnitude + ? truncated - 1 + : truncated; + } + + /// + /// Returns the magnitude of the number. + /// + /// The value. + /// The magnitude of the number. + public static int Magnitude(this float value) + { + // Can't do this with zero because the 10-log of zero doesn't exist. + if (value.Equals(0.0f)) + { + return 0; + } + + // Note that we need the absolute value of the input because Log10 doesn't + // work for negative numbers (obviously). + var magnitude = Convert.ToSingle(Math.Log10(Math.Abs(value))); + var truncated = (int)Truncate(magnitude); + + // To get the right number we need to know if the value is negative or positive + // truncating a positive number will always give use the correct magnitude + // truncating a negative number will give us a magnitude that is off by 1 (unless integer) + return magnitude < 0f && truncated != magnitude + ? truncated - 1 + : truncated; + } + + /// + /// Returns the number divided by it's magnitude, effectively returning a number between -10 and 10. + /// + /// The value. + /// The value of the number. + public static double ScaleUnitMagnitude(this double value) + { + if (value.Equals(0.0)) + { + return value; + } + + int magnitude = Magnitude(value); + return value * Math.Pow(10, -magnitude); + } + + /// + /// Returns a 'directional' long value. This is a long value which acts the same as a double, + /// e.g. a negative double value will return a negative double value starting at 0 and going + /// more negative as the double value gets more negative. + /// + /// The input double value. + /// A long value which is roughly the equivalent of the double value. + static long AsDirectionalInt64(double value) + { + // Convert in the normal way. + long result = BitConverter.DoubleToInt64Bits(value); + + // Now find out where we're at in the range + // If the value is larger/equal to zero then we can just return the value + // if the value is negative we subtract long.MinValue from it. + return (result >= 0) ? result : (long.MinValue - result); + } + + /// + /// Returns a 'directional' int value. This is a int value which acts the same as a float, + /// e.g. a negative float value will return a negative int value starting at 0 and going + /// more negative as the float value gets more negative. + /// + /// The input float value. + /// An int value which is roughly the equivalent of the double value. + static int AsDirectionalInt32(float value) + { + // Convert in the normal way. + int result = SingleToInt32Bits(value); + + // Now find out where we're at in the range + // If the value is larger/equal to zero then we can just return the value + // if the value is negative we subtract int.MinValue from it. + return (result >= 0) ? result : (int.MinValue - result); + } + + /// + /// Increments a floating point number to the next bigger number representable by the data type. + /// + /// The value which needs to be incremented. + /// How many times the number should be incremented. + /// + /// The incrementation step length depends on the provided value. + /// Increment(double.MaxValue) will return positive infinity. + /// + /// The next larger floating point value. + public static double Increment(this double value, int count = 1) + { + if (double.IsInfinity(value) || double.IsNaN(value) || count == 0) + { + return value; + } + + if (count < 0) + { + return Decrement(value, -count); + } + + // Translate the bit pattern of the double to an integer. + // Note that this leads to: + // double > 0 --> long > 0, growing as the double value grows + // double < 0 --> long < 0, increasing in absolute magnitude as the double + // gets closer to zero! + // i.e. 0 - double.epsilon will give the largest long value! + long intValue = BitConverter.DoubleToInt64Bits(value); + if (intValue < 0) + { + intValue -= count; + } + else + { + intValue += count; + } + + // Note that long.MinValue has the same bit pattern as -0.0. + if (intValue == long.MinValue) + { + return 0; + } + + // Note that not all long values can be translated into double values. There's a whole bunch of them + // which return weird values like infinity and NaN + return BitConverter.Int64BitsToDouble(intValue); + } + + /// + /// Decrements a floating point number to the next smaller number representable by the data type. + /// + /// The value which should be decremented. + /// How many times the number should be decremented. + /// + /// The decrementation step length depends on the provided value. + /// Decrement(double.MinValue) will return negative infinity. + /// + /// The next smaller floating point value. + public static double Decrement(this double value, int count = 1) + { + if (double.IsInfinity(value) || double.IsNaN(value) || count == 0) + { + return value; + } + + if (count < 0) + { + return Decrement(value, -count); + } + + // Translate the bit pattern of the double to an integer. + // Note that this leads to: + // double > 0 --> long > 0, growing as the double value grows + // double < 0 --> long < 0, increasing in absolute magnitude as the double + // gets closer to zero! + // i.e. 0 - double.epsilon will give the largest long value! + long intValue = BitConverter.DoubleToInt64Bits(value); + + // If the value is zero then we'd really like the value to be -0. So we'll make it -0 + // and then everything else should work out. + if (intValue == 0) + { + // Note that long.MinValue has the same bit pattern as -0.0. + intValue = long.MinValue; + } + + if (intValue < 0) + { + intValue += count; + } + else + { + intValue -= count; + } + + // Note that not all long values can be translated into double values. There's a whole bunch of them + // which return weird values like infinity and NaN + return BitConverter.Int64BitsToDouble(intValue); + } + + /// + /// Forces small numbers near zero to zero, according to the specified absolute accuracy. + /// + /// The real number to coerce to zero, if it is almost zero. + /// The maximum count of numbers between the zero and the number . + /// + /// Zero if || is fewer than numbers from zero, otherwise. + /// + public static double CoerceZero(this double a, int maxNumbersBetween) + { + return CoerceZero(a, (long)maxNumbersBetween); + } + + /// + /// Forces small numbers near zero to zero, according to the specified absolute accuracy. + /// + /// The real number to coerce to zero, if it is almost zero. + /// The maximum count of numbers between the zero and the number . + /// + /// Zero if || is fewer than numbers from zero, otherwise. + /// + /// + /// Thrown if is smaller than zero. + /// + public static double CoerceZero(this double a, long maxNumbersBetween) + { + if (maxNumbersBetween < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxNumbersBetween)); + } + + if (double.IsInfinity(a) || double.IsNaN(a)) + { + return a; + } + + // We allow maxNumbersBetween between 0 and the number so + // we need to check if there a + if (NumbersBetween(0.0, a) <= (ulong)maxNumbersBetween) + { + return 0.0; + } + + return a; + } + + /// + /// Forces small numbers near zero to zero, according to the specified absolute accuracy. + /// + /// The real number to coerce to zero, if it is almost zero. + /// The absolute threshold for to consider it as zero. + /// Zero if || is smaller than , otherwise. + /// + /// Thrown if is smaller than zero. + /// + public static double CoerceZero(this double a, double maximumAbsoluteError) + { + if (maximumAbsoluteError < 0) + { + throw new ArgumentOutOfRangeException(nameof(maximumAbsoluteError)); + } + + if (double.IsInfinity(a) || double.IsNaN(a)) + { + return a; + } + + if (Math.Abs(a) < maximumAbsoluteError) + { + return 0.0; + } + + return a; + } + + /// + /// Forces small numbers near zero to zero. + /// + /// The real number to coerce to zero, if it is almost zero. + /// Zero if || is smaller than 2^(-53) = 1.11e-16, otherwise. + public static double CoerceZero(this double a) + { + return CoerceZero(a, DoublePrecision); + } + + /// + /// Determines the range of floating point numbers that will match the specified value with the given tolerance. + /// + /// The value. + /// The ulps difference. + /// + /// Thrown if is smaller than zero. + /// + /// Tuple of the bottom and top range ends. + public static Tuple RangeOfMatchingFloatingPointNumbers(this double value, long maxNumbersBetween) + { + // Make sure ulpDifference is non-negative + if (maxNumbersBetween < 1) + { + throw new ArgumentOutOfRangeException(nameof(maxNumbersBetween)); + } + + // If the value is infinity (positive or negative) just + // return the same infinity for the range. + if (double.IsInfinity(value)) + { + return new Tuple(value, value); + } + + // If the value is a NaN then the range is a NaN too. + if (double.IsNaN(value)) + { + return new Tuple(double.NaN, double.NaN); + } + + // Translate the bit pattern of the double to an integer. + // Note that this leads to: + // double > 0 --> long > 0, growing as the double value grows + // double < 0 --> long < 0, increasing in absolute magnitude as the double + // gets closer to zero! + // i.e. 0 - double.epsilon will give the largest long value! + long intValue = BitConverter.DoubleToInt64Bits(value); + + // We need to protect against over- and under-flow of the intValue when + // we start to add the ulpsDifference. + if (intValue < 0) + { + // Note that long.MinValue has the same bit pattern as + // -0.0. Therefore we're working in opposite direction (i.e. add if we want to + // go more negative and subtract if we want to go less negative) + var topRangeEnd = Math.Abs(long.MinValue - intValue) < maxNumbersBetween + // Got underflow, which can be fixed by splitting the calculation into two bits + // first get the remainder of the intValue after subtracting it from the long.MinValue + // and add that to the ulpsDifference. That way we'll turn positive without underflow + ? BitConverter.Int64BitsToDouble(maxNumbersBetween + (long.MinValue - intValue)) + // No problems here, move along. + : BitConverter.Int64BitsToDouble(intValue - maxNumbersBetween); + + var bottomRangeEnd = Math.Abs(intValue) < maxNumbersBetween + // Underflow, which means we'd have to go further than a long would allow us. + // Also we couldn't translate it back to a double, so we'll return -Double.MaxValue + ? -double.MaxValue + // intValue is negative. Adding the positive ulpsDifference means that it gets less negative. + // However due to the conversion way this means that the actual double value gets more negative :-S + : BitConverter.Int64BitsToDouble(intValue + maxNumbersBetween); + + return new Tuple(bottomRangeEnd, topRangeEnd); + } + else + { + // IntValue is positive + var topRangeEnd = long.MaxValue - intValue < maxNumbersBetween + // Overflow, which means we'd have to go further than a long would allow us. + // Also we couldn't translate it back to a double, so we'll return Double.MaxValue + ? double.MaxValue + // No troubles here + : BitConverter.Int64BitsToDouble(intValue + maxNumbersBetween); + + // Check the bottom range end for underflows + var bottomRangeEnd = intValue > maxNumbersBetween + // No problems here. IntValue is larger than ulpsDifference so we'll end up with a + // positive number. + ? BitConverter.Int64BitsToDouble(intValue - maxNumbersBetween) + // Int value is bigger than zero but smaller than the ulpsDifference. So we'll need to deal with + // the reversal at the negative end + : BitConverter.Int64BitsToDouble(long.MinValue + (maxNumbersBetween - intValue)); + + return new Tuple(bottomRangeEnd, topRangeEnd); + } + } + + /// + /// Returns the floating point number that will match the value with the tolerance on the maximum size (i.e. the result is + /// always bigger than the value) + /// + /// The value. + /// The ulps difference. + /// The maximum floating point number which is larger than the given . + public static double MaximumMatchingFloatingPointNumber(this double value, long maxNumbersBetween) + { + return RangeOfMatchingFloatingPointNumbers(value, maxNumbersBetween).Item2; + } + + /// + /// Returns the floating point number that will match the value with the tolerance on the minimum size (i.e. the result is + /// always smaller than the value) + /// + /// The value. + /// The ulps difference. + /// The minimum floating point number which is smaller than the given . + public static double MinimumMatchingFloatingPointNumber(this double value, long maxNumbersBetween) + { + return RangeOfMatchingFloatingPointNumbers(value, maxNumbersBetween).Item1; + } + + /// + /// Determines the range of ulps that will match the specified value with the given tolerance. + /// + /// The value. + /// The relative difference. + /// + /// Thrown if is smaller than zero. + /// + /// + /// Thrown if is double.PositiveInfinity or double.NegativeInfinity. + /// + /// + /// Thrown if is double.NaN. + /// + /// + /// Tuple with the number of ULPS between the value and the value - relativeDifference as first, + /// and the number of ULPS between the value and the value + relativeDifference as second value. + /// + public static Tuple RangeOfMatchingNumbers(this double value, double relativeDifference) + { + // Make sure the relative is non-negative + if (relativeDifference < 0) + { + throw new ArgumentOutOfRangeException(nameof(relativeDifference)); + } + + // If the value is infinity (positive or negative) then + // we can't determine the range. + if (double.IsInfinity(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + // If the value is a NaN then we can't determine the range. + if (double.IsNaN(value)) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + // If the value is zero (0.0) then we can't calculate the relative difference + // so return the ulps counts for the difference. + if (value.Equals(0)) + { + var v = BitConverter.DoubleToInt64Bits(relativeDifference); + return new Tuple(v, v); + } + + // Calculate the ulps for the maximum and minimum values + // Note that these can overflow + long max = AsDirectionalInt64(value + (relativeDifference * Math.Abs(value))); + long min = AsDirectionalInt64(value - (relativeDifference * Math.Abs(value))); + + // Calculate the ulps from the value + long intValue = AsDirectionalInt64(value); + + // Determine the ranges + return new Tuple(Math.Abs(intValue - min), Math.Abs(max - intValue)); + } + + /// + /// Evaluates the count of numbers between two double numbers + /// + /// The first parameter. + /// The second parameter. + /// The second number is included in the number, thus two equal numbers evaluate to zero and two neighbor numbers evaluate to one. Therefore, what is returned is actually the count of numbers between plus 1. + /// The number of floating point values between and . + /// + /// Thrown if is double.PositiveInfinity or double.NegativeInfinity. + /// + /// + /// Thrown if is double.NaN. + /// + /// + /// Thrown if is double.PositiveInfinity or double.NegativeInfinity. + /// + /// + /// Thrown if is double.NaN. + /// + [CLSCompliant(false)] + public static ulong NumbersBetween(this double a, double b) + { + if (double.IsNaN(a) || double.IsInfinity(a)) + { + throw new ArgumentOutOfRangeException(nameof(a)); + } + + if (double.IsNaN(b) || double.IsInfinity(b)) + { + throw new ArgumentOutOfRangeException(nameof(b)); + } + + // Calculate the ulps for the maximum and minimum values + // Note that these can overflow + long intA = AsDirectionalInt64(a); + long intB = AsDirectionalInt64(b); + + // Now find the number of values between the two doubles. This should not overflow + // given that there are more long values than there are double values + return (a >= b) ? (ulong)(intA - intB) : (ulong)(intB - intA); + } + + /// + /// Evaluates the minimum distance to the next distinguishable number near the argument value. + /// + /// The value used to determine the minimum distance. + /// + /// Relative Epsilon (positive double or NaN). + /// + /// Evaluates the negative epsilon. The more common positive epsilon is equal to two times this negative epsilon. + /// + public static double EpsilonOf(this double value) + { + if (double.IsInfinity(value) || double.IsNaN(value)) + { + return double.NaN; + } + + long signed64 = BitConverter.DoubleToInt64Bits(value); + if (signed64 == 0) + { + signed64++; + return BitConverter.Int64BitsToDouble(signed64) - value; + } + + if (signed64-- < 0) + { + return BitConverter.Int64BitsToDouble(signed64) - value; + } + + return value - BitConverter.Int64BitsToDouble(signed64); + } + + /// + /// Evaluates the minimum distance to the next distinguishable number near the argument value. + /// + /// The value used to determine the minimum distance. + /// + /// Relative Epsilon (positive float or NaN). + /// + /// Evaluates the negative epsilon. The more common positive epsilon is equal to two times this negative epsilon. + /// + public static float EpsilonOf(this float value) + { + if (float.IsInfinity(value) || float.IsNaN(value)) + { + return float.NaN; + } + + int signed32 = SingleToInt32Bits(value); + if (signed32 == 0) + { + signed32++; + return Int32BitsToSingle(signed32) - value; + } + + if (signed32-- < 0) + { + return Int32BitsToSingle(signed32) - value; + } + + return value - Int32BitsToSingle(signed32); + } + + /// + /// Evaluates the minimum distance to the next distinguishable number near the argument value. + /// + /// The value used to determine the minimum distance. + /// Relative Epsilon (positive double or NaN) + /// Evaluates the positive epsilon. See also + /// + public static double PositiveEpsilonOf(this double value) + { + return 2 * EpsilonOf(value); + } + + /// + /// Evaluates the minimum distance to the next distinguishable number near the argument value. + /// + /// The value used to determine the minimum distance. + /// Relative Epsilon (positive float or NaN) + /// Evaluates the positive epsilon. See also + /// + public static float PositiveEpsilonOf(this float value) + { + return 2 * EpsilonOf(value); + } + + /// + /// Calculates the actual (negative) double precision machine epsilon - the smallest number that can be subtracted from 1, yielding a results different than 1. + /// This is also known as unit roundoff error. According to the definition of Prof. Demmel. + /// + /// Positive Machine epsilon + static double MeasureMachineEpsilon() + { + double eps = 1.0d; + + while ((1.0d - (eps / 2.0d)) < 1.0d) + eps /= 2.0d; + + return eps; + } + + /// + /// Calculates the actual positive double precision machine epsilon - the smallest number that can be added to 1, yielding a results different than 1. + /// This is also known as unit roundoff error. According to the definition of Prof. Higham. + /// + /// Machine epsilon + static double MeasurePositiveMachineEpsilon() + { + double eps = 1.0d; + + while ((1.0d + (eps / 2.0d)) > 1.0d) + eps /= 2.0d; + + return eps; + } + + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + static double Truncate(double value) + { + return Math.Truncate(value); + } + + static int SingleToInt32Bits(float value) + { + var union = new SingleIntUnion { Single = value }; + return union.Int32; + } + + static float Int32BitsToSingle(int value) + { + var union = new SingleIntUnion { Int32 = value }; + return union.Single; + } + + [StructLayout(LayoutKind.Explicit)] + struct SingleIntUnion + { + [FieldOffset(0)] + public float Single; + + [FieldOffset(0)] + public int Int32; + } +} diff --git a/MathNet.Numerics/Properties/AssemblyInfo.cs b/MathNet.Numerics/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f3727cd --- /dev/null +++ b/MathNet.Numerics/Properties/AssemblyInfo.cs @@ -0,0 +1,51 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(true)] +[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.MainAssembly)] +[assembly: ComVisible(false)] +[assembly: Guid("7b66646f-f0ee-425d-9065-910d1937a2df")] + +#if STRONGNAME +[assembly: InternalsVisibleTo("MathNet.Numerics.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed2314a577643d859571b8b9307c6ff2670525c4598fbb307e57ea65ebf5d4417284cb3da9181636480b623f4db8cc3c1947244ba069df0df86e2431621f51a488f9929519a1c5d0ae595f6e2d0e4094685f0c1229ff658360acbb9f63f1a0258e984dda00dc7ad4fd16dbb550ec1ef8a11df138402b7c1998ee224e652c839b")] +[assembly: InternalsVisibleTo("MathNet.Numerics.Tests.MKL, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed2314a577643d859571b8b9307c6ff2670525c4598fbb307e57ea65ebf5d4417284cb3da9181636480b623f4db8cc3c1947244ba069df0df86e2431621f51a488f9929519a1c5d0ae595f6e2d0e4094685f0c1229ff658360acbb9f63f1a0258e984dda00dc7ad4fd16dbb550ec1ef8a11df138402b7c1998ee224e652c839b")] +[assembly: InternalsVisibleTo("MathNet.Numerics.Tests.CUDA, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed2314a577643d859571b8b9307c6ff2670525c4598fbb307e57ea65ebf5d4417284cb3da9181636480b623f4db8cc3c1947244ba069df0df86e2431621f51a488f9929519a1c5d0ae595f6e2d0e4094685f0c1229ff658360acbb9f63f1a0258e984dda00dc7ad4fd16dbb550ec1ef8a11df138402b7c1998ee224e652c839b")] +[assembly: InternalsVisibleTo("MathNet.Numerics.Tests.OpenBLAS, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed2314a577643d859571b8b9307c6ff2670525c4598fbb307e57ea65ebf5d4417284cb3da9181636480b623f4db8cc3c1947244ba069df0df86e2431621f51a488f9929519a1c5d0ae595f6e2d0e4094685f0c1229ff658360acbb9f63f1a0258e984dda00dc7ad4fd16dbb550ec1ef8a11df138402b7c1998ee224e652c839b")] +[assembly: InternalsVisibleTo("Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed2314a577643d859571b8b9307c6ff2670525c4598fbb307e57ea65ebf5d4417284cb3da9181636480b623f4db8cc3c1947244ba069df0df86e2431621f51a488f9929519a1c5d0ae595f6e2d0e4094685f0c1229ff658360acbb9f63f1a0258e984dda00dc7ad4fd16dbb550ec1ef8a11df138402b7c1998ee224e652c839b")] +#else +[assembly: InternalsVisibleTo("MathNet.Numerics.Tests")] +[assembly: InternalsVisibleTo("MathNet.Numerics.Tests.MKL")] +[assembly: InternalsVisibleTo("MathNet.Numerics.Tests.CUDA")] +[assembly: InternalsVisibleTo("MathNet.Numerics.Tests.OpenBLAS")] +[assembly: InternalsVisibleTo("Benchmark")] +#endif diff --git a/MathNet.Numerics/Properties/Resources.Designer.cs b/MathNet.Numerics/Properties/Resources.Designer.cs new file mode 100644 index 0000000..dd3db82 --- /dev/null +++ b/MathNet.Numerics/Properties/Resources.Designer.cs @@ -0,0 +1,1073 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System.Reflection; + +namespace MathNet.Numerics.Properties; + +/// +/// A strongly-typed resource class, for looking up localized strings, etc. +/// +// This class was auto-generated by the StronglyTypedResourceBuilder +// class via a tool like ResGen or Visual Studio. +// To add or remove a member, edit your .ResX file then rerun ResGen +// with the /str option, or rebuild your VS project. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] +[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { +#if NETSTANDARD1_3 + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MathNet.Numerics.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + resourceMan = temp; + } +#else + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MathNet.Numerics.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } +#endif + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The accuracy couldn't be reached with the specified number of iterations.. + /// + public static string AccuracyNotReached { + get { + return ResourceManager.GetString("AccuracyNotReached", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The array arguments must have the same length.. + /// + public static string ArgumentArraysSameLength { + get { + return ResourceManager.GetString("ArgumentArraysSameLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given array has the wrong length. Should be {0}.. + /// + public static string ArgumentArrayWrongLength { + get { + return ResourceManager.GetString("ArgumentArrayWrongLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The argument must be between 0 and 1.. + /// + public static string ArgumentBetween0And1 { + get { + return ResourceManager.GetString("ArgumentBetween0And1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value cannot be in the range -1 < x < 1.. + /// + public static string ArgumentCannotBeBetweenOneAndNegativeOne { + get { + return ResourceManager.GetString("ArgumentCannotBeBetweenOneAndNegativeOne", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be even.. + /// + public static string ArgumentEven { + get { + return ResourceManager.GetString("ArgumentEven", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The histogram does not contain the value.. + /// + public static string ArgumentHistogramContainsNot { + get { + return ResourceManager.GetString("ArgumentHistogramContainsNot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value is expected to be between {0} and {1} (including {0} and {1}).. + /// + public static string ArgumentInIntervalXYInclusive { + get { + return ResourceManager.GetString("ArgumentInIntervalXYInclusive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to At least one item of {0} is a null reference (Nothing in Visual Basic).. + /// + public static string ArgumentItemNull { + get { + return ResourceManager.GetString("ArgumentItemNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be greater than or equal to one.. + /// + public static string ArgumentLessThanOne { + get { + return ResourceManager.GetString("ArgumentLessThanOne", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix dimensions must agree.. + /// + public static string ArgumentMatrixDimensions { + get { + return ResourceManager.GetString("ArgumentMatrixDimensions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix dimensions must agree: {0}.. + /// + public static string ArgumentMatrixDimensions1 { + get { + return ResourceManager.GetString("ArgumentMatrixDimensions1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix dimensions must agree: op1 is {0}, op2 is {1}.. + /// + public static string ArgumentMatrixDimensions2 { + get { + return ResourceManager.GetString("ArgumentMatrixDimensions2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix dimensions must agree: op1 is {0}, op2 is {1}, op3 is {2}.. + /// + public static string ArgumentMatrixDimensions3 { + get { + return ResourceManager.GetString("ArgumentMatrixDimensions3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The requested matrix does not exist.. + /// + public static string ArgumentMatrixDoesNotExist { + get { + return ResourceManager.GetString("ArgumentMatrixDoesNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The matrix indices must not be out of range of the given matrix.. + /// + public static string ArgumentMatrixIndexOutOfRange { + get { + return ResourceManager.GetString("ArgumentMatrixIndexOutOfRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must not be rank deficient.. + /// + public static string ArgumentMatrixNotRankDeficient { + get { + return ResourceManager.GetString("ArgumentMatrixNotRankDeficient", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must not be singular.. + /// + public static string ArgumentMatrixNotSingular { + get { + return ResourceManager.GetString("ArgumentMatrixNotSingular", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must be positive definite.. + /// + public static string ArgumentMatrixPositiveDefinite { + get { + return ResourceManager.GetString("ArgumentMatrixPositiveDefinite", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix column dimensions must agree.. + /// + public static string ArgumentMatrixSameColumnDimension { + get { + return ResourceManager.GetString("ArgumentMatrixSameColumnDimension", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix row dimensions must agree.. + /// + public static string ArgumentMatrixSameRowDimension { + get { + return ResourceManager.GetString("ArgumentMatrixSameRowDimension", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must have exactly one column.. + /// + public static string ArgumentMatrixSingleColumn { + get { + return ResourceManager.GetString("ArgumentMatrixSingleColumn", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must have exactly one column and row, thus have only one cell.. + /// + public static string ArgumentMatrixSingleColumnRow { + get { + return ResourceManager.GetString("ArgumentMatrixSingleColumnRow", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must have exactly one row.. + /// + public static string ArgumentMatrixSingleRow { + get { + return ResourceManager.GetString("ArgumentMatrixSingleRow", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must be square.. + /// + public static string ArgumentMatrixSquare { + get { + return ResourceManager.GetString("ArgumentMatrixSquare", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must be symmetric.. + /// + public static string ArgumentMatrixSymmetric { + get { + return ResourceManager.GetString("ArgumentMatrixSymmetric", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must be symmetric positive definite.. + /// + public static string ArgumentMatrixSymmetricPositiveDefinite { + get { + return ResourceManager.GetString("ArgumentMatrixSymmetricPositiveDefinite", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In the specified range, the exclusive maximum must be greater than the inclusive minimum.. + /// + public static string ArgumentMaxExclusiveMustBeLargerThanMinInclusive { + get { + return ResourceManager.GetString("ArgumentMaxExclusiveMustBeLargerThanMinInclusive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In the specified range, the minimum is greater than maximum.. + /// + public static string ArgumentMinValueGreaterThanMaxValue { + get { + return ResourceManager.GetString("ArgumentMinValueGreaterThanMaxValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be positive.. + /// + public static string ArgumentMustBePositive { + get { + return ResourceManager.GetString("ArgumentMustBePositive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must neither be infinite nor NaN.. + /// + public static string ArgumentNotInfinityNaN { + get { + return ResourceManager.GetString("ArgumentNotInfinityNaN", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must not be negative (zero is ok).. + /// + public static string ArgumentNotNegative { + get { + return ResourceManager.GetString("ArgumentNotNegative", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is a null reference (Nothing in Visual Basic).. + /// + public static string ArgumentNull { + get { + return ResourceManager.GetString("ArgumentNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be odd.. + /// + public static string ArgumentOdd { + get { + return ResourceManager.GetString("ArgumentOdd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} must be greater than {1}.. + /// + public static string ArgumentOutOfRangeGreater { + get { + return ResourceManager.GetString("ArgumentOutOfRangeGreater", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} must be greater than or equal to {1}.. + /// + public static string ArgumentOutOfRangeGreaterEqual { + get { + return ResourceManager.GetString("ArgumentOutOfRangeGreaterEqual", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} must be smaller than {1}.. + /// + public static string ArgumentOutOfRangeSmaller { + get { + return ResourceManager.GetString("ArgumentOutOfRangeSmaller", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} must be smaller than or equal to {1}.. + /// + public static string ArgumentOutOfRangeSmallerEqual { + get { + return ResourceManager.GetString("ArgumentOutOfRangeSmallerEqual", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The chosen parameter set is invalid (probably some value is out of range).. + /// + public static string ArgumentParameterSetInvalid { + get { + return ResourceManager.GetString("ArgumentParameterSetInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given expression does not represent a complex number.. + /// + public static string ArgumentParseComplexNumber { + get { + return ResourceManager.GetString("ArgumentParseComplexNumber", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be positive (and not zero).. + /// + public static string ArgumentPositive { + get { + return ResourceManager.GetString("ArgumentPositive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size must be a Power of Two.. + /// + public static string ArgumentPowerOfTwo { + get { + return ResourceManager.GetString("ArgumentPowerOfTwo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size must be a Power of Two in every dimension.. + /// + public static string ArgumentPowerOfTwoEveryDimension { + get { + return ResourceManager.GetString("ArgumentPowerOfTwoEveryDimension", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The range between {0} and {1} must be less than or equal to {2}.. + /// + public static string ArgumentRangeLessEqual { + get { + return ResourceManager.GetString("ArgumentRangeLessEqual", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Arguments must be different objects.. + /// + public static string ArgumentReferenceDifferent { + get { + return ResourceManager.GetString("ArgumentReferenceDifferent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Array must have exactly one dimension (and not be null).. + /// + public static string ArgumentSingleDimensionArray { + get { + return ResourceManager.GetString("ArgumentSingleDimensionArray", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value is too large.. + /// + public static string ArgumentTooLarge { + get { + return ResourceManager.GetString("ArgumentTooLarge", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value is too large for the current iteration limit.. + /// + public static string ArgumentTooLargeForIterationLimit { + get { + return ResourceManager.GetString("ArgumentTooLargeForIterationLimit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type mismatch.. + /// + public static string ArgumentTypeMismatch { + get { + return ResourceManager.GetString("ArgumentTypeMismatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The upper bound must be strictly larger than the lower bound.. + /// + public static string ArgumentUpperBoundMustBeLargerThanLowerBound { + get { + return ResourceManager.GetString("ArgumentUpperBoundMustBeLargerThanLowerBound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The upper bound must be at least as large as the lower bound.. + /// + public static string ArgumentUpperBoundMustBeLargerThanOrEqualToLowerBound { + get { + return ResourceManager.GetString("ArgumentUpperBoundMustBeLargerThanOrEqualToLowerBound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Array length must be a multiple of {0}.. + /// + public static string ArgumentVectorLengthsMultipleOf { + get { + return ResourceManager.GetString("ArgumentVectorLengthsMultipleOf", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All vectors must have the same dimensionality.. + /// + public static string ArgumentVectorsSameLength { + get { + return ResourceManager.GetString("ArgumentVectorsSameLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The vector must have 3 dimensions.. + /// + public static string ArgumentVectorThreeDimensional { + get { + return ResourceManager.GetString("ArgumentVectorThreeDimensional", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given array is too small. It must be at least {0} long.. + /// + public static string ArrayTooSmall { + get { + return ResourceManager.GetString("ArrayTooSmall", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Big endian files are not supported.. + /// + public static string BigEndianNotSupported { + get { + return ResourceManager.GetString("BigEndianNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The supplied collection is empty.. + /// + public static string CollectionEmpty { + get { + return ResourceManager.GetString("CollectionEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Complex matrices are not supported.. + /// + public static string ComplexMatricesNotSupported { + get { + return ResourceManager.GetString("ComplexMatricesNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An algorithm failed to converge.. + /// + public static string ConvergenceFailed { + get { + return ResourceManager.GetString("ConvergenceFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The sample size must be larger than the given degrees of freedom.. + /// + public static string DegreesOfFreedomMustBeLessThanSampleSize { + get { + return ResourceManager.GetString("DegreesOfFreedomMustBeLessThanSampleSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This feature is not implemented yet (but is planned).. + /// + public static string FeaturePlannedButNotImplementedYet { + get { + return ResourceManager.GetString("FeaturePlannedButNotImplementedYet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given file doesn't exist.. + /// + public static string FileDoesNotExist { + get { + return ResourceManager.GetString("FileDoesNotExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sample points should be sorted in strictly ascending order. + /// + public static string Interpolation_Initialize_SamplePointsNotStrictlyAscendingOrder { + get { + return ResourceManager.GetString("Interpolation_Initialize_SamplePointsNotStrictlyAscendingOrder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All sample points should be unique.. + /// + public static string Interpolation_Initialize_SamplePointsNotUnique { + get { + return ResourceManager.GetString("Interpolation_Initialize_SamplePointsNotUnique", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid parameterization for the distribution.. + /// + public static string InvalidDistributionParameters { + get { + return ResourceManager.GetString("InvalidDistributionParameters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid Left Boundary Condition.. + /// + public static string InvalidLeftBoundaryCondition { + get { + return ResourceManager.GetString("InvalidLeftBoundaryCondition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The operation could not be performed because the accumulator is empty.. + /// + public static string InvalidOperationAccumulatorEmpty { + get { + return ResourceManager.GetString("InvalidOperationAccumulatorEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The operation could not be performed because the histogram is empty.. + /// + public static string InvalidOperationHistogramEmpty { + get { + return ResourceManager.GetString("InvalidOperationHistogramEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not enough points in the distribution.. + /// + public static string InvalidOperationHistogramNotEnoughPoints { + get { + return ResourceManager.GetString("InvalidOperationHistogramNotEnoughPoints", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Samples Provided. Preparation Required.. + /// + public static string InvalidOperationNoSamplesProvided { + get { + return ResourceManager.GetString("InvalidOperationNoSamplesProvided", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An invalid parameter was passed to a native method.. + /// + public static string InvalidParameter { + get { + return ResourceManager.GetString("InvalidParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An invalid parameter was passed to a native method, parameter number : {0}. + /// + public static string InvalidParameterWithNumber { + get { + return ResourceManager.GetString("InvalidParameterWithNumber", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid Right Boundary Condition.. + /// + public static string InvalidRightBoundaryCondition { + get { + return ResourceManager.GetString("InvalidRightBoundaryCondition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lag must be positive. + /// + public static string LagMustBePositive { + get { + return ResourceManager.GetString("LagMustBePositive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lag must be smaller than the sample size. + /// + public static string LagMustBeSmallerThanTheSampleSize { + get { + return ResourceManager.GetString("LagMustBeSmallerThanTheSampleSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ddd MMM dd HH:mm:ss yyyy. + /// + public static string MatlabDateHeaderFormat { + get { + return ResourceManager.GetString("MatlabDateHeaderFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrices can not be empty and must have at least one row and column.. + /// + public static string MatrixCanNotBeEmpty { + get { + return ResourceManager.GetString("MatrixCanNotBeEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of columns of a matrix must be positive.. + /// + public static string MatrixColumnsMustBePositive { + get { + return ResourceManager.GetString("MatrixColumnsMustBePositive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Matrix must be in sparse storage format. + /// + public static string MatrixMustBeSparse { + get { + return ResourceManager.GetString("MatrixMustBeSparse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of rows of a matrix must be positive.. + /// + public static string MatrixRowsMustBePositive { + get { + return ResourceManager.GetString("MatrixRowsMustBePositive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of rows or columns of a matrix must be positive.. + /// + public static string MatrixRowsOrColumnsMustBePositive { + get { + return ResourceManager.GetString("MatrixRowsOrColumnsMustBePositive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to allocate native memory.. + /// + public static string MemoryAllocation { + get { + return ResourceManager.GetString("MemoryAllocation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only 1 and 2 dimensional arrays are supported.. + /// + public static string MoreThan2D { + get { + return ResourceManager.GetString("MoreThan2D", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Data must contain at least {0} values.. + /// + public static string MustContainAtLeast { + get { + return ResourceManager.GetString("MustContainAtLeast", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name cannot contain a space. name: {0}. + /// + public static string NameCannotContainASpace { + get { + return ResourceManager.GetString("NameCannotContainASpace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not a supported type.. + /// + public static string NotSupportedType { + get { + return ResourceManager.GetString("NotSupportedType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Algorithm experience a numerical break down + ///. + /// + public static string NumericalBreakdown { + get { + return ResourceManager.GetString("NumericalBreakdown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to: Numerical estimation of the statistic has failed. + /// + public static string NumericalEstimationFailed + { + get + { + return ResourceManager.GetString("NumericalEstimationFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The two arguments can't be compared (maybe they are part of a partial ordering?). + /// + public static string PartialOrderException { + get { + return ResourceManager.GetString("PartialOrderException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The integer array does not represent a valid permutation.. + /// + public static string PermutationAsIntArrayInvalid { + get { + return ResourceManager.GetString("PermutationAsIntArrayInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The sampler's proposal distribution is not upper bounding the target density.. + /// + public static string ProposalDistributionNoUpperBound { + get { + return ResourceManager.GetString("ProposalDistributionNoUpperBound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A regression of the requested order requires at least {0} samples. Only {1} samples have been provided. . + /// + public static string RegressionNotEnoughSamples { + get { + return ResourceManager.GetString("RegressionNotEnoughSamples", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The algorithm has failed, exceeded the number of iterations allowed or there is no root within the provided bounds.. + /// + public static string RootFindingFailed { + get { + return ResourceManager.GetString("RootFindingFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The algorithm has failed, exceeded the number of iterations allowed or there is no root within the provided bounds. Consider to use RobustNewtonRaphson instead.. + /// + public static string RootFindingFailedRecommendRobustNewtonRaphson { + get { + return ResourceManager.GetString("RootFindingFailedRecommendRobustNewtonRaphson", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The lower and upper bounds must bracket a single root.. + /// + public static string RootMustBeBracketedByBounds { + get { + return ResourceManager.GetString("RootMustBeBracketedByBounds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The algorithm ended without root in the range.. + /// + public static string RootNotFound { + get { + return ResourceManager.GetString("RootNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of rows must greater than or equal to the number of columns.. + /// + public static string RowsLessThanColumns { + get { + return ResourceManager.GetString("RowsLessThanColumns", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All sample vectors must have the same length. However, vectors with disagreeing length {0} and {1} have been provided. A sample with index i is given by the value at index i of each provided vector.. + /// + public static string SampleVectorsSameLength { + get { + return ResourceManager.GetString("SampleVectorsSameLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to U is singular, and the inversion could not be completed.. + /// + public static string SingularUMatrix { + get { + return ResourceManager.GetString("SingularUMatrix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to U is singular, and the inversion could not be completed. The {0}-th diagonal element of the factor U is zero.. + /// + public static string SingularUMatrixWithElement { + get { + return ResourceManager.GetString("SingularUMatrixWithElement", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The singular vectors were not computed.. + /// + public static string SingularVectorsNotComputed { + get { + return ResourceManager.GetString("SingularVectorsNotComputed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This special case is not supported yet (but is planned).. + /// + public static string SpecialCasePlannedButNotImplementedYet { + get { + return ResourceManager.GetString("SpecialCasePlannedButNotImplementedYet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given stop criterion already exist in the collection.. + /// + public static string StopCriterionDuplicate { + get { + return ResourceManager.GetString("StopCriterionDuplicate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is no stop criterion in the collection.. + /// + public static string StopCriterionMissing { + get { + return ResourceManager.GetString("StopCriterionMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to string parameter cannot be empty or null.. + /// + public static string StringNullOrEmpty { + get { + return ResourceManager.GetString("StringNullOrEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to We only support sparse matrix with less than int.MaxValue elements.. + /// + public static string TooManyElements { + get { + return ResourceManager.GetString("TooManyElements", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The moment of the distribution is undefined.. + /// + public static string UndefinedMoment { + get { + return ResourceManager.GetString("UndefinedMoment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A user defined provider has not been specified.. + /// + public static string UserDefinedProviderNotSpecified { + get { + return ResourceManager.GetString("UserDefinedProviderNotSpecified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User work buffers are not supported by this provider.. + /// + public static string UserWorkBufferNotSupported { + get { + return ResourceManager.GetString("UserWorkBufferNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Vectors can not be empty and must have at least one element.. + /// + public static string VectorCanNotBeEmpty { + get { + return ResourceManager.GetString("VectorCanNotBeEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The given work array is too small. Check work[0] for the corret size.. + /// + public static string WorkArrayTooSmall { + get { + return ResourceManager.GetString("WorkArrayTooSmall", resourceCulture); + } + } +} diff --git a/MathNet.Numerics/Properties/Resources.resx b/MathNet.Numerics/Properties/Resources.resx new file mode 100644 index 0000000..094344b --- /dev/null +++ b/MathNet.Numerics/Properties/Resources.resx @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The histogram does not contain the value. + + + Value is expected to be between {0} and {1} (including {0} and {1}). + + + The matrix indices must not be out of range of the given matrix. + + + Matrix must not be rank deficient. + + + Matrix must not be singular. + + + Matrix column dimensions must agree. + + + Matrix dimensions must agree. + + + Matrix dimensions must agree: {0}. + + + Matrix dimensions must agree: op1 is {0}, op2 is {1}. + + + Matrix dimensions must agree: op1 is {0}, op2 is {1}, op3 is {2}. + + + Matrix row dimensions must agree. + + + Matrix must have exactly one column. + + + Matrix must have exactly one column and row, thus have only one cell. + + + Matrix must have exactly one row. + + + Matrix must be square. + + + Matrix must be symmetric. + + + Matrix must be symmetric positive definite. + + + Value must neither be infinite nor NaN. + + + Value must not be negative (zero is ok). + + + {0} is a null reference (Nothing in Visual Basic). + + + {0} must be greater than {1}. + + + {0} must be greater than or equal to {1}. + + + {0} must be smaller than {1}. + + + {0} must be smaller than or equal to {1}. + + + The chosen parameter set is invalid (probably some value is out of range). + + + The given expression does not represent a complex number. + + + Value must be positive (and not zero). + + + Size must be a Power of Two. + + + Size must be a Power of Two in every dimension. + + + The range between {0} and {1} must be less than or equal to {2}. + + + Array must have exactly one dimension (and not be null). + + + Value is too large. + + + Value is too large for the current iteration limit. + + + Type mismatch. + + + Array length must be a multiple of {0}. + + + All vectors must have the same dimensionality. + + + The vector must have 3 dimensions. + + + This feature is not implemented yet (but is planned). + + + Invalid Left Boundary Condition. + + + The operation could not be performed because the accumulator is empty. + + + The operation could not be performed because the histogram is empty. + + + Not enough points in the distribution. + + + No Samples Provided. Preparation Required. + + + Invalid Right Boundary Condition. + + + This special case is not supported yet (but is planned). + + + Invalid parameterization for the distribution. + + + Value must be even. + + + Value must be odd. + + + At least one item of {0} is a null reference (Nothing in Visual Basic). + + + The supplied collection is empty. + + + Value cannot be in the range -1 < x < 1. + + + Value must be greater than or equal to one. + + + Value must be positive. + + + A user defined provider has not been specified. + + + In the specified range, the minimum is greater than maximum. + + + In the specified range, the exclusive maximum must be greater than the inclusive minimum. + + + The upper bound must be at least as large as the lower bound. + + + The two arguments can't be compared (maybe they are part of a partial ordering?) + + + The number of columns of a matrix must be positive. + + + The number of rows of a matrix must be positive. + + + The number of rows or columns of a matrix must be positive. + + + The argument must be between 0 and 1. + + + The sampler's proposal distribution is not upper bounding the target density. + + + Matrix must be positive definite. + + + The array arguments must have the same length. + + + The moment of the distribution is undefined. + + + Arguments must be different objects. + + + The integer array does not represent a valid permutation. + + + An algorithm failed to converge. + + + The singular vectors were not computed. + + + The given work array is too small. Check work[0] for the corret size. + + + string parameter cannot be empty or null. + + + The requested matrix does not exist. + + + Big endian files are not supported. + + + Complex matrices are not supported. + + + The given file doesn't exist. + + + Only 1 and 2 dimensional arrays are supported. + + + {0} is not a supported type. + + + The given stop criterion already exist in the collection. + + + There is no stop criterion in the collection. + + + Name cannot contain a space. name: {0} + + + ddd MMM dd HH:mm:ss yyyy + + + Data must contain at least {0} values. + + + The given array is too small. It must be at least {0} long. + + + The given array has the wrong length. Should be {0}. + + + The number of rows must greater than or equal to the number of columns. + + + We only support sparse matrix with less than int.MaxValue elements. + + + Sample points should be sorted in strictly ascending order + + + All sample points should be unique. + + + The accuracy couldn't be reached with the specified number of iterations. + + + The algorithm ended without root in the range. + + + The lower and upper bounds must bracket a single root. + + + The algorithm has failed, exceeded the number of iterations allowed or there is no root within the provided bounds. + + + The algorithm has failed, exceeded the number of iterations allowed or there is no root within the provided bounds. Consider to use RobustNewtonRaphson instead. + + + Algorithm experience a numerical break down + + + + Lag must be positive + + + Lag must be smaller than the sample size + + + Matrix must be in sparse storage format + + + The upper bound must be strictly larger than the lower bound. + + + Matrices can not be empty and must have at least one row and column. + + + Vectors can not be empty and must have at least one element. + + + User work buffers are not supported by this provider. + + + An invalid parameter was passed to a native method. + + + An invalid parameter was passed to a native method, parameter number : {0} + + + Unable to allocate native memory. + + + U is singular, and the inversion could not be completed. + + + U is singular, and the inversion could not be completed. The {0}-th diagonal element of the factor U is zero. + + + A regression of the requested order requires at least {0} samples. Only {1} samples have been provided. + + + All sample vectors must have the same length. However, vectors with disagreeing length {0} and {1} have been provided. A sample with index i is given by the value at index i of each provided vector. + + + The sample size must be larger than the given degrees of freedom. + + + Numerical estimation of the statistic has failed. The used solver did not succeed in finding a root. + + \ No newline at end of file diff --git a/MathNet.Numerics/Providers/Common/Cuda/CudaProvider.cs b/MathNet.Numerics/Providers/Common/Cuda/CudaProvider.cs new file mode 100644 index 0000000..d349484 --- /dev/null +++ b/MathNet.Numerics/Providers/Common/Cuda/CudaProvider.cs @@ -0,0 +1,143 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Providers.Common.Cuda +{ + public static class CudaProvider + { + const int DesignTimeRevision = 1; + const int MinimumCompatibleRevision = 1; + + static int _nativeRevision; + static bool _nativeX86; + static bool _nativeX64; + static bool _nativeIA64; + static bool _loaded; + + public static bool IsAvailable(string hintPath = null) + { + if (_loaded) + { + return true; + } + + try + { + if (!NativeProviderLoader.TryLoad(SafeNativeMethods.DllName, hintPath)) + { + return false; + } + + int a = SafeNativeMethods.query_capability(0); + int b = SafeNativeMethods.query_capability(1); + int nativeRevision = SafeNativeMethods.query_capability((int)ProviderConfig.Revision); + return a == 0 && b == -1 && nativeRevision >= MinimumCompatibleRevision; + } + catch + { + return false; + } + } + + /// Revision + public static int Load(string hintPath = null) + { + if (_loaded) + { + return _nativeRevision; + } + + int a, b; + try + { + NativeProviderLoader.TryLoad(SafeNativeMethods.DllName, hintPath); + + a = SafeNativeMethods.query_capability(0); + b = SafeNativeMethods.query_capability(1); + _nativeRevision = SafeNativeMethods.query_capability((int)ProviderConfig.Revision); + + _nativeX86 = SafeNativeMethods.query_capability((int)ProviderPlatform.x86) > 0; + _nativeX64 = SafeNativeMethods.query_capability((int)ProviderPlatform.x64) > 0; + _nativeIA64 = SafeNativeMethods.query_capability((int)ProviderPlatform.ia64) > 0; + } + catch (DllNotFoundException e) + { + throw new NotSupportedException("Cuda Native Provider not found.", e); + } + catch (BadImageFormatException e) + { + throw new NotSupportedException("Cuda Native Provider found but failed to load. Please verify that the platform matches (x64 vs x32, Windows vs Linux).", e); + } + catch (EntryPointNotFoundException e) + { + throw new NotSupportedException("Cuda Native Provider does not support capability querying and is therefore not compatible. Consider upgrading to a newer version.", e); + } + + if (a != 0 || b != -1 || _nativeRevision < MinimumCompatibleRevision) + { + throw new NotSupportedException("Cuda Native Provider too old. Consider upgrading to a newer version."); + } + + _loaded = true; + return _nativeRevision; + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// This method is safe to call, even if the provider is not loaded. + /// + public static void FreeResources() + { + } + + public static string Describe() + { + if (!_loaded) + { + return "Nvidia CUDA (not loaded)"; + } + + var parts = new List(); + if (_nativeX86) parts.Add("x86"); + if (_nativeX64) parts.Add("x64"); + if (_nativeIA64) parts.Add("IA64"); + parts.Add("revision " + _nativeRevision); + + return string.Concat("Nvidia CUDA (", string.Join("; ", parts.ToArray()), ")"); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/Cuda/CudaProviderCapabilities.cs b/MathNet.Numerics/Providers/Common/Cuda/CudaProviderCapabilities.cs new file mode 100644 index 0000000..6856ab4 --- /dev/null +++ b/MathNet.Numerics/Providers/Common/Cuda/CudaProviderCapabilities.cs @@ -0,0 +1,53 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +namespace MathNet.Numerics.Providers.Common.Cuda +{ + internal enum ProviderPlatform : int + { + x86 = 8, + x64 = 9, + ia64 = 10, + } + + internal enum ProviderConfig : int + { + Revision = 64, + } + + internal enum ProviderCapability : int + { + LinearAlgebraMajor = 128, + LinearAlgebraMinor = 129, + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/Cuda/SafeNativeMethods.cs b/MathNet.Numerics/Providers/Common/Cuda/SafeNativeMethods.cs new file mode 100644 index 0000000..f9aad3b --- /dev/null +++ b/MathNet.Numerics/Providers/Common/Cuda/SafeNativeMethods.cs @@ -0,0 +1,379 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Runtime.InteropServices; +using System.Security; +using MathNet.Numerics.Providers.LinearAlgebra; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.Common.Cuda +{ + /// + /// P/Invoke methods to the native math libraries. + /// + [SuppressUnmanagedCodeSecurity] + [SecurityCritical] + internal static class SafeNativeMethods + { + // ReSharper disable InconsistentNaming + + /// + /// Name of the native DLL. + /// + const string _DllName = "MathNet.Numerics.CUDA.dll"; + internal static string DllName { get { return _DllName; } } + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int query_capability(int capability); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int createBLASHandle(ref IntPtr blasHandle); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int destroyBLASHandle(IntPtr blasHandle); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int createSolverHandle(ref IntPtr solverHandle); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int destroySolverHandle(IntPtr solverHandle); + + #region BLAS + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_axpy(IntPtr blasHandle, int n, float alpha, float[] x, [In, Out] float[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_axpy(IntPtr blasHandle, int n, double alpha, double[] x, [In, Out] double[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_axpy(IntPtr blasHandle, int n, Complex32 alpha, Complex32[] x, [In, Out] Complex32[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_axpy(IntPtr blasHandle, int n, Complex alpha, Complex[] x, [In, Out] Complex[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_scale(IntPtr blasHandle, int n, float alpha, [Out] float[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_scale(IntPtr blasHandle, int n, double alpha, [Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_scale(IntPtr blasHandle, int n, Complex32 alpha, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_scale(IntPtr blasHandle, int n, Complex alpha, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float s_dot_product(IntPtr blasHandle, int n, float[] x, float[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double d_dot_product(IntPtr blasHandle, int n, double[] x, double[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern Complex32 c_dot_product(IntPtr blasHandle, int n, Complex32[] x, Complex32[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern Complex z_dot_product(IntPtr blasHandle, int n, Complex[] x, Complex[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_matrix_multiply(IntPtr blasHandle, int transA, int transB, int m, int n, int k, float alpha, float[] x, float[] y, float beta, [In, Out] float[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_matrix_multiply(IntPtr blasHandle, int transA, int transB, int m, int n, int k, double alpha, double[] x, double[] y, double beta, [In, Out] double[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_matrix_multiply(IntPtr blasHandle, int transA, int transB, int m, int n, int k, Complex32 alpha, Complex32[] x, Complex32[] y, Complex32 beta, [In, Out] Complex32[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_matrix_multiply(IntPtr blasHandle, int transA, int transB, int m, int n, int k, Complex alpha, Complex[] x, Complex[] y, Complex beta, [In, Out] Complex[] c); + + internal static int ToCUDA(this Transpose transpose) + { + switch (transpose) + { + case Transpose.DontTranspose: + return 0; + + case Transpose.Transpose: + return 1; + + case Transpose.ConjugateTranspose: + return 2; + + default: + throw new ArgumentException("Unsupported transpose: " + transpose); + } + } + + #endregion BLAS + + #region LAPACK + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern float s_matrix_norm(byte norm, int rows, int columns, [In] float[] a, [In, Out] float[] work); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern double d_matrix_norm(byte norm, int rows, int columns, [In] double[] a, [In, Out] double[] work); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern float c_matrix_norm(byte norm, int rows, int columns, [In] Complex32[] a, [In, Out] float[] work); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern double z_matrix_norm(byte norm, int rows, int columns, [In] Complex[] a, [In, Out] double[] work); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_factor(IntPtr solverHandle, int n, [In, Out] float[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_factor(IntPtr solverHandle, int n, [In, Out] double[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_factor(IntPtr solverHandle, int n, [In, Out] Complex32[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_factor(IntPtr solverHandle, int n, [In, Out] Complex[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_factor(IntPtr solverHandle, int n, [In, Out] float[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_factor(IntPtr solverHandle, int n, [In, Out] double[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_factor(IntPtr solverHandle, int n, [In, Out] Complex32[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_factor(IntPtr solverHandle, int n, [In, Out] Complex[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_inverse(IntPtr solverHandle, IntPtr blasHandle, int n, [In, Out] float[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_inverse(IntPtr solverHandle, IntPtr blasHandle, int n, [In, Out] double[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_inverse(IntPtr solverHandle, IntPtr blasHandle, int n, [In, Out] Complex32[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_inverse(IntPtr solverHandle, IntPtr blasHandle, int n, [In, Out] Complex[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_inverse_factored(IntPtr blasHandle, int n, [In, Out] float[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_inverse_factored(IntPtr blasHandle, int n, [In, Out] double[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_inverse_factored(IntPtr blasHandle, int n, [In, Out] Complex32[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_inverse_factored(IntPtr blasHandle, int n, [In, Out] Complex[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_solve_factored(IntPtr solverHandle, int n, int nrhs, float[] a, [In, Out] int[] ipiv, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_solve_factored(IntPtr solverHandle, int n, int nrhs, double[] a, [In, Out] int[] ipiv, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_solve_factored(IntPtr solverHandle, int n, int nrhs, Complex32[] a, [In, Out] int[] ipiv, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_solve_factored(IntPtr solverHandle, int n, int nrhs, Complex[] a, [In, Out] int[] ipiv, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_solve(IntPtr solverHandle, int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_solve(IntPtr solverHandle, int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_solve(IntPtr solverHandle, int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_solve(IntPtr solverHandle, int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_solve(IntPtr solverHandle, int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_solve(IntPtr solverHandle, int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_solve(IntPtr solverHandle, int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_solve(IntPtr solverHandle, int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_solve_factored(IntPtr solverHandle, int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_solve_factored(IntPtr solverHandle, int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_solve_factored(IntPtr solverHandle, int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_solve_factored(IntPtr solverHandle, int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int s_qr_factor(int m, int n, [In, Out] float[] r, [In, Out] float[] tau, [In, Out] float[] q, [In, Out] float[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int d_qr_factor(int m, int n, [In, Out] double[] r, [In, Out] double[] tau, [In, Out] double[] q, [In, Out] double[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int c_qr_factor(int m, int n, [In, Out] Complex32[] r, [In, Out] Complex32[] tau, [In, Out] Complex32[] q, [In, Out] Complex32[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int z_qr_factor(int m, int n, [In, Out] Complex[] r, [In, Out] Complex[] tau, [In, Out] Complex[] q, [In, Out] Complex[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int s_qr_thin_factor(int m, int n, [In, Out] float[] q, [In, Out] float[] tau, [In, Out] float[] r, [In, Out] float[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int d_qr_thin_factor(int m, int n, [In, Out] double[] q, [In, Out] double[] tau, [In, Out] double[] r, [In, Out] double[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int c_qr_thin_factor(int m, int n, [In, Out] Complex32[] q, [In, Out] Complex32[] tau, [In, Out] Complex32[] r, [In, Out] Complex32[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int z_qr_thin_factor(int m, int n, [In, Out] Complex[] q, [In, Out] Complex[] tau, [In, Out] Complex[] r, [In, Out] Complex[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int s_qr_solve(int m, int n, int bn, float[] r, float[] b, [In, Out] float[] x, [In, Out] float[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int d_qr_solve(int m, int n, int bn, double[] r, double[] b, [In, Out] double[] x, [In, Out] double[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int c_qr_solve(int m, int n, int bn, Complex32[] r, Complex32[] b, [In, Out] Complex32[] x, [In, Out] Complex32[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int z_qr_solve(int m, int n, int bn, Complex[] r, Complex[] b, [In, Out] Complex[] x, [In, Out] Complex[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int s_qr_solve_factored(int m, int n, int bn, float[] r, float[] b, float[] tau, [In, Out] float[] x, [In, Out] float[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int d_qr_solve_factored(int m, int n, int bn, double[] r, double[] b, double[] tau, [In, Out] double[] x, [In, Out] double[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int c_qr_solve_factored(int m, int n, int bn, Complex32[] r, Complex32[] b, Complex32[] tau, [In, Out] Complex32[] x, [In, Out] Complex32[] work, int len); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int z_qr_solve_factored(int m, int n, int bn, Complex[] r, Complex[] b, Complex[] tau, [In, Out] Complex[] x, [In, Out] Complex[] work, int len); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_svd_factor(IntPtr solverHandle, [MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] float[] a, [In, Out] float[] s, [In, Out] float[] u, [In, Out] float[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_svd_factor(IntPtr solverHandle, [MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] double[] a, [In, Out] double[] s, [In, Out] double[] u, [In, Out] double[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_svd_factor(IntPtr solverHandle, [MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] Complex32[] a, [In, Out] Complex32[] s, [In, Out] Complex32[] u, [In, Out] Complex32[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_svd_factor(IntPtr solverHandle, [MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] Complex[] a, [In, Out] Complex[] s, [In, Out] Complex[] u, [In, Out] Complex[] v); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int s_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] float[] a, [In, Out] float[] vectors, [In, Out] Complex[] values, [In, Out] float[] d); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int d_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] double[] a, [In, Out] double[] vectors, [In, Out] Complex[] values, [In, Out] double[] d); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int c_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] Complex32[] a, [In, Out] Complex32[] vectors, [In, Out] Complex[] values, [In, Out] Complex32[] d); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern int z_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] Complex[] a, [In, Out] Complex[] vectors, [In, Out] Complex[] values, [In, Out] Complex[] d); + + #endregion LAPACK + + #region Vector Functions + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void s_vector_add(int n, float[] x, float[] y, [In, Out] float[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void s_vector_subtract(int n, float[] x, float[] y, [In, Out] float[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void s_vector_multiply(int n, float[] x, float[] y, [In, Out] float[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void s_vector_divide(int n, float[] x, float[] y, [In, Out] float[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void d_vector_add(int n, double[] x, double[] y, [In, Out] double[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void d_vector_subtract(int n, double[] x, double[] y, [In, Out] double[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void d_vector_multiply(int n, double[] x, double[] y, [In, Out] double[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void d_vector_divide(int n, double[] x, double[] y, [In, Out] double[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void c_vector_add(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void c_vector_subtract(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void c_vector_multiply(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void c_vector_divide(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void z_vector_add(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void z_vector_subtract(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void z_vector_multiply(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + //[DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + //internal static extern void z_vector_divide(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + #endregion Vector Functions + + // ReSharper restore InconsistentNaming + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/Mkl/MklProvider.cs b/MathNet.Numerics/Providers/Common/Mkl/MklProvider.cs new file mode 100644 index 0000000..eda6d2b --- /dev/null +++ b/MathNet.Numerics/Providers/Common/Mkl/MklProvider.cs @@ -0,0 +1,340 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Providers.Common.Mkl +{ + public static class MklProvider + { + const int DesignTimeRevision = 12; + const int MinimumCompatibleRevision = 4; + + static int _nativeRevision; + static Version _mklVersion; + static bool _nativeX86; + static bool _nativeX64; + static bool _nativeIA64; + static bool _loaded; + + public static bool IsAvailable(string hintPath = null) + { + if (_loaded) + { + return true; + } + + try + { + if (!NativeProviderLoader.TryLoad(SafeNativeMethods.DllName, hintPath)) + { + return false; + } + + int a = SafeNativeMethods.query_capability(0); + int b = SafeNativeMethods.query_capability(1); + int nativeRevision = SafeNativeMethods.query_capability((int)ProviderConfig.Revision); + return a == 0 && b == -1 && nativeRevision >= MinimumCompatibleRevision; + } + catch + { + return false; + } + } + + /// Revision + public static int Load(string hintPath = null) + { + return Load(hintPath, MklConsistency.Auto, MklPrecision.Double, MklAccuracy.High); + } + + /// Revision + [CLSCompliant(false)] + public static int Load( + string hintPath = null, + MklConsistency consistency = MklConsistency.Auto, + MklPrecision precision = MklPrecision.Double, + MklAccuracy accuracy = MklAccuracy.High) + { + if (_loaded) + { + return _nativeRevision; + } + + int a, b; + try + { + NativeProviderLoader.TryLoad(SafeNativeMethods.DllName, hintPath); + + a = SafeNativeMethods.query_capability(0); + b = SafeNativeMethods.query_capability(1); + _nativeRevision = SafeNativeMethods.query_capability((int)ProviderConfig.Revision); + + _nativeX86 = SafeNativeMethods.query_capability((int)ProviderPlatform.x86) > 0; + _nativeX64 = SafeNativeMethods.query_capability((int)ProviderPlatform.x64) > 0; + _nativeIA64 = SafeNativeMethods.query_capability((int)ProviderPlatform.ia64) > 0; + + // set numerical consistency, precision and accuracy modes, if supported + if (SafeNativeMethods.query_capability((int)ProviderConfig.Precision) > 0) + { + SafeNativeMethods.set_consistency_mode((int)consistency); + SafeNativeMethods.set_vml_mode((uint)precision | (uint)accuracy); + } + + // set threading settings, if supported + if (SafeNativeMethods.query_capability((int)ProviderConfig.Threading) > 0) + { + SafeNativeMethods.set_max_threads(Control.MaxDegreeOfParallelism); + } + + _mklVersion = new Version( + SafeNativeMethods.query_capability((int)ProviderConfig.MklMajorVersion), + SafeNativeMethods.query_capability((int)ProviderConfig.MklMinorVersion), + SafeNativeMethods.query_capability((int)ProviderConfig.MklUpdateVersion)); + } + catch (DllNotFoundException e) + { + throw new NotSupportedException("MKL Native Provider not found.", e); + } + catch (BadImageFormatException e) + { + throw new NotSupportedException("MKL Native Provider found but failed to load. Please verify that the platform matches (x64 vs x32, Windows vs Linux).", e); + } + catch (EntryPointNotFoundException e) + { + throw new NotSupportedException("MKL Native Provider does not support capability querying and is therefore not compatible. Consider upgrading to a newer version.", e); + } + + if (a != 0 || b != -1 || _nativeRevision < MinimumCompatibleRevision) + { + throw new NotSupportedException("MKL Native Provider too old. Consider upgrading to a newer version."); + } + + _loaded = true; + return _nativeRevision; + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// This method is safe to call, even if the provider is not loaded. + /// + public static void FreeResources() + { + if (!_loaded) + { + return; + } + + FreeBuffers(); + } + + /// + /// Frees the memory allocated to the MKL memory pool. + /// + public static void FreeBuffers() + { + if (!_loaded) + { + throw new InvalidOperationException(); + } + + if (SafeNativeMethods.query_capability((int)ProviderConfig.Memory) < 1) + { + throw new NotSupportedException("MKL Native Provider does not support memory management functions. Consider upgrading to a newer version."); + } + + SafeNativeMethods.free_buffers(); + } + + /// + /// Frees the memory allocated to the MKL memory pool on the current thread. + /// + public static void ThreadFreeBuffers() + { + if (!_loaded) + { + throw new InvalidOperationException(); + } + + if (SafeNativeMethods.query_capability((int)ProviderConfig.Memory) < 1) + { + throw new NotSupportedException("MKL Native Provider does not support memory management functions. Consider upgrading to a newer version."); + } + + SafeNativeMethods.thread_free_buffers(); + } + + /// + /// Disable the MKL memory pool. May impact performance. + /// + public static void DisableMemoryPool() + { + if (!_loaded) + { + throw new InvalidOperationException(); + } + + if (SafeNativeMethods.query_capability((int)ProviderConfig.Memory) < 1) + { + throw new NotSupportedException("MKL Native Provider does not support memory management functions. Consider upgrading to a newer version."); + } + + SafeNativeMethods.disable_fast_mm(); + } + + /// + /// Retrieves information about the MKL memory pool. + /// + /// On output, returns the number of memory buffers allocated. + /// Returns the number of bytes allocated to all memory buffers. + public static long MemoryStatistics(out int allocatedBuffers) + { + if (!_loaded) + { + throw new InvalidOperationException(); + } + + if (SafeNativeMethods.query_capability((int)ProviderConfig.Memory) < 1) + { + throw new NotSupportedException("MKL Native Provider does not support memory management functions. Consider upgrading to a newer version."); + } + + return SafeNativeMethods.mem_stat(out allocatedBuffers); + } + + /// + /// Enable gathering of peak memory statistics of the MKL memory pool. + /// + public static void EnablePeakMemoryStatistics() + { + if (!_loaded) + { + throw new InvalidOperationException(); + } + + if (SafeNativeMethods.query_capability((int)ProviderConfig.Memory) < 1) + { + throw new NotSupportedException("MKL Native Provider does not support memory management functions. Consider upgrading to a newer version."); + } + + SafeNativeMethods.peak_mem_usage((int)MklMemoryRequestMode.Enable); + } + + /// + /// Disable gathering of peak memory statistics of the MKL memory pool. + /// + public static void DisablePeakMemoryStatistics() + { + if (!_loaded) + { + throw new InvalidOperationException(); + } + + if (SafeNativeMethods.query_capability((int)ProviderConfig.Memory) < 1) + { + throw new NotSupportedException("MKL Native Provider does not support memory management functions. Consider upgrading to a newer version."); + } + + SafeNativeMethods.peak_mem_usage((int)MklMemoryRequestMode.Disable); + } + + /// + /// Measures peak memory usage of the MKL memory pool. + /// + /// Whether the usage counter should be reset. + /// The peak number of bytes allocated to all memory buffers. + public static long PeakMemoryStatistics(bool reset = true) + { + if (!_loaded) + { + throw new InvalidOperationException(); + } + + if (SafeNativeMethods.query_capability((int)ProviderConfig.Memory) < 1) + { + throw new NotSupportedException("MKL Native Provider does not support memory management functions. Consider upgrading to a newer version."); + } + + return SafeNativeMethods.peak_mem_usage((int)(reset ? MklMemoryRequestMode.PeakMemoryReset : MklMemoryRequestMode.PeakMemory)); + } + + public static string Describe() + { + if (!_loaded) + { + return "Intel MKL (not loaded)"; + } + + var parts = new List(); + if (_nativeX86) parts.Add("x86"); + if (_nativeX64) parts.Add("x64"); + if (_nativeIA64) parts.Add("IA64"); + parts.Add("revision " + _nativeRevision); + if (_nativeRevision > DesignTimeRevision) parts.Add("ahead revision " + DesignTimeRevision); + if (_nativeRevision < DesignTimeRevision) parts.Add("behind revision " + DesignTimeRevision); + if (_mklVersion.Major > 0) + { + parts.Add(_mklVersion.Build == 0 + ? string.Concat("MKL ", _mklVersion.ToString(2)) + : string.Concat("MKL ", _mklVersion.ToString(2), " Update ", _mklVersion.Build)); + } + + return string.Concat("Intel MKL (", string.Join("; ", parts.ToArray()), ")"); + } + + enum MklMemoryRequestMode : int + { + /// + /// Disable gathering memory usage + /// + Disable = 0, + + /// + /// Enable gathering memory usage + /// + Enable = 1, + + /// + /// Return peak memory usage + /// + PeakMemory = 2, + + /// + /// Return peak memory usage and reset counter + /// + PeakMemoryReset = -1 + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/Mkl/MklProviderCapabilities.cs b/MathNet.Numerics/Providers/Common/Mkl/MklProviderCapabilities.cs new file mode 100644 index 0000000..711c15b --- /dev/null +++ b/MathNet.Numerics/Providers/Common/Mkl/MklProviderCapabilities.cs @@ -0,0 +1,63 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +namespace MathNet.Numerics.Providers.Common.Mkl +{ + internal enum ProviderPlatform : int + { + x86 = 8, + x64 = 9, + ia64 = 10, + } + + internal enum ProviderConfig : int + { + MklMajorVersion = 32, + MklMinorVersion = 33, + MklUpdateVersion = 34, + Revision = 64, + Precision = 65, + Threading = 66, + Memory = 67, + } + + internal enum ProviderCapability : int + { + LinearAlgebraMajor = 128, + LinearAlgebraMinor = 129, + VectorFunctionsMajor = 130, + VectorFunctionsMinor = 131, + FourierTransformMajor = 384, + FourierTransformMinor = 385 + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/Mkl/MklProviderPrecision.cs b/MathNet.Numerics/Providers/Common/Mkl/MklProviderPrecision.cs new file mode 100644 index 0000000..4272692 --- /dev/null +++ b/MathNet.Numerics/Providers/Common/Mkl/MklProviderPrecision.cs @@ -0,0 +1,65 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Providers.Common.Mkl; + +/// +/// Consistency vs. performance trade-off between runs on different machines. +/// +public enum MklConsistency : int +{ + /// Consistent on the same CPU only (maximum performance) + Auto = 2, + /// Consistent on Intel and compatible CPUs with SSE2 support (maximum compatibility) + Compatible = 3, + /// Consistent on Intel CPUs supporting SSE2 or later + SSE2 = 4, + /// Consistent on Intel CPUs supporting SSE4.2 or later + SSE4_2 = 8, + /// Consistent on Intel CPUs supporting AVX or later + AVX = 9, + /// Consistent on Intel CPUs supporting AVX2 or later + AVX2 = 10 +} + +[CLSCompliant(false)] +public enum MklAccuracy : uint +{ + Low = 0x1, + High = 0x2 +} + +[CLSCompliant(false)] +public enum MklPrecision : uint +{ + Single = 0x10, + Double = 0x20 +} diff --git a/MathNet.Numerics/Providers/Common/Mkl/SafeNativeMethods.cs b/MathNet.Numerics/Providers/Common/Mkl/SafeNativeMethods.cs new file mode 100644 index 0000000..5128379 --- /dev/null +++ b/MathNet.Numerics/Providers/Common/Mkl/SafeNativeMethods.cs @@ -0,0 +1,437 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Runtime.InteropServices; +using System.Security; +using MathNet.Numerics.Providers.LinearAlgebra; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.Common.Mkl +{ + /// + /// P/Invoke methods to the native math libraries. + /// + [SuppressUnmanagedCodeSecurity] + [SecurityCritical] + internal static class SafeNativeMethods + { + // ReSharper disable InconsistentNaming + + /// + /// Name of the native DLL. + /// + const string _DllName = "MathNet.Numerics.MKL.dll"; + internal static string DllName { get { return _DllName; } } + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int query_capability(int capability); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void set_consistency_mode(int mode); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void set_vml_mode(uint mode); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void set_max_threads(int num_threads); + + #region Memory + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void free_buffers(); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void thread_free_buffers(); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int disable_fast_mm(); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern long mem_stat([Out]out int allocatedBuffers); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern long peak_mem_usage(int mode); + + #endregion Memory + + #region BLAS + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_axpy(int n, float alpha, float[] x, [In, Out] float[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_axpy(int n, double alpha, double[] x, [In, Out] double[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_axpy(int n, Complex32 alpha, Complex32[] x, [In, Out] Complex32[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_axpy(int n, Complex alpha, Complex[] x, [In, Out] Complex[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_scale(int n, float alpha, [Out] float[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_scale(int n, double alpha, [Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_scale(int n, Complex32 alpha, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_scale(int n, Complex alpha, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float s_dot_product(int n, float[] x, float[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double d_dot_product(int n, double[] x, double[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern Complex32 c_dot_product(int n, Complex32[] x, Complex32[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern Complex z_dot_product(int n, Complex[] x, Complex[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, float alpha, float[] x, float[] y, float beta, [In, Out] float[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, double alpha, double[] x, double[] y, double beta, [In, Out] double[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, Complex32 alpha, Complex32[] x, Complex32[] y, Complex32 beta, [In, Out] Complex32[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, Complex alpha, Complex[] x, Complex[] y, Complex beta, [In, Out] Complex[] c); + + #endregion BLAS + + #region LAPACK + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float s_matrix_norm(byte norm, int rows, int columns, [In] float[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double d_matrix_norm(byte norm, int rows, int columns, [In] double[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float c_matrix_norm(byte norm, int rows, int columns, [In] Complex32[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double z_matrix_norm(byte norm, int rows, int columns, [In] Complex[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_factor(int n, [In, Out] float[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_factor(int n, [In, Out] double[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_factor(int n, [In, Out] Complex32[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_factor(int n, [In, Out] Complex[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_factor(int n, [In, Out] float[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_factor(int n, [In, Out] double[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_factor(int n, [In, Out] Complex32[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_factor(int n, [In, Out] Complex[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_inverse(int n, [In, Out] float[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_inverse(int n, [In, Out] double[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_inverse(int n, [In, Out] Complex32[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_inverse(int n, [In, Out] Complex[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_inverse_factored(int n, [In, Out] float[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_inverse_factored(int n, [In, Out] double[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_inverse_factored(int n, [In, Out] Complex32[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_inverse_factored(int n, [In, Out] Complex[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_solve_factored(int n, int nrhs, float[] a, [In, Out] int[] ipiv, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_solve_factored(int n, int nrhs, double[] a, [In, Out] int[] ipiv, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_solve_factored(int n, int nrhs, Complex32[] a, [In, Out] int[] ipiv, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_solve_factored(int n, int nrhs, Complex[] a, [In, Out] int[] ipiv, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_solve(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_solve(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_solve(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_solve(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_solve(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_solve(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_solve(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_solve(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_solve_factored(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_solve_factored(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_solve_factored(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_solve_factored(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_factor(int m, int n, [In, Out] float[] r, [In, Out] float[] tau, [In, Out] float[] q); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_factor(int m, int n, [In, Out] double[] r, [In, Out] double[] tau, [In, Out] double[] q); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_factor(int m, int n, [In, Out] Complex32[] r, [In, Out] Complex32[] tau, [In, Out] Complex32[] q); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_factor(int m, int n, [In, Out] Complex[] r, [In, Out] Complex[] tau, [In, Out] Complex[] q); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_thin_factor(int m, int n, [In, Out] float[] q, [In, Out] float[] tau, [In, Out] float[] r); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_thin_factor(int m, int n, [In, Out] double[] q, [In, Out] double[] tau, [In, Out] double[] r); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_thin_factor(int m, int n, [In, Out] Complex32[] q, [In, Out] Complex32[] tau, [In, Out] Complex32[] r); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_thin_factor(int m, int n, [In, Out] Complex[] q, [In, Out] Complex[] tau, [In, Out] Complex[] r); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_solve(int m, int n, int bn, float[] r, float[] b, [In, Out] float[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_solve(int m, int n, int bn, double[] r, double[] b, [In, Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_solve(int m, int n, int bn, Complex32[] r, Complex32[] b, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_solve(int m, int n, int bn, Complex[] r, Complex[] b, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_solve_factored(int m, int n, int bn, float[] r, float[] b, float[] tau, [In, Out] float[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_solve_factored(int m, int n, int bn, double[] r, double[] b, double[] tau, [In, Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_solve_factored(int m, int n, int bn, Complex32[] r, Complex32[] b, Complex32[] tau, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_solve_factored(int m, int n, int bn, Complex[] r, Complex[] b, Complex[] tau, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_svd_factor([MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] float[] a, [In, Out] float[] s, [In, Out] float[] u, [In, Out] float[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_svd_factor([MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] double[] a, [In, Out] double[] s, [In, Out] double[] u, [In, Out] double[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_svd_factor([MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] Complex32[] a, [In, Out] Complex32[] s, [In, Out] Complex32[] u, [In, Out] Complex32[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_svd_factor([MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] Complex[] a, [In, Out] Complex[] s, [In, Out] Complex[] u, [In, Out] Complex[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] float[] a, [In, Out] float[] vectors, [In, Out] Complex[] values, [In, Out] float[] d); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] double[] a, [In, Out] double[] vectors, [In, Out] Complex[] values, [In, Out] double[] d); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] Complex32[] a, [In, Out] Complex32[] vectors, [In, Out] Complex[] values, [In, Out] Complex32[] d); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] Complex[] a, [In, Out] Complex[] vectors, [In, Out] Complex[] values, [In, Out] Complex[] d); + + #endregion LAPACK + + #region Vector Functions + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_vector_add(int n, float[] x, float[] y, [In, Out] float[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_vector_subtract(int n, float[] x, float[] y, [In, Out] float[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_vector_multiply(int n, float[] x, float[] y, [In, Out] float[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_vector_divide(int n, float[] x, float[] y, [In, Out] float[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_vector_power(int n, float[] x, float[] y, [In, Out] float[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_vector_add(int n, double[] x, double[] y, [In, Out] double[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_vector_subtract(int n, double[] x, double[] y, [In, Out] double[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_vector_multiply(int n, double[] x, double[] y, [In, Out] double[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_vector_divide(int n, double[] x, double[] y, [In, Out] double[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_vector_power(int n, double[] x, double[] y, [In, Out] double[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_vector_add(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_vector_subtract(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_vector_multiply(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_vector_divide(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_vector_power(int n, Complex32[] x, Complex32[] y, [In, Out] Complex32[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_vector_add(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_vector_subtract(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_vector_multiply(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_vector_divide(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_vector_power(int n, Complex[] x, Complex[] y, [In, Out] Complex[] result); + + #endregion Vector Functions + + #region FFT + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int x_fft_free([In] ref IntPtr handle); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_fft_create([Out] out IntPtr handle, int n, double forward_scale, double backward_scale); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_fft_create([Out] out IntPtr handle, int n, float forward_scale, float backward_scale); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_fft_create([Out] out IntPtr handle, int n, double forward_scale, double backward_scale); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_fft_create([Out] out IntPtr handle, int n, float forward_scale, float backward_scale); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_fft_create_multidim([Out] out IntPtr handle, int dimensions, [In] int[] n, double forward_scale, double backward_scale); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_fft_create_multidim([Out] out IntPtr handle, int dimensions, [In] int[] n, float forward_scale, float backward_scale); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_fft_forward([In] IntPtr handle, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_fft_forward([In] IntPtr handle, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_fft_forward([In] IntPtr handle, [In, Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_fft_forward([In] IntPtr handle, [In, Out] float[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_fft_backward([In] IntPtr handle, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_fft_backward([In] IntPtr handle, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_fft_backward([In] IntPtr handle, [In, Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_fft_backward([In] IntPtr handle, [In, Out] float[] x); + + #endregion FFT + + // ReSharper restore InconsistentNaming + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/NativeProviderLoader.cs b/MathNet.Numerics/Providers/Common/NativeProviderLoader.cs new file mode 100644 index 0000000..6fb55c4 --- /dev/null +++ b/MathNet.Numerics/Providers/Common/NativeProviderLoader.cs @@ -0,0 +1,256 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security; +using System.Threading; + +namespace MathNet.Numerics.Providers.Common +{ + /// + /// Helper class to load native libraries depending on the architecture of the OS and process. + /// + internal static class NativeProviderLoader + { + static readonly object StaticLock = new Object(); + + const string X86 = "x86"; + const string X64 = "x64"; + const string IA64 = "ia64"; + const string ARM = "arm"; + const string ARM64 = "arm64"; + + /// + /// Dictionary of handles to previously loaded libraries, + /// + static readonly Lazy> NativeHandles = new Lazy>(LazyThreadSafetyMode.PublicationOnly); + + /// + /// Gets a string indicating the architecture and bitness of the current process. + /// + static readonly Lazy ArchitectureKey = new Lazy(EvaluateArchitectureKey, LazyThreadSafetyMode.PublicationOnly); + + /// + /// If the last native library failed to load then gets the corresponding exception + /// which occurred or null if the library was successfully loaded. + /// + internal static Exception LastException { get; private set; } + + static bool IsUnix + { + get + { + var p = Environment.OSVersion.Platform; + return p == PlatformID.Unix || p == PlatformID.MacOSX; + } + } + + static string EvaluateArchitectureKey() + { + //return (IntPtr.Size == 8) ? X64 : X86; + if (IsUnix) + { + + // Only support x86 and amd64 on Unix as there isn't a reliable way to detect the architecture + return Environment.Is64BitProcess ? X64 : X86; + + } + + var architecture = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"); + + if (string.Equals(architecture, "x86", StringComparison.OrdinalIgnoreCase)) + { + return X86; + } + + if (string.Equals(architecture, "amd64", StringComparison.OrdinalIgnoreCase) + || string.Equals(architecture, "x64", StringComparison.OrdinalIgnoreCase)) + { + return Environment.Is64BitProcess ? X64 : X86; + } + + if (string.Equals(architecture, "ia64", StringComparison.OrdinalIgnoreCase)) + { + return IA64; + } + + if (string.Equals(architecture, "arm", StringComparison.OrdinalIgnoreCase)) + { + return Environment.Is64BitProcess ? ARM64 : ARM; + } + + // Fallback if unknown + return architecture; + } + + /// + /// Load the native library with the given filename. + /// + /// The file name of the library to load. + /// Hint path where to look for the native binaries. Can be null. + /// True if the library was successfully loaded or if it has already been loaded. + internal static bool TryLoad(string fileName, string hintPath) + { + if (string.IsNullOrEmpty(fileName)) + { + throw new ArgumentNullException(nameof(fileName)); + } + + // If we have hint path provided by the user, look there first + if (TryLoadFromDirectory(fileName, hintPath)) + { + return true; + } + + // If we have an overall hint path provided by the user, look there next + if (Control.NativeProviderPath != hintPath && TryLoadFromDirectory(fileName, Control.NativeProviderPath)) + { + return true; + } + + // Look under the current AppDomain's base directory + if (TryLoadFromDirectory(fileName, AppDomain.CurrentDomain.BaseDirectory)) + { + return true; + } + + // Look at this assembly's directory + if (TryLoadFromDirectory(fileName, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))) + { + return true; + } + + return false; + } + + /// + /// Try to load a native library by providing its name and a directory. + /// Tries to load an implementation suitable for the current CPU architecture + /// and process mode if there is a matching subfolder. + /// + /// True if the library was successfully loaded or if it has already been loaded. + static bool TryLoadFromDirectory(string fileName, string directory) + { + if (!Directory.Exists(directory)) + { + return false; + } + + directory = Path.GetFullPath(directory); + + // If we have a know architecture, try the matching subdirectory first + var architecture = ArchitectureKey.Value; + if (!string.IsNullOrEmpty(architecture) && TryLoadFile(new FileInfo(Path.Combine(Path.Combine(directory, architecture), fileName)))) + { + return true; + } + + // Otherwise try to load directly from the provided directory + return TryLoadFile(new FileInfo(Path.Combine(directory, fileName))); + } + + /// + /// Try to load a native library by providing the full path including the file name of the library. + /// + /// True if the library was successfully loaded or if it has already been loaded. + static bool TryLoadFile(FileInfo file) + { + lock (StaticLock) + { + IntPtr libraryHandle; + if (NativeHandles.Value.TryGetValue(file.Name, out libraryHandle)) + { + return true; + } + + if (!file.Exists) + { + // If the library isn't found within an architecture specific folder then return false + // to allow normal P/Invoke searching behavior when the library is called + return false; + } + + // If successful this will return a handle to the library + libraryHandle = IsUnix ? UnixLoader.LoadLibrary(file.FullName) : WindowsLoader.LoadLibrary(file.FullName); + if (libraryHandle == IntPtr.Zero) + { + int lastError = Marshal.GetLastWin32Error(); + var exception = new System.ComponentModel.Win32Exception(lastError); + LastException = exception; + } + else + { + LastException = null; + NativeHandles.Value[file.Name] = libraryHandle; + } + + return libraryHandle != IntPtr.Zero; + } + } + + [SuppressUnmanagedCodeSecurity] + [SecurityCritical] + static class WindowsLoader + { + public static IntPtr LoadLibrary(string fileName) + { + return LoadLibraryEx(fileName, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH); + } + + // Search for dependencies in the library's directory rather than the calling process's directory + const uint LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008; + + [DllImport("kernel32", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + static extern IntPtr LoadLibraryEx(string fileName, IntPtr reservedNull, uint flags); + } + + [SuppressUnmanagedCodeSecurity] + [SecurityCritical] + static class UnixLoader + { + public static IntPtr LoadLibrary(string fileName) + { + return dlopen(fileName, RTLD_NOW); + } + + const int RTLD_NOW = 2; + + [DllImport("libdl.so", SetLastError = true)] + static extern IntPtr dlopen(string fileName, int flags); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/OpenBlas/OpenBlasProvider.cs b/MathNet.Numerics/Providers/Common/OpenBlas/OpenBlasProvider.cs new file mode 100644 index 0000000..b6fa1d9 --- /dev/null +++ b/MathNet.Numerics/Providers/Common/OpenBlas/OpenBlasProvider.cs @@ -0,0 +1,166 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Providers.Common.OpenBlas +{ + public static class OpenBlasProvider + { + const int DesignTimeRevision = 1; + const int MinimumCompatibleRevision = 1; + + static int _nativeRevision; + static bool _nativeX86; + static bool _nativeX64; + static bool _nativeIA64; + static bool _nativeARM; + static bool _loaded; + + public static bool IsAvailable(string hintPath = null) + { + if (_loaded) + { + return true; + } + + try + { + if (!NativeProviderLoader.TryLoad(SafeNativeMethods.DllName, hintPath)) + { + return false; + } + + int a = SafeNativeMethods.query_capability(0); + int b = SafeNativeMethods.query_capability(1); + int nativeRevision = SafeNativeMethods.query_capability((int)ProviderConfig.Revision); + return a == 0 && b == -1 && nativeRevision >= MinimumCompatibleRevision; + } + catch + { + return false; + } + } + + /// Revision + public static int Load(string hintPath = null) + { + if (_loaded) + { + return _nativeRevision; + } + + int a, b; + try + { + NativeProviderLoader.TryLoad(SafeNativeMethods.DllName, hintPath); + + a = SafeNativeMethods.query_capability(0); + b = SafeNativeMethods.query_capability(1); + _nativeRevision = SafeNativeMethods.query_capability((int)ProviderConfig.Revision); + + _nativeX86 = SafeNativeMethods.query_capability((int)ProviderPlatform.x86) > 0; + _nativeX64 = SafeNativeMethods.query_capability((int)ProviderPlatform.x64) > 0; + _nativeIA64 = SafeNativeMethods.query_capability((int)ProviderPlatform.ia64) > 0; + _nativeARM = SafeNativeMethods.query_capability((int)ProviderPlatform.arm) > 0; + } + catch (DllNotFoundException e) + { + throw new NotSupportedException("OpenBLAS Native Provider not found.", e); + } + catch (BadImageFormatException e) + { + throw new NotSupportedException("OpenBLAS Native Provider found but failed to load. Please verify that the platform matches (x64 vs x32, Windows vs Linux).", e); + } + catch (EntryPointNotFoundException e) + { + throw new NotSupportedException("OpenBLAS Native Provider does not support capability querying and is therefore not compatible. Consider upgrading to a newer version.", e); + } + + if (a != 0 || b != -1 || _nativeRevision < MinimumCompatibleRevision) + { + throw new NotSupportedException("OpenBLAS Native Provider too old. Consider upgrading to a newer version."); + } + + // set threading settings, if supported + if (SafeNativeMethods.query_capability((int)ProviderConfig.Threading) > 0) + { + SafeNativeMethods.set_max_threads(Control.MaxDegreeOfParallelism); + } + + _loaded = true; + return _nativeRevision; + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// This method is safe to call, even if the provider is not loaded. + /// + public static void FreeResources() + { + } + + internal static void ConfigureThreading() + { + if (!_loaded) + { + throw new InvalidOperationException(); + } + + // set threading settings, if supported + if (SafeNativeMethods.query_capability((int)ProviderConfig.Threading) > 0) + { + SafeNativeMethods.set_max_threads(Control.MaxDegreeOfParallelism); + } + } + + public static string Describe() + { + if (!_loaded) + { + return "OpenBLAS (not loaded)"; + } + + var parts = new List(); + if (_nativeX86) parts.Add("x86"); + if (_nativeX64) parts.Add("x64"); + if (_nativeIA64) parts.Add("IA64"); + if (_nativeARM) parts.Add("ARM"); + parts.Add("revision " + _nativeRevision); + + return string.Concat("OpenBLAS (", string.Join("; ", parts.ToArray()), ")"); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/OpenBlas/OpenBlasProviderCapabilities.cs b/MathNet.Numerics/Providers/Common/OpenBlas/OpenBlasProviderCapabilities.cs new file mode 100644 index 0000000..fdabbf0 --- /dev/null +++ b/MathNet.Numerics/Providers/Common/OpenBlas/OpenBlasProviderCapabilities.cs @@ -0,0 +1,55 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +namespace MathNet.Numerics.Providers.Common.OpenBlas +{ + internal enum ProviderPlatform : int + { + x86 = 8, + x64 = 9, + ia64 = 10, + arm = 11, + } + + internal enum ProviderConfig : int + { + Revision = 64, + Threading = 66, + } + + internal enum ProviderCapability : int + { + LinearAlgebraMajor = 128, + LinearAlgebraMinor = 129 + } +} + +#endif diff --git a/MathNet.Numerics/Providers/Common/OpenBlas/SafeNativeMethods.cs b/MathNet.Numerics/Providers/Common/OpenBlas/SafeNativeMethods.cs new file mode 100644 index 0000000..28878bd --- /dev/null +++ b/MathNet.Numerics/Providers/Common/OpenBlas/SafeNativeMethods.cs @@ -0,0 +1,305 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System.Runtime.InteropServices; +using System.Security; +using MathNet.Numerics.Providers.LinearAlgebra; +using MathNet.Numerics.Providers.LinearAlgebra.OpenBlas; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.Common.OpenBlas +{ + /// + /// P/Invoke methods to the native math libraries. + /// + [SuppressUnmanagedCodeSecurity] + [SecurityCritical] + internal static class SafeNativeMethods + { + /// + /// Name of the native DLL. + /// + const string _DllName = "MathNET.Numerics.OpenBLAS.dll"; + internal static string DllName { get { return _DllName; } } + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int query_capability(int capability); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern string get_build_config(); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern string get_cpu_core(); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern ParallelType get_parallel_type(); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void set_max_threads(int num_threads); + + #region BLAS + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_axpy(int n, float alpha, float[] x, [In, Out] float[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_axpy(int n, double alpha, double[] x, [In, Out] double[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_axpy(int n, Complex32 alpha, Complex32[] x, [In, Out] Complex32[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_axpy(int n, Complex alpha, Complex[] x, [In, Out] Complex[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_scale(int n, float alpha, [Out] float[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_scale(int n, double alpha, [Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_scale(int n, Complex32 alpha, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_scale(int n, Complex alpha, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float s_dot_product(int n, float[] x, float[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double d_dot_product(int n, double[] x, double[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern Complex32 c_dot_product(int n, Complex32[] x, Complex32[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern Complex z_dot_product(int n, Complex[] x, Complex[] y); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, float alpha, float[] x, float[] y, float beta, [In, Out] float[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, double alpha, double[] x, double[] y, double beta, [In, Out] double[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, Complex32 alpha, Complex32[] x, Complex32[] y, Complex32 beta, [In, Out] Complex32[] c); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, Complex alpha, Complex[] x, Complex[] y, Complex beta, [In, Out] Complex[] c); + + #endregion BLAS + + #region LAPACK + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float s_matrix_norm(byte norm, int rows, int columns, [In] float[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double d_matrix_norm(byte norm, int rows, int columns, [In] double[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float c_matrix_norm(byte norm, int rows, int columns, [In] Complex32[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double z_matrix_norm(byte norm, int rows, int columns, [In] Complex[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_factor(int n, [In, Out] float[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_factor(int n, [In, Out] double[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_factor(int n, [In, Out] Complex32[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_factor(int n, [In, Out] Complex[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_factor(int n, [In, Out] float[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_factor(int n, [In, Out] double[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_factor(int n, [In, Out] Complex32[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_factor(int n, [In, Out] Complex[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_inverse(int n, [In, Out] float[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_inverse(int n, [In, Out] double[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_inverse(int n, [In, Out] Complex32[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_inverse(int n, [In, Out] Complex[] a); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_inverse_factored(int n, [In, Out] float[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_inverse_factored(int n, [In, Out] double[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_inverse_factored(int n, [In, Out] Complex32[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_inverse_factored(int n, [In, Out] Complex[] a, [In, Out] int[] ipiv); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_solve_factored(int n, int nrhs, float[] a, [In, Out] int[] ipiv, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_solve_factored(int n, int nrhs, double[] a, [In, Out] int[] ipiv, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_solve_factored(int n, int nrhs, Complex32[] a, [In, Out] int[] ipiv, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_solve_factored(int n, int nrhs, Complex[] a, [In, Out] int[] ipiv, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_solve(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_solve(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_solve(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_solve(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_solve(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_solve(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_solve(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_solve(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_solve_factored(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_solve_factored(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_solve_factored(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_solve_factored(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_factor(int m, int n, [In, Out] float[] r, [In, Out] float[] tau, [In, Out] float[] q); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_factor(int m, int n, [In, Out] double[] r, [In, Out] double[] tau, [In, Out] double[] q); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_factor(int m, int n, [In, Out] Complex32[] r, [In, Out] Complex32[] tau, [In, Out] Complex32[] q); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_factor(int m, int n, [In, Out] Complex[] r, [In, Out] Complex[] tau, [In, Out] Complex[] q); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_thin_factor(int m, int n, [In, Out] float[] q, [In, Out] float[] tau, [In, Out] float[] r); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_thin_factor(int m, int n, [In, Out] double[] q, [In, Out] double[] tau, [In, Out] double[] r); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_thin_factor(int m, int n, [In, Out] Complex32[] q, [In, Out] Complex32[] tau, [In, Out] Complex32[] r); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_thin_factor(int m, int n, [In, Out] Complex[] q, [In, Out] Complex[] tau, [In, Out] Complex[] r); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_solve(int m, int n, int bn, float[] r, float[] b, [In, Out] float[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_solve(int m, int n, int bn, double[] r, double[] b, [In, Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_solve(int m, int n, int bn, Complex32[] r, Complex32[] b, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_solve(int m, int n, int bn, Complex[] r, Complex[] b, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_solve_factored(int m, int n, int bn, float[] r, float[] b, float[] tau, [In, Out] float[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_solve_factored(int m, int n, int bn, double[] r, double[] b, double[] tau, [In, Out] double[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_solve_factored(int m, int n, int bn, Complex32[] r, Complex32[] b, Complex32[] tau, [In, Out] Complex32[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_solve_factored(int m, int n, int bn, Complex[] r, Complex[] b, Complex[] tau, [In, Out] Complex[] x); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_svd_factor([MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] float[] a, [In, Out] float[] s, [In, Out] float[] u, [In, Out] float[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_svd_factor([MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] double[] a, [In, Out] double[] s, [In, Out] double[] u, [In, Out] double[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_svd_factor([MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] Complex32[] a, [In, Out] Complex32[] s, [In, Out] Complex32[] u, [In, Out] Complex32[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_svd_factor([MarshalAs(UnmanagedType.U1)] bool computeVectors, int m, int n, [In, Out] Complex[] a, [In, Out] Complex[] s, [In, Out] Complex[] u, [In, Out] Complex[] v); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] float[] a, [In, Out] float[] vectors, [In, Out] Complex[] values, [In, Out] float[] d); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] double[] a, [In, Out] double[] vectors, [In, Out] Complex[] values, [In, Out] double[] d); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] Complex32[] a, [In, Out] Complex32[] vectors, [In, Out] Complex[] values, [In, Out] Complex32[] d); + + [DllImport(_DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_eigen([MarshalAs(UnmanagedType.U1)] bool isSymmetric, int n, [In] Complex[] a, [In, Out] Complex[] vectors, [In, Out] Complex[] values, [In, Out] Complex[] d); + + #endregion LAPACK + } +} + +#endif diff --git a/MathNet.Numerics/Providers/FourierTransform/FourierTransformControl.cs b/MathNet.Numerics/Providers/FourierTransform/FourierTransformControl.cs new file mode 100644 index 0000000..fd25e53 --- /dev/null +++ b/MathNet.Numerics/Providers/FourierTransform/FourierTransformControl.cs @@ -0,0 +1,193 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Providers.FourierTransform; + +public static class FourierTransformControl +{ + const string EnvVarFFTProvider = "MathNetNumericsFFTProvider"; + const string EnvVarFFTProviderPath = "MathNetNumericsFFTProviderPath"; + + static IFourierTransformProvider _fourierTransformProvider; + static readonly object StaticLock = new object(); + + /// + /// Gets or sets the Fourier transform provider. Consider to use UseNativeMKL or UseManaged instead. + /// + /// The linear algebra provider. + public static IFourierTransformProvider Provider + { + get + { + if (_fourierTransformProvider == null) + { + lock (StaticLock) + { + if (_fourierTransformProvider == null) + { + UseDefault(); + } + } + } + + return _fourierTransformProvider; + } + set + { + value.InitializeVerify(); + + // only actually set if verification did not throw + _fourierTransformProvider = value; + } + } + + /// + /// Optional path to try to load native provider binaries from. + /// If not set, Numerics will fall back to the environment variable + /// `MathNetNumericsFFTProviderPath` or the default probing paths. + /// + public static string HintPath { get; set; } + + public static IFourierTransformProvider CreateManaged() + { + return new Managed.ManagedFourierTransformProvider(); + } + + public static void UseManaged() + { + Provider = CreateManaged(); + } + +#if NATIVE + public static IFourierTransformProvider CreateNativeMKL() + { + return new Mkl.MklFourierTransformProvider(GetCombinedHintPath()); + } + + public static void UseNativeMKL() + { + Provider = CreateNativeMKL(); + } + + public static bool TryUseNativeMKL() + { + return TryUse(CreateNativeMKL()); + } + + /// + /// Try to use a native provider, if available. + /// + public static bool TryUseNative() + { + return TryUseNativeMKL(); + } +#endif + + static bool TryUse(IFourierTransformProvider provider) + { + try + { + if (!provider.IsAvailable()) + { + return false; + } + + Provider = provider; + return true; + } + catch + { + // intentionally swallow exceptions here - use the explicit variants if you're interested in why + return false; + } + } + + /// + /// Use the best provider available. + /// + public static void UseBest() + { +#if NATIVE + if (!TryUseNative()) + { + UseManaged(); + } +#else + UseManaged(); +#endif + } + + /// + /// Use a specific provider if configured, e.g. using the + /// "MathNetNumericsFFTProvider" environment variable, + /// or fall back to the best provider. + /// + public static void UseDefault() + { +#if NATIVE + var value = Environment.GetEnvironmentVariable(EnvVarFFTProvider); + switch (value != null ? value.ToUpperInvariant() : string.Empty) + { + + case "MKL": + UseNativeMKL(); + break; + + default: + UseBest(); + break; + } +#else + UseBest(); +#endif + } + + public static void FreeResources() + { + Provider.FreeResources(); + } + + static string GetCombinedHintPath() + { + if (!string.IsNullOrEmpty(HintPath)) + { + return HintPath; + } + + var value = Environment.GetEnvironmentVariable(EnvVarFFTProviderPath); + if (!string.IsNullOrEmpty(value)) + { + return value; + } + + return null; + } +} diff --git a/MathNet.Numerics/Providers/FourierTransform/IFourierTransformProvider.cs b/MathNet.Numerics/Providers/FourierTransform/IFourierTransformProvider.cs new file mode 100644 index 0000000..2281549 --- /dev/null +++ b/MathNet.Numerics/Providers/FourierTransform/IFourierTransformProvider.cs @@ -0,0 +1,75 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.FourierTransform; + +public enum FourierTransformScaling : int +{ + NoScaling = 0, + SymmetricScaling = 1, + BackwardScaling = 2, + ForwardScaling = 3 +} + +public interface IFourierTransformProvider +{ + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + bool IsAvailable(); + + /// + /// Initialize and verify that the provided is indeed available. If not, fall back to alternatives like the managed provider + /// + void InitializeVerify(); + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + void FreeResources(); + + void Forward(Complex32[] samples, FourierTransformScaling scaling); + void Forward(Complex[] samples, FourierTransformScaling scaling); + void Backward(Complex32[] spectrum, FourierTransformScaling scaling); + void Backward(Complex[] spectrum, FourierTransformScaling scaling); + + void ForwardReal(float[] samples, int n, FourierTransformScaling scaling); + void ForwardReal(double[] samples, int n, FourierTransformScaling scaling); + void BackwardReal(float[] spectrum, int n, FourierTransformScaling scaling); + void BackwardReal(double[] spectrum, int n, FourierTransformScaling scaling); + + void ForwardMultidim(Complex32[] samples, int[] dimensions, FourierTransformScaling scaling); + void ForwardMultidim(Complex[] samples, int[] dimensions, FourierTransformScaling scaling); + void BackwardMultidim(Complex32[] spectrum, int[] dimensions, FourierTransformScaling scaling); + void BackwardMultidim(Complex[] spectrum, int[] dimensions, FourierTransformScaling scaling); +} diff --git a/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.Bluestein.cs b/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.Bluestein.cs new file mode 100644 index 0000000..6037f3f --- /dev/null +++ b/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.Bluestein.cs @@ -0,0 +1,278 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.FourierTransform.Managed; + +internal partial class ManagedFourierTransformProvider +{ + /// + /// Sequences with length greater than Math.Sqrt(Int32.MaxValue) + 1 + /// will cause k*k in the Bluestein sequence to overflow (GH-286). + /// + const int BluesteinSequenceLengthThreshold = 46341; + + /// + /// Generate the bluestein sequence for the provided problem size. + /// + /// Number of samples. + /// Bluestein sequence exp(I*Pi*k^2/N) + private static Complex32[] BluesteinSequence32(int n) + { + double s = Constants.Pi / n; + var sequence = new Complex32[n]; + + // TODO: benchmark whether the second variation is significantly + // faster than the former one. If not just use the former one always. + if (n > BluesteinSequenceLengthThreshold) + { + for (int k = 0; k < sequence.Length; k++) + { + double t = (s * k) * k; + sequence[k] = new Complex32((float)Math.Cos(t), (float)Math.Sin(t)); + } + } + else + { + for (int k = 0; k < sequence.Length; k++) + { + double t = s * (k * k); + sequence[k] = new Complex32((float)Math.Cos(t), (float)Math.Sin(t)); + } + } + + return sequence; + } + + /// + /// Generate the bluestein sequence for the provided problem size. + /// + /// Number of samples. + /// Bluestein sequence exp(I*Pi*k^2/N) + private static Complex[] BluesteinSequence(int n) + { + double s = Constants.Pi / n; + var sequence = new Complex[n]; + + // TODO: benchmark whether the second variation is significantly + // faster than the former one. If not just use the former one always. + if (n > BluesteinSequenceLengthThreshold) + { + for (int k = 0; k < sequence.Length; k++) + { + double t = (s * k) * k; + sequence[k] = new Complex(Math.Cos(t), Math.Sin(t)); + } + } + else + { + for (int k = 0; k < sequence.Length; k++) + { + double t = s * (k * k); + sequence[k] = new Complex(Math.Cos(t), Math.Sin(t)); + } + } + + return sequence; + } + + /// + /// Convolution with the bluestein sequence (Parallel Version). + /// + /// Sample Vector. + private static void BluesteinConvolutionParallel(Complex32[] samples) + { + int n = samples.Length; + Complex32[] sequence = BluesteinSequence32(n); + + // Padding to power of two >= 2N–1 so we can apply Radix-2 FFT. + int m = ((n << 1) - 1).CeilingToPowerOfTwo(); + var b = new Complex32[m]; + var a = new Complex32[m]; + + CommonParallel.Invoke( + () => + { + // Build and transform padded sequence b_k = exp(I*Pi*k^2/N) + for (int i = 0; i < n; i++) + { + b[i] = sequence[i]; + } + + for (int i = m - n + 1; i < b.Length; i++) + { + b[i] = sequence[m - i]; + } + + Radix2Forward(b); + }, + () => + { + // Build and transform padded sequence a_k = x_k * exp(-I*Pi*k^2/N) + for (int i = 0; i < samples.Length; i++) + { + a[i] = sequence[i].Conjugate() * samples[i]; + } + + Radix2Forward(a); + }); + + for (int i = 0; i < a.Length; i++) + { + a[i] *= b[i]; + } + + Radix2InverseParallel(a); + + var nbinv = 1.0f / m; + for (int i = 0; i < samples.Length; i++) + { + samples[i] = nbinv * sequence[i].Conjugate() * a[i]; + } + } + + /// + /// Convolution with the bluestein sequence (Parallel Version). + /// + /// Sample Vector. + private static void BluesteinConvolutionParallel(Complex[] samples) + { + int n = samples.Length; + Complex[] sequence = BluesteinSequence(n); + + // Padding to power of two >= 2N–1 so we can apply Radix-2 FFT. + int m = ((n << 1) - 1).CeilingToPowerOfTwo(); + var b = new Complex[m]; + var a = new Complex[m]; + + CommonParallel.Invoke( + () => + { + // Build and transform padded sequence b_k = exp(I*Pi*k^2/N) + for (int i = 0; i < n; i++) + { + b[i] = sequence[i]; + } + + for (int i = m - n + 1; i < b.Length; i++) + { + b[i] = sequence[m - i]; + } + + Radix2Forward(b); + }, + () => + { + // Build and transform padded sequence a_k = x_k * exp(-I*Pi*k^2/N) + for (int i = 0; i < samples.Length; i++) + { + a[i] = sequence[i].Conjugate() * samples[i]; + } + + Radix2Forward(a); + }); + + for (int i = 0; i < a.Length; i++) + { + a[i] *= b[i]; + } + + Radix2InverseParallel(a); + + var nbinv = 1.0 / m; + for (int i = 0; i < samples.Length; i++) + { + samples[i] = nbinv * sequence[i].Conjugate() * a[i]; + } + } + + /// + /// Swap the real and imaginary parts of each sample. + /// + /// Sample Vector. + private static void SwapRealImaginary(Complex32[] samples) + { + for (int i = 0; i < samples.Length; i++) + { + samples[i] = new Complex32(samples[i].Imaginary, samples[i].Real); + } + } + + /// + /// Swap the real and imaginary parts of each sample. + /// + /// Sample Vector. + private static void SwapRealImaginary(Complex[] samples) + { + for (int i = 0; i < samples.Length; i++) + { + samples[i] = new Complex(samples[i].Imaginary, samples[i].Real); + } + } + + /// + /// Bluestein generic FFT for arbitrary sized sample vectors. + /// + private static void BluesteinForward(Complex[] samples) + { + BluesteinConvolutionParallel(samples); + } + + /// + /// Bluestein generic FFT for arbitrary sized sample vectors. + /// + private static void BluesteinInverse(Complex[] spectrum) + { + SwapRealImaginary(spectrum); + BluesteinConvolutionParallel(spectrum); + SwapRealImaginary(spectrum); + } + + /// + /// Bluestein generic FFT for arbitrary sized sample vectors. + /// + private static void BluesteinForward(Complex32[] samples) + { + BluesteinConvolutionParallel(samples); + } + + /// + /// Bluestein generic FFT for arbitrary sized sample vectors. + /// + private static void BluesteinInverse(Complex32[] spectrum) + { + SwapRealImaginary(spectrum); + BluesteinConvolutionParallel(spectrum); + SwapRealImaginary(spectrum); + } +} diff --git a/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.Radix2.cs b/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.Radix2.cs new file mode 100644 index 0000000..e437597 --- /dev/null +++ b/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.Radix2.cs @@ -0,0 +1,259 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Threading; + +using System; +using System.Runtime.CompilerServices; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.FourierTransform.Managed; + +internal partial class ManagedFourierTransformProvider +{ + /// + /// Radix-2 Reorder Helper Method + /// + /// Sample type + /// Sample vector + private static void Radix2Reorder(T[] samples) + { + var j = 0; + for (var i = 0; i < samples.Length - 1; i++) + { + if (i < j) + { + var temp = samples[i]; + samples[i] = samples[j]; + samples[j] = temp; + } + + var m = samples.Length; + + do + { + m >>= 1; + j ^= m; + } + while ((j & m) == 0); + } + } + + /// + /// Radix-2 Step Helper Method + /// + /// Sample vector. + /// Fourier series exponent sign. + /// Level Group Size. + /// Index inside of the level. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static void Radix2Step(Complex32[] samples, int exponentSign, int levelSize, int k) + { + // Twiddle Factor + var exponent = (exponentSign * k) * Constants.Pi / levelSize; + var w = new Complex32((float)Math.Cos(exponent), (float)Math.Sin(exponent)); + + var step = levelSize << 1; + for (var i = k; i < samples.Length; i += step) + { + var ai = samples[i]; + var t = w * samples[i + levelSize]; + samples[i] = ai + t; + samples[i + levelSize] = ai - t; + } + } + + /// + /// Radix-2 Step Helper Method + /// + /// Sample vector. + /// Fourier series exponent sign. + /// Level Group Size. + /// Index inside of the level. +#if !NET40 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static void Radix2Step(Complex[] samples, int exponentSign, int levelSize, int k) + { + // Twiddle Factor + var exponent = (exponentSign * k) * Constants.Pi / levelSize; + var w = new Complex(Math.Cos(exponent), Math.Sin(exponent)); + + var step = levelSize << 1; + for (var i = k; i < samples.Length; i += step) + { + var ai = samples[i]; + var t = w * samples[i + levelSize]; + samples[i] = ai + t; + samples[i + levelSize] = ai - t; + } + } + + /// + /// Radix-2 generic FFT for power-of-two sized sample vectors. + /// + private static void Radix2Forward(Complex32[] data) + { + Radix2Reorder(data); + for (var levelSize = 1; levelSize < data.Length; levelSize *= 2) + { + for (var k = 0; k < levelSize; k++) + { + Radix2Step(data, -1, levelSize, k); + } + } + } + + /// + /// Radix-2 generic FFT for power-of-two sized sample vectors. + /// + private static void Radix2Forward(Complex[] data) + { + Radix2Reorder(data); + for (var levelSize = 1; levelSize < data.Length; levelSize *= 2) + { + for (var k = 0; k < levelSize; k++) + { + Radix2Step(data, -1, levelSize, k); + } + } + } + + /// + /// Radix-2 generic FFT for power-of-two sized sample vectors. + /// + private static void Radix2Inverse(Complex32[] data) + { + Radix2Reorder(data); + for (var levelSize = 1; levelSize < data.Length; levelSize *= 2) + { + for (var k = 0; k < levelSize; k++) + { + Radix2Step(data, 1, levelSize, k); + } + } + } + + /// + /// Radix-2 generic FFT for power-of-two sized sample vectors. + /// + private static void Radix2Inverse(Complex[] data) + { + Radix2Reorder(data); + for (var levelSize = 1; levelSize < data.Length; levelSize *= 2) + { + for (var k = 0; k < levelSize; k++) + { + Radix2Step(data, 1, levelSize, k); + } + } + } + + /// + /// Radix-2 generic FFT for power-of-two sample vectors (Parallel Version). + /// + private static void Radix2ForwardParallel(Complex32[] data) + { + Radix2Reorder(data); + for (var levelSize = 1; levelSize < data.Length; levelSize *= 2) + { + var size = levelSize; + + CommonParallel.For(0, size, 64, (u, v) => + { + for (int i = u; i < v; i++) + { + Radix2Step(data, -1, size, i); + } + }); + } + } + + /// + /// Radix-2 generic FFT for power-of-two sample vectors (Parallel Version). + /// + private static void Radix2ForwardParallel(Complex[] data) + { + Radix2Reorder(data); + for (var levelSize = 1; levelSize < data.Length; levelSize *= 2) + { + var size = levelSize; + + CommonParallel.For(0, size, 64, (u, v) => + { + for (int i = u; i < v; i++) + { + Radix2Step(data, -1, size, i); + } + }); + } + } + + /// + /// Radix-2 generic FFT for power-of-two sample vectors (Parallel Version). + /// + private static void Radix2InverseParallel(Complex32[] data) + { + Radix2Reorder(data); + for (var levelSize = 1; levelSize < data.Length; levelSize *= 2) + { + var size = levelSize; + + CommonParallel.For(0, size, 64, (u, v) => + { + for (int i = u; i < v; i++) + { + Radix2Step(data, 1, size, i); + } + }); + } + } + + /// + /// Radix-2 generic FFT for power-of-two sample vectors (Parallel Version). + /// + private static void Radix2InverseParallel(Complex[] data) + { + Radix2Reorder(data); + for (var levelSize = 1; levelSize < data.Length; levelSize *= 2) + { + var size = levelSize; + + CommonParallel.For(0, size, 64, (u, v) => + { + for (int i = u; i < v; i++) + { + Radix2Step(data, 1, size, i); + } + }); + } + } +} diff --git a/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.cs b/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.cs new file mode 100644 index 0000000..ace065b --- /dev/null +++ b/MathNet.Numerics/Providers/FourierTransform/Managed/ManagedFourierTransformProvider.cs @@ -0,0 +1,397 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.FourierTransform.Managed; + +internal partial class ManagedFourierTransformProvider : IFourierTransformProvider +{ + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + public bool IsAvailable() + { + return true; + } + + /// + /// Initialize and verify that the provided is indeed available. If not, fall back to alternatives like the managed provider + /// + public void InitializeVerify() + { + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + public virtual void FreeResources() + { + } + + public override string ToString() + { + return "Managed"; + } + + public void Forward(Complex32[] samples, FourierTransformScaling scaling) + { + if (samples.Length.IsPowerOfTwo()) + { + if (samples.Length >= 1024) + { + Radix2ForwardParallel(samples); + } + else + { + Radix2Forward(samples); + } + } + else + { + BluesteinForward(samples); + } + + switch (scaling) + { + case FourierTransformScaling.SymmetricScaling: + { + HalfRescale(samples); + break; + } + case FourierTransformScaling.ForwardScaling: + { + FullRescale(samples); + break; + } + } + } + + public void Forward(Complex[] samples, FourierTransformScaling scaling) + { + if (samples.Length.IsPowerOfTwo()) + { + if (samples.Length >= 1024) + { + Radix2ForwardParallel(samples); + } + else + { + Radix2Forward(samples); + } + } + else + { + BluesteinForward(samples); + } + + switch (scaling) + { + case FourierTransformScaling.SymmetricScaling: + { + HalfRescale(samples); + break; + } + case FourierTransformScaling.ForwardScaling: + { + FullRescale(samples); + break; + } + } + } + + public void Backward(Complex32[] spectrum, FourierTransformScaling scaling) + { + if (spectrum.Length.IsPowerOfTwo()) + { + if (spectrum.Length >= 1024) + { + Radix2InverseParallel(spectrum); + } + else + { + Radix2Inverse(spectrum); + } + } + else + { + BluesteinInverse(spectrum); + } + + switch (scaling) + { + case FourierTransformScaling.SymmetricScaling: + { + HalfRescale(spectrum); + break; + } + case FourierTransformScaling.BackwardScaling: + { + FullRescale(spectrum); + break; + } + } + } + + public void Backward(Complex[] spectrum, FourierTransformScaling scaling) + { + if (spectrum.Length.IsPowerOfTwo()) + { + if (spectrum.Length >= 1024) + { + Radix2InverseParallel(spectrum); + } + else + { + Radix2Inverse(spectrum); + } + } + else + { + BluesteinInverse(spectrum); + } + + switch (scaling) + { + case FourierTransformScaling.SymmetricScaling: + { + HalfRescale(spectrum); + break; + } + case FourierTransformScaling.BackwardScaling: + { + FullRescale(spectrum); + break; + } + } + } + + public void ForwardReal(float[] samples, int n, FourierTransformScaling scaling) + { + // TODO: backport proper, optimized implementation from Iridium + + Complex32[] data = new Complex32[n]; + for (int i = 0; i < data.Length; i++) + { + data[i] = new Complex32(samples[i], 0.0f); + } + + Forward(data, scaling); + + samples[0] = data[0].Real; + samples[1] = 0f; + for (int i = 1, j = 2; i < data.Length / 2; i++) + { + samples[j++] = data[i].Real; + samples[j++] = data[i].Imaginary; + } + + if (n.IsEven()) + { + samples[n] = data[data.Length / 2].Real; + samples[n + 1] = 0f; + } + else + { + samples[n - 1] = data[data.Length / 2].Real; + samples[n] = data[data.Length / 2].Imaginary; + } + } + + public void ForwardReal(double[] samples, int n, FourierTransformScaling scaling) + { + // TODO: backport proper, optimized implementation from Iridium + + Complex[] data = new Complex[n]; + for (int i = 0; i < data.Length; i++) + { + data[i] = new Complex(samples[i], 0.0); + } + + Forward(data, scaling); + + samples[0] = data[0].Real; + samples[1] = 0d; + for (int i = 1, j = 2; i < data.Length / 2; i++) + { + samples[j++] = data[i].Real; + samples[j++] = data[i].Imaginary; + } + + if (n.IsEven()) + { + samples[n] = data[data.Length / 2].Real; + samples[n + 1] = 0d; + } + else + { + samples[n - 1] = data[data.Length / 2].Real; + samples[n] = data[data.Length / 2].Imaginary; + } + } + + public void BackwardReal(float[] spectrum, int n, FourierTransformScaling scaling) + { + // TODO: backport proper, optimized implementation from Iridium + + Complex32[] data = new Complex32[n]; + data[0] = new Complex32(spectrum[0], 0f); + for (int i = 1, j = 2; i < data.Length / 2; i++) + { + data[i] = new Complex32(spectrum[j++], spectrum[j++]); + data[data.Length - i] = data[i].Conjugate(); + } + + if (n.IsEven()) + { + data[data.Length / 2] = new Complex32(spectrum[n], 0f); + } + else + { + data[data.Length / 2] = new Complex32(spectrum[n - 1], spectrum[n]); + data[data.Length / 2 + 1] = data[data.Length / 2].Conjugate(); + } + + Backward(data, scaling); + + for (int i = 0; i < data.Length; i++) + { + spectrum[i] = data[i].Real; + } + + spectrum[n] = 0f; + } + + public void BackwardReal(double[] spectrum, int n, FourierTransformScaling scaling) + { + // TODO: backport proper, optimized implementation from Iridium + + Complex[] data = new Complex[n]; + data[0] = new Complex(spectrum[0], 0d); + for (int i = 1, j = 2; i < data.Length / 2; i++) + { + data[i] = new Complex(spectrum[j++], spectrum[j++]); + data[data.Length - i] = data[i].Conjugate(); + } + + if (n.IsEven()) + { + data[data.Length / 2] = new Complex(spectrum[n], 0d); + } + else + { + data[data.Length / 2] = new Complex(spectrum[n - 1], spectrum[n]); + data[data.Length / 2 + 1] = data[data.Length / 2].Conjugate(); + } + + Backward(data, scaling); + + for (int i = 0; i < data.Length; i++) + { + spectrum[i] = data[i].Real; + } + + spectrum[n] = 0d; + } + + public void ForwardMultidim(Complex32[] samples, int[] dimensions, FourierTransformScaling scaling) + { + throw new NotSupportedException(); + } + + public void ForwardMultidim(Complex[] samples, int[] dimensions, FourierTransformScaling scaling) + { + throw new NotSupportedException(); + } + + public void BackwardMultidim(Complex32[] spectrum, int[] dimensions, FourierTransformScaling scaling) + { + throw new NotSupportedException(); + } + + public void BackwardMultidim(Complex[] spectrum, int[] dimensions, FourierTransformScaling scaling) + { + throw new NotSupportedException(); + } + + /// + /// Fully rescale the FFT result. + /// + /// Sample Vector. + private static void FullRescale(Complex32[] samples) + { + var scalingFactor = (float)1.0 / samples.Length; + for (int i = 0; i < samples.Length; i++) + { + samples[i] *= scalingFactor; + } + } + + /// + /// Fully rescale the FFT result. + /// + /// Sample Vector. + private static void FullRescale(Complex[] samples) + { + var scalingFactor = 1.0 / samples.Length; + for (int i = 0; i < samples.Length; i++) + { + samples[i] *= scalingFactor; + } + } + + /// + /// Half rescale the FFT result (e.g. for symmetric transforms). + /// + /// Sample Vector. + private static void HalfRescale(Complex32[] samples) + { + var scalingFactor = (float)Math.Sqrt(1.0 / samples.Length); + for (int i = 0; i < samples.Length; i++) + { + samples[i] *= scalingFactor; + } + } + + /// + /// Fully rescale the FFT result (e.g. for symmetric transforms). + /// + /// Sample Vector. + private static void HalfRescale(Complex[] samples) + { + var scalingFactor = Math.Sqrt(1.0 / samples.Length); + for (int i = 0; i < samples.Length; i++) + { + samples[i] *= scalingFactor; + } + } +} diff --git a/MathNet.Numerics/Providers/FourierTransform/Mkl/MklFourierTransformProvider.cs b/MathNet.Numerics/Providers/FourierTransform/Mkl/MklFourierTransformProvider.cs new file mode 100644 index 0000000..11733a1 --- /dev/null +++ b/MathNet.Numerics/Providers/FourierTransform/Mkl/MklFourierTransformProvider.cs @@ -0,0 +1,371 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Threading; +using MathNet.Numerics.Providers.Common.Mkl; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.FourierTransform.Mkl +{ + internal class MklFourierTransformProvider : IFourierTransformProvider, IDisposable + { + const int MinimumCompatibleRevision = 11; + + class Kernel + { + public IntPtr Handle; + public int[] Dimensions; + public FourierTransformScaling Scaling; + public bool Real; + public bool Single; + } + + readonly string _hintPath; + Kernel _kernel; + + /// Hint path where to look for the native binaries + internal MklFourierTransformProvider(string hintPath) + { + _hintPath = hintPath; + } + + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + public bool IsAvailable() + { + return MklProvider.IsAvailable(hintPath: _hintPath); + } + + /// + /// Initialize and verify that the provided is indeed available. If not, fall back to alternatives like the managed provider + /// + public void InitializeVerify() + { + int revision = MklProvider.Load(hintPath: _hintPath); + if (revision < MinimumCompatibleRevision) + { + throw new NotSupportedException($"MKL Native Provider revision r{revision} is too old. Consider upgrading to a newer version. Revision r{MinimumCompatibleRevision} and newer are supported."); + } + + // we only support exactly one major version, since major version changes imply a breaking change. + int fftMajor = SafeNativeMethods.query_capability((int) ProviderCapability.FourierTransformMajor); + int fftMinor = SafeNativeMethods.query_capability((int) ProviderCapability.FourierTransformMinor); + if (!(fftMajor == 1 && fftMinor >= 0)) + { + throw new NotSupportedException(string.Format("MKL Native Provider not compatible. Expecting Fourier transform v1 but provider implements v{0}.", fftMajor)); + } + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + public virtual void FreeResources() + { + Kernel kernel = Interlocked.Exchange(ref _kernel, null); + if (kernel != null) + { + SafeNativeMethods.x_fft_free(ref kernel.Handle); + } + + MklProvider.FreeResources(); + } + + public override string ToString() + { + return MklProvider.Describe(); + } + + Kernel Configure(int length, FourierTransformScaling scaling, bool real, bool single) + { + Kernel kernel = Interlocked.Exchange(ref _kernel, null); + + if (kernel == null) + { + kernel = new Kernel + { + Dimensions = new[] {length}, + Scaling = scaling, + Real = real, + Single = single + }; + + if (single) + { + if (real) SafeNativeMethods.s_fft_create(out kernel.Handle, length, (float)ForwardScaling(scaling, length), (float)BackwardScaling(scaling, length)); + else SafeNativeMethods.c_fft_create(out kernel.Handle, length, (float)ForwardScaling(scaling, length), (float)BackwardScaling(scaling, length)); + } + else + { + if (real) SafeNativeMethods.d_fft_create(out kernel.Handle, length, ForwardScaling(scaling, length), BackwardScaling(scaling, length)); + else SafeNativeMethods.z_fft_create(out kernel.Handle, length, ForwardScaling(scaling, length), BackwardScaling(scaling, length)); + } + + return kernel; + } + + if (kernel.Dimensions.Length != 1 || kernel.Dimensions[0] != length || kernel.Scaling != scaling || kernel.Real != real || kernel.Single != single) + { + SafeNativeMethods.x_fft_free(ref kernel.Handle); + + if (single) + { + if (real) SafeNativeMethods.s_fft_create(out kernel.Handle, length, (float)ForwardScaling(scaling, length), (float)BackwardScaling(scaling, length)); + else SafeNativeMethods.c_fft_create(out kernel.Handle, length, (float)ForwardScaling(scaling, length), (float)BackwardScaling(scaling, length)); + } + else + { + if (real) SafeNativeMethods.d_fft_create(out kernel.Handle, length, ForwardScaling(scaling, length), BackwardScaling(scaling, length)); + else SafeNativeMethods.z_fft_create(out kernel.Handle, length, ForwardScaling(scaling, length), BackwardScaling(scaling, length)); + } + + kernel.Dimensions = new[] {length}; + kernel.Scaling = scaling; + kernel.Real = real; + kernel.Single = single; + return kernel; + } + + return kernel; + } + + Kernel Configure(int[] dimensions, FourierTransformScaling scaling, bool single) + { + if (dimensions.Length == 1) + { + return Configure(dimensions[0], scaling, false, single); + } + + Kernel kernel = Interlocked.Exchange(ref _kernel, null); + + if (kernel == null) + { + kernel = new Kernel + { + Dimensions = dimensions, + Scaling = scaling, + Real = false, + Single = single + }; + + long length = 1; + for (int i = 0; i < dimensions.Length; i++) + { + length *= dimensions[i]; + } + + if (single) + { + SafeNativeMethods.c_fft_create_multidim(out kernel.Handle, dimensions.Length, dimensions, (float)ForwardScaling(scaling, length), (float)BackwardScaling(scaling, length)); + } + else + { + SafeNativeMethods.z_fft_create_multidim(out kernel.Handle, dimensions.Length, dimensions, ForwardScaling(scaling, length), BackwardScaling(scaling, length)); + } + + return kernel; + } + + bool mismatch = kernel.Dimensions.Length != dimensions.Length || kernel.Scaling != scaling || kernel.Real != false || kernel.Single != single; + if (!mismatch) + { + for (int i = 0; i < dimensions.Length; i++) + { + if (dimensions[i] != kernel.Dimensions[i]) + { + mismatch = true; + break; + } + } + } + + if (mismatch) + { + long length = 1; + for (int i = 0; i < dimensions.Length; i++) + { + length *= dimensions[i]; + } + + SafeNativeMethods.x_fft_free(ref kernel.Handle); + + if (single) + { + SafeNativeMethods.c_fft_create_multidim(out kernel.Handle, dimensions.Length, dimensions, (float)ForwardScaling(scaling, length), (float)BackwardScaling(scaling, length)); + } + else + { + SafeNativeMethods.z_fft_create_multidim(out kernel.Handle, dimensions.Length, dimensions, ForwardScaling(scaling, length), BackwardScaling(scaling, length)); + } + + kernel.Dimensions = dimensions; + kernel.Scaling = scaling; + kernel.Real = false; + kernel.Single = single; + return kernel; + } + + return kernel; + } + + void Release(Kernel kernel) + { + Kernel existing = Interlocked.Exchange(ref _kernel, kernel); + if (existing != null) + { + SafeNativeMethods.x_fft_free(ref existing.Handle); + } + } + + public void Forward(Complex32[] samples, FourierTransformScaling scaling) + { + Kernel kernel = Configure(samples.Length, scaling, false, true); + SafeNativeMethods.c_fft_forward(kernel.Handle, samples); + Release(kernel); + } + + public void Forward(Complex[] samples, FourierTransformScaling scaling) + { + Kernel kernel = Configure(samples.Length, scaling, false, false); + SafeNativeMethods.z_fft_forward(kernel.Handle, samples); + Release(kernel); + } + + public void Backward(Complex32[] spectrum, FourierTransformScaling scaling) + { + Kernel kernel = Configure(spectrum.Length, scaling, false, true); + SafeNativeMethods.c_fft_backward(kernel.Handle, spectrum); + Release(kernel); + } + + public void Backward(Complex[] spectrum, FourierTransformScaling scaling) + { + Kernel kernel = Configure(spectrum.Length, scaling, false, false); + SafeNativeMethods.z_fft_backward(kernel.Handle, spectrum); + Release(kernel); + } + + public void ForwardReal(float[] samples, int n, FourierTransformScaling scaling) + { + Kernel kernel = Configure(n, scaling, true, true); + SafeNativeMethods.s_fft_forward(kernel.Handle, samples); + Release(kernel); + } + + public void ForwardReal(double[] samples, int n, FourierTransformScaling scaling) + { + Kernel kernel = Configure(n, scaling, true, false); + SafeNativeMethods.d_fft_forward(kernel.Handle, samples); + Release(kernel); + } + + public void BackwardReal(float[] spectrum, int n, FourierTransformScaling scaling) + { + Kernel kernel = Configure(n, scaling, true, true); + SafeNativeMethods.s_fft_backward(kernel.Handle, spectrum); + Release(kernel); + + spectrum[n] = 0f; + } + + public void BackwardReal(double[] spectrum, int n, FourierTransformScaling scaling) + { + Kernel kernel = Configure(n, scaling, true, false); + SafeNativeMethods.d_fft_backward(kernel.Handle, spectrum); + Release(kernel); + + spectrum[n] = 0d; + } + + public void ForwardMultidim(Complex32[] samples, int[] dimensions, FourierTransformScaling scaling) + { + Kernel kernel = Configure(dimensions, scaling, true); + SafeNativeMethods.c_fft_forward(kernel.Handle, samples); + Release(kernel); + } + + public void ForwardMultidim(Complex[] samples, int[] dimensions, FourierTransformScaling scaling) + { + Kernel kernel = Configure(dimensions, scaling, false); + SafeNativeMethods.z_fft_forward(kernel.Handle, samples); + Release(kernel); + } + + public void BackwardMultidim(Complex32[] spectrum, int[] dimensions, FourierTransformScaling scaling) + { + Kernel kernel = Configure(dimensions, scaling, true); + SafeNativeMethods.c_fft_backward(kernel.Handle, spectrum); + Release(kernel); + } + + public void BackwardMultidim(Complex[] spectrum, int[] dimensions, FourierTransformScaling scaling) + { + Kernel kernel = Configure(dimensions, scaling, false); + SafeNativeMethods.z_fft_backward(kernel.Handle, spectrum); + Release(kernel); + } + + static double ForwardScaling(FourierTransformScaling scaling, long length) + { + switch (scaling) + { + case FourierTransformScaling.SymmetricScaling: + return Math.Sqrt(1.0/length); + case FourierTransformScaling.ForwardScaling: + return 1.0/length; + default: + return 1.0; + } + } + + static double BackwardScaling(FourierTransformScaling scaling, long length) + { + switch (scaling) + { + case FourierTransformScaling.SymmetricScaling: + return Math.Sqrt(1.0/length); + case FourierTransformScaling.BackwardScaling: + return 1.0/length; + default: + return 1.0; + } + } + + public void Dispose() + { + FreeResources(); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Complex.cs b/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Complex.cs new file mode 100644 index 0000000..7f6401e --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Complex.cs @@ -0,0 +1,1093 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// Copyright (c) 2009-2011 Math.NET +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVEACML + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using System; +using System.Security; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Acml +{ + /// + /// AMD Core Math Library (ACML) linear algebra provider. + /// + internal partial class AcmlLinearAlgebraProvider : ManagedLinearAlgebraProvider + { + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override Complex DotProduct(Complex[] x, Complex[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.z_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(Complex[] y, Complex alpha, Complex[] x, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == Complex.Zero) + { + return; + } + + SafeNativeMethods.z_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(Complex alpha, Complex[] x, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == Complex.One) + { + return; + } + + SafeNativeMethods.z_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to Complex.One and beta set to Complex.Zero, and x and y are not transposed. + public override void MatrixMultiply(Complex[] x, int rowsX, int columnsX, Complex[] y, int rowsY, int columnsY, Complex[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex.One, x, rowsX, columnsX, y, rowsY, columnsY, Complex.Zero, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex alpha, Complex[] a, int rowsA, int columnsA, Complex[] b, int rowsB, int columnsB, Complex beta, Complex[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.z_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always Complex.One + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(Complex[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + SafeNativeMethods.z_lu_factor(order, data, ipiv); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var work = new Complex[order]; + SafeNativeMethods.z_lu_inverse(order, a, work, work.Length); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var work = new Complex[order]; + SafeNativeMethods.z_lu_inverse_factored(order, a, ipiv, work, order); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex[] a, int order, Complex[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (work.Length < order) + { + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.z_lu_inverse(order, a, work, work.Length); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex[] a, int order, int[] ipiv, Complex[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (work.Length < order) + { + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.z_lu_inverse_factored(order, a, ipiv, work, order); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, Complex[] a, int order, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.z_lu_solve(order, columnsOfB, a, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, Complex[] a, int order, int[] ipiv, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.z_lu_solve_factored(order, columnsOfB, a, ipiv, b); + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.z_cholesky_factor(order, a); + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.z_cholesky_solve(orderA, columnsB, a, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.z_cholesky_solve_factored(orderA, columnsB, a, b); + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(Complex[] r, int rowsR, int columnsR, Complex[] q, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), "r"); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), "tau"); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), "q"); + } + + var work = new Complex[columnsR*Control.BlockSize]; + SafeNativeMethods.z_qr_factor(rowsR, columnsR, r, tau, q, work, work.Length); + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(Complex[] r, int rowsR, int columnsR, Complex[] q, Complex[] tau, Complex[] work) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), "r"); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), "tau"); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), "q"); + } + + if (work.Length < columnsR*Control.BlockSize) + { + work[0] = columnsR*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.z_qr_factor(rowsR, columnsR, r, tau, q, work, work.Length); + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + public override void QRSolve(Complex[] a, int rows, int columns, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows*columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new Complex[columns*Control.BlockSize]; + QRSolve(a, rows, columns, b, columnsB, x, work); + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// Rows must be greater or equal to columns. + public override void QRSolve(Complex[] a, int rows, int columns, Complex[] b, int columnsB, Complex[] x, Complex[] work, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (a.Length != rows*columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (work.Length < 1) + { + work[0] = rows*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.z_qr_solve(rows, columns, columnsB, a, b, x, work, work.Length); + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(Complex[] q, Complex[] r, int rowsR, int columnsR, Complex[] tau, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(r)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(q)); + } + + if (b.Length != rowsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rowsR < columnsR) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new Complex[columnsR*Control.BlockSize]; + QRSolveFactored(q, r, rowsR, columnsR, tau, b, columnsB, x, work); + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by QR factor. This is only used for the managed provider and can be + /// null for the native provider. The native provider uses the Q portion stored in the R matrix. + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The work array - only used in the native provider. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// Rows must be greater or equal to columns. + public override void QRSolveFactored(Complex[] q, Complex[] r, int rowsR, int columnsR, Complex[] tau, Complex[] b, int columnsB, Complex[] x, Complex[] work, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(r)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(q)); + } + + if (b.Length != rowsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rowsR < columnsR) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (work.Length < 1) + { + work[0] = rowsR*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.z_qr_solve_factored(rowsR, columnsR, columnsB, r, b, tau, x, work, work.Length); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex[] a, int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new Complex[(2*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)]; + SingularValueDecomposition(computeVectors, a, rowsA, columnsA, s, u, vt, work); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(Complex[] a, int rowsA, int columnsA, Complex[] b, int columnsB, Complex[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var work = new Complex[(2*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)]; + var s = new Complex[Math.Min(rowsA, columnsA)]; + var u = new Complex[rowsA*rowsA]; + var vt = new Complex[columnsA*columnsA]; + + var clone = new Complex[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt, work); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// The work array. For real matrices, the work array should be at least + /// Max(3*Min(M, N) + Max(M, N), 5*Min(M,N)). For complex matrices, 2*Min(M, N) + Max(M, N). + /// On exit, work[0] contains the optimal work size value. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex[] a, int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt, Complex[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (work.Length == 0) + { + throw new ArgumentException(Resources.ArgumentSingleDimensionArray, nameof(work)); + } + + if (work.Length < (2*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)) + { + work[0] = (2*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA); + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.z_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt, work, work.Length); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Complex32.cs b/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Complex32.cs new file mode 100644 index 0000000..fabad11 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Complex32.cs @@ -0,0 +1,1096 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2011 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVEACML + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using System; +using System.Security; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Acml +{ + /// + /// AMD Core Math Library (ACML) linear algebra provider. + /// + internal partial class AcmlLinearAlgebraProvider + { + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override Complex32 DotProduct(Complex32[] x, Complex32[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.c_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(Complex32[] y, Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == Complex32.Zero) + { + return; + } + + SafeNativeMethods.c_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == Complex32.One) + { + return; + } + + SafeNativeMethods.c_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to Complex32.One and beta set to Complex32.Zero, and x and y are not transposed. + public override void MatrixMultiply(Complex32[] x, int rowsX, int columnsX, Complex32[] y, int rowsY, int columnsY, Complex32[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex32.One, x, rowsX, columnsX, y, rowsY, columnsY, Complex32.Zero, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex32 alpha, Complex32[] a, int rowsA, int columnsA, Complex32[] b, int rowsB, int columnsB, Complex32 beta, Complex32[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.c_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always Complex32.One + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(Complex32[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + SafeNativeMethods.c_lu_factor(order, data, ipiv); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var work = new Complex32[order]; + SafeNativeMethods.c_lu_inverse(order, a, work, work.Length); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex32[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var work = new Complex32[order]; + SafeNativeMethods.c_lu_inverse_factored(order, a, ipiv, work, order); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex32[] a, int order, Complex32[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (work.Length < order) + { + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.c_lu_inverse(order, a, work, work.Length); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex32[] a, int order, int[] ipiv, Complex32[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (work.Length < order) + { + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.c_lu_inverse_factored(order, a, ipiv, work, order); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, Complex32[] a, int order, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.c_lu_solve(order, columnsOfB, a, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, Complex32[] a, int order, int[] ipiv, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.c_lu_solve_factored(order, columnsOfB, a, ipiv, b); + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.c_cholesky_factor(order, a); + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.c_cholesky_solve(orderA, columnsB, a, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.c_cholesky_solve_factored(orderA, columnsB, a, b); + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(Complex32[] r, int rowsR, int columnsR, Complex32[] q, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), "r"); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), "tau"); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), "q"); + } + + var work = new Complex32[columnsR*Control.BlockSize]; + SafeNativeMethods.c_qr_factor(rowsR, columnsR, r, tau, q, work, work.Length); + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(Complex32[] r, int rowsR, int columnsR, Complex32[] q, Complex32[] tau, Complex32[] work) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), "r"); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), "tau"); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), "q"); + } + + if (work.Length < columnsR*Control.BlockSize) + { + work[0] = columnsR*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.c_qr_factor(rowsR, columnsR, r, tau, q, work, work.Length); + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + public override void QRSolve(Complex32[] a, int rows, int columns, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows*columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new Complex32[columns*Control.BlockSize]; + QRSolve(a, rows, columns, b, columnsB, x, work); + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// Rows must be greater or equal to columns. + public override void QRSolve(Complex32[] a, int rows, int columns, Complex32[] b, int columnsB, Complex32[] x, Complex32[] work, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (a.Length != rows*columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (work.Length < 1) + { + work[0] = rows*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.c_qr_solve(rows, columns, columnsB, a, b, x, work, work.Length); + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(Complex32[] q, Complex32[] r, int rowsR, int columnsR, Complex32[] tau, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(r)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(q)); + } + + if (b.Length != rowsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rowsR < columnsR) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new Complex32[columnsR*Control.BlockSize]; + QRSolveFactored(q, r, rowsR, columnsR, tau, b, columnsB, x, work); + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by QR factor. This is only used for the managed provider and can be + /// null for the native provider. The native provider uses the Q portion stored in the R matrix. + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The work array - only used in the native provider. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// Rows must be greater or equal to columns. + public override void QRSolveFactored(Complex32[] q, Complex32[] r, int rowsR, int columnsR, Complex32[] tau, Complex32[] b, int columnsB, Complex32[] x, Complex32[] work, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(r)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(q)); + } + + if (b.Length != rowsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rowsR < columnsR) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (work.Length < 1) + { + work[0] = rowsR*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.c_qr_solve_factored(rowsR, columnsR, columnsB, r, b, tau, x, work, work.Length); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex32[] a, int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new Complex32[(2*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)]; + SingularValueDecomposition(computeVectors, a, rowsA, columnsA, s, u, vt, work); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(Complex32[] a, int rowsA, int columnsA, Complex32[] b, int columnsB, Complex32[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var work = new Complex32[(2*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)]; + var s = new Complex32[Math.Min(rowsA, columnsA)]; + var u = new Complex32[rowsA*rowsA]; + var vt = new Complex32[columnsA*columnsA]; + + var clone = new Complex32[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt, work); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// The work array. For real matrices, the work array should be at least + /// Max(3*Min(M, N) + Max(M, N), 5*Min(M,N)). For complex matrices, 2*Min(M, N) + Max(M, N). + /// On exit, work[0] contains the optimal work size value. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex32[] a, int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt, Complex32[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (work.Length == 0) + { + throw new ArgumentException(Resources.ArgumentSingleDimensionArray, nameof(work)); + } + + if (work.Length < (2*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)) + { + work[0] = (2*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA); + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.c_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt, work, work.Length); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Double.cs b/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Double.cs new file mode 100644 index 0000000..088400e --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Double.cs @@ -0,0 +1,1097 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2011 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVEACML + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using System; +using System.Security; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Acml +{ + /// + /// AMD Core Math Library (ACML) linear algebra provider. + /// + internal partial class AcmlLinearAlgebraProvider + { + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override double DotProduct(double[] x, double[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.d_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(double[] y, double alpha, double[] x, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == 0.0) + { + return; + } + + SafeNativeMethods.d_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(double alpha, double[] x, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == 1.0) + { + return; + } + + SafeNativeMethods.d_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public override void MatrixMultiply(double[] x, int rowsX, int columnsX, double[] y, int rowsY, int columnsY, double[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0, x, rowsX, columnsX, y, rowsY, columnsY, 0.0, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, double alpha, double[] a, int rowsA, int columnsA, double[] b, int rowsB, int columnsB, double beta, double[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.d_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(double[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + SafeNativeMethods.d_lu_factor(order, data, ipiv); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var work = new double[order]; + SafeNativeMethods.d_lu_inverse(order, a, work, work.Length); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(double[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var work = new double[order]; + SafeNativeMethods.d_lu_inverse_factored(order, a, ipiv, work, order); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(double[] a, int order, double[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (work.Length < order) + { + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.d_lu_inverse(order, a, work, work.Length); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(double[] a, int order, int[] ipiv, double[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (work.Length < order) + { + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.d_lu_inverse_factored(order, a, ipiv, work, order); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, double[] a, int order, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.d_lu_solve(order, columnsOfB, a, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, double[] a, int order, int[] ipiv, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.d_lu_solve_factored(order, columnsOfB, a, ipiv, b); + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.d_cholesky_factor(order, a); + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.d_cholesky_solve(orderA, columnsB, a, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.d_cholesky_solve_factored(orderA, columnsB, a, b); + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(double[] r, int rowsR, int columnsR, double[] q, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), "r"); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), "tau"); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), "q"); + } + + var work = new double[columnsR*Control.BlockSize]; + SafeNativeMethods.d_qr_factor(rowsR, columnsR, r, tau, q, work, work.Length); + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(double[] r, int rowsR, int columnsR, double[] q, double[] tau, double[] work) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), "r"); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), "tau"); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), "q"); + } + + if (work.Length < columnsR*Control.BlockSize) + { + work[0] = columnsR*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.d_qr_factor(rowsR, columnsR, r, tau, q, work, work.Length); + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + public override void QRSolve(double[] a, int rows, int columns, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows*columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new double[columns*Control.BlockSize]; + QRSolve(a, rows, columns, b, columnsB, x, work); + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// Rows must be greater or equal to columns. + public override void QRSolve(double[] a, int rows, int columns, double[] b, int columnsB, double[] x, double[] work, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (a.Length != rows*columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (work.Length < 1) + { + work[0] = rows*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.d_qr_solve(rows, columns, columnsB, a, b, x, work, work.Length); + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(double[] q, double[] r, int rowsR, int columnsR, double[] tau, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(r)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(q)); + } + + if (b.Length != rowsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rowsR < columnsR) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new double[columnsR*Control.BlockSize]; + QRSolveFactored(q, r, rowsR, columnsR, tau, b, columnsB, x, work); + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by QR factor. This is only used for the managed provider and can be + /// null for the native provider. The native provider uses the Q portion stored in the R matrix. + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The work array - only used in the native provider. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// Rows must be greater or equal to columns. + public override void QRSolveFactored(double[] q, double[] r, int rowsR, int columnsR, double[] tau, double[] b, int columnsB, double[] x, double[] work, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(r)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(q)); + } + + if (b.Length != rowsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rowsR < columnsR) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (work.Length < 1) + { + work[0] = rowsR*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.d_qr_solve_factored(rowsR, columnsR, columnsB, r, b, tau, x, work, work.Length); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, double[] a, int rowsA, int columnsA, double[] s, double[] u, double[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new double[Math.Max((3*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA), 5*Math.Min(rowsA, columnsA))]; + SingularValueDecomposition(computeVectors, a, rowsA, columnsA, s, u, vt, work); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(double[] a, int rowsA, int columnsA, double[] b, int columnsB, double[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var work = new double[Math.Max((3*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA), 5*Math.Min(rowsA, columnsA))]; + var s = new double[Math.Min(rowsA, columnsA)]; + var u = new double[rowsA*rowsA]; + var vt = new double[columnsA*columnsA]; + + var clone = new double[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt, work); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// The work array. For real matrices, the work array should be at least + /// Max(3*Min(M, N) + Max(M, N), 5*Min(M,N)). For complex matrices, 2*Min(M, N) + Max(M, N). + /// On exit, work[0] contains the optimal work size value. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, double[] a, int rowsA, int columnsA, double[] s, double[] u, double[] vt, double[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (work.Length == 0) + { + throw new ArgumentException(Resources.ArgumentSingleDimensionArray, nameof(work)); + } + + if (work.Length < Math.Max((3*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA), 5*Math.Min(rowsA, columnsA))) + { + work[0] = Math.Max((3*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA), 5*Math.Min(rowsA, columnsA)); + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.d_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt, work, work.Length); + } + + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Single.cs b/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Single.cs new file mode 100644 index 0000000..4089da2 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Acml/AcmlLinearAlgebraProvider.Single.cs @@ -0,0 +1,1096 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2011 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVEACML + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using System; +using System.Security; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Acml +{ + /// + /// AMD Core Math Library (ACML) linear algebra provider. + /// + internal partial class AcmlLinearAlgebraProvider + { + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override float DotProduct(float[] x, float[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.s_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(float[] y, float alpha, float[] x, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == 0.0f) + { + return; + } + + SafeNativeMethods.s_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(float alpha, float[] x, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == 1.0f) + { + return; + } + + SafeNativeMethods.s_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0f and beta set to 0.0f, and x and y are not transposed. + public override void MatrixMultiply(float[] x, int rowsX, int columnsX, float[] y, int rowsY, int columnsY, float[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0f, x, rowsX, columnsX, y, rowsY, columnsY, 0.0f, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, float alpha, float[] a, int rowsA, int columnsA, float[] b, int rowsB, int columnsB, float beta, float[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.s_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0f + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(float[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + SafeNativeMethods.s_lu_factor(order, data, ipiv); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var work = new float[order]; + SafeNativeMethods.s_lu_inverse(order, a, work, work.Length); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(float[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var work = new float[order]; + SafeNativeMethods.s_lu_inverse_factored(order, a, ipiv, work, order); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(float[] a, int order, float[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (work.Length < order) + { + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.s_lu_inverse(order, a, work, work.Length); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(float[] a, int order, int[] ipiv, float[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (work.Length < order) + { + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.s_lu_inverse_factored(order, a, ipiv, work, order); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, float[] a, int order, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.s_lu_solve(order, columnsOfB, a, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, float[] a, int order, int[] ipiv, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.s_lu_solve_factored(order, columnsOfB, a, ipiv, b); + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.s_cholesky_factor(order, a); + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.s_cholesky_solve(orderA, columnsB, a, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + SafeNativeMethods.s_cholesky_solve_factored(orderA, columnsB, a, b); + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(float[] r, int rowsR, int columnsR, float[] q, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), "r"); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), "tau"); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), "q"); + } + + var work = new float[columnsR*Control.BlockSize]; + SafeNativeMethods.s_qr_factor(rowsR, columnsR, r, tau, q, work, work.Length); + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(float[] r, int rowsR, int columnsR, float[] q, float[] tau, float[] work) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), "r"); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), "tau"); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), "q"); + } + + if (work.Length < columnsR*Control.BlockSize) + { + work[0] = columnsR*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.s_qr_factor(rowsR, columnsR, r, tau, q, work, work.Length); + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + public override void QRSolve(float[] a, int rows, int columns, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows*columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new float[columns*Control.BlockSize]; + QRSolve(a, rows, columns, b, columnsB, x, work); + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The work array. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// Rows must be greater or equal to columns. + public override void QRSolve(float[] a, int rows, int columns, float[] b, int columnsB, float[] x, float[] work, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (a.Length != rows*columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (work.Length < 1) + { + work[0] = rows*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.s_qr_solve(rows, columns, columnsB, a, b, x, work, work.Length); + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(float[] q, float[] r, int rowsR, int columnsR, float[] tau, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(r)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(q)); + } + + if (b.Length != rowsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rowsR < columnsR) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new float[columnsR*Control.BlockSize]; + QRSolveFactored(q, r, rowsR, columnsR, tau, b, columnsB, x, work); + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by QR factor. This is only used for the managed provider and can be + /// null for the native provider. The native provider uses the Q portion stored in the R matrix. + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The work array - only used in the native provider. The array must have a length of at least N, + /// but should be N*blocksize. The blocksize is machine dependent. On exit, work[0] contains the optimal + /// work size value. + /// Rows must be greater or equal to columns. + public override void QRSolveFactored(float[] q, float[] r, int rowsR, int columnsR, float[] tau, float[] b, int columnsB, float[] x, float[] work, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(r)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(q)); + } + + if (b.Length != rowsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsR*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rowsR < columnsR) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (work.Length < 1) + { + work[0] = rowsR*Control.BlockSize; + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.s_qr_solve_factored(rowsR, columnsR, columnsB, r, b, tau, x, work, work.Length); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, float[] a, int rowsA, int columnsA, float[] s, float[] u, float[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new float[Math.Max(((3*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)), 5*Math.Min(rowsA, columnsA))]; + SingularValueDecomposition(computeVectors, a, rowsA, columnsA, s, u, vt, work); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(float[] a, int rowsA, int columnsA, float[] b, int columnsB, float[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var work = new float[Math.Max(((3*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)), 5*Math.Min(rowsA, columnsA))]; + var s = new float[Math.Min(rowsA, columnsA)]; + var u = new float[rowsA*rowsA]; + var vt = new float[columnsA*columnsA]; + + var clone = new float[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt, work); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// The work array. For real matrices, the work array should be at least + /// Max(3*Min(M, N) + Max(M, N), 5*Min(M,N)). For complex matrices, 2*Min(M, N) + Max(M, N). + /// On exit, work[0] contains the optimal work size value. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, float[] a, int rowsA, int columnsA, float[] s, float[] u, float[] vt, float[] work) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (work == null) + { + throw new ArgumentNullException(nameof(work)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (work.Length == 0) + { + throw new ArgumentException(Resources.ArgumentSingleDimensionArray, nameof(work)); + } + + if (work.Length < Math.Max(((3*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA)), 5*Math.Min(rowsA, columnsA))) + { + work[0] = Math.Max((3*Math.Min(rowsA, columnsA)) + Math.Max(rowsA, columnsA), 5*Math.Min(rowsA, columnsA)); + throw new ArgumentException(Resources.WorkArrayTooSmall, nameof(work)); + } + + SafeNativeMethods.s_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt, work, work.Length); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Acml/SafeNativeMethods.cs b/MathNet.Numerics/Providers/LinearAlgebra/Acml/SafeNativeMethods.cs new file mode 100644 index 0000000..67bee70 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Acml/SafeNativeMethods.cs @@ -0,0 +1,263 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// https://numerics.mathdotnet.com +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVEACML + +using System.Runtime.InteropServices; +using System.Security; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Acml +{ + /// + /// P/Invoke methods to the native math libraries. + /// + [SuppressUnmanagedCodeSecurity] + [SecurityCritical] + internal static class SafeNativeMethods + { + /// + /// Name of the native DLL. + /// + const string DllName = "MathNET.Numerics.ACML.dll"; + +#region BLAS + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_axpy(int n, float alpha, float[] x, [In, Out] float[] y); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_axpy(int n, double alpha, double[] x, [In, Out] double[] y); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_axpy(int n, Complex32 alpha, Complex32[] x, [In, Out] Complex32[] y); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_axpy(int n, Complex alpha, Complex[] x, [In, Out] Complex[] y); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_scale(int n, float alpha, [Out] float[] x); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_scale(int n, double alpha, [Out] double[] x); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_scale(int n, Complex32 alpha, [In, Out] Complex32[] x); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_scale(int n, Complex alpha, [In, Out] Complex[] x); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float s_dot_product(int n, float[] x, float[] y); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double d_dot_product(int n, double[] x, double[] y); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern Complex32 c_dot_product(int n, Complex32[] x, Complex32[] y); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern Complex z_dot_product(int n, Complex[] x, Complex[] y); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void s_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, float alpha, float[] x, float[] y, float beta, [In, Out] float[] c); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void d_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, double alpha, double[] x, double[] y, double beta, [In, Out] double[] c); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void c_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, Complex32 alpha, Complex32[] x, Complex32[] y, Complex32 beta, [In, Out] Complex32[] c); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern void z_matrix_multiply(Transpose transA, Transpose transB, int m, int n, int k, Complex alpha, Complex[] x, Complex[] y, Complex beta, [In, Out] Complex[] c); + +#endregion BLAS + +#region LAPACK + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float s_matrix_norm(byte norm, int rows, int columns, [In] float[] a, [In, Out] float[] work); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float d_matrix_norm(byte norm, int rows, int columns, [In] double[] a, [In, Out] double[] work); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern float c_matrix_norm(byte norm, int rows, int columns, [In] Complex32[] a, [In, Out] float[] work); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern double z_matrix_norm(byte norm, int rows, int columns, [In] Complex[] a, [In, Out] double[] work); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_factor(int n, [In, Out] float[] a); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_factor(int n, [In, Out] double[] a); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_factor(int n, [In, Out] Complex32[] a); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_factor(int n, [In, Out] Complex[] a); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_factor(int n, [In, Out] float[] a, [In, Out] int[] ipiv); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_factor(int n, [In, Out] double[] a, [In, Out] int[] ipiv); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_factor(int n, [In, Out] Complex32[] a, [In, Out] int[] ipiv); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_factor(int n, [In, Out] Complex[] a, [In, Out] int[] ipiv); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_inverse(int n, [In, Out] float[] a, [In, Out] float[] work, int lwork); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_inverse(int n, [In, Out] double[] a, [In, Out] double[] work, int lwork); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_inverse(int n, [In, Out] Complex32[] a, [In, Out] Complex32[] work, int lwork); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_inverse(int n, [In, Out] Complex[] a, [In, Out] Complex[] work, int lwork); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_inverse_factored(int n, [In, Out] float[] a, [In, Out] int[] ipiv, [In, Out] float[] work, int lwork); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_inverse_factored(int n, [In, Out] double[] a, [In, Out] int[] ipiv, [In, Out] double[] work, int lwork); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_inverse_factored(int n, [In, Out] Complex32[] a, [In, Out] int[] ipiv, [In, Out] Complex32[] work, int lwork); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_inverse_factored(int n, [In, Out] Complex[] a, [In, Out] int[] ipiv, [In, Out] Complex[] work, int lwork); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_solve_factored(int n, int nrhs, float[] a, [In, Out] int[] ipiv, [In, Out] float[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_solve_factored(int n, int nrhs, double[] a, [In, Out] int[] ipiv, [In, Out] double[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_solve_factored(int n, int nrhs, Complex32[] a, [In, Out] int[] ipiv, [In, Out] Complex32[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_solve_factored(int n, int nrhs, Complex[] a, [In, Out] int[] ipiv, [In, Out] Complex[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_lu_solve(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_lu_solve(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_lu_solve(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_lu_solve(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_solve(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_solve(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_solve(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_solve(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_cholesky_solve_factored(int n, int nrhs, float[] a, [In, Out] float[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_cholesky_solve_factored(int n, int nrhs, double[] a, [In, Out] double[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_cholesky_solve_factored(int n, int nrhs, Complex32[] a, [In, Out] Complex32[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_cholesky_solve_factored(int n, int nrhs, Complex[] a, [In, Out] Complex[] b); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_factor(int m, int n, [In, Out] float[] r, [In, Out] float[] tau, [In, Out] float[] q, [In, Out] float[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_factor(int m, int n, [In, Out] double[] r, [In, Out] double[] tau, [In, Out] double[] q, [In, Out] double[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_factor(int m, int n, [In, Out] Complex32[] r, [In, Out] Complex32[] tau, [In, Out] Complex32[] q, [In, Out] Complex32[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_factor(int m, int n, [In, Out] Complex[] r, [In, Out] Complex[] tau, [In, Out] Complex[] q, [In, Out] Complex[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_solve(int m, int n, int bn, float[] r, float[] b, [In, Out] float[] x, [In, Out] float[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_solve(int m, int n, int bn, double[] r, double[] b, [In, Out] double[] x, [In, Out] double[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_solve(int m, int n, int bn, Complex32[] r, Complex32[] b, [In, Out] Complex32[] x, [In, Out] Complex32[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_solve(int m, int n, int bn, Complex[] r, Complex[] b, [In, Out] Complex[] x, [In, Out] Complex[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_qr_solve_factored(int m, int n, int bn, float[] r, float[] b, float[] tau, [In, Out] float[] x, [In, Out] float[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_qr_solve_factored(int m, int n, int bn, double[] r, double[] b, double[] tau, [In, Out] double[] x, [In, Out] double[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_qr_solve_factored(int m, int n, int bn, Complex32[] r, Complex32[] b, Complex32[] tau, [In, Out] Complex32[] x, [In, Out] Complex32[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_qr_solve_factored(int m, int n, int bn, Complex[] r, Complex[] b, Complex[] tau, [In, Out] Complex[] x, [In, Out] Complex[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int s_svd_factor(bool computeVectors, int m, int n, [In, Out] float[] a, [In, Out] float[] s, [In, Out] float[] u, [In, Out] float[] v, [In, Out] float[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int d_svd_factor(bool computeVectors, int m, int n, [In, Out] double[] a, [In, Out] double[] s, [In, Out] double[] u, [In, Out] double[] v, [In, Out] double[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int c_svd_factor(bool computeVectors, int m, int n, [In, Out] Complex32[] a, [In, Out] Complex32[] s, [In, Out] Complex32[] u, [In, Out] Complex32[] v, [In, Out] Complex32[] work, int len); + + [DllImport(DllName, ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + internal static extern int z_svd_factor(bool computeVectors, int m, int n, [In, Out] Complex[] a, [In, Out] Complex[] s, [In, Out] Complex[] u, [In, Out] Complex[] v, [In, Out] Complex[] work, int len); + +#endregion LAPACK + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Complex.cs b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Complex.cs new file mode 100644 index 0000000..55698a7 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Complex.cs @@ -0,0 +1,577 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.Cuda; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Cuda +{ + /// + /// NVidia's CUDA Toolkit linear algebra provider. + /// + internal partial class CudaLinearAlgebraProvider + { + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override Complex DotProduct(Complex[] x, Complex[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.z_dot_product(_blasHandle, x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(Complex[] y, Complex alpha, Complex[] x, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == Complex.Zero) + { + return; + } + + SafeNativeMethods.z_axpy(_blasHandle, y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(Complex alpha, Complex[] x, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == Complex.One) + { + return; + } + + SafeNativeMethods.z_scale(_blasHandle, x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to Complex.One and beta set to Complex.Zero, and x and y are not transposed. + public override void MatrixMultiply(Complex[] x, int rowsX, int columnsX, Complex[] y, int rowsY, int columnsY, Complex[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex.One, x, rowsX, columnsX, y, rowsY, columnsY, Complex.Zero, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex alpha, Complex[] a, int rowsA, int columnsA, Complex[] b, int rowsB, int columnsB, Complex beta, Complex[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.z_matrix_multiply(_blasHandle, transposeA.ToCUDA(), transposeB.ToCUDA(), m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always Complex.One + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(Complex[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + Solver(SafeNativeMethods.z_lu_factor(_solverHandle, order, data, ipiv)); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + Solver(SafeNativeMethods.z_lu_inverse(_solverHandle, _blasHandle, order, a)); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + BLAS(SafeNativeMethods.z_lu_inverse_factored(_blasHandle, order, a, ipiv)); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, Complex[] a, int order, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.z_lu_solve(_solverHandle, order, columnsOfB, a, b)); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, Complex[] a, int order, int[] ipiv, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.z_lu_solve_factored(_solverHandle, order, columnsOfB, a, ipiv, b)); + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + Solver(SafeNativeMethods.z_cholesky_factor(_solverHandle, order, a)); + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.z_cholesky_solve(_solverHandle, orderA, columnsB, a, b)); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.z_cholesky_solve_factored(_solverHandle, orderA, columnsB, a, b)); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(Complex[] a, int rowsA, int columnsA, Complex[] b, int columnsB, Complex[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex[Math.Min(rowsA, columnsA)]; + var u = new Complex[rowsA*rowsA]; + var vt = new Complex[columnsA*columnsA]; + + var clone = new Complex[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex[] a, int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (columnsA > rowsA || !computeVectors) // see remarks http://docs.nvidia.com/cuda/cusolver/index.html#cuds-lt-t-gt-gesvd + base.SingularValueDecomposition(computeVectors, a, rowsA, columnsA, s, u, vt); + else Solver(SafeNativeMethods.z_svd_factor(_solverHandle, computeVectors, rowsA, columnsA, a, s, u, vt)); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Complex32.cs b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Complex32.cs new file mode 100644 index 0000000..1ff2002 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Complex32.cs @@ -0,0 +1,576 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.Cuda; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Cuda +{ + /// + /// NVidia's CUDA Toolkit linear algebra provider. + /// + internal partial class CudaLinearAlgebraProvider + { + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override Complex32 DotProduct(Complex32[] x, Complex32[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.c_dot_product(_blasHandle, x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(Complex32[] y, Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == Complex32.Zero) + { + return; + } + + SafeNativeMethods.c_axpy(_blasHandle, y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == Complex32.One) + { + return; + } + + SafeNativeMethods.c_scale(_blasHandle, x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to Complex32.One and beta set to Complex32.Zero, and x and y are not transposed. + public override void MatrixMultiply(Complex32[] x, int rowsX, int columnsX, Complex32[] y, int rowsY, int columnsY, Complex32[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex32.One, x, rowsX, columnsX, y, rowsY, columnsY, Complex32.Zero, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex32 alpha, Complex32[] a, int rowsA, int columnsA, Complex32[] b, int rowsB, int columnsB, Complex32 beta, Complex32[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.c_matrix_multiply(_blasHandle, transposeA.ToCUDA(), transposeB.ToCUDA(), m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always Complex32.One + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(Complex32[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + Solver(SafeNativeMethods.c_lu_factor(_solverHandle, order, data, ipiv)); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + Solver(SafeNativeMethods.c_lu_inverse(_solverHandle, _blasHandle, order, a)); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex32[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + BLAS(SafeNativeMethods.c_lu_inverse_factored(_blasHandle, order, a, ipiv)); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, Complex32[] a, int order, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.c_lu_solve(_solverHandle, order, columnsOfB, a, b)); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, Complex32[] a, int order, int[] ipiv, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.c_lu_solve_factored(_solverHandle, order, columnsOfB, a, ipiv, b)); + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + Solver(SafeNativeMethods.c_cholesky_factor(_solverHandle, order, a)); + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.c_cholesky_solve(_solverHandle, orderA, columnsB, a, b)); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.c_cholesky_solve_factored(_solverHandle, orderA, columnsB, a, b)); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(Complex32[] a, int rowsA, int columnsA, Complex32[] b, int columnsB, Complex32[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex32[Math.Min(rowsA, columnsA)]; + var u = new Complex32[rowsA*rowsA]; + var vt = new Complex32[columnsA*columnsA]; + + var clone = new Complex32[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex32[] a, int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (columnsA > rowsA || !computeVectors) // see remarks http://docs.nvidia.com/cuda/cusolver/index.html#cuds-lt-t-gt-gesvd + base.SingularValueDecomposition(computeVectors, a, rowsA, columnsA, s, u, vt); + else Solver(SafeNativeMethods.c_svd_factor(_solverHandle, computeVectors, rowsA, columnsA, a, s, u, vt)); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Double.cs b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Double.cs new file mode 100644 index 0000000..e4beaca --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Double.cs @@ -0,0 +1,576 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.Cuda; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Cuda +{ + /// + /// NVidia's CUDA Toolkit linear algebra provider. + /// + internal partial class CudaLinearAlgebraProvider + { + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override double DotProduct(double[] x, double[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.d_dot_product(_blasHandle, x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(double[] y, double alpha, double[] x, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == 0.0) + { + return; + } + + SafeNativeMethods.d_axpy(_blasHandle, y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(double alpha, double[] x, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == 1.0) + { + return; + } + + SafeNativeMethods.d_scale(_blasHandle, x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public override void MatrixMultiply(double[] x, int rowsX, int columnsX, double[] y, int rowsY, int columnsY, double[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0, x, rowsX, columnsX, y, rowsY, columnsY, 0.0, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, double alpha, double[] a, int rowsA, int columnsA, double[] b, int rowsB, int columnsB, double beta, double[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.d_matrix_multiply(_blasHandle, transposeA.ToCUDA(), transposeB.ToCUDA(), m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(double[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + Solver(SafeNativeMethods.d_lu_factor(_solverHandle, order, data, ipiv)); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + Solver(SafeNativeMethods.d_lu_inverse(_solverHandle, _blasHandle, order, a)); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(double[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + BLAS(SafeNativeMethods.d_lu_inverse_factored(_blasHandle, order, a, ipiv)); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, double[] a, int order, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.d_lu_solve(_solverHandle, order, columnsOfB, a, b)); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, double[] a, int order, int[] ipiv, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.d_lu_solve_factored(_solverHandle, order, columnsOfB, a, ipiv, b)); + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + Solver(SafeNativeMethods.d_cholesky_factor(_solverHandle, order, a)); + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.d_cholesky_solve(_solverHandle, orderA, columnsB, a, b)); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.d_cholesky_solve_factored(_solverHandle, orderA, columnsB, a, b)); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(double[] a, int rowsA, int columnsA, double[] b, int columnsB, double[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new double[Math.Min(rowsA, columnsA)]; + var u = new double[rowsA*rowsA]; + var vt = new double[columnsA*columnsA]; + + var clone = new double[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, double[] a, int rowsA, int columnsA, double[] s, double[] u, double[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (columnsA > rowsA || !computeVectors) // see remarks http://docs.nvidia.com/cuda/cusolver/index.html#cuds-lt-t-gt-gesvd + base.SingularValueDecomposition(computeVectors, a, rowsA, columnsA, s, u, vt); + else Solver (SafeNativeMethods.d_svd_factor(_solverHandle, computeVectors, rowsA, columnsA, a, s, u, vt)); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Single.cs b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Single.cs new file mode 100644 index 0000000..33fbb98 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.Single.cs @@ -0,0 +1,576 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.Cuda; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Cuda +{ + /// + /// NVidia's CUDA Toolkit linear algebra provider. + /// + internal partial class CudaLinearAlgebraProvider + { + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override float DotProduct(float[] x, float[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.s_dot_product(_blasHandle, x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(float[] y, float alpha, float[] x, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == 0.0f) + { + return; + } + + SafeNativeMethods.s_axpy(_blasHandle, y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(float alpha, float[] x, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == 1.0f) + { + return; + } + + SafeNativeMethods.s_scale(_blasHandle, x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0f and beta set to 0.0f, and x and y are not transposed. + public override void MatrixMultiply(float[] x, int rowsX, int columnsX, float[] y, int rowsY, int columnsY, float[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0f, x, rowsX, columnsX, y, rowsY, columnsY, 0.0f, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, float alpha, float[] a, int rowsA, int columnsA, float[] b, int rowsB, int columnsB, float beta, float[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.s_matrix_multiply(_blasHandle, transposeA.ToCUDA(), transposeB.ToCUDA(), m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0f + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(float[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + Solver(SafeNativeMethods.s_lu_factor(_solverHandle, order, data, ipiv)); + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + Solver(SafeNativeMethods.s_lu_inverse(_solverHandle, _blasHandle, order, a)); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(float[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + BLAS(SafeNativeMethods.s_lu_inverse_factored(_blasHandle, order, a, ipiv)); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, float[] a, int order, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.s_lu_solve(_solverHandle, order, columnsOfB, a, b)); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, float[] a, int order, int[] ipiv, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.s_lu_solve_factored(_solverHandle, order, columnsOfB, a, ipiv, b)); + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + Solver(SafeNativeMethods.s_cholesky_factor(_solverHandle, order, a)); + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.s_cholesky_solve(_solverHandle, orderA, columnsB, a, b)); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + Solver(SafeNativeMethods.s_cholesky_solve_factored(_solverHandle, orderA, columnsB, a, b)); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(float[] a, int rowsA, int columnsA, float[] b, int columnsB, float[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new float[Math.Min(rowsA, columnsA)]; + var u = new float[rowsA*rowsA]; + var vt = new float[columnsA*columnsA]; + + var clone = new float[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, float[] a, int rowsA, int columnsA, float[] s, float[] u, float[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (columnsA > rowsA || !computeVectors) // see remarks http://docs.nvidia.com/cuda/cusolver/index.html#cuds-lt-t-gt-gesvd + base.SingularValueDecomposition(computeVectors, a, rowsA, columnsA, s, u, vt); + else Solver(SafeNativeMethods.s_svd_factor(_solverHandle, computeVectors, rowsA, columnsA, a, s, u, vt)); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.cs b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.cs new file mode 100644 index 0000000..32b7964 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Cuda/CudaLinearAlgebraProvider.cs @@ -0,0 +1,194 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using MathNet.Numerics.Providers.Common.Cuda; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Cuda +{ + /// + /// NVidia's CUDA Toolkit linear algebra provider. + /// + internal partial class CudaLinearAlgebraProvider : Managed.ManagedLinearAlgebraProvider, IDisposable + { + const int MinimumCompatibleRevision = 1; + + readonly string _hintPath; + IntPtr _blasHandle; + IntPtr _solverHandle; + + /// Hint path where to look for the native binaries + internal CudaLinearAlgebraProvider(string hintPath) + { + _hintPath = hintPath; + } + + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + public override bool IsAvailable() + { + return CudaProvider.IsAvailable(hintPath: _hintPath); + } + + /// + /// Initialize and verify that the provided is indeed available. + /// If calling this method fails, consider to fall back to alternatives like the managed provider. + /// + public override void InitializeVerify() + { + int revision = CudaProvider.Load(hintPath: _hintPath); + if (revision < MinimumCompatibleRevision) + { + throw new NotSupportedException($"Cuda Native Provider revision r{revision} is too old. Consider upgrading to a newer version. Revision r{MinimumCompatibleRevision} and newer are supported."); + } + + int linearAlgebra = SafeNativeMethods.query_capability((int)ProviderCapability.LinearAlgebraMajor); + + // we only support exactly one major version, since major version changes imply a breaking change. + if (linearAlgebra != 1) + { + throw new NotSupportedException(string.Format("Cuda Native Provider not compatible. Expecting linear algebra v1 but provider implements v{0}.", linearAlgebra)); + } + + BLAS(SafeNativeMethods.createBLASHandle(ref _blasHandle)); + Solver(SafeNativeMethods.createSolverHandle(ref _solverHandle)); + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + public override void FreeResources() + { + CudaProvider.FreeResources(); + } + + private void BLAS(int status) + { + switch (status) + { + case 0: // CUBLAS_STATUS_SUCCESS + return; + + case 1: // CUBLAS_STATUS_NOT_INITIALIZED + throw new Exception("The CUDA Runtime initialization failed"); + + case 2: // CUSOLVER_STATUS_ALLOC_FAILED + throw new OutOfMemoryException("The resources could not be allocated"); + + case 7: // CUBLAS_STATUS_INVALID_VALUE + throw new ArgumentException("Invalid value"); + + case 8: // CUBLAS_STATUS_ARCH_MISMATCH + throw new NotSupportedException("The device does not support this operation."); + + case 11: // CUBLAS_STATUS_MAPPING_ERROR + throw new Exception("Mapping error."); + + case 13: // CUBLAS_STATUS_EXECUTION_FAILED + throw new Exception("Execution failed"); + + case 14: // CUBLAS_STATUS_INTERNAL_ERROR + throw new Exception("Internal error"); + + case 15: // CUBLAS_STATUS_NOT_SUPPORTED + throw new NotSupportedException(); + + case 16: // CUBLAS_STATUS_LICENSE_ERROR + throw new Exception("License error"); + + default: + throw new Exception("Unrecognized cuBLAS status code: " + status); + } + } + + private void Solver(int status) + { + switch (status) + { + case 0: // CUSOLVER_STATUS_SUCCESS + return; + + case 1: // CUSOLVER_STATUS_NOT_INITIALIZED + throw new Exception("The library was not initialized"); + + case 2: // CUSOLVER_STATUS_ALLOC_FAILED + throw new OutOfMemoryException("The resources could not be allocated"); + + case 3: // CUSOLVER_STATUS_INVALID_VALUE + throw new ArgumentException("Invalid value"); + + case 4: // CUSOLVER_STATUS_ARCH_MISMATCH + throw new NotSupportedException("The device does not support compute capability 2.0 and above"); + + case 5: // CUSOLVER_STATUS_MAPPING_ERROR + throw new Exception("Mapping error"); + + case 6: // CUSOLVER_STATUS_EXECUTION_FAILED + throw new NonConvergenceException("Execution failed"); + + case 7: //CUSOLVER_STATUS_INTERNAL_ERROR + throw new Exception("Internal error"); + + case 8: // CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED + throw new ArgumentException("Matrix type not supported"); + + case 9: // CUSOLVER_STATUS_NOT_SUPPORTED + throw new NotSupportedException(); + + case 10: // CUSOLVER_STATUS_ZERO_PIVOT + throw new Exception("Zero pivot"); + + case 11: //CUSOLVER_STATUS_INVALID_LICENSE + throw new Exception("Invalid license"); + + default: + throw new Exception("Unrecognized cuSolverDn status code: " + status); + } + } + + public override string ToString() + { + return CudaProvider.Describe(); + } + + public void Dispose() + { + BLAS(SafeNativeMethods.destroyBLASHandle(_blasHandle)); + Solver(SafeNativeMethods.destroySolverHandle(_solverHandle)); + FreeResources(); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/ILinearAlgebraProvider.cs b/MathNet.Numerics/Providers/LinearAlgebra/ILinearAlgebraProvider.cs new file mode 100644 index 0000000..4801fb1 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/ILinearAlgebraProvider.cs @@ -0,0 +1,449 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra; + +/// +/// How to transpose a matrix. +/// +public enum Transpose +{ + /// + /// Don't transpose a matrix. + /// + DontTranspose = 111, + + /// + /// Transpose a matrix. + /// + Transpose = 112, + + /// + /// Conjugate transpose a complex matrix. + /// + /// If a conjugate transpose is used with a real matrix, then the matrix is just transposed. + ConjugateTranspose = 113 +} + +/// +/// Types of matrix norms. +/// +public enum Norm : byte +{ + /// + /// The 1-norm. + /// + OneNorm = (byte)'1', + + /// + /// The Frobenius norm. + /// + FrobeniusNorm = (byte)'f', + + /// + /// The infinity norm. + /// + InfinityNorm = (byte)'i', + + /// + /// The largest absolute value norm. + /// + LargestAbsoluteValue = (byte)'m' +} + +/// +/// Interface to linear algebra algorithms that work off 1-D arrays. +/// +public interface ILinearAlgebraProvider : + ILinearAlgebraProvider, + ILinearAlgebraProvider, + ILinearAlgebraProvider, + ILinearAlgebraProvider +{ + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + bool IsAvailable(); + + /// + /// Initialize and verify that the provided is indeed available. If not, fall back to alternatives like the managed provider + /// + void InitializeVerify(); + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + void FreeResources(); +} + +/// +/// Interface to linear algebra algorithms that work off 1-D arrays. +/// +/// Supported data types are Double, Single, Complex, and Complex32. +public interface ILinearAlgebraProvider + where T : struct +{ + /*/// + /// Queries the provider for the optimal, workspace block size + /// for the given routine. + /// + /// Name of the method to query. + /// -1 if the provider cannot compute the workspace size; otherwise + /// the suggested block size. + int QueryWorkspaceBlockSize(string methodName);*/ + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + void AddVectorToScaledVector(T[] y, T alpha, T[] x, T[] result); + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + void ScaleArray(T alpha, T[] x, T[] result); + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + void ConjugateArray(T[] x, T[] result); + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + T DotProduct(T[] x, T[] y); + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + void AddArrays(T[] x, T[] y, T[] result); + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + void SubtractArrays(T[] x, T[] y, T[] result); + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiply elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + void PointWiseMultiplyArrays(T[] x, T[] y, T[] result); + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + void PointWiseDivideArrays(T[] x, T[] y, T[] result); + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + void PointWisePowerArrays(T[] x, T[] y, T[] result); + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + double MatrixNorm(Norm norm, int rows, int columns, T[] matrix); + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + void MatrixMultiply(T[] x, int rowsX, int columnsX, T[] y, int rowsY, int columnsY, T[] result); + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, T alpha, T[] a, int rowsA, int columnsA, T[] b, int rowsB, int columnsB, T beta, T[] c); + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + void LUFactor(T[] data, int order, int[] ipiv); + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + void LUInverse(T[] a, int order); + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + void LUInverseFactored(T[] a, int order, int[] ipiv); + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + void LUSolve(int columnsOfB, T[] a, int order, T[] b); + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + void LUSolveFactored(int columnsOfB, T[] a, int order, int[] ipiv, T[] b); + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + void CholeskyFactor(T[] a, int order); + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + void CholeskySolve(T[] a, int orderA, T[] b, int columnsB); + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + void CholeskySolveFactored(T[] a, int orderA, T[] b, int columnsB); + + /// + /// Computes the full QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + void QRFactor(T[] a, int rowsA, int columnsA, T[] q, T[] tau); + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + void ThinQRFactor(T[] a, int rowsA, int columnsA, T[] r, T[] tau); + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + void QRSolve(T[] a, int rows, int columns, T[] b, int columnsB, T[] x, QRMethod method = QRMethod.Full); + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by QR factor. This is only used for the managed provider and can be + /// null for the native provider. The native provider uses the Q portion stored in the R matrix. + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// Rows must be greater or equal to columns. + /// The type of QR factorization to perform. + void QRSolveFactored(T[] q, T[] r, int rowsA, int columnsA, T[] tau, T[] b, int columnsB, T[] x, QRMethod method = QRMethod.Full); + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + void SingularValueDecomposition(bool computeVectors, T[] a, int rowsA, int columnsA, T[] s, T[] u, T[] vt); + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + void SvdSolve(T[] a, int rowsA, int columnsA, T[] b, int columnsB, T[] x); + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix + /// The number of columns of B. + /// On exit, the solution matrix. + void SvdSolveFactored(int rowsA, int columnsA, T[] s, T[] u, T[] vt, T[] b, int columnsB, T[] x); + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + void EigenDecomp(bool isSymmetric, int order, T[] matrix, T[] matrixEv, Complex[] vectorEv, T[] matrixD); +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/LinearAlgebraControl.cs b/MathNet.Numerics/Providers/LinearAlgebra/LinearAlgebraControl.cs new file mode 100644 index 0000000..a6bf888 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/LinearAlgebraControl.cs @@ -0,0 +1,253 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Providers.LinearAlgebra; + +public static class LinearAlgebraControl +{ + const string EnvVarLAProvider = "MathNetNumericsLAProvider"; + const string EnvVarLAProviderPath = "MathNetNumericsLAProviderPath"; + + static ILinearAlgebraProvider _linearAlgebraProvider; + static readonly object StaticLock = new object(); + + /// + /// Gets or sets the linear algebra provider. + /// Consider to use UseNativeMKL or UseManaged instead. + /// + /// The linear algebra provider. + public static ILinearAlgebraProvider Provider + { + get + { + if (_linearAlgebraProvider == null) + { + lock (StaticLock) + { + if (_linearAlgebraProvider == null) + { + UseDefault(); + } + } + } + + return _linearAlgebraProvider; + } + set + { + value.InitializeVerify(); + + // only actually set if verification did not throw + _linearAlgebraProvider = value; + } + } + + /// + /// Optional path to try to load native provider binaries from. + /// If not set, Numerics will fall back to the environment variable + /// `MathNetNumericsLAProviderPath` or the default probing paths. + /// + public static string HintPath { get; set; } + + public static ILinearAlgebraProvider CreateManaged() + { + return new Managed.ManagedLinearAlgebraProvider(); + } + + public static void UseManaged() + { + Provider = CreateManaged(); + } + + internal static ILinearAlgebraProvider CreateManagedReference() + { + return new ManagedReference.ManagedReferenceLinearAlgebraProvider(); + } + + internal static void UseManagedReference() + { + Provider = CreateManagedReference(); + } + +#if NATIVE + [CLSCompliant(false)] + public static ILinearAlgebraProvider CreateNativeMKL( + Common.Mkl.MklConsistency consistency = Common.Mkl.MklConsistency.Auto, + Common.Mkl.MklPrecision precision = Common.Mkl.MklPrecision.Double, + Common.Mkl.MklAccuracy accuracy = Common.Mkl.MklAccuracy.High) + { + return new Mkl.MklLinearAlgebraProvider(GetCombinedHintPath(), consistency, precision, accuracy); + } + + [CLSCompliant(false)] + public static void UseNativeMKL( + Common.Mkl.MklConsistency consistency = Common.Mkl.MklConsistency.Auto, + Common.Mkl.MklPrecision precision = Common.Mkl.MklPrecision.Double, + Common.Mkl.MklAccuracy accuracy = Common.Mkl.MklAccuracy.High) + { + Provider = CreateNativeMKL(consistency, precision, accuracy); + } + + [CLSCompliant(false)] + public static bool TryUseNativeMKL( + Common.Mkl.MklConsistency consistency = Common.Mkl.MklConsistency.Auto, + Common.Mkl.MklPrecision precision = Common.Mkl.MklPrecision.Double, + Common.Mkl.MklAccuracy accuracy = Common.Mkl.MklAccuracy.High) + { + return TryUse(CreateNativeMKL(consistency, precision, accuracy)); + } + + public static ILinearAlgebraProvider CreateNativeCUDA() + { + return new Cuda.CudaLinearAlgebraProvider(GetCombinedHintPath()); + } + + public static void UseNativeCUDA() + { + Provider = CreateNativeCUDA(); + } + + public static bool TryUseNativeCUDA() + { + return TryUse(CreateNativeCUDA()); + } + + public static ILinearAlgebraProvider CreateNativeOpenBLAS() + { + return new OpenBlas.OpenBlasLinearAlgebraProvider(GetCombinedHintPath()); + } + + public static void UseNativeOpenBLAS() + { + Provider = CreateNativeOpenBLAS(); + } + + public static bool TryUseNativeOpenBLAS() + { + return TryUse(CreateNativeOpenBLAS()); + } + + /// + /// Try to use a native provider, if available. + /// + public static bool TryUseNative() + { + return TryUseNativeCUDA() || TryUseNativeMKL() || TryUseNativeOpenBLAS(); + } +#endif + + static bool TryUse(ILinearAlgebraProvider provider) + { + try + { + if (!provider.IsAvailable()) + { + return false; + } + + Provider = provider; + return true; + } + catch + { + // intentionally swallow exceptions here - use the explicit variants if you're interested in why + return false; + } + } + + /// + /// Use the best provider available. + /// + public static void UseBest() + { +#if NATIVE + if (!TryUseNative()) + { + UseManaged(); + } +#else + UseManaged(); +#endif + } + + /// + /// Use a specific provider if configured, e.g. using the + /// "MathNetNumericsLAProvider" environment variable, + /// or fall back to the best provider. + /// + public static void UseDefault() + { +#if NATIVE + var value = Environment.GetEnvironmentVariable(EnvVarLAProvider); + switch (value != null ? value.ToUpperInvariant() : string.Empty) + { + case "MKL": + UseNativeMKL(); + break; + + case "CUDA": + UseNativeCUDA(); + break; + + case "OPENBLAS": + UseNativeOpenBLAS(); + break; + + default: + UseBest(); + break; + } +#else + UseBest(); +#endif + } + + public static void FreeResources() + { + Provider.FreeResources(); + } + + static string GetCombinedHintPath() + { + if (!string.IsNullOrEmpty(HintPath)) + { + return HintPath; + } + + var value = Environment.GetEnvironmentVariable(EnvVarLAProviderPath); + if (!string.IsNullOrEmpty(value)) + { + return value; + } + + return null; + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Complex.cs b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Complex.cs new file mode 100644 index 0000000..bf8b8dc --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Complex.cs @@ -0,0 +1,2567 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Complex.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Managed; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedLinearAlgebraProvider +{ + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + public virtual void AddVectorToScaledVector(Complex[] y, Complex alpha, Complex[] x, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (alpha.IsZero()) + { + y.Copy(result); + } + else if (alpha.IsOne()) + { + for (int i = 0; i < result.Length; i++) + { + result[i] = y[i] + x[i]; + } + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = y[i] + (alpha * x[i]); + } + } + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + public virtual void ScaleArray(Complex alpha, Complex[] x, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (alpha.IsZero()) + { + Array.Clear(result, 0, result.Length); + } + else if (alpha.IsOne()) + { + x.Copy(result); + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = alpha * x[i]; + } + } + } + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + public virtual void ConjugateArray(Complex[] x, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i].Conjugate(); + } + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + public virtual Complex DotProduct(Complex[] x, Complex[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Complex dot = Complex.Zero; + for (var index = 0; index < y.Length; index++) + { + dot += y[index] * x[index]; + } + + return dot; + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void AddArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] + y[i]; + } + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void SubtractArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] - y[i]; + } + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseMultiplyArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] * y[i]; + } + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseDivideArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] / y[i]; + } + }); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWisePowerArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = Complex.Pow(x[i], y[i]); + } + }); + } + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + public virtual double MatrixNorm(Norm norm, int rows, int columns, Complex[] matrix) + { + switch (norm) + { + case Norm.OneNorm: + var norm1 = 0d; + for (var j = 0; j < columns; j++) + { + var s = 0.0; + for (var i = 0; i < rows; i++) + { + s += matrix[(j * rows) + i].Magnitude; + } + + norm1 = Math.Max(norm1, s); + } + + return norm1; + case Norm.LargestAbsoluteValue: + var normMax = 0d; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + normMax = Math.Max(matrix[(j * rows) + i].Magnitude, normMax); + } + } + + return normMax; + case Norm.InfinityNorm: + var r = new double[rows]; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + r[i] += matrix[(j * rows) + i].Magnitude; + } + } + // TODO: reuse + var max = r[0]; + for (int i = 0; i < r.Length; i++) + { + if (r[i] > max) + { + max = r[i]; + } + } + + return max; + case Norm.FrobeniusNorm: + var aat = new Complex[rows * rows]; + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.ConjugateTranspose, 1.0, matrix, rows, columns, matrix, rows, columns, 0.0, aat); + var normF = 0d; + for (var i = 0; i < rows; i++) + { + normF += aat[(i * rows) + i].Magnitude; + } + + return Math.Sqrt(normF); + default: + throw new NotSupportedException(); + } + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public virtual void MatrixMultiply(Complex[] x, int rowsX, int columnsX, Complex[] y, int rowsY, int columnsY, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (columnsX != rowsY) + { + throw new ArgumentOutOfRangeException(string.Format("columnsA ({0}) != rowsB ({1})", columnsX, rowsY)); + } + + if (rowsX * columnsX != x.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsA ({1}) != a.Length ({2})", rowsX, columnsX, x.Length)); + } + + if (rowsY * columnsY != y.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsB ({0}) * columnsB ({1}) != b.Length ({2})", rowsY, columnsY, y.Length)); + } + + if (rowsX * columnsY != result.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsB ({1}) != c.Length ({2})", rowsX, columnsY, result.Length)); + } + + // handle degenerate cases + Array.Clear(result, 0, result.Length); + + // Extract column arrays + var columnDataB = new Complex[columnsY][]; + for (int i = 0; i < columnDataB.Length; i++) + { + var column = new Complex[rowsY]; + GetColumn(Transpose.DontTranspose, i, rowsY, columnsY, y, column); + columnDataB[i] = column; + } + + var shouldNotParallelize = rowsX + columnsY + columnsX < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; + if (shouldNotParallelize) + { + var row = new Complex[columnsX]; + for (int i = 0; i < rowsX; i++) + { + GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); + for (int j = 0; j < columnsY; j++) + { + var col = columnDataB[j]; + Complex sum = Complex.Zero; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * col[ii]; + } + + result[j * rowsX + i] += Complex.One * sum; + } + } + } + else + { + CommonParallel.For(0, rowsX, 1, (u, v) => + { + var row = new Complex[columnsX]; + for (int i = u; i < v; i++) + { + GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); + for (int j = 0; j < columnsY; j++) + { + var column = columnDataB[j]; + Complex sum = Complex.Zero; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * column[ii]; + } + + result[j * rowsX + i] += Complex.One * sum; + } + } + }); + } + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex alpha, Complex[] a, int rowsA, int columnsA, Complex[] b, int rowsB, int columnsB, Complex beta, Complex[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + if (transposeA != Transpose.DontTranspose) + { + var swap = rowsA; + rowsA = columnsA; + columnsA = swap; + } + + if (transposeB != Transpose.DontTranspose) + { + var swap = rowsB; + rowsB = columnsB; + columnsB = swap; + } + + if (columnsA != rowsB) + { + throw new ArgumentOutOfRangeException(string.Format("columnsA ({0}) != rowsB ({1})", columnsA, rowsB)); + } + + if (rowsA * columnsA != a.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsA ({1}) != a.Length ({2})", rowsA, columnsA, a.Length)); + } + + if (rowsB * columnsB != b.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsB ({0}) * columnsB ({1}) != b.Length ({2})", rowsB, columnsB, b.Length)); + } + + if (rowsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsB ({1}) != c.Length ({2})", rowsA, columnsB, c.Length)); + } + + // handle degenerate cases + if (beta == Complex.Zero) + { + Array.Clear(c, 0, c.Length); + } + else if (beta != Complex.One) + { + ScaleArray(beta, c, c); + } + + if (alpha == Complex.Zero) + { + return; + } + + // Extract column arrays + var columnDataB = new Complex[columnsB][]; + for (int i = 0; i < columnDataB.Length; i++) + { + var column = new Complex[rowsB]; + GetColumn(transposeB, i, rowsB, columnsB, b, column); + columnDataB[i] = column; + } + + var shouldNotParallelize = rowsA + columnsB + columnsA < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; + if (shouldNotParallelize) + { + var row = new Complex[columnsA]; + for (int i = 0; i < rowsA; i++) + { + GetRow(transposeA, i, rowsA, columnsA, a, row); + for (int j = 0; j < columnsB; j++) + { + var col = columnDataB[j]; + Complex sum = Complex.Zero; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * col[ii]; + } + + c[j * rowsA + i] += alpha * sum; + } + } + } + else + { + CommonParallel.For(0, rowsA, 1, (u, v) => + { + var row = new Complex[columnsA]; + for (int i = u; i < v; i++) + { + GetRow(transposeA, i, rowsA, columnsA, a, row); + for (int j = 0; j < columnsB; j++) + { + var column = columnDataB[j]; + Complex sum = Complex.Zero; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * column[ii]; + } + + c[j * rowsA + i] += alpha * sum; + } + } + }); + } + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + public virtual void LUFactor(Complex[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + ipiv[i] = i; + } + + var vecLUcolj = new Complex[order]; + + // Outer loop. + for (var j = 0; j < order; j++) + { + var indexj = j * order; + var indexjj = indexj + j; + + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vecLUcolj[i] = data[indexj + i]; + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + // Most of the time is spent in the following dot product. + var kmax = Math.Min(i, j); + var s = Complex.Zero; + for (var k = 0; k < kmax; k++) + { + s += data[(k * order) + i] * vecLUcolj[k]; + } + + data[indexj + i] = vecLUcolj[i] -= s; + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (vecLUcolj[i].Magnitude > vecLUcolj[p].Magnitude) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var indexk = k * order; + var indexkp = indexk + p; + var indexkj = indexk + j; + var temp = data[indexkp]; + data[indexkp] = data[indexkj]; + data[indexkj] = temp; + } + + ipiv[j] = p; + } + + // Compute multipliers. + if (j < order & data[indexjj] != 0.0) + { + for (var i = j + 1; i < order; i++) + { + data[indexj + i] /= data[indexjj]; + } + } + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + public virtual void LUInverse(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var ipiv = new int[order]; + LUFactor(a, order, ipiv); + LUInverseFactored(a, order, ipiv); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + public virtual void LUInverseFactored(Complex[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var inverse = new Complex[a.Length]; + for (var i = 0; i < order; i++) + { + inverse[i + (order * i)] = Complex.One; + } + + LUSolveFactored(order, a, order, ipiv, inverse); + inverse.Copy(a); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + public virtual void LUSolve(int columnsOfB, Complex[] a, int order, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var ipiv = new int[order]; + var clone = new Complex[a.Length]; + a.Copy(clone); + LUFactor(clone, order, ipiv); + LUSolveFactored(columnsOfB, clone, order, ipiv, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + public virtual void LUSolveFactored(int columnsOfB, Complex[] a, int order, int[] ipiv, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + // Compute the column vector P*B + for (var i = 0; i < ipiv.Length; i++) + { + if (ipiv[i] == i) + { + continue; + } + + var p = ipiv[i]; + for (var j = 0; j < columnsOfB; j++) + { + var indexk = j * order; + var indexkp = indexk + p; + var indexkj = indexk + i; + var temp = b[indexkp]; + b[indexkp] = b[indexkj]; + b[indexkj] = temp; + } + } + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + var korder = k * order; + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + var korder = k + (k * order); + for (var j = 0; j < columnsOfB; j++) + { + b[k + (j * order)] /= a[korder]; + } + + korder = k * order; + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + public virtual void CholeskyFactor(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + var tmpColumn = new Complex[order]; + + // Main loop - along the diagonal + for (var ij = 0; ij < order; ij++) + { + // "Pivot" element + var tmpVal = a[(ij * order) + ij]; + + if (tmpVal.Real > 0.0) + { + tmpVal = tmpVal.SquareRoot(); + a[(ij * order) + ij] = tmpVal; + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < order; i++) + { + a[(ij * order) + i] /= tmpVal; + tmpColumn[i] = a[(ij * order) + i]; + } + + // Remaining columns, below the diagonal + DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (var i = ij + 1; i < order; i++) + { + a[(i * order) + ij] = 0.0; + } + } + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(Complex[] data, int rowDim, int firstCol, int colLimit, Complex[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data[(j * rowDim) + i] -= multipliers[i] * tmpVal.Conjugate(); + } + } + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + public virtual void CholeskySolve(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var clone = new Complex[a.Length]; + a.Copy(clone); + CholeskyFactor(clone, orderA); + CholeskySolveFactored(clone, orderA, b, columnsB); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// The B matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + public virtual void CholeskySolveFactored(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + CommonParallel.For(0, columnsB, (u, v) => + { + for (int i = u; i < v; i++) + { + DoCholeskySolve(a, orderA, b, i); + } + }); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The column to solve for. + static void DoCholeskySolve(Complex[] a, int orderA, Complex[] b, int index) + { + var cindex = index * orderA; + + // Solve L*Y = B; + Complex sum; + for (var i = 0; i < orderA; i++) + { + sum = b[cindex + i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= a[(k * orderA) + i] * b[cindex + k]; + } + + b[cindex + i] = sum / a[(i * orderA) + i]; + } + + // Solve L'*X = Y; + for (var i = orderA - 1; i >= 0; i--) + { + sum = b[cindex + i]; + var iindex = i * orderA; + for (var k = i + 1; k < orderA; k++) + { + sum -= a[iindex + k].Conjugate() * b[cindex + k]; + } + + b[cindex + i] = sum / a[iindex + i]; + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void QRFactor(Complex[] r, int rowsR, int columnsR, Complex[] q, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR * rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var work = columnsR > rowsR ? new Complex[rowsR * rowsR] : new Complex[rowsR * columnsR]; + + CommonParallel.For(0, rowsR, (a, b) => + { + for (int i = a; i < b; i++) + { + q[(i * rowsR) + i] = Complex.One; + } + }); + + var minmn = Math.Min(rowsR, columnsR); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, r, rowsR, i, i); + ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void ThinQRFactor(Complex[] a, int rowsA, int columnsA, Complex[] r, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(a)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var work = new Complex[rowsA * columnsA]; + + var minmn = Math.Min(rowsA, columnsA); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, a, rowsA, i, i); + ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); + } + + //copy R + for (var j = 0; j < columnsA; j++) + { + var rIndex = j * columnsA; + var aIndex = j * rowsA; + for (var i = 0; i < columnsA; i++) + { + r[rIndex + i] = a[aIndex + i]; + } + } + + //clear A and set diagonals to 1 + Array.Clear(a, 0, a.Length); + for (var i = 0; i < columnsA; i++) + { + a[i * rowsA + i] = Complex.One; + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); + } + } + + #region QR Factor Helper functions + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Index of column in work array + /// Q or R matrices + /// The first row in + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(Complex[] work, int workIndex, Complex[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) + { + if (rowStart > rowCount || columnStart > columnCount) + { + return; + } + + var tmpColCount = columnCount - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), + () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); + } + else + { + for (var j = columnStart; j < columnCount; j++) + { + var scale = Complex.Zero; + for (var i = rowStart; i < rowCount; i++) + { + scale += work[(workIndex * rowCount) + i - rowStart] * a[(j * rowCount) + i]; + } + + for (var i = rowStart; i < rowCount; i++) + { + a[(j * rowCount) + i] -= work[(workIndex * rowCount) + i - rowStart].Conjugate() * scale; + } + } + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Work array + /// Initial matrix + /// The number of rows in matrix + /// The first row + /// Column index + static void GenerateColumn(Complex[] work, Complex[] a, int rowCount, int row, int column) + { + var tmp = column * rowCount; + var index = tmp + row; + + CommonParallel.For(row, rowCount, (u, v) => + { + for (int i = u; i < v; i++) + { + var iIndex = tmp + i; + work[iIndex - row] = a[iIndex]; + a[iIndex] = Complex.Zero; + } + }); + + var norm = Complex.Zero; + for (var i = 0; i < rowCount - row; ++i) + { + var index1 = tmp + i; + norm += work[index1].Magnitude * work[index1].Magnitude; + } + + norm = norm.SquareRoot(); + if (row == rowCount - 1 || norm.Magnitude == 0) + { + a[index] = -work[tmp]; + work[tmp] = new Complex(2.0, 0).SquareRoot(); + return; + } + + if (work[tmp].Magnitude != 0.0) + { + norm = norm.Magnitude * (work[tmp] / work[tmp].Magnitude); + } + + a[index] = -norm; + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] /= norm; + } + }); + work[tmp] += 1.0; + + var s = (1.0 / work[tmp]).SquareRoot(); + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] = work[tmp + i].Conjugate() * s; + } + }); + } + + #endregion + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolve(Complex[] a, int rows, int columns, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + var work = new Complex[rows * columns]; + + var clone = new Complex[a.Length]; + a.Copy(clone); + + if (method == QRMethod.Full) + { + var q = new Complex[rows * rows]; + QRFactor(clone, rows, columns, q, work); + QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); + } + else + { + var r = new Complex[columns * columns]; + ThinQRFactor(clone, rows, columns, r, work); + QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolveFactored(Complex[] q, Complex[] r, int rowsA, int columnsA, Complex[] tau, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (rowsA < columnsA) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + var sol = new Complex[b.Length]; + + // Copy B matrix to "sol", so B data will not be changed + Array.Copy(b, 0, sol, 0, b.Length); + + // Compute Y = transpose(Q)*B + var column = new Complex[rowsA]; + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + Array.Copy(sol, jm, column, 0, rowsA); + CommonParallel.For(0, columnsA, (u, v) => + { + for (int i = u; i < v; i++) + { + var im = i * rowsA; + + var sum = Complex.Zero; + for (var k = 0; k < rowsA; k++) + { + sum += q[im + k].Conjugate() * column[k]; + } + + sol[jm + i] = sum; + } + }); + } + + // Solve R*X = Y; + for (var k = columnsA - 1; k >= 0; k--) + { + var km = k * rowsR; + for (var j = 0; j < columnsB; j++) + { + sol[(j * rowsA) + k] /= r[km + k]; + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + sol[jm + i] -= sol[jm + k] * r[km + i]; + } + } + } + + // Fill result matrix + for (var col = 0; col < columnsB; col++) + { + Array.Copy(sol, col * rowsA, x, col * columnsA, columnsR); + } + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + public virtual void SingularValueDecomposition(bool computeVectors, Complex[] a, int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new Complex[rowsA]; + + const int maxiter = 1000; + + var e = new Complex[columnsA]; + var v = new Complex[vt.Length]; + var stemp = new Complex[Math.Min(rowsA + 1, columnsA)]; + + int i, j, l, lp1; + + Complex t; + + var ncu = rowsA; + + // Reduce matrix to bidiagonal form, storing the diagonal elements + // in "s" and the super-diagonal elements in "e". + var nct = Math.Min(rowsA - 1, columnsA); + var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); + var lu = Math.Max(nct, nrt); + + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and + // place the l-th diagonal in vector s[l]. + var sum = 0.0; + for (i = l; i < rowsA; i++) + { + sum += a[(l * rowsA) + i].Magnitude * a[(l * rowsA) + i].Magnitude; + } + + stemp[l] = Math.Sqrt(sum); + if (stemp[l] != 0.0) + { + if (a[(l * rowsA) + l] != 0.0) + { + stemp[l] = stemp[l].Magnitude * (a[(l * rowsA) + l] / a[(l * rowsA) + l].Magnitude); + } + + // A part of column "l" of Matrix A from row "l" to end multiply by 1.0 / s[l] + for (i = l; i < rowsA; i++) + { + a[(l * rowsA) + i] = a[(l * rowsA) + i] * (1.0 / stemp[l]); + } + + a[(l * rowsA) + l] = 1.0 + a[(l * rowsA) + l]; + } + + stemp[l] = -stemp[l]; + } + + for (j = lp1; j < columnsA; j++) + { + if (l < nct) + { + if (stemp[l] != 0.0) + { + // Apply the transformation. + t = 0.0; + for (i = l; i < rowsA; i++) + { + t += a[(l * rowsA) + i].Conjugate() * a[(j * rowsA) + i]; + } + + t = -t / a[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += t * a[(l * rowsA) + ii]; + } + } + } + + // Place the l-th row of matrix into "e" for the + // subsequent calculation of the row transformation. + e[j] = a[(j * rowsA) + l].Conjugate(); + } + + if (computeVectors && l < nct) + { + // Place the transformation in "u" for subsequent back multiplication. + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = a[(l * rowsA) + i]; + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = 0.0; + for (i = lp1; i < e.Length; i++) + { + enorm += e[i].Magnitude * e[i].Magnitude; + } + + e[l] = Math.Sqrt(enorm); + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = e[l].Magnitude * (e[lp1] / e[lp1].Magnitude); + } + + // Scale vector "e" from "lp1" by 1.0 / e[l] + for (i = lp1; i < e.Length; i++) + { + e[i] = e[i] * (1.0 / e[l]); + } + + e[lp1] = 1.0 + e[lp1]; + } + + e[l] = -e[l].Conjugate(); + + if (lp1 < rowsA && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < rowsA; i++) + { + work[i] = 0.0; + } + + for (j = lp1; j < columnsA; j++) + { + for (var ii = lp1; ii < rowsA; ii++) + { + work[ii] += e[j] * a[(j * rowsA) + ii]; + } + } + + for (j = lp1; j < columnsA; j++) + { + var ww = (-e[j] / e[lp1]).Conjugate(); + for (var ii = lp1; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += ww * work[ii]; + } + } + } + + if (!computeVectors) + { + continue; + } + + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < columnsA; i++) + { + v[(l * columnsA) + i] = e[i]; + } + } + + // Set up the final bidiagonal matrix or order m. + var m = Math.Min(columnsA, rowsA + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < columnsA) + { + stemp[nctp1 - 1] = a[((nctp1 - 1) * rowsA) + (nctp1 - 1)]; + } + + if (rowsA < m) + { + stemp[m - 1] = 0.0; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = a[((m - 1) * rowsA) + (nrtp1 - 1)]; + } + + e[m - 1] = 0.0; + + // If required, generate "u". + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < rowsA; i++) + { + u[(j * rowsA) + i] = 0.0; + } + + u[(j * rowsA) + j] = 1.0; + } + + for (l = nct - 1; l >= 0; l--) + { + if (stemp[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = 0.0; + for (i = l; i < rowsA; i++) + { + t += u[(l * rowsA) + i].Conjugate() * u[(j * rowsA) + i]; + } + + t = -t / u[(l * rowsA) + l]; + for (var ii = l; ii < rowsA; ii++) + { + u[(j * rowsA) + ii] += t * u[(l * rowsA) + ii]; + } + } + + // A part of column "l" of matrix A from row "l" to end multiply by -1.0 + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = u[(l * rowsA) + i] * -1.0; + } + + u[(l * rowsA) + l] = 1.0 + u[(l * rowsA) + l]; + for (i = 0; i < l; i++) + { + u[(l * rowsA) + i] = 0.0; + } + } + else + { + for (i = 0; i < rowsA; i++) + { + u[(l * rowsA) + i] = 0.0; + } + + u[(l * rowsA) + l] = 1.0; + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = columnsA - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < columnsA; j++) + { + t = 0.0; + for (i = lp1; i < columnsA; i++) + { + t += v[(l * columnsA) + i].Conjugate() * v[(j * columnsA) + i]; + } + + t = -t / v[(l * columnsA) + lp1]; + for (var ii = l; ii < columnsA; ii++) + { + v[(j * columnsA) + ii] += t * v[(l * columnsA) + ii]; + } + } + } + } + + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = 0.0; + } + + v[(l * columnsA) + l] = 1.0; + } + } + + // Transform "s" and "e" so that they are double + for (i = 0; i < m; i++) + { + Complex r; + if (stemp[i] != 0.0) + { + t = stemp[i].Magnitude; + r = stemp[i] / t; + stemp[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + // A part of column "i" of matrix U from row 0 to end multiply by r + for (j = 0; j < rowsA; j++) + { + u[(i * rowsA) + j] = u[(i * rowsA) + j] * r; + } + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] == 0.0) + { + continue; + } + + t = e[i].Magnitude; + r = t / e[i]; + e[i] = t; + stemp[i + 1] = stemp[i + 1] * r; + if (!computeVectors) + { + continue; + } + + // A part of column "i+1" of matrix VT from row 0 to end multiply by r + for (j = 0; j < columnsA; j++) + { + v[((i + 1) * columnsA) + j] = v[((i + 1) * columnsA) + j] * r; + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. + // If too many iterations have been performed throw exception. + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays, + // on completion the variables case and l are set as follows: + // case = 1: if mS[m] and e[l-1] are negligible and l < m + // case = 2: if mS[l] is negligible and l < m + // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). + // case = 4: if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = stemp[l].Magnitude + stemp[l + 1].Magnitude; + ztest = test + e[l].Magnitude; + if (ztest.AlmostEqualRelative(test, 15)) + { + e[l] = 0.0; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + e[ls].Magnitude; + } + + if (ls != l + 1) + { + test = test + e[ls - 1].Magnitude; + } + + ztest = test + stemp[ls].Magnitude; + if (ztest.AlmostEqualRelative(test, 15)) + { + stemp[ls] = 0.0; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + double f; + double cs; + double sn; + switch (kase) + { + // Deflate negligible s[m]. + case 1: + f = e[m - 2].Real; + e[m - 2] = 0.0; + double t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = stemp[k].Real; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + if (k != l) + { + f = -sn * e[k - 1].Real; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + // Rotate + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((m - 1) * columnsA) + i]); + v[((m - 1) * columnsA) + i] = (cs * v[((m - 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + } + + break; + + // Split at negligible s[l]. + case 2: + f = e[l - 1].Real; + e[l - 1] = 0.0; + for (k = l; k < m; k++) + { + t1 = stemp[k].Real; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + f = -sn * e[k].Real; + e[k] = cs * e[k]; + if (computeVectors) + { + // Rotate + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((l - 1) * rowsA) + i]); + u[((l - 1) * rowsA) + i] = (cs * u[((l - 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + break; + + // Perform one qr step. + case 3: + // calculate the shift. + var scale = 0.0; + scale = Math.Max(scale, stemp[m - 1].Magnitude); + scale = Math.Max(scale, stemp[m - 2].Magnitude); + scale = Math.Max(scale, e[m - 2].Magnitude); + scale = Math.Max(scale, stemp[l].Magnitude); + scale = Math.Max(scale, e[l].Magnitude); + var sm = stemp[m - 1].Real / scale; + var smm1 = stemp[m - 2].Real / scale; + var emm1 = e[m - 2].Real / scale; + var sl = stemp[l].Real / scale; + var el = e[l].Real / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0; + if (b != 0.0 || c != 0.0) + { + shift = Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * stemp[k].Real) + (sn * e[k].Real); + e[k] = (cs * e[k]) - (sn * stemp[k]); + g = sn * stemp[k + 1].Real; + stemp[k + 1] = cs * stemp[k + 1]; + if (computeVectors) + { + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((k + 1) * columnsA) + i]); + v[((k + 1) * columnsA) + i] = (cs * v[((k + 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + + Drotg(ref f, ref g, out cs, out sn); + stemp[k] = f; + f = (cs * e[k].Real) + (sn * stemp[k + 1].Real); + stemp[k + 1] = -(sn * e[k]) + (cs * stemp[k + 1]); + g = sn * e[k + 1].Real; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < rowsA) + { + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((k + 1) * rowsA) + i]); + u[((k + 1) * rowsA) + i] = (cs * u[((k + 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence + case 4: + + // Make the singular value positive + if (stemp[l].Real < 0.0) + { + stemp[l] = -stemp[l]; + if (computeVectors) + { + // A part of column "l" of matrix VT from row 0 to end multiply by -1 + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = v[(l * columnsA) + i] * -1.0; + } + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (stemp[l].Real >= stemp[l + 1].Real) + { + break; + } + + t = stemp[l]; + stemp[l] = stemp[l + 1]; + stemp[l + 1] = t; + if (computeVectors && l < columnsA) + { + // Swap columns l, l + 1 + for (i = 0; i < columnsA; i++) + { + var z = v[(l * columnsA) + i]; + v[(l * columnsA) + i] = v[((l + 1) * columnsA) + i]; + v[((l + 1) * columnsA) + i] = z; + } + } + + if (computeVectors && l < rowsA) + { + // Swap columns l, l + 1 + for (i = 0; i < rowsA; i++) + { + var z = u[(l * rowsA) + i]; + u[(l * rowsA) + i] = u[((l + 1) * rowsA) + i]; + u[((l + 1) * rowsA) + i] = z; + } + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + // Finally transpose "v" to get "vt" matrix + for (i = 0; i < columnsA; i++) + { + for (j = 0; j < columnsA; j++) + { + vt[(j * columnsA) + i] = v[(i * columnsA) + j].Conjugate(); + } + } + } + + // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses + // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. + // We should port lapack's svd routine to remove this problem. + Array.Copy(stemp, 0, s, 0, Math.Min(rowsA, columnsA)); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolve(Complex[] a, int rowsA, int columnsA, Complex[] b, int columnsB, Complex[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex[Math.Min(rowsA, columnsA)]; + var u = new Complex[rowsA * rowsA]; + var vt = new Complex[columnsA * columnsA]; + + var clone = new Complex[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolveFactored(int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt, Complex[] b, int columnsB, Complex[] x) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var mn = Math.Min(rowsA, columnsA); + var tmp = new Complex[columnsA]; + + for (var k = 0; k < columnsB; k++) + { + for (var j = 0; j < columnsA; j++) + { + var value = Complex.Zero; + if (j < mn) + { + for (var i = 0; i < rowsA; i++) + { + value += u[(j * rowsA) + i].Conjugate() * b[(k * rowsA) + i]; + } + + value /= s[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < columnsA; j++) + { + var value = Complex.Zero; + for (var i = 0; i < columnsA; i++) + { + value += vt[(j * columnsA) + i].Conjugate() * tmp[i]; + } + + x[(k * columnsA) + j] = value; + } + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public virtual void EigenDecomp(bool isSymmetric, int order, Complex[] matrix, Complex[] matrixEv, Complex[] vectorEv, Complex[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var matrixCopy = new Complex[matrix.Length]; + Array.Copy(matrix, 0, matrixCopy, 0, matrix.Length); + + if (isSymmetric) + { + var tau = new Complex[order]; + var d = new double[order]; + var e = new double[order]; + + DenseEvd.SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + DenseEvd.SymmetricDiagonalize(matrixEv, d, e, order); + DenseEvd.SymmetricUntridiagonalize(matrixEv, matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(d[i], e[i]); + } + } + else + { + DenseEvd.NonsymmetricReduceToHessenberg(matrixEv, matrixCopy, order); + DenseEvd.NonsymmetricReduceHessenberToRealSchur(vectorEv, matrixEv, matrixCopy, order); + } + + for (var i = 0; i < order; i++) + { + matrixD[i * order + i] = vectorEv[i]; + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetRow(Transpose transpose, int rowindx, int numRows, int numCols, Complex[] matrix, Complex[] row) + { + if (transpose == Transpose.DontTranspose) + { + for (int i = 0; i < numCols; i++) + { + row[i] = matrix[(i * numRows) + rowindx]; + } + } + else if (transpose == Transpose.ConjugateTranspose) + { + int offset = rowindx * numCols; + for (int i = 0; i < row.Length; i++) + { + row[i] = matrix[i + offset].Conjugate(); + } + } + else + { + Array.Copy(matrix, rowindx * numCols, row, 0, numCols); + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetColumn(Transpose transpose, int colindx, int numRows, int numCols, Complex[] matrix, Complex[] column) + { + if (transpose == Transpose.DontTranspose) + { + Array.Copy(matrix, colindx * numRows, column, 0, numRows); + } + else if (transpose == Transpose.ConjugateTranspose) + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx].Conjugate(); + } + } + else + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Complex32.cs b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Complex32.cs new file mode 100644 index 0000000..cc00b58 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Complex32.cs @@ -0,0 +1,2568 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Complex32; +using MathNet.Numerics.LinearAlgebra.Complex32.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Managed; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedLinearAlgebraProvider +{ + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + public virtual void AddVectorToScaledVector(Complex32[] y, Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (alpha.IsZero()) + { + y.Copy(result); + } + else if (alpha.IsOne()) + { + for (int i = 0; i < result.Length; i++) + { + result[i] = y[i] + x[i]; + } + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = y[i] + (alpha * x[i]); + } + } + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + public virtual void ScaleArray(Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (alpha.IsZero()) + { + Array.Clear(result, 0, result.Length); + } + else if (alpha.IsOne()) + { + x.Copy(result); + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = alpha * x[i]; + } + } + } + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + public virtual void ConjugateArray(Complex32[] x, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i].Conjugate(); + } + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + public virtual Complex32 DotProduct(Complex32[] x, Complex32[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + Complex32 d = new Complex32(0.0F, 0.0F); + for (var i = 0; i < y.Length; i++) + { + d += y[i] * x[i]; + } + + return d; + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void AddArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] + y[i]; + } + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void SubtractArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] - y[i]; + } + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseMultiplyArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] * y[i]; + } + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseDivideArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] / y[i]; + } + }); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWisePowerArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = Complex32.Pow(x[i], y[i]); + } + }); + } + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// The requested of the matrix. + public virtual double MatrixNorm(Norm norm, int rows, int columns, Complex32[] matrix) + { + switch (norm) + { + case Norm.OneNorm: + var norm1 = 0d; + for (var j = 0; j < columns; j++) + { + var s = 0d; + for (var i = 0; i < rows; i++) + { + s += matrix[(j * rows) + i].Magnitude; + } + + norm1 = Math.Max(norm1, s); + } + + return norm1; + case Norm.LargestAbsoluteValue: + var normMax = 0d; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + normMax = Math.Max(matrix[(j * rows) + i].Magnitude, normMax); + } + } + + return normMax; + case Norm.InfinityNorm: + var r = new double[rows]; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + r[i] += matrix[(j * rows) + i].Magnitude; + } + } + // TODO: reuse + var max = r[0]; + for (int i = 0; i < r.Length; i++) + { + if (r[i] > max) + { + max = r[i]; + } + } + + return max; + case Norm.FrobeniusNorm: + var aat = new Complex32[rows * rows]; + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.ConjugateTranspose, 1.0f, matrix, rows, columns, matrix, rows, columns, 0.0f, aat); + var normF = 0d; + for (var i = 0; i < rows; i++) + { + normF += aat[(i * rows) + i].Magnitude; + } + + return Math.Sqrt(normF); + default: + throw new NotSupportedException(); + } + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public virtual void MatrixMultiply(Complex32[] x, int rowsX, int columnsX, Complex32[] y, int rowsY, int columnsY, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (columnsX != rowsY) + { + throw new ArgumentOutOfRangeException(string.Format("columnsA ({0}) != rowsB ({1})", columnsX, rowsY)); + } + + if (rowsX * columnsX != x.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsA ({1}) != a.Length ({2})", rowsX, columnsX, x.Length)); + } + + if (rowsY * columnsY != y.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsB ({0}) * columnsB ({1}) != b.Length ({2})", rowsY, columnsY, y.Length)); + } + + if (rowsX * columnsY != result.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsB ({1}) != c.Length ({2})", rowsX, columnsY, result.Length)); + } + + // handle degenerate cases + Array.Clear(result, 0, result.Length); + + // Extract column arrays + var columnDataB = new Complex32[columnsY][]; + for (int i = 0; i < columnDataB.Length; i++) + { + var column = new Complex32[rowsY]; + GetColumn(Transpose.DontTranspose, i, rowsY, columnsY, y, column); + columnDataB[i] = column; + } + + var shouldNotParallelize = rowsX + columnsY + columnsX < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; + if (shouldNotParallelize) + { + var row = new Complex32[columnsX]; + for (int i = 0; i < rowsX; i++) + { + GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); + for (int j = 0; j < columnsY; j++) + { + var col = columnDataB[j]; + Complex32 sum = Complex32.Zero; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * col[ii]; + } + + result[j * rowsX + i] += Complex32.One * sum; + } + } + } + else + { + CommonParallel.For(0, rowsX, 1, (u, v) => + { + var row = new Complex32[columnsX]; + for (int i = u; i < v; i++) + { + GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); + for (int j = 0; j < columnsY; j++) + { + var column = columnDataB[j]; + Complex32 sum = Complex32.Zero; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * column[ii]; + } + + result[j * rowsX + i] += Complex32.One * sum; + } + } + }); + } + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex32 alpha, Complex32[] a, int rowsA, int columnsA, Complex32[] b, int rowsB, int columnsB, Complex32 beta, Complex32[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + if (transposeA != Transpose.DontTranspose) + { + var swap = rowsA; + rowsA = columnsA; + columnsA = swap; + } + + if (transposeB != Transpose.DontTranspose) + { + var swap = rowsB; + rowsB = columnsB; + columnsB = swap; + } + + if (columnsA != rowsB) + { + throw new ArgumentOutOfRangeException(string.Format("columnsA ({0}) != rowsB ({1})", columnsA, rowsB)); + } + + if (rowsA * columnsA != a.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsA ({1}) != a.Length ({2})", rowsA, columnsA, a.Length)); + } + + if (rowsB * columnsB != b.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsB ({0}) * columnsB ({1}) != b.Length ({2})", rowsB, columnsB, b.Length)); + } + + if (rowsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsB ({1}) != c.Length ({2})", rowsA, columnsB, c.Length)); + } + + // handle degenerate cases + if (beta == Complex32.Zero) + { + Array.Clear(c, 0, c.Length); + } + else if (beta != Complex32.One) + { + ScaleArray(beta, c, c); + } + + if (alpha == Complex32.Zero) + { + return; + } + + // Extract column arrays + var columnDataB = new Complex32[columnsB][]; + for (int i = 0; i < columnDataB.Length; i++) + { + var column = new Complex32[rowsB]; + GetColumn(transposeB, i, rowsB, columnsB, b, column); + columnDataB[i] = column; + } + + var shouldNotParallelize = rowsA + columnsB + columnsA < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; + if (shouldNotParallelize) + { + var row = new Complex32[columnsA]; + for (int i = 0; i < rowsA; i++) + { + GetRow(transposeA, i, rowsA, columnsA, a, row); + for (int j = 0; j < columnsB; j++) + { + var col = columnDataB[j]; + Complex32 sum = Complex32.Zero; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * col[ii]; + } + + c[j * rowsA + i] += alpha * sum; + } + } + } + else + { + CommonParallel.For(0, rowsA, 1, (u, v) => + { + var row = new Complex32[columnsA]; + for (int i = u; i < v; i++) + { + GetRow(transposeA, i, rowsA, columnsA, a, row); + for (int j = 0; j < columnsB; j++) + { + var column = columnDataB[j]; + Complex32 sum = Complex32.Zero; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * column[ii]; + } + + c[j * rowsA + i] += alpha * sum; + } + } + }); + } + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + public virtual void LUFactor(Complex32[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + ipiv[i] = i; + } + + var vecLUcolj = new Complex32[order]; + + // Outer loop. + for (var j = 0; j < order; j++) + { + var indexj = j * order; + var indexjj = indexj + j; + + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vecLUcolj[i] = data[indexj + i]; + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + // Most of the time is spent in the following dot product. + var kmax = Math.Min(i, j); + var s = Complex32.Zero; + for (var k = 0; k < kmax; k++) + { + s += data[(k * order) + i] * vecLUcolj[k]; + } + + data[indexj + i] = vecLUcolj[i] -= s; + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (vecLUcolj[i].Magnitude > vecLUcolj[p].Magnitude) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var indexk = k * order; + var indexkp = indexk + p; + var indexkj = indexk + j; + var temp = data[indexkp]; + data[indexkp] = data[indexkj]; + data[indexkj] = temp; + } + + ipiv[j] = p; + } + + // Compute multipliers. + if (j < order & data[indexjj] != 0.0f) + { + for (var i = j + 1; i < order; i++) + { + data[indexj + i] /= data[indexjj]; + } + } + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + public virtual void LUInverse(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var ipiv = new int[order]; + LUFactor(a, order, ipiv); + LUInverseFactored(a, order, ipiv); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + public virtual void LUInverseFactored(Complex32[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var inverse = new Complex32[a.Length]; + for (var i = 0; i < order; i++) + { + inverse[i + (order * i)] = Complex32.One; + } + + LUSolveFactored(order, a, order, ipiv, inverse); + inverse.Copy(a); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + public virtual void LUSolve(int columnsOfB, Complex32[] a, int order, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var ipiv = new int[order]; + var clone = new Complex32[a.Length]; + a.Copy(clone); + LUFactor(clone, order, ipiv); + LUSolveFactored(columnsOfB, clone, order, ipiv, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + public virtual void LUSolveFactored(int columnsOfB, Complex32[] a, int order, int[] ipiv, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + // Compute the column vector P*B + for (var i = 0; i < ipiv.Length; i++) + { + if (ipiv[i] == i) + { + continue; + } + + var p = ipiv[i]; + for (var j = 0; j < columnsOfB; j++) + { + var indexk = j * order; + var indexkp = indexk + p; + var indexkj = indexk + i; + var temp = b[indexkp]; + b[indexkp] = b[indexkj]; + b[indexkj] = temp; + } + } + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + var korder = k * order; + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + var korder = k + (k * order); + for (var j = 0; j < columnsOfB; j++) + { + b[k + (j * order)] /= a[korder]; + } + + korder = k * order; + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + public virtual void CholeskyFactor(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + var tmpColumn = new Complex32[order]; + + // Main loop - along the diagonal + for (var ij = 0; ij < order; ij++) + { + // "Pivot" element + var tmpVal = a[(ij * order) + ij]; + + if (tmpVal.Real > 0.0) + { + tmpVal = tmpVal.SquareRoot(); + a[(ij * order) + ij] = tmpVal; + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < order; i++) + { + a[(ij * order) + i] /= tmpVal; + tmpColumn[i] = a[(ij * order) + i]; + } + + // Remaining columns, below the diagonal + DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (var i = ij + 1; i < order; i++) + { + a[(i * order) + ij] = 0.0f; + } + } + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(Complex32[] data, int rowDim, int firstCol, int colLimit, Complex32[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data[(j * rowDim) + i] -= multipliers[i] * tmpVal.Conjugate(); + } + } + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + public virtual void CholeskySolve(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var clone = new Complex32[a.Length]; + a.Copy(clone); + CholeskyFactor(clone, orderA); + CholeskySolveFactored(clone, orderA, b, columnsB); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + public virtual void CholeskySolveFactored(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + CommonParallel.For(0, columnsB, (u, v) => + { + for (int i = u; i < v; i++) + { + DoCholeskySolve(a, orderA, b, i); + } + }); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The column to solve for. + static void DoCholeskySolve(Complex32[] a, int orderA, Complex32[] b, int index) + { + var cindex = index * orderA; + + // Solve L*Y = B; + Complex32 sum; + for (var i = 0; i < orderA; i++) + { + sum = b[cindex + i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= a[(k * orderA) + i] * b[cindex + k]; + } + + b[cindex + i] = sum / a[(i * orderA) + i]; + } + + // Solve L'*X = Y; + for (var i = orderA - 1; i >= 0; i--) + { + sum = b[cindex + i]; + var iindex = i * orderA; + for (var k = i + 1; k < orderA; k++) + { + sum -= a[iindex + k].Conjugate() * b[cindex + k]; + } + + b[cindex + i] = sum / a[iindex + i]; + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void QRFactor(Complex32[] r, int rowsR, int columnsR, Complex32[] q, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR * rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var work = columnsR > rowsR ? new Complex32[rowsR * rowsR] : new Complex32[rowsR * columnsR]; + + CommonParallel.For(0, rowsR, (a, b) => + { + for (int i = a; i < b; i++) + { + q[(i * rowsR) + i] = Complex32.One; + } + }); + + var minmn = Math.Min(rowsR, columnsR); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, r, rowsR, i, i); + ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void ThinQRFactor(Complex32[] a, int rowsA, int columnsA, Complex32[] r, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(a)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var work = new Complex32[rowsA * columnsA]; + + var minmn = Math.Min(rowsA, columnsA); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, a, rowsA, i, i); + ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); + } + + //copy R + for (var j = 0; j < columnsA; j++) + { + var rIndex = j * columnsA; + var aIndex = j * rowsA; + for (var i = 0; i < columnsA; i++) + { + r[rIndex + i] = a[aIndex + i]; + } + } + + //clear A and set diagonals to 1 + Array.Clear(a, 0, a.Length); + for (var i = 0; i < columnsA; i++) + { + a[i * rowsA + i] = Complex32.One; + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); + } + } + + #region QR Factor Helper functions + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Index of column in work array + /// Q or R matrices + /// The first row in + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(Complex32[] work, int workIndex, Complex32[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) + { + if (rowStart > rowCount || columnStart > columnCount) + { + return; + } + + var tmpColCount = columnCount - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), + () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); + } + else + { + for (var j = columnStart; j < columnCount; j++) + { + var scale = Complex32.Zero; + for (var i = rowStart; i < rowCount; i++) + { + scale += work[(workIndex * rowCount) + i - rowStart] * a[(j * rowCount) + i]; + } + + for (var i = rowStart; i < rowCount; i++) + { + a[(j * rowCount) + i] -= work[(workIndex * rowCount) + i - rowStart].Conjugate() * scale; + } + } + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Work array + /// Initial matrix + /// The number of rows in matrix + /// The first row + /// Column index + static void GenerateColumn(Complex32[] work, Complex32[] a, int rowCount, int row, int column) + { + var tmp = column * rowCount; + var index = tmp + row; + + CommonParallel.For(row, rowCount, (u, v) => + { + for (int i = u; i < v; i++) + { + var iIndex = tmp + i; + work[iIndex - row] = a[iIndex]; + a[iIndex] = Complex32.Zero; + } + }); + + var norm = Complex32.Zero; + for (var i = 0; i < rowCount - row; ++i) + { + var index1 = tmp + i; + norm += work[index1].Magnitude * work[index1].Magnitude; + } + + norm = norm.SquareRoot(); + if (row == rowCount - 1 || norm.Magnitude == 0) + { + a[index] = -work[tmp]; + work[tmp] = new Complex32(2.0f, 0).SquareRoot(); + return; + } + + if (work[tmp].Magnitude != 0.0f) + { + norm = norm.Magnitude * (work[tmp] / work[tmp].Magnitude); + } + + a[index] = -norm; + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] /= norm; + } + }); + work[tmp] += 1.0f; + + var s = (1.0f / work[tmp]).SquareRoot(); + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] = work[tmp + i].Conjugate() * s; + } + }); + } + + #endregion + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolve(Complex32[] a, int rows, int columns, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new Complex32[rows * columns]; + + var clone = new Complex32[a.Length]; + a.Copy(clone); + + if (method == QRMethod.Full) + { + var q = new Complex32[rows * rows]; + QRFactor(clone, rows, columns, q, work); + QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); + } + else + { + var r = new Complex32[columns * columns]; + ThinQRFactor(clone, rows, columns, r, work); + QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolveFactored(Complex32[] q, Complex32[] r, int rowsA, int columnsA, Complex32[] tau, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (rowsA < columnsA) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + var sol = new Complex32[b.Length]; + + // Copy B matrix to "sol", so B data will not be changed + Array.Copy(b, 0, sol, 0, b.Length); + + // Compute Y = transpose(Q)*B + var column = new Complex32[rowsA]; + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + Array.Copy(sol, jm, column, 0, rowsA); + CommonParallel.For(0, columnsA, (u, v) => + { + for (int i = u; i < v; i++) + { + var im = i * rowsA; + + var sum = Complex32.Zero; + for (var k = 0; k < rowsA; k++) + { + sum += q[im + k].Conjugate() * column[k]; + } + + sol[jm + i] = sum; + } + }); + } + + // Solve R*X = Y; + for (var k = columnsA - 1; k >= 0; k--) + { + var km = k * rowsR; + for (var j = 0; j < columnsB; j++) + { + sol[(j * rowsA) + k] /= r[km + k]; + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + sol[jm + i] -= sol[jm + k] * r[km + i]; + } + } + } + + // Fill result matrix + for (var col = 0; col < columnsB; col++) + { + Array.Copy(sol, col * rowsA, x, col * columnsA, columnsR); + } + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + public virtual void SingularValueDecomposition(bool computeVectors, Complex32[] a, int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new Complex32[rowsA]; + + const int maxiter = 1000; + + var e = new Complex32[columnsA]; + var v = new Complex32[vt.Length]; + var stemp = new Complex32[Math.Min(rowsA + 1, columnsA)]; + + int i, j, l, lp1; + + Complex32 t; + + var ncu = rowsA; + + // Reduce matrix to bidiagonal form, storing the diagonal elements + // in "s" and the super-diagonal elements in "e". + var nct = Math.Min(rowsA - 1, columnsA); + var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); + var lu = Math.Max(nct, nrt); + + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and + // place the l-th diagonal in vector s[l]. + var sum = 0.0f; + for (i = l; i < rowsA; i++) + { + sum += a[(l * rowsA) + i].Magnitude * a[(l * rowsA) + i].Magnitude; + } + + stemp[l] = (float)Math.Sqrt(sum); + if (stemp[l] != 0.0f) + { + if (a[(l * rowsA) + l] != 0.0f) + { + stemp[l] = stemp[l].Magnitude * (a[(l * rowsA) + l] / a[(l * rowsA) + l].Magnitude); + } + + // A part of column "l" of Matrix A from row "l" to end multiply by 1.0f / s[l] + for (i = l; i < rowsA; i++) + { + a[(l * rowsA) + i] = a[(l * rowsA) + i] * (1.0f / stemp[l]); + } + + a[(l * rowsA) + l] = 1.0f + a[(l * rowsA) + l]; + } + + stemp[l] = -stemp[l]; + } + + for (j = lp1; j < columnsA; j++) + { + if (l < nct) + { + if (stemp[l] != 0.0f) + { + // Apply the transformation. + t = 0.0f; + for (i = l; i < rowsA; i++) + { + t += a[(l * rowsA) + i].Conjugate() * a[(j * rowsA) + i]; + } + + t = -t / a[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += t * a[(l * rowsA) + ii]; + } + } + } + + // Place the l-th row of matrix into "e" for the + // subsequent calculation of the row transformation. + e[j] = a[(j * rowsA) + l].Conjugate(); + } + + if (computeVectors && l < nct) + { + // Place the transformation in "u" for subsequent back multiplication. + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = a[(l * rowsA) + i]; + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = 0.0f; + for (i = lp1; i < e.Length; i++) + { + enorm += e[i].Magnitude * e[i].Magnitude; + } + + e[l] = (float)Math.Sqrt(enorm); + if (e[l] != 0.0f) + { + if (e[lp1] != 0.0f) + { + e[l] = e[l].Magnitude * (e[lp1] / e[lp1].Magnitude); + } + + // Scale vector "e" from "lp1" by 1.0f / e[l] + for (i = lp1; i < e.Length; i++) + { + e[i] = e[i] * (1.0f / e[l]); + } + + e[lp1] = 1.0f + e[lp1]; + } + + e[l] = -e[l].Conjugate(); + + if (lp1 < rowsA && e[l] != 0.0f) + { + // Apply the transformation. + for (i = lp1; i < rowsA; i++) + { + work[i] = 0.0f; + } + + for (j = lp1; j < columnsA; j++) + { + for (var ii = lp1; ii < rowsA; ii++) + { + work[ii] += e[j] * a[(j * rowsA) + ii]; + } + } + + for (j = lp1; j < columnsA; j++) + { + var ww = (-e[j] / e[lp1]).Conjugate(); + for (var ii = lp1; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += ww * work[ii]; + } + } + } + + if (!computeVectors) + { + continue; + } + + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < columnsA; i++) + { + v[(l * columnsA) + i] = e[i]; + } + } + + // Set up the final bidiagonal matrix or order m. + var m = Math.Min(columnsA, rowsA + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < columnsA) + { + stemp[nctp1 - 1] = a[((nctp1 - 1) * rowsA) + (nctp1 - 1)]; + } + + if (rowsA < m) + { + stemp[m - 1] = 0.0f; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = a[((m - 1) * rowsA) + (nrtp1 - 1)]; + } + + e[m - 1] = 0.0f; + + // If required, generate "u". + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < rowsA; i++) + { + u[(j * rowsA) + i] = 0.0f; + } + + u[(j * rowsA) + j] = 1.0f; + } + + for (l = nct - 1; l >= 0; l--) + { + if (stemp[l] != 0.0f) + { + for (j = l + 1; j < ncu; j++) + { + t = 0.0f; + for (i = l; i < rowsA; i++) + { + t += u[(l * rowsA) + i].Conjugate() * u[(j * rowsA) + i]; + } + + t = -t / u[(l * rowsA) + l]; + for (var ii = l; ii < rowsA; ii++) + { + u[(j * rowsA) + ii] += t * u[(l * rowsA) + ii]; + } + } + + // A part of column "l" of matrix A from row "l" to end multiply by -1.0f + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = u[(l * rowsA) + i] * -1.0f; + } + + u[(l * rowsA) + l] = 1.0f + u[(l * rowsA) + l]; + for (i = 0; i < l; i++) + { + u[(l * rowsA) + i] = 0.0f; + } + } + else + { + for (i = 0; i < rowsA; i++) + { + u[(l * rowsA) + i] = 0.0f; + } + + u[(l * rowsA) + l] = 1.0f; + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = columnsA - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0f) + { + for (j = lp1; j < columnsA; j++) + { + t = 0.0f; + for (i = lp1; i < columnsA; i++) + { + t += v[(l * columnsA) + i].Conjugate() * v[(j * columnsA) + i]; + } + + t = -t / v[(l * columnsA) + lp1]; + for (var ii = l; ii < columnsA; ii++) + { + v[(j * columnsA) + ii] += t * v[(l * columnsA) + ii]; + } + } + } + } + + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = 0.0f; + } + + v[(l * columnsA) + l] = 1.0f; + } + } + + // Transform "s" and "e" so that they are float + for (i = 0; i < m; i++) + { + Complex32 r; + if (stemp[i] != 0.0f) + { + t = stemp[i].Magnitude; + r = stemp[i] / t; + stemp[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + // A part of column "i" of matrix U from row 0 to end multiply by r + for (j = 0; j < rowsA; j++) + { + u[(i * rowsA) + j] = u[(i * rowsA) + j] * r; + } + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] == 0.0f) + { + continue; + } + + t = e[i].Magnitude; + r = t / e[i]; + e[i] = t; + stemp[i + 1] = stemp[i + 1] * r; + if (!computeVectors) + { + continue; + } + + // A part of column "i+1" of matrix VT from row 0 to end multiply by r + for (j = 0; j < columnsA; j++) + { + v[((i + 1) * columnsA) + j] = v[((i + 1) * columnsA) + j] * r; + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. + // If too many iterations have been performed throw exception. + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays, + // on completion the variables case and l are set as follows: + // case = 1: if mS[m] and e[l-1] are negligible and l < m + // case = 2: if mS[l] is negligible and l < m + // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). + // case = 4: if e[m-1] is negligible (convergence). + float ztest; + float test; + for (l = m - 2; l >= 0; l--) + { + test = stemp[l].Magnitude + stemp[l + 1].Magnitude; + ztest = test + e[l].Magnitude; + if (ztest.AlmostEqualRelative(test, 7)) + { + e[l] = 0.0f; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0f; + if (ls != m - 1) + { + test = test + e[ls].Magnitude; + } + + if (ls != l + 1) + { + test = test + e[ls - 1].Magnitude; + } + + ztest = test + stemp[ls].Magnitude; + if (ztest.AlmostEqualRelative(test, 7)) + { + stemp[ls] = 0.0f; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + float f; + float sn; + float cs; + switch (kase) + { + // Deflate negligible s[m]. + case 1: + f = e[m - 2].Real; + e[m - 2] = 0.0f; + float t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = stemp[k].Real; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + if (k != l) + { + f = -sn * e[k - 1].Real; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + // Rotate + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((m - 1) * columnsA) + i]); + v[((m - 1) * columnsA) + i] = (cs * v[((m - 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + } + + break; + + // Split at negligible s[l]. + case 2: + f = e[l - 1].Real; + e[l - 1] = 0.0f; + for (k = l; k < m; k++) + { + t1 = stemp[k].Real; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + f = -sn * e[k].Real; + e[k] = cs * e[k]; + if (computeVectors) + { + // Rotate + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((l - 1) * rowsA) + i]); + u[((l - 1) * rowsA) + i] = (cs * u[((l - 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + break; + + // Perform one qr step. + case 3: + // calculate the shift. + var scale = 0.0f; + scale = Math.Max(scale, stemp[m - 1].Magnitude); + scale = Math.Max(scale, stemp[m - 2].Magnitude); + scale = Math.Max(scale, e[m - 2].Magnitude); + scale = Math.Max(scale, stemp[l].Magnitude); + scale = Math.Max(scale, e[l].Magnitude); + var sm = stemp[m - 1].Real / scale; + var smm1 = stemp[m - 2].Real / scale; + var emm1 = e[m - 2].Real / scale; + var sl = stemp[l].Real / scale; + var el = e[l].Real / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0f; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0f; + if (b != 0.0f || c != 0.0f) + { + shift = (float)Math.Sqrt((b * b) + c); + if (b < 0.0f) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * stemp[k].Real) + (sn * e[k].Real); + e[k] = (cs * e[k]) - (sn * stemp[k]); + g = sn * stemp[k + 1].Real; + stemp[k + 1] = cs * stemp[k + 1]; + if (computeVectors) + { + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((k + 1) * columnsA) + i]); + v[((k + 1) * columnsA) + i] = (cs * v[((k + 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + + Drotg(ref f, ref g, out cs, out sn); + stemp[k] = f; + f = (cs * e[k].Real) + (sn * stemp[k + 1].Real); + stemp[k + 1] = -(sn * e[k]) + (cs * stemp[k + 1]); + g = sn * e[k + 1].Real; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < rowsA) + { + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((k + 1) * rowsA) + i]); + u[((k + 1) * rowsA) + i] = (cs * u[((k + 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence + case 4: + + // Make the singular value positive + if (stemp[l].Real < 0.0f) + { + stemp[l] = -stemp[l]; + if (computeVectors) + { + // A part of column "l" of matrix VT from row 0 to end multiply by -1 + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = v[(l * columnsA) + i] * -1.0f; + } + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (stemp[l].Real >= stemp[l + 1].Real) + { + break; + } + + t = stemp[l]; + stemp[l] = stemp[l + 1]; + stemp[l + 1] = t; + if (computeVectors && l < columnsA) + { + // Swap columns l, l + 1 + for (i = 0; i < columnsA; i++) + { + var z = v[(l * columnsA) + i]; + v[(l * columnsA) + i] = v[((l + 1) * columnsA) + i]; + v[((l + 1) * columnsA) + i] = z; + } + } + + if (computeVectors && l < rowsA) + { + // Swap columns l, l + 1 + for (i = 0; i < rowsA; i++) + { + var z = u[(l * rowsA) + i]; + u[(l * rowsA) + i] = u[((l + 1) * rowsA) + i]; + u[((l + 1) * rowsA) + i] = z; + } + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + // Finally transpose "v" to get "vt" matrix + for (i = 0; i < columnsA; i++) + { + for (j = 0; j < columnsA; j++) + { + vt[(j * columnsA) + i] = v[(i * columnsA) + j].Conjugate(); + } + } + } + + // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses + // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. + // We should port lapack's svd routine to remove this problem. + Array.Copy(stemp, 0, s, 0, Math.Min(rowsA, columnsA)); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolve(Complex32[] a, int rowsA, int columnsA, Complex32[] b, int columnsB, Complex32[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex32[Math.Min(rowsA, columnsA)]; + var u = new Complex32[rowsA * rowsA]; + var vt = new Complex32[columnsA * columnsA]; + + var clone = new Complex32[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolveFactored(int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt, Complex32[] b, int columnsB, Complex32[] x) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var mn = Math.Min(rowsA, columnsA); + var tmp = new Complex32[columnsA]; + + for (var k = 0; k < columnsB; k++) + { + for (var j = 0; j < columnsA; j++) + { + var value = Complex32.Zero; + if (j < mn) + { + for (var i = 0; i < rowsA; i++) + { + value += u[(j * rowsA) + i].Conjugate() * b[(k * rowsA) + i]; + } + + value /= s[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < columnsA; j++) + { + var value = Complex32.Zero; + for (var i = 0; i < columnsA; i++) + { + value += vt[(j * columnsA) + i].Conjugate() * tmp[i]; + } + + x[(k * columnsA) + j] = value; + } + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public virtual void EigenDecomp(bool isSymmetric, int order, Complex32[] matrix, Complex32[] matrixEv, Complex[] vectorEv, Complex32[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var matrixCopy = new Complex32[matrix.Length]; + Array.Copy(matrix, 0, matrixCopy, 0, matrix.Length); + if (isSymmetric) + { + var tau = new Complex32[order]; + var d = new float[order]; + var e = new float[order]; + + DenseEvd.SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + DenseEvd.SymmetricDiagonalize(matrixEv, d, e, order); + DenseEvd.SymmetricUntridiagonalize(matrixEv, matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(d[i], e[i]); + matrixD[i * order + i] = new Complex32(d[i], e[i]); + } + } + else + { + var v = new DenseVector(order); + + DenseEvd.NonsymmetricReduceToHessenberg(matrixEv, matrixCopy, order); + DenseEvd.NonsymmetricReduceHessenberToRealSchur(v.Values, matrixEv, matrixCopy, order); + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(v[i].Real, v[i].Imaginary); + matrixD[i * order + i] = v[i]; + } + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetRow(Transpose transpose, int rowindx, int numRows, int numCols, Complex32[] matrix, Complex32[] row) + { + if (transpose == Transpose.DontTranspose) + { + for (int i = 0; i < numCols; i++) + { + row[i] = matrix[(i * numRows) + rowindx]; + } + } + else if (transpose == Transpose.ConjugateTranspose) + { + int offset = rowindx * numCols; + for (int i = 0; i < row.Length; i++) + { + row[i] = matrix[i + offset].Conjugate(); + } + } + else + { + Array.Copy(matrix, rowindx * numCols, row, 0, numCols); + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetColumn(Transpose transpose, int colindx, int numRows, int numCols, Complex32[] matrix, Complex32[] column) + { + if (transpose == Transpose.DontTranspose) + { + Array.Copy(matrix, colindx * numRows, column, 0, numRows); + } + else if (transpose == Transpose.ConjugateTranspose) + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx].Conjugate(); + } + } + else + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Double.cs b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Double.cs new file mode 100644 index 0000000..2b1ba43 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Double.cs @@ -0,0 +1,2588 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Managed; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedLinearAlgebraProvider +{ + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + public virtual void AddVectorToScaledVector(double[] y, double alpha, double[] x, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (alpha == 0.0) + { + y.Copy(result); + } + else if (alpha == 1.0) + { + for (int i = 0; i < result.Length; i++) + { + result[i] = y[i] + x[i]; + } + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = y[i] + (alpha * x[i]); + } + } + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + public virtual void ScaleArray(double alpha, double[] x, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (alpha == 0.0) + { + Array.Clear(result, 0, result.Length); + } + else if (alpha == 1.0) + { + x.Copy(result); + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = alpha * x[i]; + } + } + } + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + public virtual void ConjugateArray(double[] x, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + x.CopyTo(result, 0); + } + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + public virtual double DotProduct(double[] x, double[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + double sum = 0.0; + for (var index = 0; index < y.Length; index++) + { + sum += y[index] * x[index]; + } + + return sum; + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void AddArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] + y[i]; + } + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void SubtractArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] - y[i]; + } + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseMultiplyArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] * y[i]; + } + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseDivideArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] / y[i]; + } + }); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWisePowerArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = Math.Pow(x[i], y[i]); + } + }); + } + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + public virtual double MatrixNorm(Norm norm, int rows, int columns, double[] matrix) + { + switch (norm) + { + case Norm.OneNorm: + var norm1 = 0d; + for (var j = 0; j < columns; j++) + { + var s = 0.0; + for (var i = 0; i < rows; i++) + { + s += Math.Abs(matrix[(j * rows) + i]); + } + + norm1 = Math.Max(norm1, s); + } + + return norm1; + case Norm.LargestAbsoluteValue: + var normMax = 0d; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + normMax = Math.Max(Math.Abs(matrix[(j * rows) + i]), normMax); + } + } + + return normMax; + case Norm.InfinityNorm: + var r = new double[rows]; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + r[i] += Math.Abs(matrix[(j * rows) + i]); + } + } + // TODO: reuse + var max = r[0]; + for (int i = 0; i < r.Length; i++) + { + if (r[i] > max) + { + max = r[i]; + } + } + + return max; + case Norm.FrobeniusNorm: + var aat = new double[rows * rows]; + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.Transpose, 1.0, matrix, rows, columns, matrix, rows, columns, 0.0, aat); + var normF = 0d; + for (var i = 0; i < rows; i++) + { + normF += Math.Abs(aat[(i * rows) + i]); + } + + return Math.Sqrt(normF); + default: + throw new NotSupportedException(); + } + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public virtual void MatrixMultiply(double[] x, int rowsX, int columnsX, double[] y, int rowsY, int columnsY, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (columnsX != rowsY) + { + throw new ArgumentOutOfRangeException(string.Format("columnsA ({0}) != rowsB ({1})", columnsX, rowsY)); + } + + if (rowsX * columnsX != x.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsA ({1}) != a.Length ({2})", rowsX, columnsX, x.Length)); + } + + if (rowsY * columnsY != y.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsB ({0}) * columnsB ({1}) != b.Length ({2})", rowsY, columnsY, y.Length)); + } + + if (rowsX * columnsY != result.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsB ({1}) != c.Length ({2})", rowsX, columnsY, result.Length)); + } + + // handle degenerate cases + Array.Clear(result, 0, result.Length); + + // Extract column arrays + var columnDataB = new double[columnsY][]; + for (int i = 0; i < columnDataB.Length; i++) + { + var column = new double[rowsY]; + GetColumn(Transpose.DontTranspose, i, rowsY, columnsY, y, column); + columnDataB[i] = column; + } + + var shouldNotParallelize = rowsX + columnsY + columnsX < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; + if (shouldNotParallelize) + { + var row = new double[columnsX]; + for (int i = 0; i < rowsX; i++) + { + GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); + for (int j = 0; j < columnsY; j++) + { + var col = columnDataB[j]; + double sum = 0; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * col[ii]; + } + + result[j * rowsX + i] += 1.0 * sum; + } + } + } + else + { + CommonParallel.For(0, rowsX, 1, (u, v) => + { + var row = new double[columnsX]; + for (int i = u; i < v; i++) + { + GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); + for (int j = 0; j < columnsY; j++) + { + var column = columnDataB[j]; + double sum = 0; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * column[ii]; + } + + result[j * rowsX + i] += 1.0 * sum; + } + } + }); + } + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, double alpha, double[] a, int rowsA, int columnsA, double[] b, int rowsB, int columnsB, double beta, double[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + if (transposeA != Transpose.DontTranspose) + { + var swap = rowsA; + rowsA = columnsA; + columnsA = swap; + } + + if (transposeB != Transpose.DontTranspose) + { + var swap = rowsB; + rowsB = columnsB; + columnsB = swap; + } + + if (columnsA != rowsB) + { + throw new ArgumentOutOfRangeException(string.Format("columnsA ({0}) != rowsB ({1})", columnsA, rowsB)); + } + + if (rowsA * columnsA != a.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsA ({1}) != a.Length ({2})", rowsA, columnsA, a.Length)); + } + + if (rowsB * columnsB != b.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsB ({0}) * columnsB ({1}) != b.Length ({2})", rowsB, columnsB, b.Length)); + } + + if (rowsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsB ({1}) != c.Length ({2})", rowsA, columnsB, c.Length)); + } + + // handle degenerate cases + if (beta == 0.0) + { + Array.Clear(c, 0, c.Length); + } + else if (beta != 1.0) + { + ScaleArray(beta, c, c); + } + + if (alpha == 0.0) + { + return; + } + + // Extract column arrays + var columnDataB = new double[columnsB][]; + for (int i = 0; i < columnDataB.Length; i++) + { + var column = new double[rowsB]; + GetColumn(transposeB, i, rowsB, columnsB, b, column); + columnDataB[i] = column; + } + + var shouldNotParallelize = rowsA + columnsB + columnsA < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; + if (shouldNotParallelize) + { + var row = new double[columnsA]; + for (int i = 0; i < rowsA; i++) + { + GetRow(transposeA, i, rowsA, columnsA, a, row); + for (int j = 0; j < columnsB; j++) + { + var col = columnDataB[j]; + double sum = 0; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * col[ii]; + } + + c[j * rowsA + i] += alpha * sum; + } + } + } + else + { + CommonParallel.For(0, rowsA, 1, (u, v) => + { + var row = new double[columnsA]; + for (int i = u; i < v; i++) + { + GetRow(transposeA, i, rowsA, columnsA, a, row); + for (int j = 0; j < columnsB; j++) + { + var column = columnDataB[j]; + double sum = 0; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * column[ii]; + } + + c[j * rowsA + i] += alpha * sum; + } + } + }); + } + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + public virtual void LUFactor(double[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + ipiv[i] = i; + } + + var vecLUcolj = new double[order]; + + // Outer loop. + for (var j = 0; j < order; j++) + { + var indexj = j * order; + var indexjj = indexj + j; + + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vecLUcolj[i] = data[indexj + i]; + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + // Most of the time is spent in the following dot product. + var kmax = Math.Min(i, j); + var s = 0.0; + for (var k = 0; k < kmax; k++) + { + s += data[(k * order) + i] * vecLUcolj[k]; + } + + data[indexj + i] = vecLUcolj[i] -= s; + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (Math.Abs(vecLUcolj[i]) > Math.Abs(vecLUcolj[p])) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var indexk = k * order; + var indexkp = indexk + p; + var indexkj = indexk + j; + var temp = data[indexkp]; + data[indexkp] = data[indexkj]; + data[indexkj] = temp; + } + + ipiv[j] = p; + } + + // Compute multipliers. + if (j < order & data[indexjj] != 0.0) + { + for (var i = j + 1; i < order; i++) + { + data[indexj + i] /= data[indexjj]; + } + } + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + public virtual void LUInverse(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var ipiv = new int[order]; + LUFactor(a, order, ipiv); + LUInverseFactored(a, order, ipiv); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + public virtual void LUInverseFactored(double[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var inverse = new double[a.Length]; + for (var i = 0; i < order; i++) + { + inverse[i + (order * i)] = 1.0; + } + + LUSolveFactored(order, a, order, ipiv, inverse); + inverse.Copy(a); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + public virtual void LUSolve(int columnsOfB, double[] a, int order, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var ipiv = new int[order]; + var clone = new double[a.Length]; + a.Copy(clone); + LUFactor(clone, order, ipiv); + LUSolveFactored(columnsOfB, clone, order, ipiv, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + public virtual void LUSolveFactored(int columnsOfB, double[] a, int order, int[] ipiv, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + // Compute the column vector P*B + for (var i = 0; i < ipiv.Length; i++) + { + if (ipiv[i] == i) + { + continue; + } + + var p = ipiv[i]; + for (var j = 0; j < columnsOfB; j++) + { + var indexk = j * order; + var indexkp = indexk + p; + var indexkj = indexk + i; + var temp = b[indexkp]; + b[indexkp] = b[indexkj]; + b[indexkj] = temp; + } + } + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + var korder = k * order; + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + var korder = k + (k * order); + for (var j = 0; j < columnsOfB; j++) + { + b[k + (j * order)] /= a[korder]; + } + + korder = k * order; + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + public virtual void CholeskyFactor(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + var tmpColumn = new double[order]; + + // Main loop - along the diagonal + for (int ij = 0; ij < order; ij++) + { + // "Pivot" element + double tmpVal = a[(ij * order) + ij]; + + if (tmpVal > 0.0) + { + tmpVal = Math.Sqrt(tmpVal); + a[(ij * order) + ij] = tmpVal; + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (int i = ij + 1; i < order; i++) + { + a[(ij * order) + i] /= tmpVal; + tmpColumn[i] = a[(ij * order) + i]; + } + + // Remaining columns, below the diagonal + DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (int i = ij + 1; i < order; i++) + { + a[(i * order) + ij] = 0.0; + } + } + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(double[] data, int rowDim, int firstCol, int colLimit, double[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data[(j * rowDim) + i] -= multipliers[i] * tmpVal; + } + } + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + public virtual void CholeskySolve(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var clone = new double[a.Length]; + a.Copy(clone); + CholeskyFactor(clone, orderA); + CholeskySolveFactored(clone, orderA, b, columnsB); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + public virtual void CholeskySolveFactored(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + CommonParallel.For(0, columnsB, (u, v) => + { + for (int i = u; i < v; i++) + { + DoCholeskySolve(a, orderA, b, i); + } + }); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The column to solve for. + static void DoCholeskySolve(double[] a, int orderA, double[] b, int index) + { + var cindex = index * orderA; + + // Solve L*Y = B; + double sum; + for (var i = 0; i < orderA; i++) + { + sum = b[cindex + i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= a[(k * orderA) + i] * b[cindex + k]; + } + + b[cindex + i] = sum / a[(i * orderA) + i]; + } + + // Solve L'*X = Y; + for (var i = orderA - 1; i >= 0; i--) + { + sum = b[cindex + i]; + var iindex = i * orderA; + for (var k = i + 1; k < orderA; k++) + { + sum -= a[iindex + k] * b[cindex + k]; + } + + b[cindex + i] = sum / a[iindex + i]; + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void QRFactor(double[] r, int rowsR, int columnsR, double[] q, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR * rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + CommonParallel.For(0, rowsR, (a, b) => + { + for (var i = a; i < b; i++) + { + q[(i * rowsR) + i] = 1.0; + } + }); + + var work = columnsR > rowsR ? new double[rowsR * rowsR] : new double[rowsR * columnsR]; + var minmn = Math.Min(rowsR, columnsR); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, r, rowsR, i, i); + ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void ThinQRFactor(double[] a, int rowsA, int columnsA, double[] r, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(a)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var work = new double[rowsA * columnsA]; + + var minmn = Math.Min(rowsA, columnsA); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, a, rowsA, i, i); + ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); + } + + //copy R + for (var j = 0; j < columnsA; j++) + { + var rIndex = j * columnsA; + var aIndex = j * rowsA; + for (var i = 0; i < columnsA; i++) + { + r[rIndex + i] = a[aIndex + i]; + } + } + + //clear A and set diagonals to 1 + Array.Clear(a, 0, a.Length); + for (var i = 0; i < columnsA; i++) + { + a[i * rowsA + i] = 1.0; + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); + } + } + + #region QR Factor Helper functions + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Index of column in work array + /// Q or R matrices + /// The first row in + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(double[] work, int workIndex, double[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) + { + if (rowStart > rowCount || columnStart > columnCount) + { + return; + } + + var tmpColCount = columnCount - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), + () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); + } + else + { + for (var j = columnStart; j < columnCount; j++) + { + var scale = 0.0; + for (var i = rowStart; i < rowCount; i++) + { + scale += work[(workIndex * rowCount) + i - rowStart] * a[(j * rowCount) + i]; + } + + for (var i = rowStart; i < rowCount; i++) + { + a[(j * rowCount) + i] -= work[(workIndex * rowCount) + i - rowStart] * scale; + } + } + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Work array + /// Initial matrix + /// The number of rows in matrix + /// The first row + /// Column index + static void GenerateColumn(double[] work, double[] a, int rowCount, int row, int column) + { + var tmp = column * rowCount; + var index = tmp + row; + + CommonParallel.For(row, rowCount, (u, v) => + { + for (int i = u; i < v; i++) + { + var iIndex = tmp + i; + work[iIndex - row] = a[iIndex]; + a[iIndex] = 0.0; + } + }); + + var norm = 0.0; + for (var i = 0; i < rowCount - row; ++i) + { + var iindex = tmp + i; + norm += work[iindex] * work[iindex]; + } + + norm = Math.Sqrt(norm); + if (row == rowCount - 1 || norm == 0) + { + a[index] = -work[tmp]; + work[tmp] = Constants.Sqrt2; + return; + } + + var scale = 1.0 / norm; + if (work[tmp] < 0.0) + { + scale *= -1.0; + } + + a[index] = -1.0 / scale; + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] *= scale; + } + }); + work[tmp] += 1.0; + + var s = Math.Sqrt(1.0 / work[tmp]); + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] *= s; + } + }); + } + + #endregion + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolve(double[] a, int rows, int columns, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new double[rows * columns]; + + var clone = new double[a.Length]; + a.Copy(clone); + + if (method == QRMethod.Full) + { + var q = new double[rows * rows]; + QRFactor(clone, rows, columns, q, work); + QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); + } + else + { + var r = new double[columns * columns]; + ThinQRFactor(clone, rows, columns, r, work); + QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolveFactored(double[] q, double[] r, int rowsA, int columnsA, double[] tau, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (rowsA < columnsA) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + var sol = new double[b.Length]; + + // Copy B matrix to "sol", so B data will not be changed + Buffer.BlockCopy(b, 0, sol, 0, b.Length * Constants.SizeOfDouble); + + // Compute Y = transpose(Q)*B + var column = new double[rowsA]; + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + Array.Copy(sol, jm, column, 0, rowsA); + CommonParallel.For(0, columnsA, (u, v) => + { + for (int i = u; i < v; i++) + { + var im = i * rowsA; + + var sum = 0.0; + for (var k = 0; k < rowsA; k++) + { + sum += q[im + k] * column[k]; + } + + sol[jm + i] = sum; + } + }); + } + + // Solve R*X = Y; + for (var k = columnsA - 1; k >= 0; k--) + { + var km = k * rowsR; + for (var j = 0; j < columnsB; j++) + { + sol[(j * rowsA) + k] /= r[km + k]; + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + sol[jm + i] -= sol[jm + k] * r[km + i]; + } + } + } + + // Fill result matrix + for (var col = 0; col < columnsB; col++) + { + Array.Copy(sol, col * rowsA, x, col * columnsA, columnsR); + } + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + public virtual void SingularValueDecomposition(bool computeVectors, double[] a, int rowsA, int columnsA, double[] s, double[] u, double[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new double[rowsA]; + + const int maxiter = 1000; + + var e = new double[columnsA]; + var v = new double[vt.Length]; + var stemp = new double[Math.Min(rowsA + 1, columnsA)]; + + int i, j, l, lp1; + + double t; + + var ncu = rowsA; + + // Reduce matrix to bidiagonal form, storing the diagonal elements + // in "s" and the super-diagonal elements in "e". + var nct = Math.Min(rowsA - 1, columnsA); + var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); + var lu = Math.Max(nct, nrt); + + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and + // place the l-th diagonal in vector s[l]. + var sum = 0.0; + for (var i1 = l; i1 < rowsA; i1++) + { + sum += a[(l * rowsA) + i1] * a[(l * rowsA) + i1]; + } + + stemp[l] = Math.Sqrt(sum); + + if (stemp[l] != 0.0) + { + if (a[(l * rowsA) + l] != 0.0) + { + stemp[l] = Math.Abs(stemp[l]) * (a[(l * rowsA) + l] / Math.Abs(a[(l * rowsA) + l])); + } + + // A part of column "l" of Matrix A from row "l" to end multiply by 1.0 / s[l] + for (i = l; i < rowsA; i++) + { + a[(l * rowsA) + i] = a[(l * rowsA) + i] * (1.0 / stemp[l]); + } + + a[(l * rowsA) + l] = 1.0 + a[(l * rowsA) + l]; + } + + stemp[l] = -stemp[l]; + } + + for (j = lp1; j < columnsA; j++) + { + if (l < nct) + { + if (stemp[l] != 0.0) + { + // Apply the transformation. + t = 0.0; + for (i = l; i < rowsA; i++) + { + t += a[(j * rowsA) + i] * a[(l * rowsA) + i]; + } + + t = -t / a[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += t * a[(l * rowsA) + ii]; + } + } + } + + // Place the l-th row of matrix into "e" for the + // subsequent calculation of the row transformation. + e[j] = a[(j * rowsA) + l]; + } + + if (computeVectors && l < nct) + { + // Place the transformation in "u" for subsequent back multiplication. + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = a[(l * rowsA) + i]; + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = 0.0; + for (i = lp1; i < e.Length; i++) + { + enorm += e[i] * e[i]; + } + + e[l] = Math.Sqrt(enorm); + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = Math.Abs(e[l]) * (e[lp1] / Math.Abs(e[lp1])); + } + + // Scale vector "e" from "lp1" by 1.0 / e[l] + for (i = lp1; i < e.Length; i++) + { + e[i] = e[i] * (1.0 / e[l]); + } + + e[lp1] = 1.0 + e[lp1]; + } + + e[l] = -e[l]; + + if (lp1 < rowsA && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < rowsA; i++) + { + work[i] = 0.0; + } + + for (j = lp1; j < columnsA; j++) + { + for (var ii = lp1; ii < rowsA; ii++) + { + work[ii] += e[j] * a[(j * rowsA) + ii]; + } + } + + for (j = lp1; j < columnsA; j++) + { + var ww = -e[j] / e[lp1]; + for (var ii = lp1; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += ww * work[ii]; + } + } + } + + if (!computeVectors) + { + continue; + } + + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < columnsA; i++) + { + v[(l * columnsA) + i] = e[i]; + } + } + + // Set up the final bidiagonal matrix or order m. + var m = Math.Min(columnsA, rowsA + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < columnsA) + { + stemp[nctp1 - 1] = a[((nctp1 - 1) * rowsA) + (nctp1 - 1)]; + } + + if (rowsA < m) + { + stemp[m - 1] = 0.0; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = a[((m - 1) * rowsA) + (nrtp1 - 1)]; + } + + e[m - 1] = 0.0; + + // If required, generate "u". + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < rowsA; i++) + { + u[(j * rowsA) + i] = 0.0; + } + + u[(j * rowsA) + j] = 1.0; + } + + for (l = nct - 1; l >= 0; l--) + { + if (stemp[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = 0.0; + for (i = l; i < rowsA; i++) + { + t += u[(j * rowsA) + i] * u[(l * rowsA) + i]; + } + + t = -t / u[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + u[(j * rowsA) + ii] += t * u[(l * rowsA) + ii]; + } + } + + // A part of column "l" of matrix A from row "l" to end multiply by -1.0 + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = u[(l * rowsA) + i] * -1.0; + } + + u[(l * rowsA) + l] = 1.0 + u[(l * rowsA) + l]; + for (i = 0; i < l; i++) + { + u[(l * rowsA) + i] = 0.0; + } + } + else + { + for (i = 0; i < rowsA; i++) + { + u[(l * rowsA) + i] = 0.0; + } + + u[(l * rowsA) + l] = 1.0; + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = columnsA - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < columnsA; j++) + { + t = 0.0; + for (i = lp1; i < columnsA; i++) + { + t += v[(j * columnsA) + i] * v[(l * columnsA) + i]; + } + + t = -t / v[(l * columnsA) + lp1]; + for (var ii = l; ii < columnsA; ii++) + { + v[(j * columnsA) + ii] += t * v[(l * columnsA) + ii]; + } + } + } + } + + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = 0.0; + } + + v[(l * columnsA) + l] = 1.0; + } + } + + // Transform "s" and "e" so that they are double + for (i = 0; i < m; i++) + { + double r; + if (stemp[i] != 0.0) + { + t = stemp[i]; + r = stemp[i] / t; + stemp[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + // A part of column "i" of matrix U from row 0 to end multiply by r + for (j = 0; j < rowsA; j++) + { + u[(i * rowsA) + j] = u[(i * rowsA) + j] * r; + } + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] == 0.0) + { + continue; + } + + t = e[i]; + r = t / e[i]; + e[i] = t; + stemp[i + 1] = stemp[i + 1] * r; + if (!computeVectors) + { + continue; + } + + // A part of column "i+1" of matrix VT from row 0 to end multiply by r + for (j = 0; j < columnsA; j++) + { + v[((i + 1) * columnsA) + j] = v[((i + 1) * columnsA) + j] * r; + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. + // If too many iterations have been performed throw exception. + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays, + // on completion the variables case and l are set as follows: + // case = 1: if mS[m] and e[l-1] are negligible and l < m + // case = 2: if mS[l] is negligible and l < m + // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). + // case = 4: if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = Math.Abs(stemp[l]) + Math.Abs(stemp[l + 1]); + ztest = test + Math.Abs(e[l]); + if (ztest.AlmostEqualRelative(test, 15)) + { + e[l] = 0.0; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + Math.Abs(e[ls]); + } + + if (ls != l + 1) + { + test = test + Math.Abs(e[ls - 1]); + } + + ztest = test + Math.Abs(stemp[ls]); + if (ztest.AlmostEqualRelative(test, 15)) + { + stemp[ls] = 0.0; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + double f; + double cs; + double sn; + switch (kase) + { + // Deflate negligible s[m]. + case 1: + f = e[m - 2]; + e[m - 2] = 0.0; + double t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = stemp[k]; + + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + if (k != l) + { + f = -sn * e[k - 1]; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + // Rotate + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((m - 1) * columnsA) + i]); + v[((m - 1) * columnsA) + i] = (cs * v[((m - 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + } + + break; + + // Split at negligible s[l]. + case 2: + f = e[l - 1]; + e[l - 1] = 0.0; + for (k = l; k < m; k++) + { + t1 = stemp[k]; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + f = -sn * e[k]; + e[k] = cs * e[k]; + if (computeVectors) + { + // Rotate + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((l - 1) * rowsA) + i]); + u[((l - 1) * rowsA) + i] = (cs * u[((l - 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + break; + + // Perform one qr step. + case 3: + + // calculate the shift. + var scale = 0.0; + scale = Math.Max(scale, Math.Abs(stemp[m - 1])); + scale = Math.Max(scale, Math.Abs(stemp[m - 2])); + scale = Math.Max(scale, Math.Abs(e[m - 2])); + scale = Math.Max(scale, Math.Abs(stemp[l])); + scale = Math.Max(scale, Math.Abs(e[l])); + var sm = stemp[m - 1] / scale; + var smm1 = stemp[m - 2] / scale; + var emm1 = e[m - 2] / scale; + var sl = stemp[l] / scale; + var el = e[l] / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0; + if (b != 0.0 || c != 0.0) + { + shift = Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * stemp[k]) + (sn * e[k]); + e[k] = (cs * e[k]) - (sn * stemp[k]); + g = sn * stemp[k + 1]; + stemp[k + 1] = cs * stemp[k + 1]; + if (computeVectors) + { + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((k + 1) * columnsA) + i]); + v[((k + 1) * columnsA) + i] = (cs * v[((k + 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + + Drotg(ref f, ref g, out cs, out sn); + stemp[k] = f; + f = (cs * e[k]) + (sn * stemp[k + 1]); + stemp[k + 1] = -(sn * e[k]) + (cs * stemp[k + 1]); + g = sn * e[k + 1]; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < rowsA) + { + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((k + 1) * rowsA) + i]); + u[((k + 1) * rowsA) + i] = (cs * u[((k + 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence + case 4: + + // Make the singular value positive + if (stemp[l] < 0.0) + { + stemp[l] = -stemp[l]; + if (computeVectors) + { + // A part of column "l" of matrix VT from row 0 to end multiply by -1 + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = v[(l * columnsA) + i] * -1.0; + } + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (stemp[l] >= stemp[l + 1]) + { + break; + } + + t = stemp[l]; + stemp[l] = stemp[l + 1]; + stemp[l + 1] = t; + if (computeVectors && l < columnsA) + { + // Swap columns l, l + 1 + for (i = 0; i < columnsA; i++) + { + var z = v[(l * columnsA) + i]; + v[(l * columnsA) + i] = v[((l + 1) * columnsA) + i]; + v[((l + 1) * columnsA) + i] = z; + } + } + + if (computeVectors && l < rowsA) + { + // Swap columns l, l + 1 + for (i = 0; i < rowsA; i++) + { + var z = u[(l * rowsA) + i]; + u[(l * rowsA) + i] = u[((l + 1) * rowsA) + i]; + u[((l + 1) * rowsA) + i] = z; + } + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + // Finally transpose "v" to get "vt" matrix + for (i = 0; i < columnsA; i++) + { + for (j = 0; j < columnsA; j++) + { + vt[(j * columnsA) + i] = v[(i * columnsA) + j]; + } + } + } + + // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses + // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. + // We should port lapack's svd routine to remove this problem. + Buffer.BlockCopy(stemp, 0, s, 0, Math.Min(rowsA, columnsA) * Constants.SizeOfDouble); + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + static void Drotg(ref double da, ref double db, out double c, out double s) + { + double r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0) + { + c = 1.0; + s = 0.0; + r = 0.0; + z = 0.0; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0) + { + z = 1.0 / c; + } + } + + da = r; + db = z; + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolve(double[] a, int rowsA, int columnsA, double[] b, int columnsB, double[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new double[Math.Min(rowsA, columnsA)]; + var u = new double[rowsA * rowsA]; + var vt = new double[columnsA * columnsA]; + + var clone = new double[a.Length]; + Buffer.BlockCopy(a, 0, clone, 0, a.Length * Constants.SizeOfDouble); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolveFactored(int rowsA, int columnsA, double[] s, double[] u, double[] vt, double[] b, int columnsB, double[] x) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var mn = Math.Min(rowsA, columnsA); + var tmp = new double[columnsA]; + + for (var k = 0; k < columnsB; k++) + { + for (var j = 0; j < columnsA; j++) + { + double value = 0; + if (j < mn) + { + for (var i = 0; i < rowsA; i++) + { + value += u[(j * rowsA) + i] * b[(k * rowsA) + i]; + } + + value /= s[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < columnsA; j++) + { + double value = 0; + for (var i = 0; i < columnsA; i++) + { + value += vt[(j * columnsA) + i] * tmp[i]; + } + + x[(k * columnsA) + j] = value; + } + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public virtual void EigenDecomp(bool isSymmetric, int order, double[] matrix, double[] matrixEv, Complex[] vectorEv, double[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var d = new double[order]; + var e = new double[order]; + + if (isSymmetric) + { + Buffer.BlockCopy(matrix, 0, matrixEv, 0, matrix.Length * Constants.SizeOfDouble); + var om1 = order - 1; + for (var i = 0; i < order; i++) + { + d[i] = matrixEv[i * order + om1]; + } + + Numerics.LinearAlgebra.Double.Factorization.DenseEvd.SymmetricTridiagonalize(matrixEv, d, e, order); + Numerics.LinearAlgebra.Double.Factorization.DenseEvd.SymmetricDiagonalize(matrixEv, d, e, order); + } + else + { + var matrixH = new double[matrix.Length]; + Buffer.BlockCopy(matrix, 0, matrixH, 0, matrix.Length * Constants.SizeOfDouble); + Numerics.LinearAlgebra.Double.Factorization.DenseEvd.NonsymmetricReduceToHessenberg(matrixEv, matrixH, order); + Numerics.LinearAlgebra.Double.Factorization.DenseEvd.NonsymmetricReduceHessenberToRealSchur(matrixEv, matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(d[i], e[i]); + + var io = i * order; + matrixD[io + i] = d[i]; + + if (e[i] > 0) + { + matrixD[io + order + i] = e[i]; + matrixD[(i + 1) * order + i] = e[i]; + } + else if (e[i] < 0) + { + matrixD[io - order + i] = e[i]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Single.cs b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Single.cs new file mode 100644 index 0000000..7495925 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.Single.cs @@ -0,0 +1,2590 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Managed; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedLinearAlgebraProvider +{ + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + public virtual void AddVectorToScaledVector(float[] y, float alpha, float[] x, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (alpha == 0.0) + { + y.Copy(result); + } + else if (alpha == 1.0) + { + for (int i = 0; i < result.Length; i++) + { + result[i] = y[i] + x[i]; + } + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = y[i] + (alpha * x[i]); + } + } + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + public virtual void ScaleArray(float alpha, float[] x, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (alpha == 0.0) + { + Array.Clear(result, 0, result.Length); + } + else if (alpha == 1.0) + { + x.Copy(result); + } + else + { + for (int i = 0; i < result.Length; i++) + { + result[i] = alpha * x[i]; + } + } + } + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + public virtual void ConjugateArray(float[] x, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + x.CopyTo(result, 0); + } + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + public virtual float DotProduct(float[] x, float[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + float sum = 0.0f; + for (var index = 0; index < y.Length; index++) + { + sum += y[index] * x[index]; + } + + return sum; + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void AddArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] + y[i]; + } + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void SubtractArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] - y[i]; + } + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseMultiplyArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + for (int i = 0; i < result.Length; i++) + { + result[i] = x[i] * y[i]; + } + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseDivideArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] / y[i]; + } + }); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWisePowerArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = (float)Math.Pow(x[i], y[i]); + } + }); + } + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + public virtual double MatrixNorm(Norm norm, int rows, int columns, float[] matrix) + { + switch (norm) + { + case Norm.OneNorm: + var norm1 = 0d; + for (var j = 0; j < columns; j++) + { + var s = 0d; + for (var i = 0; i < rows; i++) + { + s += Math.Abs(matrix[(j * rows) + i]); + } + + norm1 = Math.Max(norm1, s); + } + + return norm1; + case Norm.LargestAbsoluteValue: + var normMax = 0d; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + normMax = Math.Max(Math.Abs(matrix[(j * rows) + i]), normMax); + } + } + + return normMax; + case Norm.InfinityNorm: + var r = new double[rows]; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + r[i] += Math.Abs(matrix[(j * rows) + i]); + } + } + // TODO: reuse + var max = r[0]; + for (int i = 0; i < r.Length; i++) + { + if (r[i] > max) + { + max = r[i]; + } + } + + return max; + case Norm.FrobeniusNorm: + var aat = new float[rows * rows]; + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.Transpose, 1.0f, matrix, rows, columns, matrix, rows, columns, 0.0f, aat); + var normF = 0d; + for (var i = 0; i < rows; i++) + { + normF += Math.Abs(aat[(i * rows) + i]); + } + + return Math.Sqrt(normF); + default: + throw new NotSupportedException(); + } + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public virtual void MatrixMultiply(float[] x, int rowsX, int columnsX, float[] y, int rowsY, int columnsY, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (columnsX != rowsY) + { + throw new ArgumentOutOfRangeException(string.Format("columnsA ({0}) != rowsB ({1})", columnsX, rowsY)); + } + + if (rowsX * columnsX != x.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsA ({1}) != a.Length ({2})", rowsX, columnsX, x.Length)); + } + + if (rowsY * columnsY != y.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsB ({0}) * columnsB ({1}) != b.Length ({2})", rowsY, columnsY, y.Length)); + } + + if (rowsX * columnsY != result.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsB ({1}) != c.Length ({2})", rowsX, columnsY, result.Length)); + } + + // handle degenerate cases + Array.Clear(result, 0, result.Length); + + // Extract column arrays + var columnDataB = new float[columnsY][]; + for (int i = 0; i < columnDataB.Length; i++) + { + var column = new float[rowsY]; + GetColumn(Transpose.DontTranspose, i, rowsY, columnsY, y, column); + columnDataB[i] = column; + } + + var shouldNotParallelize = rowsX + columnsY + columnsX < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; + if (shouldNotParallelize) + { + var row = new float[columnsX]; + for (int i = 0; i < rowsX; i++) + { + GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); + for (int j = 0; j < columnsY; j++) + { + var col = columnDataB[j]; + float sum = 0; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * col[ii]; + } + + result[j * rowsX + i] += 1.0f * sum; + } + } + } + else + { + CommonParallel.For(0, rowsX, 1, (u, v) => + { + var row = new float[columnsX]; + for (int i = u; i < v; i++) + { + GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); + for (int j = 0; j < columnsY; j++) + { + var column = columnDataB[j]; + float sum = 0; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * column[ii]; + } + + result[j * rowsX + i] += 1.0f * sum; + } + } + }); + } + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, float alpha, float[] a, int rowsA, int columnsA, float[] b, int rowsB, int columnsB, float beta, float[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + if (transposeA != Transpose.DontTranspose) + { + var swap = rowsA; + rowsA = columnsA; + columnsA = swap; + } + + if (transposeB != Transpose.DontTranspose) + { + var swap = rowsB; + rowsB = columnsB; + columnsB = swap; + } + + if (columnsA != rowsB) + { + throw new ArgumentOutOfRangeException(string.Format("columnsA ({0}) != rowsB ({1})", columnsA, rowsB)); + } + + if (rowsA * columnsA != a.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsA ({1}) != a.Length ({2})", rowsA, columnsA, a.Length)); + } + + if (rowsB * columnsB != b.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsB ({0}) * columnsB ({1}) != b.Length ({2})", rowsB, columnsB, b.Length)); + } + + if (rowsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(string.Format("rowsA ({0}) * columnsB ({1}) != c.Length ({2})", rowsA, columnsB, c.Length)); + } + + // handle degenerate cases + if (beta == 0.0) + { + Array.Clear(c, 0, c.Length); + } + else if (beta != 1.0) + { + ScaleArray(beta, c, c); + } + + if (alpha == 0.0) + { + return; + } + + // Extract column arrays + var columnDataB = new float[columnsB][]; + for (int i = 0; i < columnDataB.Length; i++) + { + var column = new float[rowsB]; + GetColumn(transposeB, i, rowsB, columnsB, b, column); + columnDataB[i] = column; + } + + var shouldNotParallelize = rowsA + columnsB + columnsA < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; + if (shouldNotParallelize) + { + var row = new float[columnsA]; + for (int i = 0; i < rowsA; i++) + { + GetRow(transposeA, i, rowsA, columnsA, a, row); + for (int j = 0; j < columnsB; j++) + { + var col = columnDataB[j]; + float sum = 0; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * col[ii]; + } + + c[j * rowsA + i] += alpha * sum; + } + } + } + else + { + CommonParallel.For(0, rowsA, 1, (u, v) => + { + var row = new float[columnsA]; + for (int i = u; i < v; i++) + { + GetRow(transposeA, i, rowsA, columnsA, a, row); + for (int j = 0; j < columnsB; j++) + { + var column = columnDataB[j]; + float sum = 0; + for (int ii = 0; ii < row.Length; ii++) + { + sum += row[ii] * column[ii]; + } + + c[j * rowsA + i] += alpha * sum; + } + } + }); + } + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + public virtual void LUFactor(float[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + ipiv[i] = i; + } + + var vecLUcolj = new float[order]; + + // Outer loop. + for (var j = 0; j < order; j++) + { + var indexj = j * order; + var indexjj = indexj + j; + + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vecLUcolj[i] = data[indexj + i]; + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + // Most of the time is spent in the following dot product. + var kmax = Math.Min(i, j); + var s = 0.0f; + for (var k = 0; k < kmax; k++) + { + s += data[(k * order) + i] * vecLUcolj[k]; + } + + data[indexj + i] = vecLUcolj[i] -= s; + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (Math.Abs(vecLUcolj[i]) > Math.Abs(vecLUcolj[p])) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var indexk = k * order; + var indexkp = indexk + p; + var indexkj = indexk + j; + var temp = data[indexkp]; + data[indexkp] = data[indexkj]; + data[indexkj] = temp; + } + + ipiv[j] = p; + } + + // Compute multipliers. + if (j < order & data[indexjj] != 0.0) + { + for (var i = j + 1; i < order; i++) + { + data[indexj + i] /= data[indexjj]; + } + } + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + public virtual void LUInverse(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var ipiv = new int[order]; + LUFactor(a, order, ipiv); + LUInverseFactored(a, order, ipiv); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + public virtual void LUInverseFactored(float[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var inverse = new float[a.Length]; + for (var i = 0; i < order; i++) + { + inverse[i + (order * i)] = 1.0f; + } + + LUSolveFactored(order, a, order, ipiv, inverse); + inverse.Copy(a); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + public virtual void LUSolve(int columnsOfB, float[] a, int order, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var ipiv = new int[order]; + var clone = new float[a.Length]; + a.Copy(clone); + LUFactor(clone, order, ipiv); + LUSolveFactored(columnsOfB, clone, order, ipiv, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + public virtual void LUSolveFactored(int columnsOfB, float[] a, int order, int[] ipiv, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + // Compute the column vector P*B + for (var i = 0; i < ipiv.Length; i++) + { + if (ipiv[i] == i) + { + continue; + } + + var p = ipiv[i]; + for (var j = 0; j < columnsOfB; j++) + { + var indexk = j * order; + var indexkp = indexk + p; + var indexkj = indexk + i; + var temp = b[indexkp]; + b[indexkp] = b[indexkj]; + b[indexkj] = temp; + } + } + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + var korder = k * order; + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + var korder = k + (k * order); + for (var j = 0; j < columnsOfB; j++) + { + b[k + (j * order)] /= a[korder]; + } + + korder = k * order; + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + public virtual void CholeskyFactor(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + var tmpColumn = new float[order]; + + // Main loop - along the diagonal + for (var ij = 0; ij < order; ij++) + { + // "Pivot" element + var tmpVal = a[(ij * order) + ij]; + + if (tmpVal > 0.0) + { + tmpVal = (float)Math.Sqrt(tmpVal); + a[(ij * order) + ij] = tmpVal; + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < order; i++) + { + a[(ij * order) + i] /= tmpVal; + tmpColumn[i] = a[(ij * order) + i]; + } + + // Remaining columns, below the diagonal + DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (int i = ij + 1; i < order; i++) + { + a[(i * order) + ij] = 0.0f; + } + } + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(float[] data, int rowDim, int firstCol, int colLimit, float[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data[(j * rowDim) + i] -= multipliers[i] * tmpVal; + } + } + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + public virtual void CholeskySolve(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var clone = new float[a.Length]; + a.Copy(clone); + CholeskyFactor(clone, orderA); + CholeskySolveFactored(clone, orderA, b, columnsB); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + public virtual void CholeskySolveFactored(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + CommonParallel.For(0, columnsB, (u, v) => + { + for (int i = u; i < v; i++) + { + DoCholeskySolve(a, orderA, b, i); + } + }); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The column to solve for. + static void DoCholeskySolve(float[] a, int orderA, float[] b, int index) + { + var cindex = index * orderA; + + // Solve L*Y = B; + float sum; + for (var i = 0; i < orderA; i++) + { + sum = b[cindex + i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= a[(k * orderA) + i] * b[cindex + k]; + } + + b[cindex + i] = sum / a[(i * orderA) + i]; + } + + // Solve L'*X = Y; + for (var i = orderA - 1; i >= 0; i--) + { + sum = b[cindex + i]; + var iindex = i * orderA; + for (var k = i + 1; k < orderA; k++) + { + sum -= a[iindex + k] * b[cindex + k]; + } + + b[cindex + i] = sum / a[iindex + i]; + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void QRFactor(float[] r, int rowsR, int columnsR, float[] q, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR * rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var work = columnsR > rowsR ? new float[rowsR * rowsR] : new float[rowsR * columnsR]; + + CommonParallel.For(0, rowsR, (a, b) => + { + for (int i = a; i < b; i++) + { + q[(i * rowsR) + i] = 1.0f; + } + }); + + var minmn = Math.Min(rowsR, columnsR); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, r, rowsR, i, i); + ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void ThinQRFactor(float[] a, int rowsA, int columnsA, float[] r, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(a)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var work = new float[rowsA * columnsA]; + + var minmn = Math.Min(rowsA, columnsA); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, a, rowsA, i, i); + ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); + } + + //copy R + for (var j = 0; j < columnsA; j++) + { + var rIndex = j * columnsA; + var aIndex = j * rowsA; + for (var i = 0; i < columnsA; i++) + { + r[rIndex + i] = a[aIndex + i]; + } + } + + //clear A and set diagonals to 1 + Array.Clear(a, 0, a.Length); + for (var i = 0; i < columnsA; i++) + { + a[i * rowsA + i] = 1.0f; + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); + } + } + + #region QR Factor Helper functions + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Index of column in work array + /// Q or R matrices + /// The first row in + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(float[] work, int workIndex, float[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) + { + if (rowStart > rowCount || columnStart > columnCount) + { + return; + } + + var tmpColCount = columnCount - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), + () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); + } + else + { + for (var j = columnStart; j < columnCount; j++) + { + var scale = 0.0f; + for (var i = rowStart; i < rowCount; i++) + { + scale += work[(workIndex * rowCount) + i - rowStart] * a[(j * rowCount) + i]; + } + + for (var i = rowStart; i < rowCount; i++) + { + a[(j * rowCount) + i] -= work[(workIndex * rowCount) + i - rowStart] * scale; + } + } + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Work array + /// Initial matrix + /// The number of rows in matrix + /// The first row + /// Column index + static void GenerateColumn(float[] work, float[] a, int rowCount, int row, int column) + { + var tmp = column * rowCount; + var index = tmp + row; + + CommonParallel.For(row, rowCount, (u, v) => + { + for (int i = u; i < v; i++) + { + var iIndex = tmp + i; + work[iIndex - row] = a[iIndex]; + a[iIndex] = 0.0f; + } + }); + + var norm = 0.0; + for (var i = 0; i < rowCount - row; ++i) + { + var iindex = tmp + i; + norm += work[iindex] * work[iindex]; + } + + norm = Math.Sqrt(norm); + if (row == rowCount - 1 || norm == 0) + { + a[index] = -work[tmp]; + work[tmp] = (float)Constants.Sqrt2; + return; + } + + var scale = 1.0f / (float)norm; + if (work[tmp] < 0.0) + { + scale *= -1.0f; + } + + a[index] = -1.0f / scale; + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] *= scale; + } + }); + work[tmp] += 1.0f; + + var s = (float)Math.Sqrt(1.0 / work[tmp]); + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] *= s; + } + }); + } + + #endregion + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolve(float[] a, int rows, int columns, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new float[rows * columns]; + + var clone = new float[a.Length]; + a.Copy(clone); + + if (method == QRMethod.Full) + { + var q = new float[rows * rows]; + QRFactor(clone, rows, columns, q, work); + QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); + } + else + { + var r = new float[columns * columns]; + ThinQRFactor(clone, rows, columns, r, work); + QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolveFactored(float[] q, float[] r, int rowsA, int columnsA, float[] tau, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (rowsA < columnsA) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + var sol = new float[b.Length]; + + // Copy B matrix to "sol", so B data will not be changed + Buffer.BlockCopy(b, 0, sol, 0, b.Length * Constants.SizeOfFloat); + + // Compute Y = transpose(Q)*B + var column = new float[rowsA]; + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + Array.Copy(sol, jm, column, 0, rowsA); + CommonParallel.For(0, columnsA, (u, v) => + { + for (int i = u; i < v; i++) + { + var im = i * rowsA; + + var sum = 0.0f; + for (var k = 0; k < rowsA; k++) + { + sum += q[im + k] * column[k]; + } + + sol[jm + i] = sum; + } + }); + } + + // Solve R*X = Y; + for (var k = columnsA - 1; k >= 0; k--) + { + var km = k * rowsR; + for (var j = 0; j < columnsB; j++) + { + sol[(j * rowsA) + k] /= r[km + k]; + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + sol[jm + i] -= sol[jm + k] * r[km + i]; + } + } + } + + // Fill result matrix + for (var col = 0; col < columnsB; col++) + { + Array.Copy(sol, col * rowsA, x, col * columnsA, columnsR); + } + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + public virtual void SingularValueDecomposition(bool computeVectors, float[] a, int rowsA, int columnsA, float[] s, float[] u, float[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new float[rowsA]; + + const int maxiter = 1000; + + var e = new float[columnsA]; + var v = new float[vt.Length]; + var stemp = new float[Math.Min(rowsA + 1, columnsA)]; + + int i, j, l, lp1; + + float t; + + var ncu = rowsA; + + // Reduce matrix to bidiagonal form, storing the diagonal elements + // in "s" and the super-diagonal elements in "e". + var nct = Math.Min(rowsA - 1, columnsA); + var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); + var lu = Math.Max(nct, nrt); + + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and + // place the l-th diagonal in vector s[l]. + var l1 = l; + + var sum = 0.0f; + for (var i1 = l; i1 < rowsA; i1++) + { + sum += a[(l1 * rowsA) + i1] * a[(l1 * rowsA) + i1]; + } + + stemp[l] = (float)Math.Sqrt(sum); + + if (stemp[l] != 0.0) + { + if (a[(l * rowsA) + l] != 0.0) + { + stemp[l] = Math.Abs(stemp[l]) * (a[(l * rowsA) + l] / Math.Abs(a[(l * rowsA) + l])); + } + + // A part of column "l" of Matrix A from row "l" to end multiply by 1.0 / s[l] + for (i = l; i < rowsA; i++) + { + a[(l * rowsA) + i] = a[(l * rowsA) + i] * (1.0f / stemp[l]); + } + + a[(l * rowsA) + l] = 1.0f + a[(l * rowsA) + l]; + } + + stemp[l] = -stemp[l]; + } + + for (j = lp1; j < columnsA; j++) + { + if (l < nct) + { + if (stemp[l] != 0.0) + { + // Apply the transformation. + t = 0.0f; + for (i = l; i < rowsA; i++) + { + t += a[(j * rowsA) + i] * a[(l * rowsA) + i]; + } + + t = -t / a[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += t * a[(l * rowsA) + ii]; + } + } + } + + // Place the l-th row of matrix into "e" for the + // subsequent calculation of the row transformation. + e[j] = a[(j * rowsA) + l]; + } + + if (computeVectors && l < nct) + { + // Place the transformation in "u" for subsequent back multiplication. + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = a[(l * rowsA) + i]; + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = 0.0; + for (i = lp1; i < e.Length; i++) + { + enorm += e[i] * e[i]; + } + + e[l] = (float)Math.Sqrt(enorm); + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = Math.Abs(e[l]) * (e[lp1] / Math.Abs(e[lp1])); + } + + // Scale vector "e" from "lp1" by 1.0 / e[l] + for (i = lp1; i < e.Length; i++) + { + e[i] = e[i] * (1.0f / e[l]); + } + + e[lp1] = 1.0f + e[lp1]; + } + + e[l] = -e[l]; + + if (lp1 < rowsA && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < rowsA; i++) + { + work[i] = 0.0f; + } + + for (j = lp1; j < columnsA; j++) + { + for (var ii = lp1; ii < rowsA; ii++) + { + work[ii] += e[j] * a[(j * rowsA) + ii]; + } + } + + for (j = lp1; j < columnsA; j++) + { + var ww = -e[j] / e[lp1]; + for (var ii = lp1; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += ww * work[ii]; + } + } + } + + if (!computeVectors) + { + continue; + } + + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < columnsA; i++) + { + v[(l * columnsA) + i] = e[i]; + } + } + + // Set up the final bidiagonal matrix or order m. + var m = Math.Min(columnsA, rowsA + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < columnsA) + { + stemp[nctp1 - 1] = a[((nctp1 - 1) * rowsA) + (nctp1 - 1)]; + } + + if (rowsA < m) + { + stemp[m - 1] = 0.0f; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = a[((m - 1) * rowsA) + (nrtp1 - 1)]; + } + + e[m - 1] = 0.0f; + + // If required, generate "u". + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < rowsA; i++) + { + u[(j * rowsA) + i] = 0.0f; + } + + u[(j * rowsA) + j] = 1.0f; + } + + for (l = nct - 1; l >= 0; l--) + { + if (stemp[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = 0.0f; + for (i = l; i < rowsA; i++) + { + t += u[(j * rowsA) + i] * u[(l * rowsA) + i]; + } + + t = -t / u[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + u[(j * rowsA) + ii] += t * u[(l * rowsA) + ii]; + } + } + + // A part of column "l" of matrix A from row "l" to end multiply by -1.0 + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = u[(l * rowsA) + i] * -1.0f; + } + + u[(l * rowsA) + l] = 1.0f + u[(l * rowsA) + l]; + for (i = 0; i < l; i++) + { + u[(l * rowsA) + i] = 0.0f; + } + } + else + { + for (i = 0; i < rowsA; i++) + { + u[(l * rowsA) + i] = 0.0f; + } + + u[(l * rowsA) + l] = 1.0f; + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = columnsA - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < columnsA; j++) + { + t = 0.0f; + for (i = lp1; i < columnsA; i++) + { + t += v[(j * columnsA) + i] * v[(l * columnsA) + i]; + } + + t = -t / v[(l * columnsA) + lp1]; + for (var ii = l; ii < columnsA; ii++) + { + v[(j * columnsA) + ii] += t * v[(l * columnsA) + ii]; + } + } + } + } + + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = 0.0f; + } + + v[(l * columnsA) + l] = 1.0f; + } + } + + // Transform "s" and "e" so that they are double + for (i = 0; i < m; i++) + { + float r; + if (stemp[i] != 0.0) + { + t = stemp[i]; + r = stemp[i] / t; + stemp[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + // A part of column "i" of matrix U from row 0 to end multiply by r + for (j = 0; j < rowsA; j++) + { + u[(i * rowsA) + j] = u[(i * rowsA) + j] * r; + } + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] == 0.0) + { + continue; + } + + t = e[i]; + r = t / e[i]; + e[i] = t; + stemp[i + 1] = stemp[i + 1] * r; + if (!computeVectors) + { + continue; + } + + // A part of column "i+1" of matrix VT from row 0 to end multiply by r + for (j = 0; j < columnsA; j++) + { + v[((i + 1) * columnsA) + j] = v[((i + 1) * columnsA) + j] * r; + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. + // If too many iterations have been performed throw exception. + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays, + // on completion the variables case and l are set as follows: + // case = 1: if mS[m] and e[l-1] are negligible and l < m + // case = 2: if mS[l] is negligible and l < m + // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). + // case = 4: if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = Math.Abs(stemp[l]) + Math.Abs(stemp[l + 1]); + ztest = test + Math.Abs(e[l]); + if (ztest.AlmostEqualRelative(test, 7)) + { + e[l] = 0.0f; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + Math.Abs(e[ls]); + } + + if (ls != l + 1) + { + test = test + Math.Abs(e[ls - 1]); + } + + ztest = test + Math.Abs(stemp[ls]); + if (ztest.AlmostEqualRelative(test, 7)) + { + stemp[ls] = 0.0f; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + float f; + float cs; + float sn; + switch (kase) + { + // Deflate negligible s[m]. + case 1: + f = e[m - 2]; + e[m - 2] = 0.0f; + float t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = stemp[k]; + + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + if (k != l) + { + f = -sn * e[k - 1]; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + // Rotate + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((m - 1) * columnsA) + i]); + v[((m - 1) * columnsA) + i] = (cs * v[((m - 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + } + + break; + + // Split at negligible s[l]. + case 2: + f = e[l - 1]; + e[l - 1] = 0.0f; + for (k = l; k < m; k++) + { + t1 = stemp[k]; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + f = -sn * e[k]; + e[k] = cs * e[k]; + if (computeVectors) + { + // Rotate + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((l - 1) * rowsA) + i]); + u[((l - 1) * rowsA) + i] = (cs * u[((l - 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + break; + + // Perform one qr step. + case 3: + + // calculate the shift. + var scale = 0.0f; + scale = Math.Max(scale, Math.Abs(stemp[m - 1])); + scale = Math.Max(scale, Math.Abs(stemp[m - 2])); + scale = Math.Max(scale, Math.Abs(e[m - 2])); + scale = Math.Max(scale, Math.Abs(stemp[l])); + scale = Math.Max(scale, Math.Abs(e[l])); + var sm = stemp[m - 1] / scale; + var smm1 = stemp[m - 2] / scale; + var emm1 = e[m - 2] / scale; + var sl = stemp[l] / scale; + var el = e[l] / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0f; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0f; + if (b != 0.0 || c != 0.0) + { + shift = (float)Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * stemp[k]) + (sn * e[k]); + e[k] = (cs * e[k]) - (sn * stemp[k]); + g = sn * stemp[k + 1]; + stemp[k + 1] = cs * stemp[k + 1]; + if (computeVectors) + { + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((k + 1) * columnsA) + i]); + v[((k + 1) * columnsA) + i] = (cs * v[((k + 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + + Drotg(ref f, ref g, out cs, out sn); + stemp[k] = f; + f = (cs * e[k]) + (sn * stemp[k + 1]); + stemp[k + 1] = -(sn * e[k]) + (cs * stemp[k + 1]); + g = sn * e[k + 1]; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < rowsA) + { + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((k + 1) * rowsA) + i]); + u[((k + 1) * rowsA) + i] = (cs * u[((k + 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence + case 4: + + // Make the singular value positive + if (stemp[l] < 0.0) + { + stemp[l] = -stemp[l]; + if (computeVectors) + { + // A part of column "l" of matrix VT from row 0 to end multiply by -1 + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = v[(l * columnsA) + i] * -1.0f; + } + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (stemp[l] >= stemp[l + 1]) + { + break; + } + + t = stemp[l]; + stemp[l] = stemp[l + 1]; + stemp[l + 1] = t; + if (computeVectors && l < columnsA) + { + // Swap columns l, l + 1 + for (i = 0; i < columnsA; i++) + { + var z = v[(l * columnsA) + i]; + v[(l * columnsA) + i] = v[((l + 1) * columnsA) + i]; + v[((l + 1) * columnsA) + i] = z; + } + } + + if (computeVectors && l < rowsA) + { + // Swap columns l, l + 1 + for (i = 0; i < rowsA; i++) + { + var z = u[(l * rowsA) + i]; + u[(l * rowsA) + i] = u[((l + 1) * rowsA) + i]; + u[((l + 1) * rowsA) + i] = z; + } + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + // Finally transpose "v" to get "vt" matrix + for (i = 0; i < columnsA; i++) + { + for (j = 0; j < columnsA; j++) + { + vt[(j * columnsA) + i] = v[(i * columnsA) + j]; + } + } + } + + // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses + // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. + // We should port lapack's svd routine to remove this problem. + Buffer.BlockCopy(stemp, 0, s, 0, Math.Min(rowsA, columnsA) * Constants.SizeOfFloat); + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + static void Drotg(ref float da, ref float db, out float c, out float s) + { + float r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0) + { + c = 1.0f; + s = 0.0f; + r = 0.0f; + z = 0.0f; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * (float)Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0f; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0) + { + z = 1.0f / c; + } + } + + da = r; + db = z; + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolve(float[] a, int rowsA, int columnsA, float[] b, int columnsB, float[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new float[Math.Min(rowsA, columnsA)]; + var u = new float[rowsA * rowsA]; + var vt = new float[columnsA * columnsA]; + + var clone = new float[a.Length]; + Buffer.BlockCopy(a, 0, clone, 0, a.Length * Constants.SizeOfFloat); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolveFactored(int rowsA, int columnsA, float[] s, float[] u, float[] vt, float[] b, int columnsB, float[] x) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var mn = Math.Min(rowsA, columnsA); + var tmp = new float[columnsA]; + + for (var k = 0; k < columnsB; k++) + { + for (var j = 0; j < columnsA; j++) + { + float value = 0; + if (j < mn) + { + for (var i = 0; i < rowsA; i++) + { + value += u[(j * rowsA) + i] * b[(k * rowsA) + i]; + } + + value /= s[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < columnsA; j++) + { + float value = 0; + for (var i = 0; i < columnsA; i++) + { + value += vt[(j * columnsA) + i] * tmp[i]; + } + + x[(k * columnsA) + j] = value; + } + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public virtual void EigenDecomp(bool isSymmetric, int order, float[] matrix, float[] matrixEv, Complex[] vectorEv, float[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var d = new float[order]; + var e = new float[order]; + + if (isSymmetric) + { + Buffer.BlockCopy(matrix, 0, matrixEv, 0, matrix.Length * Constants.SizeOfFloat); + var om1 = order - 1; + for (var i = 0; i < order; i++) + { + d[i] = matrixEv[i * order + om1]; + } + + Numerics.LinearAlgebra.Single.Factorization.DenseEvd.SymmetricTridiagonalize(matrixEv, d, e, order); + Numerics.LinearAlgebra.Single.Factorization.DenseEvd.SymmetricDiagonalize(matrixEv, d, e, order); + } + else + { + var matrixH = new float[matrix.Length]; + Buffer.BlockCopy(matrix, 0, matrixH, 0, matrix.Length * Constants.SizeOfFloat); + Numerics.LinearAlgebra.Single.Factorization.DenseEvd.NonsymmetricReduceToHessenberg(matrixEv, matrixH, order); + Numerics.LinearAlgebra.Single.Factorization.DenseEvd.NonsymmetricReduceHessenberToRealSchur(matrixEv, matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(d[i], e[i]); + + var io = i * order; + matrixD[io + i] = d[i]; + + if (e[i] > 0) + { + matrixD[io + order + i] = e[i]; + } + else if (e[i] < 0) + { + matrixD[io - order + i] = e[i]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.cs b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.cs new file mode 100644 index 0000000..d288ac2 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Managed/ManagedLinearAlgebraProvider.cs @@ -0,0 +1,103 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Managed; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedLinearAlgebraProvider : ILinearAlgebraProvider +{ + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + public virtual bool IsAvailable() + { + return true; + } + + /// + /// Initialize and verify that the provided is indeed available. If not, fall back to alternatives like the managed provider + /// + public virtual void InitializeVerify() + { + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + public virtual void FreeResources() + { + } + + public override string ToString() + { + return "Managed"; + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetRow(Transpose transpose, int rowindx, int numRows, int numCols, T[] matrix, T[] row) + { + if (transpose == Transpose.DontTranspose) + { + for (int i = 0; i < numCols; i++) + { + row[i] = matrix[(i * numRows) + rowindx]; + } + } + else + { + Array.Copy(matrix, rowindx * numCols, row, 0, numCols); + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetColumn(Transpose transpose, int colindx, int numRows, int numCols, T[] matrix, T[] column) + { + if (transpose == Transpose.DontTranspose) + { + Array.Copy(matrix, colindx * numRows, column, 0, numRows); + } + else + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Complex.cs b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Complex.cs new file mode 100644 index 0000000..2addcb2 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Complex.cs @@ -0,0 +1,2822 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Complex.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.ManagedReference; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedReferenceLinearAlgebraProvider +{ + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + public virtual void AddVectorToScaledVector(Complex[] y, Complex alpha, Complex[] x, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (alpha.IsZero()) + { + y.Copy(result); + } + else if (alpha.IsOne()) + { + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = y[i] + x[i]; + } + }); + } + else + { + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = y[i] + (alpha * x[i]); + } + }); + } + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + public virtual void ScaleArray(Complex alpha, Complex[] x, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (alpha.IsZero()) + { + Array.Clear(result, 0, result.Length); + } + else if (alpha.IsOne()) + { + x.Copy(result); + } + else + { + CommonParallel.For(0, x.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = alpha * x[i]; + } + }); + } + } + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + public virtual void ConjugateArray(Complex[] x, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + CommonParallel.For(0, x.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i].Conjugate(); + } + }); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + public virtual Complex DotProduct(Complex[] x, Complex[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var dot = Complex.Zero; + for (var index = 0; index < y.Length; index++) + { + dot += y[index] * x[index]; + } + + return dot; + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void AddArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] + y[i]; + } + }); + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void SubtractArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] - y[i]; + } + }); + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseMultiplyArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] * y[i]; + } + }); + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseDivideArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] / y[i]; + } + }); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWisePowerArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = Complex.Pow(x[i], y[i]); + } + }); + } + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + public virtual double MatrixNorm(Norm norm, int rows, int columns, Complex[] matrix) + { + switch (norm) + { + case Norm.OneNorm: + var norm1 = 0d; + for (var j = 0; j < columns; j++) + { + var s = 0.0; + for (var i = 0; i < rows; i++) + { + s += matrix[(j * rows) + i].Magnitude; + } + + norm1 = Math.Max(norm1, s); + } + + return norm1; + case Norm.LargestAbsoluteValue: + var normMax = 0d; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + normMax = Math.Max(matrix[(j * rows) + i].Magnitude, normMax); + } + } + + return normMax; + case Norm.InfinityNorm: + var r = new double[rows]; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + r[i] += matrix[(j * rows) + i].Magnitude; + } + } + // TODO: reuse + var max = r[0]; + for (int i = 0; i < r.Length; i++) + { + if (r[i] > max) + { + max = r[i]; + } + } + + return max; + case Norm.FrobeniusNorm: + var aat = new Complex[rows * rows]; + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.ConjugateTranspose, 1.0, matrix, rows, columns, matrix, rows, columns, 0.0, aat); + var normF = 0d; + for (var i = 0; i < rows; i++) + { + normF += aat[(i * rows) + i].Magnitude; + } + + return Math.Sqrt(normF); + default: + throw new NotSupportedException(); + } + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public virtual void MatrixMultiply(Complex[] x, int rowsX, int columnsX, Complex[] y, int rowsY, int columnsY, Complex[] result) + { + // First check some basic requirement on the parameters of the matrix multiplication. + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (rowsX * columnsX != x.Length) + { + throw new ArgumentException("x.Length != xRows * xColumns"); + } + + if (rowsY * columnsY != y.Length) + { + throw new ArgumentException("y.Length != yRows * yColumns"); + } + + if (columnsX != rowsY) + { + throw new ArgumentException("xColumns != yRows"); + } + + if (rowsX * columnsY != result.Length) + { + throw new ArgumentException("xRows * yColumns != result.Length"); + } + + // Check whether we will be overwriting any of our inputs and make copies if necessary. + // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory + // as result, we can do it on a row wise basis. We should investigate this. + Complex[] xdata; + if (ReferenceEquals(x, result)) + { + xdata = (Complex[])x.Clone(); + } + else + { + xdata = x; + } + + Complex[] ydata; + if (ReferenceEquals(y, result)) + { + ydata = (Complex[])y.Clone(); + } + else + { + ydata = y; + } + + Array.Clear(result, 0, result.Length); + + CacheObliviousMatrixMultiply(Transpose.DontTranspose, Transpose.DontTranspose, Complex.One, xdata, 0, 0, ydata, 0, 0, result, 0, 0, rowsX, columnsY, columnsX, rowsX, columnsY, columnsX, true); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex alpha, Complex[] a, int rowsA, int columnsA, Complex[] b, int rowsB, int columnsB, Complex beta, Complex[] c) + { + int m; // The number of rows of matrix op(A) and of the matrix C. + int n; // The number of columns of matrix op(B) and of the matrix C. + int k; // The number of columns of matrix op(A) and the rows of the matrix op(B). + + // First check some basic requirement on the parameters of the matrix multiplication. + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if ((int)transposeA > 111 && (int)transposeB > 111) + { + if (rowsA != columnsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (columnsA * rowsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = columnsA; + n = rowsB; + k = rowsA; + } + else if ((int)transposeA > 111) + { + if (rowsA != rowsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (columnsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = columnsA; + n = columnsB; + k = rowsA; + } + else if ((int)transposeB > 111) + { + if (columnsA != columnsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (rowsA * rowsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = rowsA; + n = rowsB; + k = columnsA; + } + else + { + if (columnsA != rowsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (rowsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = rowsA; + n = columnsB; + k = columnsA; + } + + if (alpha.IsZero() && beta.IsZero()) + { + Array.Clear(c, 0, c.Length); + return; + } + + // Check whether we will be overwriting any of our inputs and make copies if necessary. + // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory + // as result, we can do it on a row wise basis. We should investigate this. + Complex[] adata; + if (ReferenceEquals(a, c)) + { + adata = (Complex[])a.Clone(); + } + else + { + adata = a; + } + + Complex[] bdata; + if (ReferenceEquals(b, c)) + { + bdata = (Complex[])b.Clone(); + } + else + { + bdata = b; + } + + if (beta.IsZero()) + { + Array.Clear(c, 0, c.Length); + } + else if (!beta.IsOne()) + { + ScaleArray(beta, c, c); + } + + if (alpha.IsZero()) + { + return; + } + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, adata, 0, 0, bdata, 0, 0, c, 0, 0, m, n, k, m, n, k, true); + } + + /// + /// Cache-Oblivious Matrix Multiplication + /// + /// if set to true transpose matrix A. + /// if set to true transpose matrix B. + /// The value to scale the matrix A with. + /// The matrix A. + /// Row-shift of the left matrix + /// Column-shift of the left matrix + /// The matrix B. + /// Row-shift of the right matrix + /// Column-shift of the right matrix + /// The matrix C. + /// Row-shift of the result matrix + /// Column-shift of the result matrix + /// The number of rows of matrix op(A) and of the matrix C. + /// The number of columns of matrix op(B) and of the matrix C. + /// The number of columns of matrix op(A) and the rows of the matrix op(B). + /// The constant number of rows of matrix op(A) and of the matrix C. + /// The constant number of columns of matrix op(B) and of the matrix C. + /// The constant number of columns of matrix op(A) and the rows of the matrix op(B). + /// Indicates if this is the first recursion. + static void CacheObliviousMatrixMultiply(Transpose transposeA, Transpose transposeB, Complex alpha, Complex[] matrixA, int shiftArow, int shiftAcol, Complex[] matrixB, int shiftBrow, int shiftBcol, Complex[] result, int shiftCrow, int shiftCcol, int m, int n, int k, int constM, int constN, int constK, bool first) + { + if (m + n <= Control.ParallelizeOrder || m == 1 || n == 1 || k == 1) + { + if ((int)transposeA > 111 && (int)transposeB > 111) + { + if ((int)transposeA > 112 && (int)transposeB > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol].Conjugate() * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos].Conjugate(); + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else if ((int)transposeA > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol].Conjugate() * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else if ((int)transposeB > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos].Conjugate(); + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else if ((int)transposeA > 111) + { + if ((int)transposeA > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol].Conjugate() * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else if ((int)transposeB > 111) + { + if ((int)transposeB > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos].Conjugate(); + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else + { + // divide and conquer + int m2 = m / 2, n2 = n / 2, k2 = k / 2; + + if (first) + { + CommonParallel.Invoke( + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false)); + + CommonParallel.Invoke( + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false)); + } + else + { + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false); + } + } + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + public virtual void LUFactor(Complex[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + ipiv[i] = i; + } + + var vecLUcolj = new Complex[order]; + + // Outer loop. + for (var j = 0; j < order; j++) + { + var indexj = j * order; + var indexjj = indexj + j; + + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vecLUcolj[i] = data[indexj + i]; + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + // Most of the time is spent in the following dot product. + var kmax = Math.Min(i, j); + var s = Complex.Zero; + for (var k = 0; k < kmax; k++) + { + s += data[(k * order) + i] * vecLUcolj[k]; + } + + data[indexj + i] = vecLUcolj[i] -= s; + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (vecLUcolj[i].Magnitude > vecLUcolj[p].Magnitude) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var indexk = k * order; + var indexkp = indexk + p; + var indexkj = indexk + j; + var temp = data[indexkp]; + data[indexkp] = data[indexkj]; + data[indexkj] = temp; + } + + ipiv[j] = p; + } + + // Compute multipliers. + if (j < order & data[indexjj] != 0.0) + { + for (var i = j + 1; i < order; i++) + { + data[indexj + i] /= data[indexjj]; + } + } + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + public virtual void LUInverse(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var ipiv = new int[order]; + LUFactor(a, order, ipiv); + LUInverseFactored(a, order, ipiv); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + public virtual void LUInverseFactored(Complex[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var inverse = new Complex[a.Length]; + for (var i = 0; i < order; i++) + { + inverse[i + (order * i)] = Complex.One; + } + + LUSolveFactored(order, a, order, ipiv, inverse); + inverse.Copy(a); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + public virtual void LUSolve(int columnsOfB, Complex[] a, int order, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var ipiv = new int[order]; + var clone = new Complex[a.Length]; + a.Copy(clone); + LUFactor(clone, order, ipiv); + LUSolveFactored(columnsOfB, clone, order, ipiv, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + public virtual void LUSolveFactored(int columnsOfB, Complex[] a, int order, int[] ipiv, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + // Compute the column vector P*B + for (var i = 0; i < ipiv.Length; i++) + { + if (ipiv[i] == i) + { + continue; + } + + var p = ipiv[i]; + for (var j = 0; j < columnsOfB; j++) + { + var indexk = j * order; + var indexkp = indexk + p; + var indexkj = indexk + i; + var temp = b[indexkp]; + b[indexkp] = b[indexkj]; + b[indexkj] = temp; + } + } + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + var korder = k * order; + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + var korder = k + (k * order); + for (var j = 0; j < columnsOfB; j++) + { + b[k + (j * order)] /= a[korder]; + } + + korder = k * order; + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + public virtual void CholeskyFactor(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + var tmpColumn = new Complex[order]; + + // Main loop - along the diagonal + for (var ij = 0; ij < order; ij++) + { + // "Pivot" element + var tmpVal = a[(ij * order) + ij]; + + if (tmpVal.Real > 0.0) + { + tmpVal = tmpVal.SquareRoot(); + a[(ij * order) + ij] = tmpVal; + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < order; i++) + { + a[(ij * order) + i] /= tmpVal; + tmpColumn[i] = a[(ij * order) + i]; + } + + // Remaining columns, below the diagonal + DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (var i = ij + 1; i < order; i++) + { + a[(i * order) + ij] = 0.0; + } + } + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(Complex[] data, int rowDim, int firstCol, int colLimit, Complex[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data[(j * rowDim) + i] -= multipliers[i] * tmpVal.Conjugate(); + } + } + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + public virtual void CholeskySolve(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var clone = new Complex[a.Length]; + a.Copy(clone); + CholeskyFactor(clone, orderA); + CholeskySolveFactored(clone, orderA, b, columnsB); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// The B matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + public virtual void CholeskySolveFactored(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + CommonParallel.For(0, columnsB, (u, v) => + { + for (int i = u; i < v; i++) + { + DoCholeskySolve(a, orderA, b, i); + } + }); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The column to solve for. + static void DoCholeskySolve(Complex[] a, int orderA, Complex[] b, int index) + { + var cindex = index * orderA; + + // Solve L*Y = B; + Complex sum; + for (var i = 0; i < orderA; i++) + { + sum = b[cindex + i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= a[(k * orderA) + i] * b[cindex + k]; + } + + b[cindex + i] = sum / a[(i * orderA) + i]; + } + + // Solve L'*X = Y; + for (var i = orderA - 1; i >= 0; i--) + { + sum = b[cindex + i]; + var iindex = i * orderA; + for (var k = i + 1; k < orderA; k++) + { + sum -= a[iindex + k].Conjugate() * b[cindex + k]; + } + + b[cindex + i] = sum / a[iindex + i]; + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void QRFactor(Complex[] r, int rowsR, int columnsR, Complex[] q, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR * rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var work = columnsR > rowsR ? new Complex[rowsR * rowsR] : new Complex[rowsR * columnsR]; + + CommonParallel.For(0, rowsR, (a, b) => + { + for (int i = a; i < b; i++) + { + q[(i * rowsR) + i] = Complex.One; + } + }); + + var minmn = Math.Min(rowsR, columnsR); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, r, rowsR, i, i); + ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void ThinQRFactor(Complex[] a, int rowsA, int columnsA, Complex[] r, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(a)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var work = new Complex[rowsA * columnsA]; + + var minmn = Math.Min(rowsA, columnsA); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, a, rowsA, i, i); + ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); + } + + //copy R + for (var j = 0; j < columnsA; j++) + { + var rIndex = j * columnsA; + var aIndex = j * rowsA; + for (var i = 0; i < columnsA; i++) + { + r[rIndex + i] = a[aIndex + i]; + } + } + + //clear A and set diagonals to 1 + Array.Clear(a, 0, a.Length); + for (var i = 0; i < columnsA; i++) + { + a[i * rowsA + i] = Complex.One; + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); + } + } + + #region QR Factor Helper functions + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Index of column in work array + /// Q or R matrices + /// The first row in + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(Complex[] work, int workIndex, Complex[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) + { + if (rowStart > rowCount || columnStart > columnCount) + { + return; + } + + var tmpColCount = columnCount - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), + () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); + } + else + { + for (var j = columnStart; j < columnCount; j++) + { + var scale = Complex.Zero; + for (var i = rowStart; i < rowCount; i++) + { + scale += work[(workIndex * rowCount) + i - rowStart] * a[(j * rowCount) + i]; + } + + for (var i = rowStart; i < rowCount; i++) + { + a[(j * rowCount) + i] -= work[(workIndex * rowCount) + i - rowStart].Conjugate() * scale; + } + } + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Work array + /// Initial matrix + /// The number of rows in matrix + /// The first row + /// Column index + static void GenerateColumn(Complex[] work, Complex[] a, int rowCount, int row, int column) + { + var tmp = column * rowCount; + var index = tmp + row; + + CommonParallel.For(row, rowCount, (u, v) => + { + for (int i = u; i < v; i++) + { + var iIndex = tmp + i; + work[iIndex - row] = a[iIndex]; + a[iIndex] = Complex.Zero; + } + }); + + var norm = Complex.Zero; + for (var i = 0; i < rowCount - row; ++i) + { + var index1 = tmp + i; + norm += work[index1].Magnitude * work[index1].Magnitude; + } + + norm = norm.SquareRoot(); + if (row == rowCount - 1 || norm.Magnitude == 0) + { + a[index] = -work[tmp]; + work[tmp] = new Complex(2.0, 0).SquareRoot(); + return; + } + + if (work[tmp].Magnitude != 0.0) + { + norm = norm.Magnitude * (work[tmp] / work[tmp].Magnitude); + } + + a[index] = -norm; + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] /= norm; + } + }); + work[tmp] += 1.0; + + var s = (1.0 / work[tmp]).SquareRoot(); + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] = work[tmp + i].Conjugate() * s; + } + }); + } + + #endregion + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolve(Complex[] a, int rows, int columns, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + var work = new Complex[rows * columns]; + + var clone = new Complex[a.Length]; + a.Copy(clone); + + if (method == QRMethod.Full) + { + var q = new Complex[rows * rows]; + QRFactor(clone, rows, columns, q, work); + QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); + } + else + { + var r = new Complex[columns * columns]; + ThinQRFactor(clone, rows, columns, r, work); + QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolveFactored(Complex[] q, Complex[] r, int rowsA, int columnsA, Complex[] tau, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (rowsA < columnsA) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + var sol = new Complex[b.Length]; + + // Copy B matrix to "sol", so B data will not be changed + Array.Copy(b, 0, sol, 0, b.Length); + + // Compute Y = transpose(Q)*B + var column = new Complex[rowsA]; + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + Array.Copy(sol, jm, column, 0, rowsA); + CommonParallel.For(0, columnsA, (u, v) => + { + for (int i = u; i < v; i++) + { + var im = i * rowsA; + + var sum = Complex.Zero; + for (var k = 0; k < rowsA; k++) + { + sum += q[im + k].Conjugate() * column[k]; + } + + sol[jm + i] = sum; + } + }); + } + + // Solve R*X = Y; + for (var k = columnsA - 1; k >= 0; k--) + { + var km = k * rowsR; + for (var j = 0; j < columnsB; j++) + { + sol[(j * rowsA) + k] /= r[km + k]; + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + sol[jm + i] -= sol[jm + k] * r[km + i]; + } + } + } + + // Fill result matrix + for (var col = 0; col < columnsB; col++) + { + Array.Copy(sol, col * rowsA, x, col * columnsA, columnsR); + } + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + public virtual void SingularValueDecomposition(bool computeVectors, Complex[] a, int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new Complex[rowsA]; + + const int maxiter = 1000; + + var e = new Complex[columnsA]; + var v = new Complex[vt.Length]; + var stemp = new Complex[Math.Min(rowsA + 1, columnsA)]; + + int i, j, l, lp1; + + Complex t; + + var ncu = rowsA; + + // Reduce matrix to bidiagonal form, storing the diagonal elements + // in "s" and the super-diagonal elements in "e". + var nct = Math.Min(rowsA - 1, columnsA); + var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); + var lu = Math.Max(nct, nrt); + + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and + // place the l-th diagonal in vector s[l]. + var sum = 0.0; + for (i = l; i < rowsA; i++) + { + sum += a[(l * rowsA) + i].Magnitude * a[(l * rowsA) + i].Magnitude; + } + + stemp[l] = Math.Sqrt(sum); + if (stemp[l] != 0.0) + { + if (a[(l * rowsA) + l] != 0.0) + { + stemp[l] = stemp[l].Magnitude * (a[(l * rowsA) + l] / a[(l * rowsA) + l].Magnitude); + } + + // A part of column "l" of Matrix A from row "l" to end multiply by 1.0 / s[l] + for (i = l; i < rowsA; i++) + { + a[(l * rowsA) + i] = a[(l * rowsA) + i] * (1.0 / stemp[l]); + } + + a[(l * rowsA) + l] = 1.0 + a[(l * rowsA) + l]; + } + + stemp[l] = -stemp[l]; + } + + for (j = lp1; j < columnsA; j++) + { + if (l < nct) + { + if (stemp[l] != 0.0) + { + // Apply the transformation. + t = 0.0; + for (i = l; i < rowsA; i++) + { + t += a[(l * rowsA) + i].Conjugate() * a[(j * rowsA) + i]; + } + + t = -t / a[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += t * a[(l * rowsA) + ii]; + } + } + } + + // Place the l-th row of matrix into "e" for the + // subsequent calculation of the row transformation. + e[j] = a[(j * rowsA) + l].Conjugate(); + } + + if (computeVectors && l < nct) + { + // Place the transformation in "u" for subsequent back multiplication. + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = a[(l * rowsA) + i]; + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = 0.0; + for (i = lp1; i < e.Length; i++) + { + enorm += e[i].Magnitude * e[i].Magnitude; + } + + e[l] = Math.Sqrt(enorm); + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = e[l].Magnitude * (e[lp1] / e[lp1].Magnitude); + } + + // Scale vector "e" from "lp1" by 1.0 / e[l] + for (i = lp1; i < e.Length; i++) + { + e[i] = e[i] * (1.0 / e[l]); + } + + e[lp1] = 1.0 + e[lp1]; + } + + e[l] = -e[l].Conjugate(); + + if (lp1 < rowsA && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < rowsA; i++) + { + work[i] = 0.0; + } + + for (j = lp1; j < columnsA; j++) + { + for (var ii = lp1; ii < rowsA; ii++) + { + work[ii] += e[j] * a[(j * rowsA) + ii]; + } + } + + for (j = lp1; j < columnsA; j++) + { + var ww = (-e[j] / e[lp1]).Conjugate(); + for (var ii = lp1; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += ww * work[ii]; + } + } + } + + if (!computeVectors) + { + continue; + } + + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < columnsA; i++) + { + v[(l * columnsA) + i] = e[i]; + } + } + + // Set up the final bidiagonal matrix or order m. + var m = Math.Min(columnsA, rowsA + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < columnsA) + { + stemp[nctp1 - 1] = a[((nctp1 - 1) * rowsA) + (nctp1 - 1)]; + } + + if (rowsA < m) + { + stemp[m - 1] = 0.0; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = a[((m - 1) * rowsA) + (nrtp1 - 1)]; + } + + e[m - 1] = 0.0; + + // If required, generate "u". + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < rowsA; i++) + { + u[(j * rowsA) + i] = 0.0; + } + + u[(j * rowsA) + j] = 1.0; + } + + for (l = nct - 1; l >= 0; l--) + { + if (stemp[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = 0.0; + for (i = l; i < rowsA; i++) + { + t += u[(l * rowsA) + i].Conjugate() * u[(j * rowsA) + i]; + } + + t = -t / u[(l * rowsA) + l]; + for (var ii = l; ii < rowsA; ii++) + { + u[(j * rowsA) + ii] += t * u[(l * rowsA) + ii]; + } + } + + // A part of column "l" of matrix A from row "l" to end multiply by -1.0 + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = u[(l * rowsA) + i] * -1.0; + } + + u[(l * rowsA) + l] = 1.0 + u[(l * rowsA) + l]; + for (i = 0; i < l; i++) + { + u[(l * rowsA) + i] = 0.0; + } + } + else + { + for (i = 0; i < rowsA; i++) + { + u[(l * rowsA) + i] = 0.0; + } + + u[(l * rowsA) + l] = 1.0; + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = columnsA - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < columnsA; j++) + { + t = 0.0; + for (i = lp1; i < columnsA; i++) + { + t += v[(l * columnsA) + i].Conjugate() * v[(j * columnsA) + i]; + } + + t = -t / v[(l * columnsA) + lp1]; + for (var ii = l; ii < columnsA; ii++) + { + v[(j * columnsA) + ii] += t * v[(l * columnsA) + ii]; + } + } + } + } + + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = 0.0; + } + + v[(l * columnsA) + l] = 1.0; + } + } + + // Transform "s" and "e" so that they are double + for (i = 0; i < m; i++) + { + Complex r; + if (stemp[i] != 0.0) + { + t = stemp[i].Magnitude; + r = stemp[i] / t; + stemp[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + // A part of column "i" of matrix U from row 0 to end multiply by r + for (j = 0; j < rowsA; j++) + { + u[(i * rowsA) + j] = u[(i * rowsA) + j] * r; + } + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] == 0.0) + { + continue; + } + + t = e[i].Magnitude; + r = t / e[i]; + e[i] = t; + stemp[i + 1] = stemp[i + 1] * r; + if (!computeVectors) + { + continue; + } + + // A part of column "i+1" of matrix VT from row 0 to end multiply by r + for (j = 0; j < columnsA; j++) + { + v[((i + 1) * columnsA) + j] = v[((i + 1) * columnsA) + j] * r; + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. + // If too many iterations have been performed throw exception. + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays, + // on completion the variables case and l are set as follows: + // case = 1: if mS[m] and e[l-1] are negligible and l < m + // case = 2: if mS[l] is negligible and l < m + // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). + // case = 4: if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = stemp[l].Magnitude + stemp[l + 1].Magnitude; + ztest = test + e[l].Magnitude; + if (ztest.AlmostEqualRelative(test, 15)) + { + e[l] = 0.0; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + e[ls].Magnitude; + } + + if (ls != l + 1) + { + test = test + e[ls - 1].Magnitude; + } + + ztest = test + stemp[ls].Magnitude; + if (ztest.AlmostEqualRelative(test, 15)) + { + stemp[ls] = 0.0; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + double f; + double cs; + double sn; + switch (kase) + { + // Deflate negligible s[m]. + case 1: + f = e[m - 2].Real; + e[m - 2] = 0.0; + double t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = stemp[k].Real; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + if (k != l) + { + f = -sn * e[k - 1].Real; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + // Rotate + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((m - 1) * columnsA) + i]); + v[((m - 1) * columnsA) + i] = (cs * v[((m - 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + } + + break; + + // Split at negligible s[l]. + case 2: + f = e[l - 1].Real; + e[l - 1] = 0.0; + for (k = l; k < m; k++) + { + t1 = stemp[k].Real; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + f = -sn * e[k].Real; + e[k] = cs * e[k]; + if (computeVectors) + { + // Rotate + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((l - 1) * rowsA) + i]); + u[((l - 1) * rowsA) + i] = (cs * u[((l - 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + break; + + // Perform one qr step. + case 3: + // calculate the shift. + var scale = 0.0; + scale = Math.Max(scale, stemp[m - 1].Magnitude); + scale = Math.Max(scale, stemp[m - 2].Magnitude); + scale = Math.Max(scale, e[m - 2].Magnitude); + scale = Math.Max(scale, stemp[l].Magnitude); + scale = Math.Max(scale, e[l].Magnitude); + var sm = stemp[m - 1].Real / scale; + var smm1 = stemp[m - 2].Real / scale; + var emm1 = e[m - 2].Real / scale; + var sl = stemp[l].Real / scale; + var el = e[l].Real / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0; + if (b != 0.0 || c != 0.0) + { + shift = Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * stemp[k].Real) + (sn * e[k].Real); + e[k] = (cs * e[k]) - (sn * stemp[k]); + g = sn * stemp[k + 1].Real; + stemp[k + 1] = cs * stemp[k + 1]; + if (computeVectors) + { + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((k + 1) * columnsA) + i]); + v[((k + 1) * columnsA) + i] = (cs * v[((k + 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + + Drotg(ref f, ref g, out cs, out sn); + stemp[k] = f; + f = (cs * e[k].Real) + (sn * stemp[k + 1].Real); + stemp[k + 1] = -(sn * e[k]) + (cs * stemp[k + 1]); + g = sn * e[k + 1].Real; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < rowsA) + { + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((k + 1) * rowsA) + i]); + u[((k + 1) * rowsA) + i] = (cs * u[((k + 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence + case 4: + + // Make the singular value positive + if (stemp[l].Real < 0.0) + { + stemp[l] = -stemp[l]; + if (computeVectors) + { + // A part of column "l" of matrix VT from row 0 to end multiply by -1 + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = v[(l * columnsA) + i] * -1.0; + } + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (stemp[l].Real >= stemp[l + 1].Real) + { + break; + } + + t = stemp[l]; + stemp[l] = stemp[l + 1]; + stemp[l + 1] = t; + if (computeVectors && l < columnsA) + { + // Swap columns l, l + 1 + for (i = 0; i < columnsA; i++) + { + var z = v[(l * columnsA) + i]; + v[(l * columnsA) + i] = v[((l + 1) * columnsA) + i]; + v[((l + 1) * columnsA) + i] = z; + } + } + + if (computeVectors && l < rowsA) + { + // Swap columns l, l + 1 + for (i = 0; i < rowsA; i++) + { + var z = u[(l * rowsA) + i]; + u[(l * rowsA) + i] = u[((l + 1) * rowsA) + i]; + u[((l + 1) * rowsA) + i] = z; + } + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + // Finally transpose "v" to get "vt" matrix + for (i = 0; i < columnsA; i++) + { + for (j = 0; j < columnsA; j++) + { + vt[(j * columnsA) + i] = v[(i * columnsA) + j].Conjugate(); + } + } + } + + // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses + // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. + // We should port lapack's svd routine to remove this problem. + Array.Copy(stemp, 0, s, 0, Math.Min(rowsA, columnsA)); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolve(Complex[] a, int rowsA, int columnsA, Complex[] b, int columnsB, Complex[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex[Math.Min(rowsA, columnsA)]; + var u = new Complex[rowsA * rowsA]; + var vt = new Complex[columnsA * columnsA]; + + var clone = new Complex[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolveFactored(int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt, Complex[] b, int columnsB, Complex[] x) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var mn = Math.Min(rowsA, columnsA); + var tmp = new Complex[columnsA]; + + for (var k = 0; k < columnsB; k++) + { + for (var j = 0; j < columnsA; j++) + { + var value = Complex.Zero; + if (j < mn) + { + for (var i = 0; i < rowsA; i++) + { + value += u[(j * rowsA) + i].Conjugate() * b[(k * rowsA) + i]; + } + + value /= s[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < columnsA; j++) + { + var value = Complex.Zero; + for (var i = 0; i < columnsA; i++) + { + value += vt[(j * columnsA) + i].Conjugate() * tmp[i]; + } + + x[(k * columnsA) + j] = value; + } + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public virtual void EigenDecomp(bool isSymmetric, int order, Complex[] matrix, Complex[] matrixEv, Complex[] vectorEv, Complex[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var matrixCopy = new Complex[matrix.Length]; + Array.Copy(matrix, 0, matrixCopy, 0, matrix.Length); + + if (isSymmetric) + { + var tau = new Complex[order]; + var d = new double[order]; + var e = new double[order]; + + DenseEvd.SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + DenseEvd.SymmetricDiagonalize(matrixEv, d, e, order); + DenseEvd.SymmetricUntridiagonalize(matrixEv, matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(d[i], e[i]); + } + } + else + { + DenseEvd.NonsymmetricReduceToHessenberg(matrixEv, matrixCopy, order); + DenseEvd.NonsymmetricReduceHessenberToRealSchur(vectorEv, matrixEv, matrixCopy, order); + } + + for (var i = 0; i < order; i++) + { + matrixD[i * order + i] = vectorEv[i]; + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetRow(Transpose transpose, int rowindx, int numRows, int numCols, Complex[] matrix, Complex[] row) + { + if (transpose == Transpose.DontTranspose) + { + for (int i = 0; i < numCols; i++) + { + row[i] = matrix[(i * numRows) + rowindx]; + } + } + else if (transpose == Transpose.ConjugateTranspose) + { + int offset = rowindx * numCols; + for (int i = 0; i < row.Length; i++) + { + row[i] = matrix[i + offset].Conjugate(); + } + } + else + { + Array.Copy(matrix, rowindx * numCols, row, 0, numCols); + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetColumn(Transpose transpose, int colindx, int numRows, int numCols, Complex[] matrix, Complex[] column) + { + if (transpose == Transpose.DontTranspose) + { + Array.Copy(matrix, colindx * numRows, column, 0, numRows); + } + else if (transpose == Transpose.ConjugateTranspose) + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx].Conjugate(); + } + } + else + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Complex32.cs b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Complex32.cs new file mode 100644 index 0000000..4b84108 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Complex32.cs @@ -0,0 +1,2824 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Complex32; +using MathNet.Numerics.LinearAlgebra.Complex32.Factorization; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.ManagedReference; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedReferenceLinearAlgebraProvider +{ + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + public virtual void AddVectorToScaledVector(Complex32[] y, Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (alpha.IsZero()) + { + y.Copy(result); + } + else if (alpha.IsOne()) + { + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = y[i] + x[i]; + } + }); + } + else + { + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = y[i] + (alpha * x[i]); + } + }); + } + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + public virtual void ScaleArray(Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (alpha.IsZero()) + { + Array.Clear(result, 0, result.Length); + } + else if (alpha.IsOne()) + { + x.Copy(result); + } + else + { + CommonParallel.For(0, x.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = alpha * x[i]; + } + }); + } + } + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + public virtual void ConjugateArray(Complex32[] x, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + CommonParallel.For(0, x.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i].Conjugate(); + } + }); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + public virtual Complex32 DotProduct(Complex32[] x, Complex32[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var d = new Complex32(0.0F, 0.0F); + + for (var i = 0; i < y.Length; i++) + { + d += y[i] * x[i]; + } + + return d; + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void AddArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] + y[i]; + } + }); + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void SubtractArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] - y[i]; + } + }); + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseMultiplyArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] * y[i]; + } + }); + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseDivideArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] / y[i]; + } + }); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWisePowerArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = Complex32.Pow(x[i], y[i]); + } + }); + } + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// The requested of the matrix. + public virtual double MatrixNorm(Norm norm, int rows, int columns, Complex32[] matrix) + { + switch (norm) + { + case Norm.OneNorm: + var norm1 = 0d; + for (var j = 0; j < columns; j++) + { + var s = 0d; + for (var i = 0; i < rows; i++) + { + s += matrix[(j * rows) + i].Magnitude; + } + + norm1 = Math.Max(norm1, s); + } + + return norm1; + case Norm.LargestAbsoluteValue: + var normMax = 0d; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + normMax = Math.Max(matrix[(j * rows) + i].Magnitude, normMax); + } + } + + return normMax; + case Norm.InfinityNorm: + var r = new double[rows]; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + r[i] += matrix[(j * rows) + i].Magnitude; + } + } + // TODO: reuse + var max = r[0]; + for (int i = 0; i < r.Length; i++) + { + if (r[i] > max) + { + max = r[i]; + } + } + + return max; + case Norm.FrobeniusNorm: + var aat = new Complex32[rows * rows]; + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.ConjugateTranspose, 1.0f, matrix, rows, columns, matrix, rows, columns, 0.0f, aat); + var normF = 0d; + for (var i = 0; i < rows; i++) + { + normF += aat[(i * rows) + i].Magnitude; + } + + return Math.Sqrt(normF); + default: + throw new NotSupportedException(); + } + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public virtual void MatrixMultiply(Complex32[] x, int rowsX, int columnsX, Complex32[] y, int rowsY, int columnsY, Complex32[] result) + { + // First check some basic requirement on the parameters of the matrix multiplication. + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (rowsX * columnsX != x.Length) + { + throw new ArgumentException("x.Length != xRows * xColumns"); + } + + if (rowsY * columnsY != y.Length) + { + throw new ArgumentException("y.Length != yRows * yColumns"); + } + + if (columnsX != rowsY) + { + throw new ArgumentException("xColumns != yRows"); + } + + if (rowsX * columnsY != result.Length) + { + throw new ArgumentException("xRows * yColumns != result.Length"); + } + + // Check whether we will be overwriting any of our inputs and make copies if necessary. + // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory + // as result, we can do it on a row wise basis. We should investigate this. + Complex32[] xdata; + if (ReferenceEquals(x, result)) + { + xdata = (Complex32[])x.Clone(); + } + else + { + xdata = x; + } + + Complex32[] ydata; + if (ReferenceEquals(y, result)) + { + ydata = (Complex32[])y.Clone(); + } + else + { + ydata = y; + } + + Array.Clear(result, 0, result.Length); + + CacheObliviousMatrixMultiply(Transpose.DontTranspose, Transpose.DontTranspose, Complex32.One, xdata, 0, 0, ydata, 0, 0, result, 0, 0, rowsX, columnsY, columnsX, rowsX, columnsY, columnsX, true); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex32 alpha, Complex32[] a, int rowsA, int columnsA, Complex32[] b, int rowsB, int columnsB, Complex32 beta, Complex32[] c) + { + int m; // The number of rows of matrix op(A) and of the matrix C. + int n; // The number of columns of matrix op(B) and of the matrix C. + int k; // The number of columns of matrix op(A) and the rows of the matrix op(B). + + // First check some basic requirement on the parameters of the matrix multiplication. + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if ((int)transposeA > 111 && (int)transposeB > 111) + { + if (rowsA != columnsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (columnsA * rowsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = columnsA; + n = rowsB; + k = rowsA; + } + else if ((int)transposeA > 111) + { + if (rowsA != rowsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (columnsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = columnsA; + n = columnsB; + k = rowsA; + } + else if ((int)transposeB > 111) + { + if (columnsA != columnsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (rowsA * rowsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = rowsA; + n = rowsB; + k = columnsA; + } + else + { + if (columnsA != rowsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (rowsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = rowsA; + n = columnsB; + k = columnsA; + } + + if (alpha.IsZero() && beta.IsZero()) + { + Array.Clear(c, 0, c.Length); + return; + } + + // Check whether we will be overwriting any of our inputs and make copies if necessary. + // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory + // as result, we can do it on a row wise basis. We should investigate this. + Complex32[] adata; + if (ReferenceEquals(a, c)) + { + adata = (Complex32[])a.Clone(); + } + else + { + adata = a; + } + + Complex32[] bdata; + if (ReferenceEquals(b, c)) + { + bdata = (Complex32[])b.Clone(); + } + else + { + bdata = b; + } + + if (beta.IsZero()) + { + Array.Clear(c, 0, c.Length); + } + else if (!beta.IsOne()) + { + ScaleArray(beta, c, c); + } + + if (alpha.IsZero()) + { + return; + } + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, adata, 0, 0, bdata, 0, 0, c, 0, 0, m, n, k, m, n, k, true); + } + + /// + /// Cache-Oblivious Matrix Multiplication + /// + /// if set to true transpose matrix A. + /// if set to true transpose matrix B. + /// The value to scale the matrix A with. + /// The matrix A. + /// Row-shift of the left matrix + /// Column-shift of the left matrix + /// The matrix B. + /// Row-shift of the right matrix + /// Column-shift of the right matrix + /// The matrix C. + /// Row-shift of the result matrix + /// Column-shift of the result matrix + /// The number of rows of matrix op(A) and of the matrix C. + /// The number of columns of matrix op(B) and of the matrix C. + /// The number of columns of matrix op(A) and the rows of the matrix op(B). + /// The constant number of rows of matrix op(A) and of the matrix C. + /// The constant number of columns of matrix op(B) and of the matrix C. + /// The constant number of columns of matrix op(A) and the rows of the matrix op(B). + /// Indicates if this is the first recursion. + static void CacheObliviousMatrixMultiply(Transpose transposeA, Transpose transposeB, Complex32 alpha, Complex32[] matrixA, int shiftArow, int shiftAcol, Complex32[] matrixB, int shiftBrow, int shiftBcol, Complex32[] result, int shiftCrow, int shiftCcol, int m, int n, int k, int constM, int constN, int constK, bool first) + { + if (m + n <= Control.ParallelizeOrder || m == 1 || n == 1 || k == 1) + { + if ((int)transposeA > 111 && (int)transposeB > 111) + { + if ((int)transposeA > 112 && (int)transposeB > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol].Conjugate() * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos].Conjugate(); + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else if ((int)transposeA > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol].Conjugate() * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else if ((int)transposeB > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos].Conjugate(); + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else if ((int)transposeA > 111) + { + if ((int)transposeA > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol].Conjugate() * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else if ((int)transposeB > 111) + { + if ((int)transposeB > 112) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos].Conjugate(); + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + var sum = Complex32.Zero; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else + { + // divide and conquer + int m2 = m / 2, n2 = n / 2, k2 = k / 2; + + if (first) + { + CommonParallel.Invoke( + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false)); + + CommonParallel.Invoke( + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false)); + } + else + { + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false); + } + } + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + public virtual void LUFactor(Complex32[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + ipiv[i] = i; + } + + var vecLUcolj = new Complex32[order]; + + // Outer loop. + for (var j = 0; j < order; j++) + { + var indexj = j * order; + var indexjj = indexj + j; + + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vecLUcolj[i] = data[indexj + i]; + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + // Most of the time is spent in the following dot product. + var kmax = Math.Min(i, j); + var s = Complex32.Zero; + for (var k = 0; k < kmax; k++) + { + s += data[(k * order) + i] * vecLUcolj[k]; + } + + data[indexj + i] = vecLUcolj[i] -= s; + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (vecLUcolj[i].Magnitude > vecLUcolj[p].Magnitude) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var indexk = k * order; + var indexkp = indexk + p; + var indexkj = indexk + j; + var temp = data[indexkp]; + data[indexkp] = data[indexkj]; + data[indexkj] = temp; + } + + ipiv[j] = p; + } + + // Compute multipliers. + if (j < order & data[indexjj] != 0.0f) + { + for (var i = j + 1; i < order; i++) + { + data[indexj + i] /= data[indexjj]; + } + } + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + public virtual void LUInverse(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var ipiv = new int[order]; + LUFactor(a, order, ipiv); + LUInverseFactored(a, order, ipiv); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + public virtual void LUInverseFactored(Complex32[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var inverse = new Complex32[a.Length]; + for (var i = 0; i < order; i++) + { + inverse[i + (order * i)] = Complex32.One; + } + + LUSolveFactored(order, a, order, ipiv, inverse); + inverse.Copy(a); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + public virtual void LUSolve(int columnsOfB, Complex32[] a, int order, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var ipiv = new int[order]; + var clone = new Complex32[a.Length]; + a.Copy(clone); + LUFactor(clone, order, ipiv); + LUSolveFactored(columnsOfB, clone, order, ipiv, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + public virtual void LUSolveFactored(int columnsOfB, Complex32[] a, int order, int[] ipiv, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + // Compute the column vector P*B + for (var i = 0; i < ipiv.Length; i++) + { + if (ipiv[i] == i) + { + continue; + } + + var p = ipiv[i]; + for (var j = 0; j < columnsOfB; j++) + { + var indexk = j * order; + var indexkp = indexk + p; + var indexkj = indexk + i; + var temp = b[indexkp]; + b[indexkp] = b[indexkj]; + b[indexkj] = temp; + } + } + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + var korder = k * order; + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + var korder = k + (k * order); + for (var j = 0; j < columnsOfB; j++) + { + b[k + (j * order)] /= a[korder]; + } + + korder = k * order; + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + public virtual void CholeskyFactor(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + var tmpColumn = new Complex32[order]; + + // Main loop - along the diagonal + for (var ij = 0; ij < order; ij++) + { + // "Pivot" element + var tmpVal = a[(ij * order) + ij]; + + if (tmpVal.Real > 0.0) + { + tmpVal = tmpVal.SquareRoot(); + a[(ij * order) + ij] = tmpVal; + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < order; i++) + { + a[(ij * order) + i] /= tmpVal; + tmpColumn[i] = a[(ij * order) + i]; + } + + // Remaining columns, below the diagonal + DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (var i = ij + 1; i < order; i++) + { + a[(i * order) + ij] = 0.0f; + } + } + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(Complex32[] data, int rowDim, int firstCol, int colLimit, Complex32[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data[(j * rowDim) + i] -= multipliers[i] * tmpVal.Conjugate(); + } + } + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + public virtual void CholeskySolve(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var clone = new Complex32[a.Length]; + a.Copy(clone); + CholeskyFactor(clone, orderA); + CholeskySolveFactored(clone, orderA, b, columnsB); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + public virtual void CholeskySolveFactored(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + CommonParallel.For(0, columnsB, (u, v) => + { + for (int i = u; i < v; i++) + { + DoCholeskySolve(a, orderA, b, i); + } + }); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The column to solve for. + static void DoCholeskySolve(Complex32[] a, int orderA, Complex32[] b, int index) + { + var cindex = index * orderA; + + // Solve L*Y = B; + Complex32 sum; + for (var i = 0; i < orderA; i++) + { + sum = b[cindex + i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= a[(k * orderA) + i] * b[cindex + k]; + } + + b[cindex + i] = sum / a[(i * orderA) + i]; + } + + // Solve L'*X = Y; + for (var i = orderA - 1; i >= 0; i--) + { + sum = b[cindex + i]; + var iindex = i * orderA; + for (var k = i + 1; k < orderA; k++) + { + sum -= a[iindex + k].Conjugate() * b[cindex + k]; + } + + b[cindex + i] = sum / a[iindex + i]; + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void QRFactor(Complex32[] r, int rowsR, int columnsR, Complex32[] q, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR * rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var work = columnsR > rowsR ? new Complex32[rowsR * rowsR] : new Complex32[rowsR * columnsR]; + + CommonParallel.For(0, rowsR, (a, b) => + { + for (int i = a; i < b; i++) + { + q[(i * rowsR) + i] = Complex32.One; + } + }); + + var minmn = Math.Min(rowsR, columnsR); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, r, rowsR, i, i); + ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void ThinQRFactor(Complex32[] a, int rowsA, int columnsA, Complex32[] r, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(a)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var work = new Complex32[rowsA * columnsA]; + + var minmn = Math.Min(rowsA, columnsA); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, a, rowsA, i, i); + ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); + } + + //copy R + for (var j = 0; j < columnsA; j++) + { + var rIndex = j * columnsA; + var aIndex = j * rowsA; + for (var i = 0; i < columnsA; i++) + { + r[rIndex + i] = a[aIndex + i]; + } + } + + //clear A and set diagonals to 1 + Array.Clear(a, 0, a.Length); + for (var i = 0; i < columnsA; i++) + { + a[i * rowsA + i] = Complex32.One; + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); + } + } + + #region QR Factor Helper functions + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Index of column in work array + /// Q or R matrices + /// The first row in + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(Complex32[] work, int workIndex, Complex32[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) + { + if (rowStart > rowCount || columnStart > columnCount) + { + return; + } + + var tmpColCount = columnCount - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), + () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); + } + else + { + for (var j = columnStart; j < columnCount; j++) + { + var scale = Complex32.Zero; + for (var i = rowStart; i < rowCount; i++) + { + scale += work[(workIndex * rowCount) + i - rowStart] * a[(j * rowCount) + i]; + } + + for (var i = rowStart; i < rowCount; i++) + { + a[(j * rowCount) + i] -= work[(workIndex * rowCount) + i - rowStart].Conjugate() * scale; + } + } + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Work array + /// Initial matrix + /// The number of rows in matrix + /// The first row + /// Column index + static void GenerateColumn(Complex32[] work, Complex32[] a, int rowCount, int row, int column) + { + var tmp = column * rowCount; + var index = tmp + row; + + CommonParallel.For(row, rowCount, (u, v) => + { + for (int i = u; i < v; i++) + { + var iIndex = tmp + i; + work[iIndex - row] = a[iIndex]; + a[iIndex] = Complex32.Zero; + } + }); + + var norm = Complex32.Zero; + for (var i = 0; i < rowCount - row; ++i) + { + var index1 = tmp + i; + norm += work[index1].Magnitude * work[index1].Magnitude; + } + + norm = norm.SquareRoot(); + if (row == rowCount - 1 || norm.Magnitude == 0) + { + a[index] = -work[tmp]; + work[tmp] = new Complex32(2.0f, 0).SquareRoot(); + return; + } + + if (work[tmp].Magnitude != 0.0f) + { + norm = norm.Magnitude * (work[tmp] / work[tmp].Magnitude); + } + + a[index] = -norm; + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] /= norm; + } + }); + work[tmp] += 1.0f; + + var s = (1.0f / work[tmp]).SquareRoot(); + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] = work[tmp + i].Conjugate() * s; + } + }); + } + + #endregion + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolve(Complex32[] a, int rows, int columns, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new Complex32[rows * columns]; + + var clone = new Complex32[a.Length]; + a.Copy(clone); + + if (method == QRMethod.Full) + { + var q = new Complex32[rows * rows]; + QRFactor(clone, rows, columns, q, work); + QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); + } + else + { + var r = new Complex32[columns * columns]; + ThinQRFactor(clone, rows, columns, r, work); + QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolveFactored(Complex32[] q, Complex32[] r, int rowsA, int columnsA, Complex32[] tau, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (rowsA < columnsA) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + var sol = new Complex32[b.Length]; + + // Copy B matrix to "sol", so B data will not be changed + Array.Copy(b, 0, sol, 0, b.Length); + + // Compute Y = transpose(Q)*B + var column = new Complex32[rowsA]; + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + Array.Copy(sol, jm, column, 0, rowsA); + CommonParallel.For(0, columnsA, (u, v) => + { + for (int i = u; i < v; i++) + { + var im = i * rowsA; + + var sum = Complex32.Zero; + for (var k = 0; k < rowsA; k++) + { + sum += q[im + k].Conjugate() * column[k]; + } + + sol[jm + i] = sum; + } + }); + } + + // Solve R*X = Y; + for (var k = columnsA - 1; k >= 0; k--) + { + var km = k * rowsR; + for (var j = 0; j < columnsB; j++) + { + sol[(j * rowsA) + k] /= r[km + k]; + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + sol[jm + i] -= sol[jm + k] * r[km + i]; + } + } + } + + // Fill result matrix + for (var col = 0; col < columnsB; col++) + { + Array.Copy(sol, col * rowsA, x, col * columnsA, columnsR); + } + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + public virtual void SingularValueDecomposition(bool computeVectors, Complex32[] a, int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new Complex32[rowsA]; + + const int maxiter = 1000; + + var e = new Complex32[columnsA]; + var v = new Complex32[vt.Length]; + var stemp = new Complex32[Math.Min(rowsA + 1, columnsA)]; + + int i, j, l, lp1; + + Complex32 t; + + var ncu = rowsA; + + // Reduce matrix to bidiagonal form, storing the diagonal elements + // in "s" and the super-diagonal elements in "e". + var nct = Math.Min(rowsA - 1, columnsA); + var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); + var lu = Math.Max(nct, nrt); + + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and + // place the l-th diagonal in vector s[l]. + var sum = 0.0f; + for (i = l; i < rowsA; i++) + { + sum += a[(l * rowsA) + i].Magnitude * a[(l * rowsA) + i].Magnitude; + } + + stemp[l] = (float)Math.Sqrt(sum); + if (stemp[l] != 0.0f) + { + if (a[(l * rowsA) + l] != 0.0f) + { + stemp[l] = stemp[l].Magnitude * (a[(l * rowsA) + l] / a[(l * rowsA) + l].Magnitude); + } + + // A part of column "l" of Matrix A from row "l" to end multiply by 1.0f / s[l] + for (i = l; i < rowsA; i++) + { + a[(l * rowsA) + i] = a[(l * rowsA) + i] * (1.0f / stemp[l]); + } + + a[(l * rowsA) + l] = 1.0f + a[(l * rowsA) + l]; + } + + stemp[l] = -stemp[l]; + } + + for (j = lp1; j < columnsA; j++) + { + if (l < nct) + { + if (stemp[l] != 0.0f) + { + // Apply the transformation. + t = 0.0f; + for (i = l; i < rowsA; i++) + { + t += a[(l * rowsA) + i].Conjugate() * a[(j * rowsA) + i]; + } + + t = -t / a[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += t * a[(l * rowsA) + ii]; + } + } + } + + // Place the l-th row of matrix into "e" for the + // subsequent calculation of the row transformation. + e[j] = a[(j * rowsA) + l].Conjugate(); + } + + if (computeVectors && l < nct) + { + // Place the transformation in "u" for subsequent back multiplication. + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = a[(l * rowsA) + i]; + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = 0.0f; + for (i = lp1; i < e.Length; i++) + { + enorm += e[i].Magnitude * e[i].Magnitude; + } + + e[l] = (float)Math.Sqrt(enorm); + if (e[l] != 0.0f) + { + if (e[lp1] != 0.0f) + { + e[l] = e[l].Magnitude * (e[lp1] / e[lp1].Magnitude); + } + + // Scale vector "e" from "lp1" by 1.0f / e[l] + for (i = lp1; i < e.Length; i++) + { + e[i] = e[i] * (1.0f / e[l]); + } + + e[lp1] = 1.0f + e[lp1]; + } + + e[l] = -e[l].Conjugate(); + + if (lp1 < rowsA && e[l] != 0.0f) + { + // Apply the transformation. + for (i = lp1; i < rowsA; i++) + { + work[i] = 0.0f; + } + + for (j = lp1; j < columnsA; j++) + { + for (var ii = lp1; ii < rowsA; ii++) + { + work[ii] += e[j] * a[(j * rowsA) + ii]; + } + } + + for (j = lp1; j < columnsA; j++) + { + var ww = (-e[j] / e[lp1]).Conjugate(); + for (var ii = lp1; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += ww * work[ii]; + } + } + } + + if (!computeVectors) + { + continue; + } + + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < columnsA; i++) + { + v[(l * columnsA) + i] = e[i]; + } + } + + // Set up the final bidiagonal matrix or order m. + var m = Math.Min(columnsA, rowsA + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < columnsA) + { + stemp[nctp1 - 1] = a[((nctp1 - 1) * rowsA) + (nctp1 - 1)]; + } + + if (rowsA < m) + { + stemp[m - 1] = 0.0f; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = a[((m - 1) * rowsA) + (nrtp1 - 1)]; + } + + e[m - 1] = 0.0f; + + // If required, generate "u". + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < rowsA; i++) + { + u[(j * rowsA) + i] = 0.0f; + } + + u[(j * rowsA) + j] = 1.0f; + } + + for (l = nct - 1; l >= 0; l--) + { + if (stemp[l] != 0.0f) + { + for (j = l + 1; j < ncu; j++) + { + t = 0.0f; + for (i = l; i < rowsA; i++) + { + t += u[(l * rowsA) + i].Conjugate() * u[(j * rowsA) + i]; + } + + t = -t / u[(l * rowsA) + l]; + for (var ii = l; ii < rowsA; ii++) + { + u[(j * rowsA) + ii] += t * u[(l * rowsA) + ii]; + } + } + + // A part of column "l" of matrix A from row "l" to end multiply by -1.0f + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = u[(l * rowsA) + i] * -1.0f; + } + + u[(l * rowsA) + l] = 1.0f + u[(l * rowsA) + l]; + for (i = 0; i < l; i++) + { + u[(l * rowsA) + i] = 0.0f; + } + } + else + { + for (i = 0; i < rowsA; i++) + { + u[(l * rowsA) + i] = 0.0f; + } + + u[(l * rowsA) + l] = 1.0f; + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = columnsA - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0f) + { + for (j = lp1; j < columnsA; j++) + { + t = 0.0f; + for (i = lp1; i < columnsA; i++) + { + t += v[(l * columnsA) + i].Conjugate() * v[(j * columnsA) + i]; + } + + t = -t / v[(l * columnsA) + lp1]; + for (var ii = l; ii < columnsA; ii++) + { + v[(j * columnsA) + ii] += t * v[(l * columnsA) + ii]; + } + } + } + } + + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = 0.0f; + } + + v[(l * columnsA) + l] = 1.0f; + } + } + + // Transform "s" and "e" so that they are float + for (i = 0; i < m; i++) + { + Complex32 r; + if (stemp[i] != 0.0f) + { + t = stemp[i].Magnitude; + r = stemp[i] / t; + stemp[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + // A part of column "i" of matrix U from row 0 to end multiply by r + for (j = 0; j < rowsA; j++) + { + u[(i * rowsA) + j] = u[(i * rowsA) + j] * r; + } + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] == 0.0f) + { + continue; + } + + t = e[i].Magnitude; + r = t / e[i]; + e[i] = t; + stemp[i + 1] = stemp[i + 1] * r; + if (!computeVectors) + { + continue; + } + + // A part of column "i+1" of matrix VT from row 0 to end multiply by r + for (j = 0; j < columnsA; j++) + { + v[((i + 1) * columnsA) + j] = v[((i + 1) * columnsA) + j] * r; + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. + // If too many iterations have been performed throw exception. + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays, + // on completion the variables case and l are set as follows: + // case = 1: if mS[m] and e[l-1] are negligible and l < m + // case = 2: if mS[l] is negligible and l < m + // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). + // case = 4: if e[m-1] is negligible (convergence). + float ztest; + float test; + for (l = m - 2; l >= 0; l--) + { + test = stemp[l].Magnitude + stemp[l + 1].Magnitude; + ztest = test + e[l].Magnitude; + if (ztest.AlmostEqualRelative(test, 7)) + { + e[l] = 0.0f; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0f; + if (ls != m - 1) + { + test = test + e[ls].Magnitude; + } + + if (ls != l + 1) + { + test = test + e[ls - 1].Magnitude; + } + + ztest = test + stemp[ls].Magnitude; + if (ztest.AlmostEqualRelative(test, 7)) + { + stemp[ls] = 0.0f; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + float f; + float sn; + float cs; + switch (kase) + { + // Deflate negligible s[m]. + case 1: + f = e[m - 2].Real; + e[m - 2] = 0.0f; + float t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = stemp[k].Real; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + if (k != l) + { + f = -sn * e[k - 1].Real; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + // Rotate + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((m - 1) * columnsA) + i]); + v[((m - 1) * columnsA) + i] = (cs * v[((m - 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + } + + break; + + // Split at negligible s[l]. + case 2: + f = e[l - 1].Real; + e[l - 1] = 0.0f; + for (k = l; k < m; k++) + { + t1 = stemp[k].Real; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + f = -sn * e[k].Real; + e[k] = cs * e[k]; + if (computeVectors) + { + // Rotate + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((l - 1) * rowsA) + i]); + u[((l - 1) * rowsA) + i] = (cs * u[((l - 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + break; + + // Perform one qr step. + case 3: + // calculate the shift. + var scale = 0.0f; + scale = Math.Max(scale, stemp[m - 1].Magnitude); + scale = Math.Max(scale, stemp[m - 2].Magnitude); + scale = Math.Max(scale, e[m - 2].Magnitude); + scale = Math.Max(scale, stemp[l].Magnitude); + scale = Math.Max(scale, e[l].Magnitude); + var sm = stemp[m - 1].Real / scale; + var smm1 = stemp[m - 2].Real / scale; + var emm1 = e[m - 2].Real / scale; + var sl = stemp[l].Real / scale; + var el = e[l].Real / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0f; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0f; + if (b != 0.0f || c != 0.0f) + { + shift = (float)Math.Sqrt((b * b) + c); + if (b < 0.0f) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * stemp[k].Real) + (sn * e[k].Real); + e[k] = (cs * e[k]) - (sn * stemp[k]); + g = sn * stemp[k + 1].Real; + stemp[k + 1] = cs * stemp[k + 1]; + if (computeVectors) + { + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((k + 1) * columnsA) + i]); + v[((k + 1) * columnsA) + i] = (cs * v[((k + 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + + Drotg(ref f, ref g, out cs, out sn); + stemp[k] = f; + f = (cs * e[k].Real) + (sn * stemp[k + 1].Real); + stemp[k + 1] = -(sn * e[k]) + (cs * stemp[k + 1]); + g = sn * e[k + 1].Real; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < rowsA) + { + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((k + 1) * rowsA) + i]); + u[((k + 1) * rowsA) + i] = (cs * u[((k + 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence + case 4: + + // Make the singular value positive + if (stemp[l].Real < 0.0f) + { + stemp[l] = -stemp[l]; + if (computeVectors) + { + // A part of column "l" of matrix VT from row 0 to end multiply by -1 + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = v[(l * columnsA) + i] * -1.0f; + } + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (stemp[l].Real >= stemp[l + 1].Real) + { + break; + } + + t = stemp[l]; + stemp[l] = stemp[l + 1]; + stemp[l + 1] = t; + if (computeVectors && l < columnsA) + { + // Swap columns l, l + 1 + for (i = 0; i < columnsA; i++) + { + var z = v[(l * columnsA) + i]; + v[(l * columnsA) + i] = v[((l + 1) * columnsA) + i]; + v[((l + 1) * columnsA) + i] = z; + } + } + + if (computeVectors && l < rowsA) + { + // Swap columns l, l + 1 + for (i = 0; i < rowsA; i++) + { + var z = u[(l * rowsA) + i]; + u[(l * rowsA) + i] = u[((l + 1) * rowsA) + i]; + u[((l + 1) * rowsA) + i] = z; + } + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + // Finally transpose "v" to get "vt" matrix + for (i = 0; i < columnsA; i++) + { + for (j = 0; j < columnsA; j++) + { + vt[(j * columnsA) + i] = v[(i * columnsA) + j].Conjugate(); + } + } + } + + // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses + // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. + // We should port lapack's svd routine to remove this problem. + Array.Copy(stemp, 0, s, 0, Math.Min(rowsA, columnsA)); + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolve(Complex32[] a, int rowsA, int columnsA, Complex32[] b, int columnsB, Complex32[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex32[Math.Min(rowsA, columnsA)]; + var u = new Complex32[rowsA * rowsA]; + var vt = new Complex32[columnsA * columnsA]; + + var clone = new Complex32[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolveFactored(int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt, Complex32[] b, int columnsB, Complex32[] x) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var mn = Math.Min(rowsA, columnsA); + var tmp = new Complex32[columnsA]; + + for (var k = 0; k < columnsB; k++) + { + for (var j = 0; j < columnsA; j++) + { + var value = Complex32.Zero; + if (j < mn) + { + for (var i = 0; i < rowsA; i++) + { + value += u[(j * rowsA) + i].Conjugate() * b[(k * rowsA) + i]; + } + + value /= s[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < columnsA; j++) + { + var value = Complex32.Zero; + for (var i = 0; i < columnsA; i++) + { + value += vt[(j * columnsA) + i].Conjugate() * tmp[i]; + } + + x[(k * columnsA) + j] = value; + } + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public virtual void EigenDecomp(bool isSymmetric, int order, Complex32[] matrix, Complex32[] matrixEv, Complex[] vectorEv, Complex32[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var matrixCopy = new Complex32[matrix.Length]; + Array.Copy(matrix, 0, matrixCopy, 0, matrix.Length); + if (isSymmetric) + { + var tau = new Complex32[order]; + var d = new float[order]; + var e = new float[order]; + + DenseEvd.SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + DenseEvd.SymmetricDiagonalize(matrixEv, d, e, order); + DenseEvd.SymmetricUntridiagonalize(matrixEv, matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(d[i], e[i]); + matrixD[i * order + i] = new Complex32(d[i], e[i]); + } + } + else + { + var v = new DenseVector(order); + + DenseEvd.NonsymmetricReduceToHessenberg(matrixEv, matrixCopy, order); + DenseEvd.NonsymmetricReduceHessenberToRealSchur(v.Values, matrixEv, matrixCopy, order); + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(v[i].Real, v[i].Imaginary); + matrixD[i * order + i] = v[i]; + } + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetRow(Transpose transpose, int rowindx, int numRows, int numCols, Complex32[] matrix, Complex32[] row) + { + if (transpose == Transpose.DontTranspose) + { + for (int i = 0; i < numCols; i++) + { + row[i] = matrix[(i * numRows) + rowindx]; + } + } + else if (transpose == Transpose.ConjugateTranspose) + { + int offset = rowindx * numCols; + for (int i = 0; i < row.Length; i++) + { + row[i] = matrix[i + offset].Conjugate(); + } + } + else + { + Array.Copy(matrix, rowindx * numCols, row, 0, numCols); + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetColumn(Transpose transpose, int colindx, int numRows, int numCols, Complex32[] matrix, Complex32[] column) + { + if (transpose == Transpose.DontTranspose) + { + Array.Copy(matrix, colindx * numRows, column, 0, numRows); + } + else if (transpose == Transpose.ConjugateTranspose) + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx].Conjugate(); + } + } + else + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Double.cs b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Double.cs new file mode 100644 index 0000000..f640a6b --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Double.cs @@ -0,0 +1,2732 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.ManagedReference; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedReferenceLinearAlgebraProvider +{ + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + public virtual void AddVectorToScaledVector(double[] y, double alpha, double[] x, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (alpha == 0.0) + { + y.Copy(result); + } + else if (alpha == 1.0) + { + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = y[i] + x[i]; + } + }); + } + else + { + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = y[i] + (alpha * x[i]); + } + }); + } + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + public virtual void ScaleArray(double alpha, double[] x, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (alpha == 0.0) + { + Array.Clear(result, 0, result.Length); + } + else if (alpha == 1.0) + { + x.Copy(result); + } + else + { + CommonParallel.For(0, x.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = alpha * x[i]; + } + }); + } + } + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + public virtual void ConjugateArray(double[] x, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + x.CopyTo(result, 0); + } + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + public virtual double DotProduct(double[] x, double[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var sum = 0.0; + + for (var index = 0; index < y.Length; index++) + { + sum += y[index] * x[index]; + } + + return sum; + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void AddArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] + y[i]; + } + }); + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void SubtractArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] - y[i]; + } + }); + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseMultiplyArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] * y[i]; + } + }); + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseDivideArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] / y[i]; + } + }); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWisePowerArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = Math.Pow(x[i], y[i]); + } + }); + } + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + public virtual double MatrixNorm(Norm norm, int rows, int columns, double[] matrix) + { + switch (norm) + { + case Norm.OneNorm: + var norm1 = 0d; + for (var j = 0; j < columns; j++) + { + var s = 0.0; + for (var i = 0; i < rows; i++) + { + s += Math.Abs(matrix[(j * rows) + i]); + } + + norm1 = Math.Max(norm1, s); + } + + return norm1; + case Norm.LargestAbsoluteValue: + var normMax = 0d; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + normMax = Math.Max(Math.Abs(matrix[(j * rows) + i]), normMax); + } + } + + return normMax; + case Norm.InfinityNorm: + var r = new double[rows]; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + r[i] += Math.Abs(matrix[(j * rows) + i]); + } + } + // TODO: reuse + var max = r[0]; + for (int i = 0; i < r.Length; i++) + { + if (r[i] > max) + { + max = r[i]; + } + } + + return max; + case Norm.FrobeniusNorm: + var aat = new double[rows * rows]; + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.Transpose, 1.0, matrix, rows, columns, matrix, rows, columns, 0.0, aat); + var normF = 0d; + for (var i = 0; i < rows; i++) + { + normF += Math.Abs(aat[(i * rows) + i]); + } + + return Math.Sqrt(normF); + default: + throw new NotSupportedException(); + } + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public virtual void MatrixMultiply(double[] x, int rowsX, int columnsX, double[] y, int rowsY, int columnsY, double[] result) + { + // First check some basic requirement on the parameters of the matrix multiplication. + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (rowsX * columnsX != x.Length) + { + throw new ArgumentException("x.Length != xRows * xColumns"); + } + + if (rowsY * columnsY != y.Length) + { + throw new ArgumentException("y.Length != yRows * yColumns"); + } + + if (columnsX != rowsY) + { + throw new ArgumentException("xColumns != yRows"); + } + + if (rowsX * columnsY != result.Length) + { + throw new ArgumentException("xRows * yColumns != result.Length"); + } + + // Check whether we will be overwriting any of our inputs and make copies if necessary. + // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory + // as result, we can do it on a row wise basis. We should investigate this. + double[] xdata; + if (ReferenceEquals(x, result)) + { + xdata = (double[])x.Clone(); + } + else + { + xdata = x; + } + + double[] ydata; + if (ReferenceEquals(y, result)) + { + ydata = (double[])y.Clone(); + } + else + { + ydata = y; + } + + Array.Clear(result, 0, result.Length); + + CacheObliviousMatrixMultiply(Transpose.DontTranspose, Transpose.DontTranspose, 1.0, xdata, 0, 0, ydata, 0, 0, result, 0, 0, rowsX, columnsY, columnsX, rowsX, columnsY, columnsX, true); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, double alpha, double[] a, int rowsA, int columnsA, double[] b, int rowsB, int columnsB, double beta, double[] c) + { + int m; // The number of rows of matrix op(A) and of the matrix C. + int n; // The number of columns of matrix op(B) and of the matrix C. + int k; // The number of columns of matrix op(A) and the rows of the matrix op(B). + + // First check some basic requirement on the parameters of the matrix multiplication. + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if ((int)transposeA > 111 && (int)transposeB > 111) + { + if (rowsA != columnsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (columnsA * rowsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = columnsA; + n = rowsB; + k = rowsA; + } + else if ((int)transposeA > 111) + { + if (rowsA != rowsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (columnsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = columnsA; + n = columnsB; + k = rowsA; + } + else if ((int)transposeB > 111) + { + if (columnsA != columnsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (rowsA * rowsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = rowsA; + n = rowsB; + k = columnsA; + } + else + { + if (columnsA != rowsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (rowsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = rowsA; + n = columnsB; + k = columnsA; + } + + if (alpha == 0.0 && beta == 0.0) + { + Array.Clear(c, 0, c.Length); + return; + } + + // Check whether we will be overwriting any of our inputs and make copies if necessary. + // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory + // as result, we can do it on a row wise basis. We should investigate this. + double[] adata; + if (ReferenceEquals(a, c)) + { + adata = (double[])a.Clone(); + } + else + { + adata = a; + } + + double[] bdata; + if (ReferenceEquals(b, c)) + { + bdata = (double[])b.Clone(); + } + else + { + bdata = b; + } + + if (beta == 0.0) + { + Array.Clear(c, 0, c.Length); + } + else if (beta != 1.0) + { + ScaleArray(beta, c, c); + } + + if (alpha == 0.0) + { + return; + } + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, adata, 0, 0, bdata, 0, 0, c, 0, 0, m, n, k, m, n, k, true); + } + + /// + /// Cache-Oblivious Matrix Multiplication + /// + /// if set to true transpose matrix A. + /// if set to true transpose matrix B. + /// The value to scale the matrix A with. + /// The matrix A. + /// Row-shift of the left matrix + /// Column-shift of the left matrix + /// The matrix B. + /// Row-shift of the right matrix + /// Column-shift of the right matrix + /// The matrix C. + /// Row-shift of the result matrix + /// Column-shift of the result matrix + /// The number of rows of matrix op(A) and of the matrix C. + /// The number of columns of matrix op(B) and of the matrix C. + /// The number of columns of matrix op(A) and the rows of the matrix op(B). + /// The constant number of rows of matrix op(A) and of the matrix C. + /// The constant number of columns of matrix op(B) and of the matrix C. + /// The constant number of columns of matrix op(A) and the rows of the matrix op(B). + /// Indicates if this is the first recursion. + static void CacheObliviousMatrixMultiply(Transpose transposeA, Transpose transposeB, double alpha, double[] matrixA, int shiftArow, int shiftAcol, double[] matrixB, int shiftBrow, int shiftBcol, double[] result, int shiftCrow, int shiftCcol, int m, int n, int k, int constM, int constN, int constK, bool first) + { + if (m + n <= Control.ParallelizeOrder || m == 1 || n == 1 || k == 1) + { + if ((int)transposeA > 111 && (int)transposeB > 111) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + double sum = 0; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else if ((int)transposeA > 111) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + double sum = 0; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else if ((int)transposeB > 111) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + double sum = 0; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + double sum = 0; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else + { + // divide and conquer + int m2 = m / 2, n2 = n / 2, k2 = k / 2; + + if (first) + { + CommonParallel.Invoke( + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false)); + + CommonParallel.Invoke( + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false)); + } + else + { + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false); + } + } + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + public virtual void LUFactor(double[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + ipiv[i] = i; + } + + var vecLUcolj = new double[order]; + + // Outer loop. + for (var j = 0; j < order; j++) + { + var indexj = j * order; + var indexjj = indexj + j; + + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vecLUcolj[i] = data[indexj + i]; + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + // Most of the time is spent in the following dot product. + var kmax = Math.Min(i, j); + var s = 0.0; + for (var k = 0; k < kmax; k++) + { + s += data[(k * order) + i] * vecLUcolj[k]; + } + + data[indexj + i] = vecLUcolj[i] -= s; + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (Math.Abs(vecLUcolj[i]) > Math.Abs(vecLUcolj[p])) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var indexk = k * order; + var indexkp = indexk + p; + var indexkj = indexk + j; + var temp = data[indexkp]; + data[indexkp] = data[indexkj]; + data[indexkj] = temp; + } + + ipiv[j] = p; + } + + // Compute multipliers. + if (j < order & data[indexjj] != 0.0) + { + for (var i = j + 1; i < order; i++) + { + data[indexj + i] /= data[indexjj]; + } + } + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + public virtual void LUInverse(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var ipiv = new int[order]; + LUFactor(a, order, ipiv); + LUInverseFactored(a, order, ipiv); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + public virtual void LUInverseFactored(double[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var inverse = new double[a.Length]; + for (var i = 0; i < order; i++) + { + inverse[i + (order * i)] = 1.0; + } + + LUSolveFactored(order, a, order, ipiv, inverse); + inverse.Copy(a); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + public virtual void LUSolve(int columnsOfB, double[] a, int order, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var ipiv = new int[order]; + var clone = new double[a.Length]; + a.Copy(clone); + LUFactor(clone, order, ipiv); + LUSolveFactored(columnsOfB, clone, order, ipiv, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + public virtual void LUSolveFactored(int columnsOfB, double[] a, int order, int[] ipiv, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + // Compute the column vector P*B + for (var i = 0; i < ipiv.Length; i++) + { + if (ipiv[i] == i) + { + continue; + } + + var p = ipiv[i]; + for (var j = 0; j < columnsOfB; j++) + { + var indexk = j * order; + var indexkp = indexk + p; + var indexkj = indexk + i; + var temp = b[indexkp]; + b[indexkp] = b[indexkj]; + b[indexkj] = temp; + } + } + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + var korder = k * order; + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + var korder = k + (k * order); + for (var j = 0; j < columnsOfB; j++) + { + b[k + (j * order)] /= a[korder]; + } + + korder = k * order; + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + public virtual void CholeskyFactor(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + var tmpColumn = new double[order]; + + // Main loop - along the diagonal + for (int ij = 0; ij < order; ij++) + { + // "Pivot" element + double tmpVal = a[(ij * order) + ij]; + + if (tmpVal > 0.0) + { + tmpVal = Math.Sqrt(tmpVal); + a[(ij * order) + ij] = tmpVal; + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (int i = ij + 1; i < order; i++) + { + a[(ij * order) + i] /= tmpVal; + tmpColumn[i] = a[(ij * order) + i]; + } + + // Remaining columns, below the diagonal + DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (int i = ij + 1; i < order; i++) + { + a[(i * order) + ij] = 0.0; + } + } + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(double[] data, int rowDim, int firstCol, int colLimit, double[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data[(j * rowDim) + i] -= multipliers[i] * tmpVal; + } + } + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + public virtual void CholeskySolve(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var clone = new double[a.Length]; + a.Copy(clone); + CholeskyFactor(clone, orderA); + CholeskySolveFactored(clone, orderA, b, columnsB); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + public virtual void CholeskySolveFactored(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + CommonParallel.For(0, columnsB, (u, v) => + { + for (int i = u; i < v; i++) + { + DoCholeskySolve(a, orderA, b, i); + } + }); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The column to solve for. + static void DoCholeskySolve(double[] a, int orderA, double[] b, int index) + { + var cindex = index * orderA; + + // Solve L*Y = B; + double sum; + for (var i = 0; i < orderA; i++) + { + sum = b[cindex + i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= a[(k * orderA) + i] * b[cindex + k]; + } + + b[cindex + i] = sum / a[(i * orderA) + i]; + } + + // Solve L'*X = Y; + for (var i = orderA - 1; i >= 0; i--) + { + sum = b[cindex + i]; + var iindex = i * orderA; + for (var k = i + 1; k < orderA; k++) + { + sum -= a[iindex + k] * b[cindex + k]; + } + + b[cindex + i] = sum / a[iindex + i]; + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void QRFactor(double[] r, int rowsR, int columnsR, double[] q, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR * rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + CommonParallel.For(0, rowsR, (a, b) => + { + for (var i = a; i < b; i++) + { + q[(i * rowsR) + i] = 1.0; + } + }); + + var work = columnsR > rowsR ? new double[rowsR * rowsR] : new double[rowsR * columnsR]; + var minmn = Math.Min(rowsR, columnsR); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, r, rowsR, i, i); + ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void ThinQRFactor(double[] a, int rowsA, int columnsA, double[] r, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(a)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var work = new double[rowsA * columnsA]; + + var minmn = Math.Min(rowsA, columnsA); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, a, rowsA, i, i); + ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); + } + + //copy R + for (var j = 0; j < columnsA; j++) + { + var rIndex = j * columnsA; + var aIndex = j * rowsA; + for (var i = 0; i < columnsA; i++) + { + r[rIndex + i] = a[aIndex + i]; + } + } + + //clear A and set diagonals to 1 + Array.Clear(a, 0, a.Length); + for (var i = 0; i < columnsA; i++) + { + a[i * rowsA + i] = 1.0; + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); + } + } + + #region QR Factor Helper functions + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Index of column in work array + /// Q or R matrices + /// The first row in + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(double[] work, int workIndex, double[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) + { + if (rowStart > rowCount || columnStart > columnCount) + { + return; + } + + var tmpColCount = columnCount - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), + () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); + } + else + { + for (var j = columnStart; j < columnCount; j++) + { + var scale = 0.0; + for (var i = rowStart; i < rowCount; i++) + { + scale += work[(workIndex * rowCount) + i - rowStart] * a[(j * rowCount) + i]; + } + + for (var i = rowStart; i < rowCount; i++) + { + a[(j * rowCount) + i] -= work[(workIndex * rowCount) + i - rowStart] * scale; + } + } + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Work array + /// Initial matrix + /// The number of rows in matrix + /// The first row + /// Column index + static void GenerateColumn(double[] work, double[] a, int rowCount, int row, int column) + { + var tmp = column * rowCount; + var index = tmp + row; + + CommonParallel.For(row, rowCount, (u, v) => + { + for (int i = u; i < v; i++) + { + var iIndex = tmp + i; + work[iIndex - row] = a[iIndex]; + a[iIndex] = 0.0; + } + }); + + var norm = 0.0; + for (var i = 0; i < rowCount - row; ++i) + { + var iindex = tmp + i; + norm += work[iindex] * work[iindex]; + } + + norm = Math.Sqrt(norm); + if (row == rowCount - 1 || norm == 0) + { + a[index] = -work[tmp]; + work[tmp] = Constants.Sqrt2; + return; + } + + var scale = 1.0 / norm; + if (work[tmp] < 0.0) + { + scale *= -1.0; + } + + a[index] = -1.0 / scale; + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] *= scale; + } + }); + work[tmp] += 1.0; + + var s = Math.Sqrt(1.0 / work[tmp]); + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] *= s; + } + }); + } + + #endregion + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolve(double[] a, int rows, int columns, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new double[rows * columns]; + + var clone = new double[a.Length]; + a.Copy(clone); + + if (method == QRMethod.Full) + { + var q = new double[rows * rows]; + QRFactor(clone, rows, columns, q, work); + QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); + } + else + { + var r = new double[columns * columns]; + ThinQRFactor(clone, rows, columns, r, work); + QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolveFactored(double[] q, double[] r, int rowsA, int columnsA, double[] tau, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (rowsA < columnsA) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + var sol = new double[b.Length]; + + // Copy B matrix to "sol", so B data will not be changed + Buffer.BlockCopy(b, 0, sol, 0, b.Length * Constants.SizeOfDouble); + + // Compute Y = transpose(Q)*B + var column = new double[rowsA]; + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + Array.Copy(sol, jm, column, 0, rowsA); + CommonParallel.For(0, columnsA, (u, v) => + { + for (int i = u; i < v; i++) + { + var im = i * rowsA; + + var sum = 0.0; + for (var k = 0; k < rowsA; k++) + { + sum += q[im + k] * column[k]; + } + + sol[jm + i] = sum; + } + }); + } + + // Solve R*X = Y; + for (var k = columnsA - 1; k >= 0; k--) + { + var km = k * rowsR; + for (var j = 0; j < columnsB; j++) + { + sol[(j * rowsA) + k] /= r[km + k]; + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + sol[jm + i] -= sol[jm + k] * r[km + i]; + } + } + } + + // Fill result matrix + for (var col = 0; col < columnsB; col++) + { + Array.Copy(sol, col * rowsA, x, col * columnsA, columnsR); + } + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + public virtual void SingularValueDecomposition(bool computeVectors, double[] a, int rowsA, int columnsA, double[] s, double[] u, double[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new double[rowsA]; + + const int maxiter = 1000; + + var e = new double[columnsA]; + var v = new double[vt.Length]; + var stemp = new double[Math.Min(rowsA + 1, columnsA)]; + + int i, j, l, lp1; + + double t; + + var ncu = rowsA; + + // Reduce matrix to bidiagonal form, storing the diagonal elements + // in "s" and the super-diagonal elements in "e". + var nct = Math.Min(rowsA - 1, columnsA); + var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); + var lu = Math.Max(nct, nrt); + + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and + // place the l-th diagonal in vector s[l]. + var sum = 0.0; + for (var i1 = l; i1 < rowsA; i1++) + { + sum += a[(l * rowsA) + i1] * a[(l * rowsA) + i1]; + } + + stemp[l] = Math.Sqrt(sum); + + if (stemp[l] != 0.0) + { + if (a[(l * rowsA) + l] != 0.0) + { + stemp[l] = Math.Abs(stemp[l]) * (a[(l * rowsA) + l] / Math.Abs(a[(l * rowsA) + l])); + } + + // A part of column "l" of Matrix A from row "l" to end multiply by 1.0 / s[l] + for (i = l; i < rowsA; i++) + { + a[(l * rowsA) + i] = a[(l * rowsA) + i] * (1.0 / stemp[l]); + } + + a[(l * rowsA) + l] = 1.0 + a[(l * rowsA) + l]; + } + + stemp[l] = -stemp[l]; + } + + for (j = lp1; j < columnsA; j++) + { + if (l < nct) + { + if (stemp[l] != 0.0) + { + // Apply the transformation. + t = 0.0; + for (i = l; i < rowsA; i++) + { + t += a[(j * rowsA) + i] * a[(l * rowsA) + i]; + } + + t = -t / a[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += t * a[(l * rowsA) + ii]; + } + } + } + + // Place the l-th row of matrix into "e" for the + // subsequent calculation of the row transformation. + e[j] = a[(j * rowsA) + l]; + } + + if (computeVectors && l < nct) + { + // Place the transformation in "u" for subsequent back multiplication. + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = a[(l * rowsA) + i]; + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = 0.0; + for (i = lp1; i < e.Length; i++) + { + enorm += e[i] * e[i]; + } + + e[l] = Math.Sqrt(enorm); + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = Math.Abs(e[l]) * (e[lp1] / Math.Abs(e[lp1])); + } + + // Scale vector "e" from "lp1" by 1.0 / e[l] + for (i = lp1; i < e.Length; i++) + { + e[i] = e[i] * (1.0 / e[l]); + } + + e[lp1] = 1.0 + e[lp1]; + } + + e[l] = -e[l]; + + if (lp1 < rowsA && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < rowsA; i++) + { + work[i] = 0.0; + } + + for (j = lp1; j < columnsA; j++) + { + for (var ii = lp1; ii < rowsA; ii++) + { + work[ii] += e[j] * a[(j * rowsA) + ii]; + } + } + + for (j = lp1; j < columnsA; j++) + { + var ww = -e[j] / e[lp1]; + for (var ii = lp1; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += ww * work[ii]; + } + } + } + + if (!computeVectors) + { + continue; + } + + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < columnsA; i++) + { + v[(l * columnsA) + i] = e[i]; + } + } + + // Set up the final bidiagonal matrix or order m. + var m = Math.Min(columnsA, rowsA + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < columnsA) + { + stemp[nctp1 - 1] = a[((nctp1 - 1) * rowsA) + (nctp1 - 1)]; + } + + if (rowsA < m) + { + stemp[m - 1] = 0.0; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = a[((m - 1) * rowsA) + (nrtp1 - 1)]; + } + + e[m - 1] = 0.0; + + // If required, generate "u". + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < rowsA; i++) + { + u[(j * rowsA) + i] = 0.0; + } + + u[(j * rowsA) + j] = 1.0; + } + + for (l = nct - 1; l >= 0; l--) + { + if (stemp[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = 0.0; + for (i = l; i < rowsA; i++) + { + t += u[(j * rowsA) + i] * u[(l * rowsA) + i]; + } + + t = -t / u[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + u[(j * rowsA) + ii] += t * u[(l * rowsA) + ii]; + } + } + + // A part of column "l" of matrix A from row "l" to end multiply by -1.0 + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = u[(l * rowsA) + i] * -1.0; + } + + u[(l * rowsA) + l] = 1.0 + u[(l * rowsA) + l]; + for (i = 0; i < l; i++) + { + u[(l * rowsA) + i] = 0.0; + } + } + else + { + for (i = 0; i < rowsA; i++) + { + u[(l * rowsA) + i] = 0.0; + } + + u[(l * rowsA) + l] = 1.0; + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = columnsA - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < columnsA; j++) + { + t = 0.0; + for (i = lp1; i < columnsA; i++) + { + t += v[(j * columnsA) + i] * v[(l * columnsA) + i]; + } + + t = -t / v[(l * columnsA) + lp1]; + for (var ii = l; ii < columnsA; ii++) + { + v[(j * columnsA) + ii] += t * v[(l * columnsA) + ii]; + } + } + } + } + + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = 0.0; + } + + v[(l * columnsA) + l] = 1.0; + } + } + + // Transform "s" and "e" so that they are double + for (i = 0; i < m; i++) + { + double r; + if (stemp[i] != 0.0) + { + t = stemp[i]; + r = stemp[i] / t; + stemp[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + // A part of column "i" of matrix U from row 0 to end multiply by r + for (j = 0; j < rowsA; j++) + { + u[(i * rowsA) + j] = u[(i * rowsA) + j] * r; + } + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] == 0.0) + { + continue; + } + + t = e[i]; + r = t / e[i]; + e[i] = t; + stemp[i + 1] = stemp[i + 1] * r; + if (!computeVectors) + { + continue; + } + + // A part of column "i+1" of matrix VT from row 0 to end multiply by r + for (j = 0; j < columnsA; j++) + { + v[((i + 1) * columnsA) + j] = v[((i + 1) * columnsA) + j] * r; + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. + // If too many iterations have been performed throw exception. + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays, + // on completion the variables case and l are set as follows: + // case = 1: if mS[m] and e[l-1] are negligible and l < m + // case = 2: if mS[l] is negligible and l < m + // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). + // case = 4: if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = Math.Abs(stemp[l]) + Math.Abs(stemp[l + 1]); + ztest = test + Math.Abs(e[l]); + if (ztest.AlmostEqualRelative(test, 15)) + { + e[l] = 0.0; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + Math.Abs(e[ls]); + } + + if (ls != l + 1) + { + test = test + Math.Abs(e[ls - 1]); + } + + ztest = test + Math.Abs(stemp[ls]); + if (ztest.AlmostEqualRelative(test, 15)) + { + stemp[ls] = 0.0; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + double f; + double cs; + double sn; + switch (kase) + { + // Deflate negligible s[m]. + case 1: + f = e[m - 2]; + e[m - 2] = 0.0; + double t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = stemp[k]; + + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + if (k != l) + { + f = -sn * e[k - 1]; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + // Rotate + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((m - 1) * columnsA) + i]); + v[((m - 1) * columnsA) + i] = (cs * v[((m - 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + } + + break; + + // Split at negligible s[l]. + case 2: + f = e[l - 1]; + e[l - 1] = 0.0; + for (k = l; k < m; k++) + { + t1 = stemp[k]; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + f = -sn * e[k]; + e[k] = cs * e[k]; + if (computeVectors) + { + // Rotate + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((l - 1) * rowsA) + i]); + u[((l - 1) * rowsA) + i] = (cs * u[((l - 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + break; + + // Perform one qr step. + case 3: + + // calculate the shift. + var scale = 0.0; + scale = Math.Max(scale, Math.Abs(stemp[m - 1])); + scale = Math.Max(scale, Math.Abs(stemp[m - 2])); + scale = Math.Max(scale, Math.Abs(e[m - 2])); + scale = Math.Max(scale, Math.Abs(stemp[l])); + scale = Math.Max(scale, Math.Abs(e[l])); + var sm = stemp[m - 1] / scale; + var smm1 = stemp[m - 2] / scale; + var emm1 = e[m - 2] / scale; + var sl = stemp[l] / scale; + var el = e[l] / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0; + if (b != 0.0 || c != 0.0) + { + shift = Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * stemp[k]) + (sn * e[k]); + e[k] = (cs * e[k]) - (sn * stemp[k]); + g = sn * stemp[k + 1]; + stemp[k + 1] = cs * stemp[k + 1]; + if (computeVectors) + { + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((k + 1) * columnsA) + i]); + v[((k + 1) * columnsA) + i] = (cs * v[((k + 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + + Drotg(ref f, ref g, out cs, out sn); + stemp[k] = f; + f = (cs * e[k]) + (sn * stemp[k + 1]); + stemp[k + 1] = -(sn * e[k]) + (cs * stemp[k + 1]); + g = sn * e[k + 1]; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < rowsA) + { + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((k + 1) * rowsA) + i]); + u[((k + 1) * rowsA) + i] = (cs * u[((k + 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence + case 4: + + // Make the singular value positive + if (stemp[l] < 0.0) + { + stemp[l] = -stemp[l]; + if (computeVectors) + { + // A part of column "l" of matrix VT from row 0 to end multiply by -1 + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = v[(l * columnsA) + i] * -1.0; + } + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (stemp[l] >= stemp[l + 1]) + { + break; + } + + t = stemp[l]; + stemp[l] = stemp[l + 1]; + stemp[l + 1] = t; + if (computeVectors && l < columnsA) + { + // Swap columns l, l + 1 + for (i = 0; i < columnsA; i++) + { + var z = v[(l * columnsA) + i]; + v[(l * columnsA) + i] = v[((l + 1) * columnsA) + i]; + v[((l + 1) * columnsA) + i] = z; + } + } + + if (computeVectors && l < rowsA) + { + // Swap columns l, l + 1 + for (i = 0; i < rowsA; i++) + { + var z = u[(l * rowsA) + i]; + u[(l * rowsA) + i] = u[((l + 1) * rowsA) + i]; + u[((l + 1) * rowsA) + i] = z; + } + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + // Finally transpose "v" to get "vt" matrix + for (i = 0; i < columnsA; i++) + { + for (j = 0; j < columnsA; j++) + { + vt[(j * columnsA) + i] = v[(i * columnsA) + j]; + } + } + } + + // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses + // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. + // We should port lapack's svd routine to remove this problem. + Buffer.BlockCopy(stemp, 0, s, 0, Math.Min(rowsA, columnsA) * Constants.SizeOfDouble); + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + static void Drotg(ref double da, ref double db, out double c, out double s) + { + double r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0) + { + c = 1.0; + s = 0.0; + r = 0.0; + z = 0.0; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0) + { + z = 1.0 / c; + } + } + + da = r; + db = z; + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolve(double[] a, int rowsA, int columnsA, double[] b, int columnsB, double[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new double[Math.Min(rowsA, columnsA)]; + var u = new double[rowsA * rowsA]; + var vt = new double[columnsA * columnsA]; + + var clone = new double[a.Length]; + Buffer.BlockCopy(a, 0, clone, 0, a.Length * Constants.SizeOfDouble); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolveFactored(int rowsA, int columnsA, double[] s, double[] u, double[] vt, double[] b, int columnsB, double[] x) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var mn = Math.Min(rowsA, columnsA); + var tmp = new double[columnsA]; + + for (var k = 0; k < columnsB; k++) + { + for (var j = 0; j < columnsA; j++) + { + double value = 0; + if (j < mn) + { + for (var i = 0; i < rowsA; i++) + { + value += u[(j * rowsA) + i] * b[(k * rowsA) + i]; + } + + value /= s[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < columnsA; j++) + { + double value = 0; + for (var i = 0; i < columnsA; i++) + { + value += vt[(j * columnsA) + i] * tmp[i]; + } + + x[(k * columnsA) + j] = value; + } + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public virtual void EigenDecomp(bool isSymmetric, int order, double[] matrix, double[] matrixEv, Complex[] vectorEv, double[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var d = new double[order]; + var e = new double[order]; + + if (isSymmetric) + { + Buffer.BlockCopy(matrix, 0, matrixEv, 0, matrix.Length * Constants.SizeOfDouble); + var om1 = order - 1; + for (var i = 0; i < order; i++) + { + d[i] = matrixEv[i * order + om1]; + } + + Numerics.LinearAlgebra.Double.Factorization.DenseEvd.SymmetricTridiagonalize(matrixEv, d, e, order); + Numerics.LinearAlgebra.Double.Factorization.DenseEvd.SymmetricDiagonalize(matrixEv, d, e, order); + } + else + { + var matrixH = new double[matrix.Length]; + Buffer.BlockCopy(matrix, 0, matrixH, 0, matrix.Length * Constants.SizeOfDouble); + Numerics.LinearAlgebra.Double.Factorization.DenseEvd.NonsymmetricReduceToHessenberg(matrixEv, matrixH, order); + Numerics.LinearAlgebra.Double.Factorization.DenseEvd.NonsymmetricReduceHessenberToRealSchur(matrixEv, matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(d[i], e[i]); + + var io = i * order; + matrixD[io + i] = d[i]; + + if (e[i] > 0) + { + matrixD[io + order + i] = e[i]; + matrixD[(i + 1) * order + i] = e[i]; + } + else if (e[i] < 0) + { + matrixD[io - order + i] = e[i]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Single.cs b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Single.cs new file mode 100644 index 0000000..6445a7c --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.Single.cs @@ -0,0 +1,2734 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Threading; + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.ManagedReference; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedReferenceLinearAlgebraProvider +{ + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + public virtual void AddVectorToScaledVector(float[] y, float alpha, float[] x, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (alpha == 0.0) + { + y.Copy(result); + } + else if (alpha == 1.0) + { + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = y[i] + x[i]; + } + }); + } + else + { + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = y[i] + (alpha * x[i]); + } + }); + } + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + public virtual void ScaleArray(float alpha, float[] x, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (alpha == 0.0) + { + Array.Clear(result, 0, result.Length); + } + else if (alpha == 1.0) + { + x.Copy(result); + } + else + { + CommonParallel.For(0, x.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = alpha * x[i]; + } + }); + } + } + + /// + /// Conjugates an array. Can be used to conjugate a vector and a matrix. + /// + /// The values to conjugate. + /// This result of the conjugation. + public virtual void ConjugateArray(float[] x, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + x.CopyTo(result, 0); + } + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + public virtual float DotProduct(float[] x, float[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var sum = 0.0f; + + for (var index = 0; index < y.Length; index++) + { + sum += y[index] * x[index]; + } + + return sum; + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void AddArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] + y[i]; + } + }); + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void SubtractArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] - y[i]; + } + }); + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseMultiplyArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] * y[i]; + } + }); + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWiseDivideArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = x[i] / y[i]; + } + }); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public virtual void PointWisePowerArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (y.Length != x.Length || y.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + CommonParallel.For(0, y.Length, 4096, (a, b) => + { + for (int i = a; i < b; i++) + { + result[i] = (float)Math.Pow(x[i], y[i]); + } + }); + } + + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows. + /// The number of columns. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + public virtual double MatrixNorm(Norm norm, int rows, int columns, float[] matrix) + { + switch (norm) + { + case Norm.OneNorm: + var norm1 = 0d; + for (var j = 0; j < columns; j++) + { + var s = 0d; + for (var i = 0; i < rows; i++) + { + s += Math.Abs(matrix[(j * rows) + i]); + } + + norm1 = Math.Max(norm1, s); + } + + return norm1; + case Norm.LargestAbsoluteValue: + var normMax = 0d; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + normMax = Math.Max(Math.Abs(matrix[(j * rows) + i]), normMax); + } + } + + return normMax; + case Norm.InfinityNorm: + var r = new double[rows]; + for (var j = 0; j < columns; j++) + { + for (var i = 0; i < rows; i++) + { + r[i] += Math.Abs(matrix[(j * rows) + i]); + } + } + // TODO: reuse + var max = r[0]; + for (int i = 0; i < r.Length; i++) + { + if (r[i] > max) + { + max = r[i]; + } + } + + return max; + case Norm.FrobeniusNorm: + var aat = new float[rows * rows]; + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.Transpose, 1.0f, matrix, rows, columns, matrix, rows, columns, 0.0f, aat); + var normF = 0d; + for (var i = 0; i < rows; i++) + { + normF += Math.Abs(aat[(i * rows) + i]); + } + + return Math.Sqrt(normF); + default: + throw new NotSupportedException(); + } + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public virtual void MatrixMultiply(float[] x, int rowsX, int columnsX, float[] y, int rowsY, int columnsY, float[] result) + { + // First check some basic requirement on the parameters of the matrix multiplication. + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (rowsX * columnsX != x.Length) + { + throw new ArgumentException("x.Length != xRows * xColumns"); + } + + if (rowsY * columnsY != y.Length) + { + throw new ArgumentException("y.Length != yRows * yColumns"); + } + + if (columnsX != rowsY) + { + throw new ArgumentException("xColumns != yRows"); + } + + if (rowsX * columnsY != result.Length) + { + throw new ArgumentException("xRows * yColumns != result.Length"); + } + + // Check whether we will be overwriting any of our inputs and make copies if necessary. + // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory + // as result, we can do it on a row wise basis. We should investigate this. + float[] xdata; + if (ReferenceEquals(x, result)) + { + xdata = (float[])x.Clone(); + } + else + { + xdata = x; + } + + float[] ydata; + if (ReferenceEquals(y, result)) + { + ydata = (float[])y.Clone(); + } + else + { + ydata = y; + } + + Array.Clear(result, 0, result.Length); + + CacheObliviousMatrixMultiply(Transpose.DontTranspose, Transpose.DontTranspose, 1.0f, xdata, 0, 0, ydata, 0, 0, result, 0, 0, rowsX, columnsY, columnsX, rowsX, columnsY, columnsX, true); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, float alpha, float[] a, int rowsA, int columnsA, float[] b, int rowsB, int columnsB, float beta, float[] c) + { + int m; // The number of rows of matrix op(A) and of the matrix C. + int n; // The number of columns of matrix op(B) and of the matrix C. + int k; // The number of columns of matrix op(A) and the rows of the matrix op(B). + + // First check some basic requirement on the parameters of the matrix multiplication. + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if ((int)transposeA > 111 && (int)transposeB > 111) + { + if (rowsA != columnsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (columnsA * rowsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = columnsA; + n = rowsB; + k = rowsA; + } + else if ((int)transposeA > 111) + { + if (rowsA != rowsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (columnsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = columnsA; + n = columnsB; + k = rowsA; + } + else if ((int)transposeB > 111) + { + if (columnsA != columnsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (rowsA * rowsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = rowsA; + n = rowsB; + k = columnsA; + } + else + { + if (columnsA != rowsB) + { + throw new ArgumentOutOfRangeException(); + } + + if (rowsA * columnsB != c.Length) + { + throw new ArgumentOutOfRangeException(); + } + + m = rowsA; + n = columnsB; + k = columnsA; + } + + if (alpha == 0.0 && beta == 0.0) + { + Array.Clear(c, 0, c.Length); + return; + } + + // Check whether we will be overwriting any of our inputs and make copies if necessary. + // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory + // as result, we can do it on a row wise basis. We should investigate this. + float[] adata; + if (ReferenceEquals(a, c)) + { + adata = (float[])a.Clone(); + } + else + { + adata = a; + } + + float[] bdata; + if (ReferenceEquals(b, c)) + { + bdata = (float[])b.Clone(); + } + else + { + bdata = b; + } + + if (beta == 0.0f) + { + Array.Clear(c, 0, c.Length); + } + else if (beta != 1.0f) + { + ScaleArray(beta, c, c); + } + + if (alpha == 0.0f) + { + return; + } + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, adata, 0, 0, bdata, 0, 0, c, 0, 0, m, n, k, m, n, k, true); + } + + /// + /// Cache-Oblivious Matrix Multiplication + /// + /// if set to true transpose matrix A. + /// if set to true transpose matrix B. + /// The value to scale the matrix A with. + /// The matrix A. + /// Row-shift of the left matrix + /// Column-shift of the left matrix + /// The matrix B. + /// Row-shift of the right matrix + /// Column-shift of the right matrix + /// The matrix C. + /// Row-shift of the result matrix + /// Column-shift of the result matrix + /// The number of rows of matrix op(A) and of the matrix C. + /// The number of columns of matrix op(B) and of the matrix C. + /// The number of columns of matrix op(A) and the rows of the matrix op(B). + /// The constant number of rows of matrix op(A) and of the matrix C. + /// The constant number of columns of matrix op(B) and of the matrix C. + /// The constant number of columns of matrix op(A) and the rows of the matrix op(B). + /// Indicates if this is the first recursion. + static void CacheObliviousMatrixMultiply(Transpose transposeA, Transpose transposeB, float alpha, float[] matrixA, int shiftArow, int shiftAcol, float[] matrixB, int shiftBrow, int shiftBcol, float[] result, int shiftCrow, int shiftCcol, int m, int n, int k, int constM, int constN, int constK, bool first) + { + if (m + n <= Control.ParallelizeOrder || m == 1 || n == 1 || k == 1) + { + if ((int)transposeA > 111 && (int)transposeB > 111) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + float sum = 0; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else if ((int)transposeA > 111) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + float sum = 0; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[(matArowPos * constK) + k1 + shiftAcol] * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else if ((int)transposeB > 111) + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + float sum = 0; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[((k1 + shiftBrow) * constN) + matBcolPos]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + else + { + for (var m1 = 0; m1 < m; m1++) + { + var matArowPos = m1 + shiftArow; + var matCrowPos = m1 + shiftCrow; + for (var n1 = 0; n1 < n; ++n1) + { + var matBcolPos = n1 + shiftBcol; + float sum = 0; + for (var k1 = 0; k1 < k; ++k1) + { + sum += matrixA[((k1 + shiftAcol) * constM) + matArowPos] * + matrixB[(matBcolPos * constK) + k1 + shiftBrow]; + } + + result[((n1 + shiftCcol) * constM) + matCrowPos] += alpha * sum; + } + } + } + } + else + { + // divide and conquer + int m2 = m / 2, n2 = n / 2, k2 = k / 2; + + if (first) + { + CommonParallel.Invoke( + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false)); + + CommonParallel.Invoke( + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false), + () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false)); + } + else + { + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false); + + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false); + CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false); + } + } + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + public virtual void LUFactor(float[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + ipiv[i] = i; + } + + var vecLUcolj = new float[order]; + + // Outer loop. + for (var j = 0; j < order; j++) + { + var indexj = j * order; + var indexjj = indexj + j; + + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vecLUcolj[i] = data[indexj + i]; + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + // Most of the time is spent in the following dot product. + var kmax = Math.Min(i, j); + var s = 0.0f; + for (var k = 0; k < kmax; k++) + { + s += data[(k * order) + i] * vecLUcolj[k]; + } + + data[indexj + i] = vecLUcolj[i] -= s; + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (Math.Abs(vecLUcolj[i]) > Math.Abs(vecLUcolj[p])) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var indexk = k * order; + var indexkp = indexk + p; + var indexkj = indexk + j; + var temp = data[indexkp]; + data[indexkp] = data[indexkj]; + data[indexkj] = temp; + } + + ipiv[j] = p; + } + + // Compute multipliers. + if (j < order & data[indexjj] != 0.0) + { + for (var i = j + 1; i < order; i++) + { + data[indexj + i] /= data[indexjj]; + } + } + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + public virtual void LUInverse(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var ipiv = new int[order]; + LUFactor(a, order, ipiv); + LUInverseFactored(a, order, ipiv); + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + public virtual void LUInverseFactored(float[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var inverse = new float[a.Length]; + for (var i = 0; i < order; i++) + { + inverse[i + (order * i)] = 1.0f; + } + + LUSolveFactored(order, a, order, ipiv, inverse); + inverse.Copy(a); + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + public virtual void LUSolve(int columnsOfB, float[] a, int order, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var ipiv = new int[order]; + var clone = new float[a.Length]; + a.Copy(clone); + LUFactor(clone, order, ipiv); + LUSolveFactored(columnsOfB, clone, order, ipiv, b); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + public virtual void LUSolveFactored(int columnsOfB, float[] a, int order, int[] ipiv, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (a.Length != order * order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != order * columnsOfB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + // Compute the column vector P*B + for (var i = 0; i < ipiv.Length; i++) + { + if (ipiv[i] == i) + { + continue; + } + + var p = ipiv[i]; + for (var j = 0; j < columnsOfB; j++) + { + var indexk = j * order; + var indexkp = indexk + p; + var indexkj = indexk + i; + var temp = b[indexkp]; + b[indexkp] = b[indexkj]; + b[indexkj] = temp; + } + } + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + var korder = k * order; + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + var korder = k + (k * order); + for (var j = 0; j < columnsOfB; j++) + { + b[k + (j * order)] /= a[korder]; + } + + korder = k * order; + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsOfB; j++) + { + var index = j * order; + b[i + index] -= b[k + index] * a[i + korder]; + } + } + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + public virtual void CholeskyFactor(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + var tmpColumn = new float[order]; + + // Main loop - along the diagonal + for (var ij = 0; ij < order; ij++) + { + // "Pivot" element + var tmpVal = a[(ij * order) + ij]; + + if (tmpVal > 0.0) + { + tmpVal = (float)Math.Sqrt(tmpVal); + a[(ij * order) + ij] = tmpVal; + tmpColumn[ij] = tmpVal; + + // Calculate multipliers and copy to local column + // Current column, below the diagonal + for (var i = ij + 1; i < order; i++) + { + a[(ij * order) + i] /= tmpVal; + tmpColumn[i] = a[(ij * order) + i]; + } + + // Remaining columns, below the diagonal + DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + for (int i = ij + 1; i < order; i++) + { + a[(i * order) + ij] = 0.0f; + } + } + } + + /// + /// Calculate Cholesky step + /// + /// Factor matrix + /// Number of rows + /// Column start + /// Total columns + /// Multipliers calculated previously + /// Number of available processors + static void DoCholeskyStep(float[] data, int rowDim, int firstCol, int colLimit, float[] multipliers, int availableCores) + { + var tmpColCount = colLimit - firstCol; + + if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) + { + var tmpSplit = firstCol + (tmpColCount / 3); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), + () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); + } + else + { + for (var j = firstCol; j < colLimit; j++) + { + var tmpVal = multipliers[j]; + for (var i = j; i < rowDim; i++) + { + data[(j * rowDim) + i] -= multipliers[i] * tmpVal; + } + } + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + public virtual void CholeskySolve(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var clone = new float[a.Length]; + a.Copy(clone); + CholeskyFactor(clone, orderA); + CholeskySolveFactored(clone, orderA, b, columnsB); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + public virtual void CholeskySolveFactored(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + CommonParallel.For(0, columnsB, (u, v) => + { + for (int i = u; i < v; i++) + { + DoCholeskySolve(a, orderA, b, i); + } + }); + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. Has to be different than . + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The column to solve for. + static void DoCholeskySolve(float[] a, int orderA, float[] b, int index) + { + var cindex = index * orderA; + + // Solve L*Y = B; + float sum; + for (var i = 0; i < orderA; i++) + { + sum = b[cindex + i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= a[(k * orderA) + i] * b[cindex + k]; + } + + b[cindex + i] = sum / a[(i * orderA) + i]; + } + + // Solve L'*X = Y; + for (var i = orderA - 1; i >= 0; i--) + { + sum = b[cindex + i]; + var iindex = i * orderA; + for (var k = i + 1; k < orderA; k++) + { + sum -= a[iindex + k] * b[cindex + k]; + } + + b[cindex + i] = sum / a[iindex + i]; + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void QRFactor(float[] r, int rowsR, int columnsR, float[] q, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR * rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var work = columnsR > rowsR ? new float[rowsR * rowsR] : new float[rowsR * columnsR]; + + CommonParallel.For(0, rowsR, (a, b) => + { + for (int i = a; i < b; i++) + { + q[(i * rowsR) + i] = 1.0f; + } + }); + + var minmn = Math.Min(rowsR, columnsR); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, r, rowsR, i, i); + ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + public virtual void ThinQRFactor(float[] a, int rowsA, int columnsA, float[] r, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(a)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var work = new float[rowsA * columnsA]; + + var minmn = Math.Min(rowsA, columnsA); + for (var i = 0; i < minmn; i++) + { + GenerateColumn(work, a, rowsA, i, i); + ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); + } + + //copy R + for (var j = 0; j < columnsA; j++) + { + var rIndex = j * columnsA; + var aIndex = j * rowsA; + for (var i = 0; i < columnsA; i++) + { + r[rIndex + i] = a[aIndex + i]; + } + } + + //clear A and set diagonals to 1 + Array.Clear(a, 0, a.Length); + for (var i = 0; i < columnsA; i++) + { + a[i * rowsA + i] = 1.0f; + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); + } + } + + #region QR Factor Helper functions + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Index of column in work array + /// Q or R matrices + /// The first row in + /// The last row + /// The first column + /// The last column + /// Number of available CPUs + static void ComputeQR(float[] work, int workIndex, float[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) + { + if (rowStart > rowCount || columnStart > columnCount) + { + return; + } + + var tmpColCount = columnCount - columnStart; + + if ((availableCores > 1) && (tmpColCount > 200)) + { + var tmpSplit = columnStart + (tmpColCount / 2); + var tmpCores = availableCores / 2; + + CommonParallel.Invoke( + () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), + () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); + } + else + { + for (var j = columnStart; j < columnCount; j++) + { + var scale = 0.0f; + for (var i = rowStart; i < rowCount; i++) + { + scale += work[(workIndex * rowCount) + i - rowStart] * a[(j * rowCount) + i]; + } + + for (var i = rowStart; i < rowCount; i++) + { + a[(j * rowCount) + i] -= work[(workIndex * rowCount) + i - rowStart] * scale; + } + } + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Work array + /// Initial matrix + /// The number of rows in matrix + /// The first row + /// Column index + static void GenerateColumn(float[] work, float[] a, int rowCount, int row, int column) + { + var tmp = column * rowCount; + var index = tmp + row; + + CommonParallel.For(row, rowCount, (u, v) => + { + for (int i = u; i < v; i++) + { + var iIndex = tmp + i; + work[iIndex - row] = a[iIndex]; + a[iIndex] = 0.0f; + } + }); + + var norm = 0.0; + for (var i = 0; i < rowCount - row; ++i) + { + var iindex = tmp + i; + norm += work[iindex] * work[iindex]; + } + + norm = Math.Sqrt(norm); + if (row == rowCount - 1 || norm == 0) + { + a[index] = -work[tmp]; + work[tmp] = (float)Constants.Sqrt2; + return; + } + + var scale = 1.0f / (float)norm; + if (work[tmp] < 0.0) + { + scale *= -1.0f; + } + + a[index] = -1.0f / scale; + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] *= scale; + } + }); + work[tmp] += 1.0f; + + var s = (float)Math.Sqrt(1.0 / work[tmp]); + CommonParallel.For(0, rowCount - row, 4096, (u, v) => + { + for (int i = u; i < v; i++) + { + work[tmp + i] *= s; + } + }); + } + + #endregion + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolve(float[] a, int rows, int columns, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var work = new float[rows * columns]; + + var clone = new float[a.Length]; + a.Copy(clone); + + if (method == QRMethod.Full) + { + var q = new float[rows * rows]; + QRFactor(clone, rows, columns, q, work); + QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); + } + else + { + var r = new float[columns * columns]; + ThinQRFactor(clone, rows, columns, r, work); + QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + public virtual void QRSolveFactored(float[] q, float[] r, int rowsA, int columnsA, float[] tau, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (rowsA < columnsA) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + var sol = new float[b.Length]; + + // Copy B matrix to "sol", so B data will not be changed + Buffer.BlockCopy(b, 0, sol, 0, b.Length * Constants.SizeOfFloat); + + // Compute Y = transpose(Q)*B + var column = new float[rowsA]; + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + Array.Copy(sol, jm, column, 0, rowsA); + CommonParallel.For(0, columnsA, (u, v) => + { + for (int i = u; i < v; i++) + { + var im = i * rowsA; + + var sum = 0.0f; + for (var k = 0; k < rowsA; k++) + { + sum += q[im + k] * column[k]; + } + + sol[jm + i] = sum; + } + }); + } + + // Solve R*X = Y; + for (var k = columnsA - 1; k >= 0; k--) + { + var km = k * rowsR; + for (var j = 0; j < columnsB; j++) + { + sol[(j * rowsA) + k] /= r[km + k]; + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < columnsB; j++) + { + var jm = j * rowsA; + sol[jm + i] -= sol[jm + k] * r[km + i]; + } + } + } + + // Fill result matrix + for (var col = 0; col < columnsB; col++) + { + Array.Copy(sol, col * rowsA, x, col * columnsA, columnsR); + } + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + public virtual void SingularValueDecomposition(bool computeVectors, float[] a, int rowsA, int columnsA, float[] s, float[] u, float[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var work = new float[rowsA]; + + const int maxiter = 1000; + + var e = new float[columnsA]; + var v = new float[vt.Length]; + var stemp = new float[Math.Min(rowsA + 1, columnsA)]; + + int i, j, l, lp1; + + float t; + + var ncu = rowsA; + + // Reduce matrix to bidiagonal form, storing the diagonal elements + // in "s" and the super-diagonal elements in "e". + var nct = Math.Min(rowsA - 1, columnsA); + var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); + var lu = Math.Max(nct, nrt); + + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and + // place the l-th diagonal in vector s[l]. + var l1 = l; + + var sum = 0.0f; + for (var i1 = l; i1 < rowsA; i1++) + { + sum += a[(l1 * rowsA) + i1] * a[(l1 * rowsA) + i1]; + } + + stemp[l] = (float)Math.Sqrt(sum); + + if (stemp[l] != 0.0) + { + if (a[(l * rowsA) + l] != 0.0) + { + stemp[l] = Math.Abs(stemp[l]) * (a[(l * rowsA) + l] / Math.Abs(a[(l * rowsA) + l])); + } + + // A part of column "l" of Matrix A from row "l" to end multiply by 1.0 / s[l] + for (i = l; i < rowsA; i++) + { + a[(l * rowsA) + i] = a[(l * rowsA) + i] * (1.0f / stemp[l]); + } + + a[(l * rowsA) + l] = 1.0f + a[(l * rowsA) + l]; + } + + stemp[l] = -stemp[l]; + } + + for (j = lp1; j < columnsA; j++) + { + if (l < nct) + { + if (stemp[l] != 0.0) + { + // Apply the transformation. + t = 0.0f; + for (i = l; i < rowsA; i++) + { + t += a[(j * rowsA) + i] * a[(l * rowsA) + i]; + } + + t = -t / a[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += t * a[(l * rowsA) + ii]; + } + } + } + + // Place the l-th row of matrix into "e" for the + // subsequent calculation of the row transformation. + e[j] = a[(j * rowsA) + l]; + } + + if (computeVectors && l < nct) + { + // Place the transformation in "u" for subsequent back multiplication. + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = a[(l * rowsA) + i]; + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = 0.0; + for (i = lp1; i < e.Length; i++) + { + enorm += e[i] * e[i]; + } + + e[l] = (float)Math.Sqrt(enorm); + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = Math.Abs(e[l]) * (e[lp1] / Math.Abs(e[lp1])); + } + + // Scale vector "e" from "lp1" by 1.0 / e[l] + for (i = lp1; i < e.Length; i++) + { + e[i] = e[i] * (1.0f / e[l]); + } + + e[lp1] = 1.0f + e[lp1]; + } + + e[l] = -e[l]; + + if (lp1 < rowsA && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < rowsA; i++) + { + work[i] = 0.0f; + } + + for (j = lp1; j < columnsA; j++) + { + for (var ii = lp1; ii < rowsA; ii++) + { + work[ii] += e[j] * a[(j * rowsA) + ii]; + } + } + + for (j = lp1; j < columnsA; j++) + { + var ww = -e[j] / e[lp1]; + for (var ii = lp1; ii < rowsA; ii++) + { + a[(j * rowsA) + ii] += ww * work[ii]; + } + } + } + + if (!computeVectors) + { + continue; + } + + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < columnsA; i++) + { + v[(l * columnsA) + i] = e[i]; + } + } + + // Set up the final bidiagonal matrix or order m. + var m = Math.Min(columnsA, rowsA + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < columnsA) + { + stemp[nctp1 - 1] = a[((nctp1 - 1) * rowsA) + (nctp1 - 1)]; + } + + if (rowsA < m) + { + stemp[m - 1] = 0.0f; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = a[((m - 1) * rowsA) + (nrtp1 - 1)]; + } + + e[m - 1] = 0.0f; + + // If required, generate "u". + if (computeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < rowsA; i++) + { + u[(j * rowsA) + i] = 0.0f; + } + + u[(j * rowsA) + j] = 1.0f; + } + + for (l = nct - 1; l >= 0; l--) + { + if (stemp[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = 0.0f; + for (i = l; i < rowsA; i++) + { + t += u[(j * rowsA) + i] * u[(l * rowsA) + i]; + } + + t = -t / u[(l * rowsA) + l]; + + for (var ii = l; ii < rowsA; ii++) + { + u[(j * rowsA) + ii] += t * u[(l * rowsA) + ii]; + } + } + + // A part of column "l" of matrix A from row "l" to end multiply by -1.0 + for (i = l; i < rowsA; i++) + { + u[(l * rowsA) + i] = u[(l * rowsA) + i] * -1.0f; + } + + u[(l * rowsA) + l] = 1.0f + u[(l * rowsA) + l]; + for (i = 0; i < l; i++) + { + u[(l * rowsA) + i] = 0.0f; + } + } + else + { + for (i = 0; i < rowsA; i++) + { + u[(l * rowsA) + i] = 0.0f; + } + + u[(l * rowsA) + l] = 1.0f; + } + } + } + + // If it is required, generate v. + if (computeVectors) + { + for (l = columnsA - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < columnsA; j++) + { + t = 0.0f; + for (i = lp1; i < columnsA; i++) + { + t += v[(j * columnsA) + i] * v[(l * columnsA) + i]; + } + + t = -t / v[(l * columnsA) + lp1]; + for (var ii = l; ii < columnsA; ii++) + { + v[(j * columnsA) + ii] += t * v[(l * columnsA) + ii]; + } + } + } + } + + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = 0.0f; + } + + v[(l * columnsA) + l] = 1.0f; + } + } + + // Transform "s" and "e" so that they are double + for (i = 0; i < m; i++) + { + float r; + if (stemp[i] != 0.0) + { + t = stemp[i]; + r = stemp[i] / t; + stemp[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (computeVectors) + { + // A part of column "i" of matrix U from row 0 to end multiply by r + for (j = 0; j < rowsA; j++) + { + u[(i * rowsA) + j] = u[(i * rowsA) + j] * r; + } + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] == 0.0) + { + continue; + } + + t = e[i]; + r = t / e[i]; + e[i] = t; + stemp[i + 1] = stemp[i + 1] * r; + if (!computeVectors) + { + continue; + } + + // A part of column "i+1" of matrix VT from row 0 to end multiply by r + for (j = 0; j < columnsA; j++) + { + v[((i + 1) * columnsA) + j] = v[((i + 1) * columnsA) + j] * r; + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. + // If too many iterations have been performed throw exception. + if (iter >= maxiter) + { + throw new NonConvergenceException(); + } + + // This section of the program inspects for negligible elements in the s and e arrays, + // on completion the variables case and l are set as follows: + // case = 1: if mS[m] and e[l-1] are negligible and l < m + // case = 2: if mS[l] is negligible and l < m + // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). + // case = 4: if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = Math.Abs(stemp[l]) + Math.Abs(stemp[l + 1]); + ztest = test + Math.Abs(e[l]); + if (ztest.AlmostEqualRelative(test, 7)) + { + e[l] = 0.0f; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + Math.Abs(e[ls]); + } + + if (ls != l + 1) + { + test = test + Math.Abs(e[ls - 1]); + } + + ztest = test + Math.Abs(stemp[ls]); + if (ztest.AlmostEqualRelative(test, 7)) + { + stemp[ls] = 0.0f; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by case. + int k; + float f; + float cs; + float sn; + switch (kase) + { + // Deflate negligible s[m]. + case 1: + f = e[m - 2]; + e[m - 2] = 0.0f; + float t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = stemp[k]; + + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + if (k != l) + { + f = -sn * e[k - 1]; + e[k - 1] = cs * e[k - 1]; + } + + if (computeVectors) + { + // Rotate + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((m - 1) * columnsA) + i]); + v[((m - 1) * columnsA) + i] = (cs * v[((m - 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + } + + break; + + // Split at negligible s[l]. + case 2: + f = e[l - 1]; + e[l - 1] = 0.0f; + for (k = l; k < m; k++) + { + t1 = stemp[k]; + Drotg(ref t1, ref f, out cs, out sn); + stemp[k] = t1; + f = -sn * e[k]; + e[k] = cs * e[k]; + if (computeVectors) + { + // Rotate + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((l - 1) * rowsA) + i]); + u[((l - 1) * rowsA) + i] = (cs * u[((l - 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + break; + + // Perform one qr step. + case 3: + + // calculate the shift. + var scale = 0.0f; + scale = Math.Max(scale, Math.Abs(stemp[m - 1])); + scale = Math.Max(scale, Math.Abs(stemp[m - 2])); + scale = Math.Max(scale, Math.Abs(e[m - 2])); + scale = Math.Max(scale, Math.Abs(stemp[l])); + scale = Math.Max(scale, Math.Abs(e[l])); + var sm = stemp[m - 1] / scale; + var smm1 = stemp[m - 2] / scale; + var emm1 = e[m - 2] / scale; + var sl = stemp[l] / scale; + var el = e[l] / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0f; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0f; + if (b != 0.0 || c != 0.0) + { + shift = (float)Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, out cs, out sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * stemp[k]) + (sn * e[k]); + e[k] = (cs * e[k]) - (sn * stemp[k]); + g = sn * stemp[k + 1]; + stemp[k + 1] = cs * stemp[k + 1]; + if (computeVectors) + { + for (i = 0; i < columnsA; i++) + { + var z = (cs * v[(k * columnsA) + i]) + (sn * v[((k + 1) * columnsA) + i]); + v[((k + 1) * columnsA) + i] = (cs * v[((k + 1) * columnsA) + i]) - (sn * v[(k * columnsA) + i]); + v[(k * columnsA) + i] = z; + } + } + + Drotg(ref f, ref g, out cs, out sn); + stemp[k] = f; + f = (cs * e[k]) + (sn * stemp[k + 1]); + stemp[k + 1] = -(sn * e[k]) + (cs * stemp[k + 1]); + g = sn * e[k + 1]; + e[k + 1] = cs * e[k + 1]; + if (computeVectors && k < rowsA) + { + for (i = 0; i < rowsA; i++) + { + var z = (cs * u[(k * rowsA) + i]) + (sn * u[((k + 1) * rowsA) + i]); + u[((k + 1) * rowsA) + i] = (cs * u[((k + 1) * rowsA) + i]) - (sn * u[(k * rowsA) + i]); + u[(k * rowsA) + i] = z; + } + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence + case 4: + + // Make the singular value positive + if (stemp[l] < 0.0) + { + stemp[l] = -stemp[l]; + if (computeVectors) + { + // A part of column "l" of matrix VT from row 0 to end multiply by -1 + for (i = 0; i < columnsA; i++) + { + v[(l * columnsA) + i] = v[(l * columnsA) + i] * -1.0f; + } + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (stemp[l] >= stemp[l + 1]) + { + break; + } + + t = stemp[l]; + stemp[l] = stemp[l + 1]; + stemp[l + 1] = t; + if (computeVectors && l < columnsA) + { + // Swap columns l, l + 1 + for (i = 0; i < columnsA; i++) + { + var z = v[(l * columnsA) + i]; + v[(l * columnsA) + i] = v[((l + 1) * columnsA) + i]; + v[((l + 1) * columnsA) + i] = z; + } + } + + if (computeVectors && l < rowsA) + { + // Swap columns l, l + 1 + for (i = 0; i < rowsA; i++) + { + var z = u[(l * rowsA) + i]; + u[(l * rowsA) + i] = u[((l + 1) * rowsA) + i]; + u[((l + 1) * rowsA) + i] = z; + } + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (computeVectors) + { + // Finally transpose "v" to get "vt" matrix + for (i = 0; i < columnsA; i++) + { + for (j = 0; j < columnsA; j++) + { + vt[(j * columnsA) + i] = v[(i * columnsA) + j]; + } + } + } + + // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses + // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. + // We should port lapack's svd routine to remove this problem. + Buffer.BlockCopy(stemp, 0, s, 0, Math.Min(rowsA, columnsA) * Constants.SizeOfFloat); + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + static void Drotg(ref float da, ref float db, out float c, out float s) + { + float r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0) + { + c = 1.0f; + s = 0.0f; + r = 0.0f; + z = 0.0f; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * (float)Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0f; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0) + { + z = 1.0f / c; + } + } + + da = r; + db = z; + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolve(float[] a, int rowsA, int columnsA, float[] b, int columnsB, float[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new float[Math.Min(rowsA, columnsA)]; + var u = new float[rowsA * rowsA]; + var vt = new float[columnsA * columnsA]; + + var clone = new float[a.Length]; + Buffer.BlockCopy(a, 0, clone, 0, a.Length * Constants.SizeOfFloat); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Solves A*X=B for X using a previously SVD decomposed matrix. + /// + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The s values returned by . + /// The left singular vectors returned by . + /// The right singular vectors returned by . + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public virtual void SvdSolveFactored(int rowsA, int columnsA, float[] s, float[] u, float[] vt, float[] b, int columnsB, float[] x) + { + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var mn = Math.Min(rowsA, columnsA); + var tmp = new float[columnsA]; + + for (var k = 0; k < columnsB; k++) + { + for (var j = 0; j < columnsA; j++) + { + float value = 0; + if (j < mn) + { + for (var i = 0; i < rowsA; i++) + { + value += u[(j * rowsA) + i] * b[(k * rowsA) + i]; + } + + value /= s[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < columnsA; j++) + { + float value = 0; + for (var i = 0; i < columnsA; i++) + { + value += vt[(j * columnsA) + i] * tmp[i]; + } + + x[(k * columnsA) + j] = value; + } + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public virtual void EigenDecomp(bool isSymmetric, int order, float[] matrix, float[] matrixEv, Complex[] vectorEv, float[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var d = new float[order]; + var e = new float[order]; + + if (isSymmetric) + { + Buffer.BlockCopy(matrix, 0, matrixEv, 0, matrix.Length * Constants.SizeOfFloat); + var om1 = order - 1; + for (var i = 0; i < order; i++) + { + d[i] = matrixEv[i * order + om1]; + } + + Numerics.LinearAlgebra.Single.Factorization.DenseEvd.SymmetricTridiagonalize(matrixEv, d, e, order); + Numerics.LinearAlgebra.Single.Factorization.DenseEvd.SymmetricDiagonalize(matrixEv, d, e, order); + } + else + { + var matrixH = new float[matrix.Length]; + Buffer.BlockCopy(matrix, 0, matrixH, 0, matrix.Length * Constants.SizeOfFloat); + Numerics.LinearAlgebra.Single.Factorization.DenseEvd.NonsymmetricReduceToHessenberg(matrixEv, matrixH, order); + Numerics.LinearAlgebra.Single.Factorization.DenseEvd.NonsymmetricReduceHessenberToRealSchur(matrixEv, matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + vectorEv[i] = new Complex(d[i], e[i]); + + var io = i * order; + matrixD[io + i] = d[i]; + + if (e[i] > 0) + { + matrixD[io + order + i] = e[i]; + } + else if (e[i] < 0) + { + matrixD[io - order + i] = e[i]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.cs b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.cs new file mode 100644 index 0000000..39b7ef0 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/ManagedReference/ManagedReferenceLinearAlgebraProvider.cs @@ -0,0 +1,103 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Providers.LinearAlgebra.ManagedReference; + +/// +/// The managed linear algebra provider. +/// +internal partial class ManagedReferenceLinearAlgebraProvider : ILinearAlgebraProvider +{ + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + public virtual bool IsAvailable() + { + return true; + } + + /// + /// Initialize and verify that the provided is indeed available. If not, fall back to alternatives like the managed provider + /// + public virtual void InitializeVerify() + { + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + public virtual void FreeResources() + { + } + + public override string ToString() + { + return "Managed"; + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetRow(Transpose transpose, int rowindx, int numRows, int numCols, T[] matrix, T[] row) + { + if (transpose == Transpose.DontTranspose) + { + for (int i = 0; i < numCols; i++) + { + row[i] = matrix[(i * numRows) + rowindx]; + } + } + else + { + Array.Copy(matrix, rowindx * numCols, row, 0, numCols); + } + } + + /// + /// Assumes that and have already been transposed. + /// + protected static void GetColumn(Transpose transpose, int colindx, int numRows, int numCols, T[] matrix, T[] column) + { + if (transpose == Transpose.DontTranspose) + { + Array.Copy(matrix, colindx * numRows, column, 0, numRows); + } + else + { + for (int i = 0; i < numRows; i++) + { + column[i] = matrix[(i * numCols) + colindx]; + } + } + } +} diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Complex.cs b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Complex.cs new file mode 100644 index 0000000..6d1f800 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Complex.cs @@ -0,0 +1,1205 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.Mkl; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Mkl +{ + /// + /// Intel's Math Kernel Library (MKL) linear algebra provider. + /// + internal partial class MklLinearAlgebraProvider + { + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + [SecuritySafeCritical] + public override double MatrixNorm(Norm norm, int rows, int columns, Complex[] matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (rows <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(rows)); + } + + if (columns <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(columns)); + } + + if (matrix.Length < rows * columns) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, rows * columns), nameof(matrix)); + } + + return SafeNativeMethods.z_matrix_norm((byte)norm, rows, columns, matrix); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override Complex DotProduct(Complex[] x, Complex[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.z_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(Complex[] y, Complex alpha, Complex[] x, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == Complex.Zero) + { + return; + } + + SafeNativeMethods.z_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(Complex alpha, Complex[] x, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == Complex.One) + { + return; + } + + SafeNativeMethods.z_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to Complex.One and beta set to Complex.Zero, and x and y are not transposed. + public override void MatrixMultiply(Complex[] x, int rowsX, int columnsX, Complex[] y, int rowsY, int columnsY, Complex[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex.One, x, rowsX, columnsX, y, rowsY, columnsY, Complex.Zero, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex alpha, Complex[] a, int rowsA, int columnsA, Complex[] b, int rowsB, int columnsB, Complex beta, Complex[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.z_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always Complex.One + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(Complex[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.z_lu_factor(order, data, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.z_lu_inverse(order, a); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.z_lu_inverse_factored(order, a, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, Complex[] a, int order, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.z_lu_solve(order, columnsOfB, a, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, Complex[] a, int order, int[] ipiv, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.z_lu_solve_factored(order, columnsOfB, a, ipiv, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.z_cholesky_factor(order, a); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.z_cholesky_solve(orderA, columnsB, a, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.z_cholesky_solve_factored(orderA, columnsB, a, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(Complex[] r, int rowsR, int columnsR, Complex[] q, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var info = SafeNativeMethods.z_qr_factor(rowsR, columnsR, r, tau, q); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void ThinQRFactor(Complex[] q, int rowsA, int columnsA, Complex[] r, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (q.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(q)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var info = SafeNativeMethods.z_qr_thin_factor(rowsA, columnsA, q, tau, r); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolve(Complex[] a, int rows, int columns, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var info = SafeNativeMethods.z_qr_solve(rows, columns, columnsB, a, b, x); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient, nameof(a)); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(Complex[] q, Complex[] r, int rowsA, int columnsA, Complex[] tau, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + if (method == QRMethod.Full) + { + var info = SafeNativeMethods.z_qr_solve_factored(rowsA, columnsA, columnsB, r, b, tau, x); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + else + { + // we don't have access to the raw Q matrix any more(it is stored in R in the full QR), need to think about this. + // let just call the managed version in the meantime. The heavy lifting has already been done. -marcus + base.QRSolveFactored(q, r, rowsA, columnsA, tau, b, columnsB, x, QRMethod.Thin); + } + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(Complex[] a, int rowsA, int columnsA, Complex[] b, int columnsB, Complex[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex[Math.Min(rowsA, columnsA)]; + var u = new Complex[rowsA*rowsA]; + var vt = new Complex[columnsA*columnsA]; + + var clone = new Complex[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex[] a, int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var info = SafeNativeMethods.z_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt); + + if (info == (int) MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void AddArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.z_vector_add(x.Length, x, y, result); + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void SubtractArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.z_vector_subtract(x.Length, x, y, result); + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWiseMultiplyArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.z_vector_multiply(x.Length, x, y, result); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWisePowerArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (_vectorFunctionsMajor != 0 || _vectorFunctionsMinor < 1) + { + base.PointWisePowerArrays(x, y, result); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.z_vector_power(x.Length, x, y, result); + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWiseDivideArrays(Complex[] x, Complex[] y, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.z_vector_divide(x.Length, x, y, result); + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public override void EigenDecomp(bool isSymmetric, int order, Complex[] matrix, Complex[] matrixEv, Complex[] vectorEv, Complex[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var info = SafeNativeMethods.z_eigen(isSymmetric, order, matrix, matrixEv, vectorEv, matrixD); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Complex32.cs b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Complex32.cs new file mode 100644 index 0000000..bc1a3f8 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Complex32.cs @@ -0,0 +1,1200 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.Mkl; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Mkl +{ + /// + /// Intel's Math Kernel Library (MKL) linear algebra provider. + /// + internal partial class MklLinearAlgebraProvider + { + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + [SecuritySafeCritical] + public override double MatrixNorm(Norm norm, int rows, int columns, Complex32[] matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (rows <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(rows)); + } + + if (columns <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(columns)); + } + + if (matrix.Length < rows * columns) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, rows * columns), nameof(matrix)); + } + + return SafeNativeMethods.c_matrix_norm((byte)norm, rows, columns, matrix); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override Complex32 DotProduct(Complex32[] x, Complex32[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.c_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(Complex32[] y, Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == Complex32.Zero) + { + return; + } + + SafeNativeMethods.c_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == Complex32.One) + { + return; + } + + SafeNativeMethods.c_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to Complex32.One and beta set to Complex32.Zero, and x and y are not transposed. + public override void MatrixMultiply(Complex32[] x, int rowsX, int columnsX, Complex32[] y, int rowsY, int columnsY, Complex32[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex32.One, x, rowsX, columnsX, y, rowsY, columnsY, Complex32.Zero, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex32 alpha, Complex32[] a, int rowsA, int columnsA, Complex32[] b, int rowsB, int columnsB, Complex32 beta, Complex32[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.c_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always Complex32.One + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(Complex32[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.c_lu_factor(order, data, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.c_lu_inverse(order, a); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex32[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.c_lu_inverse_factored(order, a, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, Complex32[] a, int order, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.c_lu_solve(order, columnsOfB, a, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, Complex32[] a, int order, int[] ipiv, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.c_lu_solve_factored(order, columnsOfB, a, ipiv, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.c_cholesky_factor(order, a); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.c_cholesky_solve(orderA, columnsB, a, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.c_cholesky_solve_factored(orderA, columnsB, a, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(Complex32[] r, int rowsR, int columnsR, Complex32[] q, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var info = SafeNativeMethods.c_qr_factor(rowsR, columnsR, r, tau, q); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void ThinQRFactor(Complex32[] q, int rowsA, int columnsA, Complex32[] r, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (q.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(q)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var info = SafeNativeMethods.c_qr_thin_factor(rowsA, columnsA, q, tau, r); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolve(Complex32[] a, int rows, int columns, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var info = SafeNativeMethods.c_qr_solve(rows, columns, columnsB, a, b, x); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient, nameof(a)); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(Complex32[] q, Complex32[] r, int rowsA, int columnsA, Complex32[] tau, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + if (method == QRMethod.Full) + { + var info = SafeNativeMethods.c_qr_solve_factored(rowsA, columnsA, columnsB, r, b, tau, x); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + else + { + // we don't have access to the raw Q matrix any more(it is stored in R in the full QR), need to think about this. + // let just call the managed version in the meantime. The heavy lifting has already been done. -marcus + base.QRSolveFactored(q, r, rowsA, columnsA, tau, b, columnsB, x, QRMethod.Thin); + } + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(Complex32[] a, int rowsA, int columnsA, Complex32[] b, int columnsB, Complex32[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex32[Math.Min(rowsA, columnsA)]; + var u = new Complex32[rowsA*rowsA]; + var vt = new Complex32[columnsA*columnsA]; + + var clone = new Complex32[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex32[] a, int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var info = SafeNativeMethods.c_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void AddArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.c_vector_add(x.Length, x, y, result); + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void SubtractArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.c_vector_subtract(x.Length, x, y, result); + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWiseMultiplyArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.c_vector_multiply(x.Length, x, y, result); + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWiseDivideArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.c_vector_divide(x.Length, x, y, result); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWisePowerArrays(Complex32[] x, Complex32[] y, Complex32[] result) + { + if (_vectorFunctionsMajor != 0 || _vectorFunctionsMinor < 1) + { + base.PointWisePowerArrays(x, y, result); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.c_vector_power(x.Length, x, y, result); + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public override void EigenDecomp(bool isSymmetric, int order, Complex32[] matrix, Complex32[] matrixEv, Complex[] vectorEv, Complex32[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var info = SafeNativeMethods.c_eigen(isSymmetric, order, matrix, matrixEv, vectorEv, matrixD); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Double.cs b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Double.cs new file mode 100644 index 0000000..1d7c79a --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Double.cs @@ -0,0 +1,1205 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.Mkl; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Mkl +{ + /// + /// Intel's Math Kernel Library (MKL) linear algebra provider. + /// + internal partial class MklLinearAlgebraProvider + { + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + [SecuritySafeCritical] + public override double MatrixNorm(Norm norm, int rows, int columns, double[] matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (rows <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(rows)); + } + + if (columns <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(columns)); + } + + if (matrix.Length < rows * columns) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, rows * columns), nameof(matrix)); + } + + return SafeNativeMethods.d_matrix_norm((byte)norm, rows, columns, matrix); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override double DotProduct(double[] x, double[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.d_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(double[] y, double alpha, double[] x, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == 0.0) + { + return; + } + + SafeNativeMethods.d_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(double alpha, double[] x, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == 1.0) + { + return; + } + + SafeNativeMethods.d_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public override void MatrixMultiply(double[] x, int rowsX, int columnsX, double[] y, int rowsY, int columnsY, double[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0, x, rowsX, columnsX, y, rowsY, columnsY, 0.0, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, double alpha, double[] a, int rowsA, int columnsA, double[] b, int rowsB, int columnsB, double beta, double[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.d_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(double[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.d_lu_factor(order, data, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.d_lu_inverse(order, a); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(double[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.d_lu_inverse_factored(order, a, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, double[] a, int order, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.d_lu_solve(order, columnsOfB, a, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, double[] a, int order, int[] ipiv, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.d_lu_solve_factored(order, columnsOfB, a, ipiv, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.d_cholesky_factor(order, a); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.d_cholesky_solve(orderA, columnsB, a, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.d_cholesky_solve_factored(orderA, columnsB, a, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(double[] r, int rowsR, int columnsR, double[] q, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var info = SafeNativeMethods.d_qr_factor(rowsR, columnsR, r, tau, q); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void ThinQRFactor(double[] q, int rowsA, int columnsA, double[] r, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (q.Length != rowsA*columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(q)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA*columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var info = SafeNativeMethods.d_qr_thin_factor(rowsA, columnsA, q, tau, r); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolve(double[] a, int rows, int columns, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var info = SafeNativeMethods.d_qr_solve(rows, columns, columnsB, a, b, x); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient, nameof(a)); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(double[] q, double[] r, int rowsA, int columnsA, double[] tau, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + if (method == QRMethod.Full) + { + var info = SafeNativeMethods.d_qr_solve_factored(rowsA, columnsA, columnsB, r, b, tau, x); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + else + { + // we don't have access to the raw Q matrix any more(it is stored in R in the full QR), need to think about this. + // let just call the managed version in the meantime. The heavy lifting has already been done. -marcus + base.QRSolveFactored(q, r, rowsA, columnsA, tau, b, columnsB, x, QRMethod.Thin); + } + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(double[] a, int rowsA, int columnsA, double[] b, int columnsB, double[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new double[Math.Min(rowsA, columnsA)]; + var u = new double[rowsA*rowsA]; + var vt = new double[columnsA*columnsA]; + + var clone = new double[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, double[] a, int rowsA, int columnsA, double[] s, double[] u, double[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var info = SafeNativeMethods.d_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void AddArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.d_vector_add(x.Length, x, y, result); + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void SubtractArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.d_vector_subtract(x.Length, x, y, result); + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWiseMultiplyArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.d_vector_multiply(x.Length, x, y, result); + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWiseDivideArrays(double[] x, double[] y, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.d_vector_divide(x.Length, x, y, result); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWisePowerArrays(double[] x, double[] y, double[] result) + { + if (_vectorFunctionsMajor != 0 || _vectorFunctionsMinor < 1) + { + base.PointWisePowerArrays(x, y, result); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.d_vector_power(x.Length, x, y, result); + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public override void EigenDecomp(bool isSymmetric, int order, double[] matrix, double[] matrixEv, Complex[] vectorEv, double[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrixD)); + } + + var info = SafeNativeMethods.d_eigen(isSymmetric, order, matrix, matrixEv, vectorEv, matrixD); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Single.cs b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Single.cs new file mode 100644 index 0000000..422838e --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.Single.cs @@ -0,0 +1,1200 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.Mkl; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Mkl +{ + /// + /// Intel's Math Kernel Library (MKL) linear algebra provider. + /// + internal partial class MklLinearAlgebraProvider + { + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + [SecuritySafeCritical] + public override double MatrixNorm(Norm norm, int rows, int columns, float[] matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (rows <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(rows)); + } + + if (columns <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(columns)); + } + + if (matrix.Length < rows * columns) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, rows * columns), nameof(matrix)); + } + + return SafeNativeMethods.s_matrix_norm((byte)norm, rows, columns, matrix); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override float DotProduct(float[] x, float[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.s_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(float[] y, float alpha, float[] x, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == 0.0f) + { + return; + } + + SafeNativeMethods.s_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(float alpha, float[] x, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == 1.0f) + { + return; + } + + SafeNativeMethods.s_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0f and beta set to 0.0f, and x and y are not transposed. + public override void MatrixMultiply(float[] x, int rowsX, int columnsX, float[] y, int rowsY, int columnsY, float[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0f, x, rowsX, columnsX, y, rowsY, columnsY, 0.0f, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, float alpha, float[] a, int rowsA, int columnsA, float[] b, int rowsB, int columnsB, float beta, float[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.s_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0f + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(float[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.s_lu_factor(order, data, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.s_lu_inverse(order, a); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(float[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.s_lu_inverse_factored(order, a, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, float[] a, int order, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.s_lu_solve(order, columnsOfB, a, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, float[] a, int order, int[] ipiv, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.s_lu_solve_factored(order, columnsOfB, a, ipiv, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.s_cholesky_factor(order, a); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.s_cholesky_solve(orderA, columnsB, a, b); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.s_cholesky_solve_factored(orderA, columnsB, a, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(float[] r, int rowsR, int columnsR, float[] q, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var info = SafeNativeMethods.s_qr_factor(rowsR, columnsR, r, tau, q); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void ThinQRFactor(float[] q, int rowsA, int columnsA, float[] r, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (q.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(q)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var info = SafeNativeMethods.s_qr_thin_factor(rowsA, columnsA, q, tau, r); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolve(float[] a, int rows, int columns, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var info = SafeNativeMethods.s_qr_solve(rows, columns, columnsB, a, b, x); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient, nameof(a)); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(float[] q, float[] r, int rowsA, int columnsA, float[] tau, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + if (method == QRMethod.Full) + { + var info = SafeNativeMethods.s_qr_solve_factored(rowsA, columnsA, columnsB, r, b, tau, x); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + else + { + // we don't have access to the raw Q matrix any more(it is stored in R in the full QR), need to think about this. + // let just call the managed version in the meantime. The heavy lifting has already been done. -marcus + base.QRSolveFactored(q, r, rowsA, columnsA, tau, b, columnsB, x, QRMethod.Thin); + } + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(float[] a, int rowsA, int columnsA, float[] b, int columnsB, float[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new float[Math.Min(rowsA, columnsA)]; + var u = new float[rowsA*rowsA]; + var vt = new float[columnsA*columnsA]; + + var clone = new float[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, float[] a, int rowsA, int columnsA, float[] s, float[] u, float[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var info = SafeNativeMethods.s_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + + /// + /// Does a point wise add of two arrays z = x + y. This can be used + /// to add vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the addition. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void AddArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.s_vector_add(x.Length, x, y, result); + } + + /// + /// Does a point wise subtraction of two arrays z = x - y. This can be used + /// to subtract vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the subtraction. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void SubtractArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.s_vector_subtract(x.Length, x, y, result); + } + + /// + /// Does a point wise multiplication of two arrays z = x * y. This can be used + /// to multiple elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise multiplication. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWiseMultiplyArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.s_vector_multiply(x.Length, x, y, result); + } + + /// + /// Does a point wise division of two arrays z = x / y. This can be used + /// to divide elements of vectors or matrices. + /// + /// The array x. + /// The array y. + /// The result of the point wise division. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWiseDivideArrays(float[] x, float[] y, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.s_vector_divide(x.Length, x, y, result); + } + + /// + /// Does a point wise power of two arrays z = x ^ y. This can be used + /// to raise elements of vectors or matrices to the powers of another vector or matrix. + /// + /// The array x. + /// The array y. + /// The result of the point wise power. + /// There is no equivalent BLAS routine, but many libraries + /// provide optimized (parallel and/or vectorized) versions of this + /// routine. + public override void PointWisePowerArrays(float[] x, float[] y, float[] result) + { + if (_vectorFunctionsMajor != 0 || _vectorFunctionsMinor < 1) + { + base.PointWisePowerArrays(x, y, result); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (x.Length != result.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + SafeNativeMethods.s_vector_power(x.Length, x, y, result); + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public override void EigenDecomp(bool isSymmetric, int order, float[] matrix, float[] matrixEv, Complex[] vectorEv, float[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrixD)); + } + + var info = SafeNativeMethods.s_eigen(isSymmetric, order, matrix, matrixEv, vectorEv, matrixD); + + if (info == (int)MklError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.cs b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.cs new file mode 100644 index 0000000..04ac735 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/Mkl/MklLinearAlgebraProvider.cs @@ -0,0 +1,134 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using MathNet.Numerics.Providers.Common.Mkl; + +namespace MathNet.Numerics.Providers.LinearAlgebra.Mkl +{ + /// + /// Error codes return from the MKL provider. + /// + internal enum MklError : int + { + /// + /// Unable to allocate memory. + /// + MemoryAllocation = -999999 + } + + /// + /// Intel's Math Kernel Library (MKL) linear algebra provider. + /// + internal partial class MklLinearAlgebraProvider : Managed.ManagedLinearAlgebraProvider, IDisposable + { + const int MinimumCompatibleRevision = 4; + + readonly string _hintPath; + readonly MklConsistency _consistency; + readonly MklPrecision _precision; + readonly MklAccuracy _accuracy; + + int _linearAlgebraMajor; + int _linearAlgebraMinor; + int _vectorFunctionsMajor; + int _vectorFunctionsMinor; + + /// Hint path where to look for the native binaries + /// + /// Sets the desired bit consistency on repeated identical computations on varying CPU architectures, + /// as a trade-off with performance. + /// + /// VML optimal precision and rounding. + /// VML accuracy mode. + internal MklLinearAlgebraProvider(string hintPath, MklConsistency consistency, MklPrecision precision, MklAccuracy accuracy) + { + _hintPath = hintPath; + _consistency = consistency; + _precision = precision; + _accuracy = accuracy; + } + + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + public override bool IsAvailable() + { + return MklProvider.IsAvailable(hintPath: _hintPath); + } + + /// + /// Initialize and verify that the provided is indeed available. + /// If calling this method fails, consider to fall back to alternatives like the managed provider. + /// + public override void InitializeVerify() + { + int revision = MklProvider.Load(_hintPath, _consistency, _precision, _accuracy); + if (revision < MinimumCompatibleRevision) + { + throw new NotSupportedException($"MKL Native Provider revision r{revision} is too old. Consider upgrading to a newer version. Revision r{MinimumCompatibleRevision} and newer are supported."); + } + + _linearAlgebraMajor = SafeNativeMethods.query_capability((int)ProviderCapability.LinearAlgebraMajor); + _linearAlgebraMinor = SafeNativeMethods.query_capability((int)ProviderCapability.LinearAlgebraMinor); + _vectorFunctionsMajor = SafeNativeMethods.query_capability((int)ProviderCapability.VectorFunctionsMajor); + _vectorFunctionsMinor = SafeNativeMethods.query_capability((int)ProviderCapability.VectorFunctionsMinor); + + // we only support exactly one major version, since major version changes imply a breaking change. + if (_linearAlgebraMajor != 2) + { + throw new NotSupportedException(string.Format("MKL Native Provider not compatible. Expecting linear algebra v2 but provider implements v{0}.", _linearAlgebraMajor)); + } + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + public override void FreeResources() + { + MklProvider.FreeResources(); + } + + public override string ToString() + { + return MklProvider.Describe(); + } + + public void Dispose() + { + FreeResources(); + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Complex.cs b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Complex.cs new file mode 100644 index 0000000..a1f2cc6 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Complex.cs @@ -0,0 +1,1025 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.OpenBlas; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.OpenBlas +{ + /// + /// OpenBLAS linear algebra provider. + /// + internal partial class OpenBlasLinearAlgebraProvider + { + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + [SecuritySafeCritical] + public override double MatrixNorm(Norm norm, int rows, int columns, Complex[] matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (rows <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(rows)); + } + + if (columns <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(columns)); + } + + if (matrix.Length < rows * columns) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, rows * columns), nameof(matrix)); + } + + return SafeNativeMethods.z_matrix_norm((byte)norm, rows, columns, matrix); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override Complex DotProduct(Complex[] x, Complex[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.z_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(Complex[] y, Complex alpha, Complex[] x, Complex[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == Complex.Zero) + { + return; + } + + SafeNativeMethods.z_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(Complex alpha, Complex[] x, Complex[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == Complex.One) + { + return; + } + + SafeNativeMethods.z_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to Complex.One and beta set to Complex.Zero, and x and y are not transposed. + public override void MatrixMultiply(Complex[] x, int rowsX, int columnsX, Complex[] y, int rowsY, int columnsY, Complex[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex.One, x, rowsX, columnsX, y, rowsY, columnsY, Complex.Zero, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex alpha, Complex[] a, int rowsA, int columnsA, Complex[] b, int rowsB, int columnsB, Complex beta, Complex[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.z_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always Complex.One + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(Complex[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.z_lu_factor(order, data, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.z_lu_inverse(order, a); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.z_lu_inverse_factored(order, a, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, Complex[] a, int order, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.z_lu_solve(order, columnsOfB, a, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, Complex[] a, int order, int[] ipiv, Complex[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.z_lu_solve_factored(order, columnsOfB, a, ipiv, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(Complex[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.z_cholesky_factor(order, a); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.z_cholesky_solve(orderA, columnsB, a, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(Complex[] a, int orderA, Complex[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.z_cholesky_solve_factored(orderA, columnsB, a, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(Complex[] r, int rowsR, int columnsR, Complex[] q, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var info = SafeNativeMethods.z_qr_factor(rowsR, columnsR, r, tau, q); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void ThinQRFactor(Complex[] q, int rowsA, int columnsA, Complex[] r, Complex[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (q.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(q)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var info = SafeNativeMethods.z_qr_thin_factor(rowsA, columnsA, q, tau, r); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolve(Complex[] a, int rows, int columns, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var info = SafeNativeMethods.z_qr_solve(rows, columns, columnsB, a, b, x); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient, nameof(a)); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(Complex[] q, Complex[] r, int rowsA, int columnsA, Complex[] tau, Complex[] b, int columnsB, Complex[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + if (method == QRMethod.Full) + { + var info = SafeNativeMethods.z_qr_solve_factored(rowsA, columnsA, columnsB, r, b, tau, x); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + else + { + // we don't have access to the raw Q matrix any more(it is stored in R in the full QR), need to think about this. + // let just call the managed version in the meantime. The heavy lifting has already been done. -marcus + base.QRSolveFactored(q, r, rowsA, columnsA, tau, b, columnsB, x, QRMethod.Thin); + } + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(Complex[] a, int rowsA, int columnsA, Complex[] b, int columnsB, Complex[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex[Math.Min(rowsA, columnsA)]; + var u = new Complex[rowsA*rowsA]; + var vt = new Complex[columnsA*columnsA]; + + var clone = new Complex[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex[] a, int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var info = SafeNativeMethods.z_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt); + + if (info == (int) NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public override void EigenDecomp(bool isSymmetric, int order, Complex[] matrix, Complex[] matrixEv, Complex[] vectorEv, Complex[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var info = SafeNativeMethods.z_eigen(isSymmetric, order, matrix, matrixEv, vectorEv, matrixD); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Complex32.cs b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Complex32.cs new file mode 100644 index 0000000..78ac98d --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Complex32.cs @@ -0,0 +1,1020 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.OpenBlas; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.OpenBlas +{ + /// + /// OpenBLAS linear algebra provider. + /// + internal partial class OpenBlasLinearAlgebraProvider + { + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + [SecuritySafeCritical] + public override double MatrixNorm(Norm norm, int rows, int columns, Complex32[] matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (rows <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(rows)); + } + + if (columns <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(columns)); + } + + if (matrix.Length < rows * columns) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, rows * columns), nameof(matrix)); + } + + return SafeNativeMethods.c_matrix_norm((byte)norm, rows, columns, matrix); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override Complex32 DotProduct(Complex32[] x, Complex32[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.c_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(Complex32[] y, Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == Complex32.Zero) + { + return; + } + + SafeNativeMethods.c_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(Complex32 alpha, Complex32[] x, Complex32[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == Complex32.One) + { + return; + } + + SafeNativeMethods.c_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to Complex32.One and beta set to Complex32.Zero, and x and y are not transposed. + public override void MatrixMultiply(Complex32[] x, int rowsX, int columnsX, Complex32[] y, int rowsY, int columnsY, Complex32[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex32.One, x, rowsX, columnsX, y, rowsY, columnsY, Complex32.Zero, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, Complex32 alpha, Complex32[] a, int rowsA, int columnsA, Complex32[] b, int rowsB, int columnsB, Complex32 beta, Complex32[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.c_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always Complex32.One + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(Complex32[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.c_lu_factor(order, data, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.c_lu_inverse(order, a); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(Complex32[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.c_lu_inverse_factored(order, a, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, Complex32[] a, int order, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.c_lu_solve(order, columnsOfB, a, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, Complex32[] a, int order, int[] ipiv, Complex32[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.c_lu_solve_factored(order, columnsOfB, a, ipiv, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(Complex32[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.c_cholesky_factor(order, a); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.c_cholesky_solve(orderA, columnsB, a, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(Complex32[] a, int orderA, Complex32[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.c_cholesky_solve_factored(orderA, columnsB, a, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(Complex32[] r, int rowsR, int columnsR, Complex32[] q, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var info = SafeNativeMethods.c_qr_factor(rowsR, columnsR, r, tau, q); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void ThinQRFactor(Complex32[] q, int rowsA, int columnsA, Complex32[] r, Complex32[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (q.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(q)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var info = SafeNativeMethods.c_qr_thin_factor(rowsA, columnsA, q, tau, r); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolve(Complex32[] a, int rows, int columns, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var info = SafeNativeMethods.c_qr_solve(rows, columns, columnsB, a, b, x); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient, nameof(a)); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(Complex32[] q, Complex32[] r, int rowsA, int columnsA, Complex32[] tau, Complex32[] b, int columnsB, Complex32[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + if (method == QRMethod.Full) + { + var info = SafeNativeMethods.c_qr_solve_factored(rowsA, columnsA, columnsB, r, b, tau, x); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + else + { + // we don't have access to the raw Q matrix any more(it is stored in R in the full QR), need to think about this. + // let just call the managed version in the meantime. The heavy lifting has already been done. -marcus + base.QRSolveFactored(q, r, rowsA, columnsA, tau, b, columnsB, x, QRMethod.Thin); + } + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(Complex32[] a, int rowsA, int columnsA, Complex32[] b, int columnsB, Complex32[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new Complex32[Math.Min(rowsA, columnsA)]; + var u = new Complex32[rowsA*rowsA]; + var vt = new Complex32[columnsA*columnsA]; + + var clone = new Complex32[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, Complex32[] a, int rowsA, int columnsA, Complex32[] s, Complex32[] u, Complex32[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var info = SafeNativeMethods.c_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public override void EigenDecomp(bool isSymmetric, int order, Complex32[] matrix, Complex32[] matrixEv, Complex[] vectorEv, Complex32[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order * order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order * order), nameof(matrixD)); + } + + var info = SafeNativeMethods.c_eigen(isSymmetric, order, matrix, matrixEv, vectorEv, matrixD); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Double.cs b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Double.cs new file mode 100644 index 0000000..ab3f7a3 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Double.cs @@ -0,0 +1,1025 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.OpenBlas; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.OpenBlas +{ + /// + /// OpenBLAS linear algebra provider. + /// + internal partial class OpenBlasLinearAlgebraProvider + { + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + [SecuritySafeCritical] + public override double MatrixNorm(Norm norm, int rows, int columns, double[] matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (rows <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(rows)); + } + + if (columns <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(columns)); + } + + if (matrix.Length < rows * columns) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, rows * columns), nameof(matrix)); + } + + return SafeNativeMethods.d_matrix_norm((byte)norm, rows, columns, matrix); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override double DotProduct(double[] x, double[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.d_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(double[] y, double alpha, double[] x, double[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == 0.0) + { + return; + } + + SafeNativeMethods.d_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(double alpha, double[] x, double[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == 1.0) + { + return; + } + + SafeNativeMethods.d_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0 and beta set to 0.0, and x and y are not transposed. + public override void MatrixMultiply(double[] x, int rowsX, int columnsX, double[] y, int rowsY, int columnsY, double[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0, x, rowsX, columnsX, y, rowsY, columnsY, 0.0, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, double alpha, double[] a, int rowsA, int columnsA, double[] b, int rowsB, int columnsB, double beta, double[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.d_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(double[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.d_lu_factor(order, data, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.d_lu_inverse(order, a); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(double[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.d_lu_inverse_factored(order, a, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, double[] a, int order, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.d_lu_solve(order, columnsOfB, a, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, double[] a, int order, int[] ipiv, double[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.d_lu_solve_factored(order, columnsOfB, a, ipiv, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(double[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.d_cholesky_factor(order, a); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.d_cholesky_solve(orderA, columnsB, a, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(double[] a, int orderA, double[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.d_cholesky_solve_factored(orderA, columnsB, a, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(double[] r, int rowsR, int columnsR, double[] q, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var info = SafeNativeMethods.d_qr_factor(rowsR, columnsR, r, tau, q); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void ThinQRFactor(double[] q, int rowsA, int columnsA, double[] r, double[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (q.Length != rowsA*columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(q)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA*columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var info = SafeNativeMethods.d_qr_thin_factor(rowsA, columnsA, q, tau, r); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolve(double[] a, int rows, int columns, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var info = SafeNativeMethods.d_qr_solve(rows, columns, columnsB, a, b, x); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient, nameof(a)); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(double[] q, double[] r, int rowsA, int columnsA, double[] tau, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + if (method == QRMethod.Full) + { + var info = SafeNativeMethods.d_qr_solve_factored(rowsA, columnsA, columnsB, r, b, tau, x); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + else + { + // we don't have access to the raw Q matrix any more(it is stored in R in the full QR), need to think about this. + // let just call the managed version in the meantime. The heavy lifting has already been done. -marcus + base.QRSolveFactored(q, r, rowsA, columnsA, tau, b, columnsB, x, QRMethod.Thin); + } + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(double[] a, int rowsA, int columnsA, double[] b, int columnsB, double[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new double[Math.Min(rowsA, columnsA)]; + var u = new double[rowsA*rowsA]; + var vt = new double[columnsA*columnsA]; + + var clone = new double[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, double[] a, int rowsA, int columnsA, double[] s, double[] u, double[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA * rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA * columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var info = SafeNativeMethods.d_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public override void EigenDecomp(bool isSymmetric, int order, double[] matrix, double[] matrixEv, Complex[] vectorEv, double[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrixD)); + } + + var info = SafeNativeMethods.d_eigen(isSymmetric, order, matrix, matrixEv, vectorEv, matrixD); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Single.cs b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Single.cs new file mode 100644 index 0000000..fa53961 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.Single.cs @@ -0,0 +1,1020 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using System.Security; +using MathNet.Numerics.LinearAlgebra.Factorization; +using MathNet.Numerics.Properties; +using MathNet.Numerics.Providers.Common.OpenBlas; +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Providers.LinearAlgebra.OpenBlas +{ + /// + /// OpenBLAS linear algebra provider. + /// + internal partial class OpenBlasLinearAlgebraProvider + { + /// + /// Computes the requested of the matrix. + /// + /// The type of norm to compute. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The matrix to compute the norm from. + /// + /// The requested of the matrix. + /// + [SecuritySafeCritical] + public override double MatrixNorm(Norm norm, int rows, int columns, float[] matrix) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (rows <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(rows)); + } + + if (columns <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(columns)); + } + + if (matrix.Length < rows * columns) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, rows * columns), nameof(matrix)); + } + + return SafeNativeMethods.s_matrix_norm((byte)norm, rows, columns, matrix); + } + + /// + /// Computes the dot product of x and y. + /// + /// The vector x. + /// The vector y. + /// The dot product of x and y. + /// This is equivalent to the DOT BLAS routine. + [SecuritySafeCritical] + public override float DotProduct(float[] x, float[] y) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (x.Length != y.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + return SafeNativeMethods.s_dot_product(x.Length, x, y); + } + + /// + /// Adds a scaled vector to another: result = y + alpha*x. + /// + /// The vector to update. + /// The value to scale by. + /// The vector to add to . + /// The result of the addition. + /// This is similar to the AXPY BLAS routine. + [SecuritySafeCritical] + public override void AddVectorToScaledVector(float[] y, float alpha, float[] x, float[] result) + { + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y.Length != x.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (!ReferenceEquals(y, result)) + { + Array.Copy(y, 0, result, 0, y.Length); + } + + if (alpha == 0.0f) + { + return; + } + + SafeNativeMethods.s_axpy(y.Length, alpha, x, result); + } + + /// + /// Scales an array. Can be used to scale a vector and a matrix. + /// + /// The scalar. + /// The values to scale. + /// This result of the scaling. + /// This is similar to the SCAL BLAS routine. + [SecuritySafeCritical] + public override void ScaleArray(float alpha, float[] x, float[] result) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (!ReferenceEquals(x, result)) + { + Array.Copy(x, 0, result, 0, x.Length); + } + + if (alpha == 1.0f) + { + return; + } + + SafeNativeMethods.s_scale(x.Length, alpha, result); + } + + /// + /// Multiples two matrices. result = x * y + /// + /// The x matrix. + /// The number of rows in the x matrix. + /// The number of columns in the x matrix. + /// The y matrix. + /// The number of rows in the y matrix. + /// The number of columns in the y matrix. + /// Where to store the result of the multiplication. + /// This is a simplified version of the BLAS GEMM routine with alpha + /// set to 1.0f and beta set to 0.0f, and x and y are not transposed. + public override void MatrixMultiply(float[] x, int rowsX, int columnsX, float[] y, int rowsY, int columnsY, float[] result) + { + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0f, x, rowsX, columnsX, y, rowsY, columnsY, 0.0f, result); + } + + /// + /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c + /// + /// How to transpose the matrix. + /// How to transpose the matrix. + /// The value to scale matrix. + /// The a matrix. + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The b matrix + /// The number of rows in the matrix. + /// The number of columns in the matrix. + /// The value to scale the matrix. + /// The c matrix. + [SecuritySafeCritical] + public override void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, float alpha, float[] a, int rowsA, int columnsA, float[] b, int rowsB, int columnsB, float beta, float[] c) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (c == null) + { + throw new ArgumentNullException(nameof(c)); + } + + var m = transposeA == Transpose.DontTranspose ? rowsA : columnsA; + var n = transposeB == Transpose.DontTranspose ? columnsB : rowsB; + var k = transposeA == Transpose.DontTranspose ? columnsA : rowsA; + var l = transposeB == Transpose.DontTranspose ? rowsB : columnsB; + + if (c.Length != m*n) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (k != l) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + SafeNativeMethods.s_matrix_multiply(transposeA, transposeB, m, n, k, alpha, a, b, beta, c); + } + + /// + /// Computes the LUP factorization of A. P*A = L*U. + /// + /// An by matrix. The matrix is overwritten with the + /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0f + /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . + /// The order of the square matrix . + /// On exit, it contains the pivot indices. The size of the array must be . + /// This is equivalent to the GETRF LAPACK routine. + [SecuritySafeCritical] + public override void LUFactor(float[] data, int order, int[] ipiv) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (data.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(data)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.s_lu_factor(order, data, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the inverse of matrix using LU factorization. + /// + /// The N by N matrix to invert. Contains the inverse On exit. + /// The order of the square matrix . + /// This is equivalent to the GETRF and GETRI LAPACK routines. + [SecuritySafeCritical] + public override void LUInverse(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.s_lu_inverse(order, a); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Computes the inverse of a previously factored matrix. + /// + /// The LU factored N by N matrix. Contains the inverse On exit. + /// The order of the square matrix . + /// The pivot indices of . + /// This is equivalent to the GETRI LAPACK routine. + [SecuritySafeCritical] + public override void LUInverseFactored(float[] a, int order, int[] ipiv) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + var info = SafeNativeMethods.s_lu_inverse_factored(order, a, ipiv); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new SingularUMatrixException(info); + } + } + + /// + /// Solves A*X=B for X using LU factorization. + /// + /// The number of columns of B. + /// The square matrix A. + /// The order of the square matrix . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRF and GETRS LAPACK routines. + [SecuritySafeCritical] + public override void LUSolve(int columnsOfB, float[] a, int order, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.s_lu_solve(order, columnsOfB, a, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The number of columns of B. + /// The factored A matrix. + /// The order of the square matrix . + /// The pivot indices of . + /// On entry the B matrix; on exit the X matrix. + /// This is equivalent to the GETRS LAPACK routine. + [SecuritySafeCritical] + public override void LUSolveFactored(int columnsOfB, float[] a, int order, int[] ipiv, float[] b) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (ipiv == null) + { + throw new ArgumentNullException(nameof(ipiv)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (ipiv.Length != order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(ipiv)); + } + + if (b.Length != columnsOfB*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.s_lu_solve_factored(order, columnsOfB, a, ipiv, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the Cholesky factorization of A. + /// + /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the + /// the Cholesky factorization. + /// The number of rows or columns in the matrix. + /// This is equivalent to the POTRF LAPACK routine. + [SecuritySafeCritical] + public override void CholeskyFactor(float[] a, int order) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (order < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(order)); + } + + if (a.Length != order*order) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + var info = SafeNativeMethods.s_cholesky_factor(order, a); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + } + + /// + /// Solves A*X=B for X using Cholesky factorization. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRF add POTRS LAPACK routines. + /// + [SecuritySafeCritical] + public override void CholeskySolve(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.s_cholesky_solve(orderA, columnsB, a, b); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using a previously factored A matrix. + /// + /// The square, positive definite matrix A. + /// The number of rows and columns in A. + /// On entry the B matrix; on exit the X matrix. + /// The number of columns in the B matrix. + /// This is equivalent to the POTRS LAPACK routine. + [SecuritySafeCritical] + public override void CholeskySolveFactored(float[] a, int orderA, float[] b, int columnsB) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (b.Length != orderA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (ReferenceEquals(a, b)) + { + throw new ArgumentException(Resources.ArgumentReferenceDifferent); + } + + var info = SafeNativeMethods.s_cholesky_solve_factored(orderA, columnsB, a, b); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the QR factorization of A. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the R matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A M by M matrix that holds the Q matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void QRFactor(float[] r, int rowsR, int columnsR, float[] q, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (r.Length != rowsR*columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(r)); + } + + if (tau.Length < Math.Min(rowsR, columnsR)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (q.Length != rowsR*rowsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * rowsR"), nameof(q)); + } + + var info = SafeNativeMethods.s_qr_factor(rowsR, columnsR, r, tau, q); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Computes the thin QR factorization of A where M > N. + /// + /// On entry, it is the M by N A matrix to factor. On exit, + /// it is overwritten with the Q matrix of the QR factorization. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// On exit, A N by N matrix that holds the R matrix of the + /// QR factorization. + /// A min(m,n) vector. On exit, contains additional information + /// to be used by the QR solve routine. + /// This is similar to the GEQRF and ORGQR LAPACK routines. + [SecuritySafeCritical] + public override void ThinQRFactor(float[] q, int rowsA, int columnsA, float[] r, float[] tau) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (q.Length != rowsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "rowsR * columnsR"), nameof(q)); + } + + if (tau.Length < Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(string.Format(Resources.ArrayTooSmall, "min(m,n)"), nameof(tau)); + } + + if (r.Length != columnsA * columnsA) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, "columnsA * columnsA"), nameof(r)); + } + + var info = SafeNativeMethods.s_qr_thin_factor(rowsA, columnsA, q, tau, r); + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + + /// + /// Solves A*X=B for X using QR factorization of A. + /// + /// The A matrix. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolve(float[] a, int rows, int columns, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (a.Length != rows * columns) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(a)); + } + + if (b.Length != rows * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columns * columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(x)); + } + + if (rows < columns) + { + throw new ArgumentException(Resources.RowsLessThanColumns); + } + + var info = SafeNativeMethods.s_qr_solve(rows, columns, columnsB, a, b, x); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient, nameof(a)); + } + } + + /// + /// Solves A*X=B for X using a previously QR factored matrix. + /// + /// The Q matrix obtained by calling . + /// The R matrix obtained by calling . + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// Contains additional information on Q. Only used for the native solver + /// and can be null for the managed provider. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + /// The type of QR factorization to perform. + /// Rows must be greater or equal to columns. + [SecuritySafeCritical] + public override void QRSolveFactored(float[] q, float[] r, int rowsA, int columnsA, float[] tau, float[] b, int columnsB, float[] x, QRMethod method = QRMethod.Full) + { + if (r == null) + { + throw new ArgumentNullException(nameof(r)); + } + + if (q == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(q)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(q)); + } + + int rowsQ, columnsQ, rowsR, columnsR; + if (method == QRMethod.Full) + { + rowsQ = columnsQ = rowsR = rowsA; + columnsR = columnsA; + } + else + { + rowsQ = rowsA; + columnsQ = rowsR = columnsR = columnsA; + } + + if (r.Length != rowsR * columnsR) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsR * columnsR), nameof(r)); + } + + if (q.Length != rowsQ * columnsQ) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsQ * columnsQ), nameof(q)); + } + + if (b.Length != rowsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, rowsA * columnsB), nameof(b)); + } + + if (x.Length != columnsA * columnsB) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, columnsA * columnsB), nameof(x)); + } + + if (method == QRMethod.Full) + { + var info = SafeNativeMethods.s_qr_solve_factored(rowsA, columnsA, columnsB, r, b, tau, x); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + } + else + { + // we don't have access to the raw Q matrix any more(it is stored in R in the full QR), need to think about this. + // let just call the managed version in the meantime. The heavy lifting has already been done. -marcus + base.QRSolveFactored(q, r, rowsA, columnsA, tau, b, columnsB, x, QRMethod.Thin); + } + } + + /// + /// Solves A*X=B for X using the singular value decomposition of A. + /// + /// On entry, the M by N matrix to decompose. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The B matrix. + /// The number of columns of B. + /// On exit, the solution matrix. + public override void SvdSolve(float[] a, int rowsA, int columnsA, float[] b, int columnsB, float[] x) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (b == null) + { + throw new ArgumentNullException(nameof(b)); + } + + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (b.Length != rowsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + if (x.Length != columnsA*columnsB) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(b)); + } + + var s = new float[Math.Min(rowsA, columnsA)]; + var u = new float[rowsA*rowsA]; + var vt = new float[columnsA*columnsA]; + + var clone = new float[a.Length]; + a.Copy(clone); + SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); + SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); + } + + /// + /// Computes the singular value decomposition of A. + /// + /// Compute the singular U and VT vectors or not. + /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. + /// The number of rows in the A matrix. + /// The number of columns in the A matrix. + /// The singular values of A in ascending value. + /// If is true, on exit U contains the left + /// singular vectors. + /// If is true, on exit VT contains the transposed + /// right singular vectors. + /// This is equivalent to the GESVD LAPACK routine. + [SecuritySafeCritical] + public override void SingularValueDecomposition(bool computeVectors, float[] a, int rowsA, int columnsA, float[] s, float[] u, float[] vt) + { + if (a == null) + { + throw new ArgumentNullException(nameof(a)); + } + + if (s == null) + { + throw new ArgumentNullException(nameof(s)); + } + + if (u == null) + { + throw new ArgumentNullException(nameof(u)); + } + + if (vt == null) + { + throw new ArgumentNullException(nameof(vt)); + } + + if (u.Length != rowsA*rowsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(u)); + } + + if (vt.Length != columnsA*columnsA) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(vt)); + } + + if (s.Length != Math.Min(rowsA, columnsA)) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(s)); + } + + var info = SafeNativeMethods.s_svd_factor(computeVectors, rowsA, columnsA, a, s, u, vt); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + + /// + /// Computes the eigenvalues and eigenvectors of a matrix. + /// + /// Whether the matrix is symmetric or not. + /// The order of the matrix. + /// The matrix to decompose. The length of the array must be order * order. + /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. + /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . + /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. + public override void EigenDecomp(bool isSymmetric, int order, float[] matrix, float[] matrixEv, Complex[] vectorEv, float[] matrixD) + { + if (matrix == null) + { + throw new ArgumentNullException(nameof(matrix)); + } + + if (matrix.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrix)); + } + + if (matrixEv == null) + { + throw new ArgumentNullException(nameof(matrixEv)); + } + + if (matrixEv.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrixEv)); + } + + if (vectorEv == null) + { + throw new ArgumentNullException(nameof(vectorEv)); + } + + if (vectorEv.Length != order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order), nameof(vectorEv)); + } + + if (matrixD == null) + { + throw new ArgumentNullException(nameof(matrixD)); + } + + if (matrixD.Length != order*order) + { + throw new ArgumentException(string.Format(Resources.ArgumentArrayWrongLength, order*order), nameof(matrixD)); + } + + var info = SafeNativeMethods.s_eigen(isSymmetric, order, matrix, matrixEv, vectorEv, matrixD); + + if (info == (int)NativeError.MemoryAllocation) + { + throw new MemoryAllocationException(); + } + + if (info < 0) + { + throw new InvalidParameterException(Math.Abs(info)); + } + + if (info > 0) + { + throw new NonConvergenceException(); + } + } + } +} + +#endif diff --git a/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.cs b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.cs new file mode 100644 index 0000000..639ed80 --- /dev/null +++ b/MathNet.Numerics/Providers/LinearAlgebra/OpenBlas/OpenBlasLinearAlgebraProvider.cs @@ -0,0 +1,121 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +#if NATIVE + +using System; +using MathNet.Numerics.Providers.Common.OpenBlas; + +namespace MathNet.Numerics.Providers.LinearAlgebra.OpenBlas +{ + /// + /// Error codes return from the native OpenBLAS provider. + /// + public enum NativeError : int + { + /// + /// Unable to allocate memory. + /// + MemoryAllocation = -999999 + } + + internal enum ParallelType : int + { + Sequential = 0, + Thread = 1, + OpenMP = 2 + } + + /// + /// OpenBLAS linear algebra provider. + /// + internal partial class OpenBlasLinearAlgebraProvider : Managed.ManagedLinearAlgebraProvider, IDisposable + { + const int MinimumCompatibleRevision = 1; + + readonly string _hintPath; + + /// Hint path where to look for the native binaries + internal OpenBlasLinearAlgebraProvider(string hintPath) + { + _hintPath = hintPath; + } + + /// + /// Try to find out whether the provider is available, at least in principle. + /// Verification may still fail if available, but it will certainly fail if unavailable. + /// + public override bool IsAvailable() + { + return OpenBlasProvider.IsAvailable(hintPath: _hintPath); + } + + /// + /// Initialize and verify that the provided is indeed available. + /// If not, fall back to alternatives like the managed provider + /// + public override void InitializeVerify() + { + int revision = OpenBlasProvider.Load(hintPath: _hintPath); + if (revision < MinimumCompatibleRevision) + { + throw new NotSupportedException($"OpenBLAS Native Provider revision r{revision} is too old. Consider upgrading to a newer version. Revision r{MinimumCompatibleRevision} and newer are supported."); + } + + int linearAlgebra = SafeNativeMethods.query_capability((int)ProviderCapability.LinearAlgebraMajor); + + // we only support exactly one major version, since major version changes imply a breaking change. + if (linearAlgebra != 1) + { + throw new NotSupportedException(string.Format("OpenBLAS Native Provider not compatible. Expecting linear algebra v1 but provider implements v{0}.", linearAlgebra)); + } + } + + /// + /// Frees memory buffers, caches and handles allocated in or to the provider. + /// Does not unload the provider itself, it is still usable afterwards. + /// + public override void FreeResources() + { + OpenBlasProvider.FreeResources(); + } + + public override string ToString() + { + return OpenBlasProvider.Describe(); + } + + public void Dispose() + { + FreeResources(); + } + } +} + +#endif diff --git a/MathNet.Numerics/Random/CryptoRandomSource.cs b/MathNet.Numerics/Random/CryptoRandomSource.cs new file mode 100644 index 0000000..2261d74 --- /dev/null +++ b/MathNet.Numerics/Random/CryptoRandomSource.cs @@ -0,0 +1,177 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Security.Cryptography; + +#if !NETSTANDARD1_3 +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// A random number generator based on the class in the .NET library. +/// +public sealed class CryptoRandomSource : RandomSource, IDisposable +{ + const double Reciprocal = 1.0 / 4294967296.0; // 1.0/(uint.MaxValue + 1.0) + readonly RandomNumberGenerator _crypto; + + /// + /// Construct a new random number generator with a random seed. + /// + /// Uses and uses the value of + /// to set whether the instance is thread safe. + public CryptoRandomSource() + { + _crypto = RandomNumberGenerator.Create(); + } + + /// + /// Construct a new random number generator with random seed. + /// + /// The to use. + /// Uses the value of to set whether the instance is thread safe. + public CryptoRandomSource(RandomNumberGenerator rng) + { + _crypto = rng; + } + + /// + /// Construct a new random number generator with random seed. + /// + /// Uses + /// if set to true , the class is thread safe. + public CryptoRandomSource(bool threadSafe) : base(threadSafe) + { + _crypto = RandomNumberGenerator.Create(); + } + + /// + /// Construct a new random number generator with random seed. + /// + /// The to use. + /// if set to true , the class is thread safe. + public CryptoRandomSource(RandomNumberGenerator rng, bool threadSafe) : base(threadSafe) + { + _crypto = rng; + } + + /// + /// Fills the elements of a specified array of bytes with random numbers in full range, including zero and 255 (). + /// + protected override void DoSampleBytes(byte[] buffer) + { + _crypto.GetBytes(buffer); + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + var bytes = new byte[4]; + _crypto.GetBytes(bytes); + return BitConverter.ToUInt32(bytes, 0) * Reciprocal; + } + + /// + /// Returns a random 32-bit signed integer greater than or equal to zero and less than + /// + protected sealed override int DoSampleInteger() + { + var bytes = new byte[4]; + _crypto.GetBytes(bytes); + uint uint32 = BitConverter.ToUInt32(bytes, 0); + int int31 = (int)(uint32 >> 1); + if (int31 == int.MaxValue) + { + return DoSampleInteger(); + } + + return int31; + } + + public void Dispose() + { + _crypto.Dispose(); + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values) + { + var bytes = new byte[values.Length * 4]; + + using (var rnd = RandomNumberGenerator.Create()) + { + rnd.GetBytes(bytes); + } + + for (int i = 0; i < values.Length; i++) + { + values[i] = BitConverter.ToUInt32(bytes, i * 4) * Reciprocal; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length) + { + var data = new double[length]; + Doubles(data); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static IEnumerable DoubleSequence() + { + var rnd = RandomNumberGenerator.Create(); + var buffer = new byte[1024 * 4]; + + while (true) + { + rnd.GetBytes(buffer); + for (int i = 0; i < buffer.Length; i += 4) + { + yield return BitConverter.ToUInt32(buffer, i) * Reciprocal; + } + } + } +} diff --git a/MathNet.Numerics/Random/Mcg31m1.cs b/MathNet.Numerics/Random/Mcg31m1.cs new file mode 100644 index 0000000..0036913 --- /dev/null +++ b/MathNet.Numerics/Random/Mcg31m1.cs @@ -0,0 +1,164 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Runtime.Serialization; + +#if !NETSTANDARD1_3 +using System; +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// Multiplicative congruential generator using a modulus of 2^31-1 and a multiplier of 1132489760. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class Mcg31m1 : RandomSource +{ + const ulong Modulus = 2147483647; + const ulong Multiplier = 1132489760; + const double Reciprocal = 1.0 / Modulus; + + [DataMember(Order = 1)] + ulong _xn; + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + public Mcg31m1() : this(RandomSeed.Robust()) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + public Mcg31m1(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public Mcg31m1(int seed) + { + if (seed == 0) + { + seed = 1; + } + + _xn = (uint)seed % Modulus; + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// if set to true, the class is thread safe. + public Mcg31m1(int seed, bool threadSafe) : base(threadSafe) + { + if (seed == 0) + { + seed = 1; + } + + _xn = (uint)seed % Modulus; + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + double ret = _xn * Reciprocal; + _xn = (_xn * Multiplier) % Modulus; + return ret; + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + if (seed == 0) + { + seed = 1; + } + + ulong xn = (uint)seed % Modulus; + + for (int i = 0; i < values.Length; i++) + { + values[i] = xn * Reciprocal; + xn = (xn * Multiplier) % Modulus; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + if (seed == 0) + { + seed = 1; + } + + ulong xn = (uint)seed % Modulus; + + while (true) + { + yield return xn * Reciprocal; + xn = (xn * Multiplier) % Modulus; + } + } +} diff --git a/MathNet.Numerics/Random/Mcg59.cs b/MathNet.Numerics/Random/Mcg59.cs new file mode 100644 index 0000000..cd59ea2 --- /dev/null +++ b/MathNet.Numerics/Random/Mcg59.cs @@ -0,0 +1,165 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Runtime.Serialization; + +#if !NETSTANDARD1_3 +using System; +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// Multiplicative congruential generator using a modulus of 2^59 and a multiplier of 13^13. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class Mcg59 : RandomSource +{ + const ulong Modulus = 576460752303423488; + const ulong Multiplier = 302875106592253; + const double Reciprocal = 1.0 / Modulus; + + [DataMember(Order = 1)] + ulong _xn; + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + public Mcg59() : this(RandomSeed.Robust()) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + public Mcg59(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public Mcg59(int seed) + { + if (seed == 0) + { + seed = 1; + } + + _xn = (uint)seed % Modulus; + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// The seed is set to 1, if the zero is used as the seed. + /// if set to true , the class is thread safe. + public Mcg59(int seed, bool threadSafe) : base(threadSafe) + { + if (seed == 0) + { + seed = 1; + } + + _xn = (uint)seed % Modulus; + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + double ret = _xn * Reciprocal; + _xn = (_xn * Multiplier) % Modulus; + return ret; + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + if (seed == 0) + { + seed = 1; + } + + ulong xn = (uint)seed % Modulus; + + for (int i = 0; i < values.Length; i++) + { + values[i] = xn * Reciprocal; + xn = (xn * Multiplier) % Modulus; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + if (seed == 0) + { + seed = 1; + } + + ulong xn = (uint)seed % Modulus; + + while (true) + { + yield return xn * Reciprocal; + xn = (xn * Multiplier) % Modulus; + } + } +} diff --git a/MathNet.Numerics/Random/MersenneTwister.cs b/MathNet.Numerics/Random/MersenneTwister.cs new file mode 100644 index 0000000..a8f4d78 --- /dev/null +++ b/MathNet.Numerics/Random/MersenneTwister.cs @@ -0,0 +1,468 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2017 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +/* + Original code's copyright and license: + Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Any feedback is very welcome. + http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) +*/ + +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Threading; + +#if !NETSTANDARD1_3 +using System; +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// Random number generator using Mersenne Twister 19937 algorithm. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class MersenneTwister : RandomSource +{ + /// + /// Mersenne twister constant. + /// + const uint LowerMask = 0x7fffffff; + + /// + /// Mersenne twister constant. + /// + const int M = 397; + + /// + /// Mersenne twister constant. + /// + const uint MatrixA = 0x9908b0df; + + /// + /// Mersenne twister constant. + /// + const int N = 624; + + /// + /// Mersenne twister constant. + /// + const double Reciprocal = 1.0 / 4294967296.0; // 1.0/(uint.MaxValue + 1.0) + + /// + /// Mersenne twister constant. + /// + const uint UpperMask = 0x80000000; + + /// + /// Mersenne twister constant. + /// + static readonly uint[] Mag01 = { 0x0U, MatrixA }; + + /// + /// Mersenne twister constant. + /// + [DataMember(Order = 1)] + readonly uint[] _mt = new uint[N]; + + /// + /// Mersenne twister constant. + /// + [DataMember(Order = 2)] + int _mti = N + 1; + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public MersenneTwister() : this(RandomSeed.Robust()) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + public MersenneTwister(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// Uses the value of to + /// set whether the instance is thread safe. + public MersenneTwister(int seed) + { + init_genrand((uint)seed); + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// if set to true, the class is thread safe. + public MersenneTwister(int seed, bool threadSafe) : base(threadSafe) + { + init_genrand((uint)seed); + } + + static readonly ThreadLocal DefaultInstance = new ThreadLocal(() => new MersenneTwister(RandomSeed.Robust(), true)); + + /// + /// Default instance, thread-safe. + /// + public static MersenneTwister Default + { + get { return DefaultInstance.Value; } + } + + /*/// + /// Initializes a new instance of the class. + /// + /// The initialization key. + public MersenneTwister(int[] init_key) + { + if (init_key == null) + { + throw new ArgumentNullException("init_key"); + } + uint[] array = new uint[init_key.Length]; + for (int i = 0; i < array.Length; i++) + { + array[i] = (uint) init_key[i]; + } + init_by_array(array); + } + */ + /* initializes _mt[_n] with a seed */ + + void init_genrand(uint s) + { + _mt[0] = s & 0xffffffff; + for (_mti = 1; _mti < N; _mti++) + { + _mt[_mti] = 1812433253 * (_mt[_mti - 1] ^ (_mt[_mti - 1] >> 30)) + (uint)_mti; + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array _mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + _mt[_mti] &= 0xffffffff; + /* for >32 bit machines */ + } + } + + /* initialize by an array with array-length */ + /* init_key is the array for initializing keys */ + /* slight change for C++, 2004/2/26 */ + + /* private void init_by_array(uint[] init_key) + { + uint key_length = (uint) init_key.Length; + init_genrand(19650218); + uint i = 1; + uint j = 0; + uint k = (_n > key_length ? _n : key_length); + for (; k > 0; k--) + { + _mt[i] = (_mt[i] ^ ((_mt[i - 1] ^ (_mt[i - 1] >> 30))*1664525)) + init_key[j] + j; //non linear + _mt[i] &= 0xffffffff; // for WORDSIZE > 32 machines + i++; + j++; + if (i >= _n) + { + _mt[0] = _mt[_n - 1]; + i = 1; + } + if (j >= key_length) j = 0; + } + for (k = _n - 1; k > 0; k--) + { + _mt[i] = (_mt[i] ^ ((_mt[i - 1] ^ (_mt[i - 1] >> 30))*1566083941)) - i; // non linear + _mt[i] &= 0xffffffff; // for WORDSIZE > 32 machines + i++; + if (i >= _n) + { + _mt[0] = _mt[_n - 1]; + i = 1; + } + } + + _mt[0] = 0x80000000; // MSB is 1; assuring non-zero initial array + }*/ + + /* generates a random number on [0,0xffffffff]-interval */ + + uint genrand_int32() + { + uint y; + + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + if (_mti >= N) + { + /* generate _n words at one time */ + int kk; + + if (_mti == N + 1) /* if init_genrand() has not been called, */ + { + init_genrand(5489); /* a default initial seed is used */ + } + + for (kk = 0; kk < N - M; kk++) + { + y = (_mt[kk] & UpperMask) | (_mt[kk + 1] & LowerMask); + _mt[kk] = _mt[kk + M] ^ (y >> 1) ^ Mag01[y & 0x1]; + } + + for (; kk < N - 1; kk++) + { + y = (_mt[kk] & UpperMask) | (_mt[kk + 1] & LowerMask); + _mt[kk] = _mt[kk + (M - N)] ^ (y >> 1) ^ Mag01[y & 0x1]; + } + + y = (_mt[N - 1] & UpperMask) | (_mt[0] & LowerMask); + _mt[N - 1] = _mt[M - 1] ^ (y >> 1) ^ Mag01[y & 0x1]; + + _mti = 0; + } + + y = _mt[_mti++]; + + /* Tempering */ + y ^= y >> 11; + y ^= (y << 7) & 0x9d2c5680; + y ^= (y << 15) & 0xefc60000; + y ^= y >> 18; + + return y; + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + return genrand_int32() * Reciprocal; + } + + /// + /// Returns a random 32-bit signed integer greater than or equal to zero and less than + /// + protected sealed override int DoSampleInteger() + { + uint uint32 = genrand_int32(); + int int31 = (int)(uint32 >> 1); + if (int31 == int.MaxValue) + { + return DoSampleInteger(); + } + + return int31; + } + + /// + /// Fills the elements of a specified array of bytes with random numbers in full range, including zero and 255 (). + /// + protected sealed override void DoSampleBytes(byte[] buffer) + { + for (var i = 0; i < buffer.Length; i++) + { + buffer[i] = (byte)(genrand_int32() % 256); + } + } + + /* /// + /// Generates a random number on [0,1) with 53-bit resolution. + /// + /// A random number on [0,1) with 53-bit resolution. + public double NextDoubleResolution53() + { + ulong a = genrand_int32() >> 5, b = genrand_int32() >> 6; + return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0); + }*/ + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + uint[] t = new uint[624]; + int k; + uint s = (uint)seed; + + t[0] = s & 0xffffffff; + for (k = 1; k < N; k++) + { + t[k] = 1812433253 * (t[k - 1] ^ (t[k - 1] >> 30)) + (uint)k; + t[k] &= 0xffffffff; + } + + for (int i = 0; i < values.Length; i++) + { + uint y; + + if (k >= N) + { + int kk; + for (kk = 0; kk < N - M; kk++) + { + y = (t[kk] & UpperMask) | (t[kk + 1] & LowerMask); + t[kk] = t[kk + M] ^ (y >> 1) ^ Mag01[y & 0x1]; + } + + for (; kk < N - 1; kk++) + { + y = (t[kk] & UpperMask) | (t[kk + 1] & LowerMask); + t[kk] = t[kk + (M - N)] ^ (y >> 1) ^ Mag01[y & 0x1]; + } + + y = (t[N - 1] & UpperMask) | (t[0] & LowerMask); + t[N - 1] = t[M - 1] ^ (y >> 1) ^ Mag01[y & 0x1]; + + k = 0; + } + + y = t[k++]; + + /* Tempering */ + y ^= y >> 11; + y ^= (y << 7) & 0x9d2c5680; + y ^= (y << 15) & 0xefc60000; + y ^= y >> 18; + + values[i] = y * Reciprocal; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + uint[] t = new uint[624]; + int k; + uint s = (uint)seed; + + t[0] = s & 0xffffffff; + for (k = 1; k < N; k++) + { + t[k] = 1812433253 * (t[k - 1] ^ (t[k - 1] >> 30)) + (uint)k; + t[k] &= 0xffffffff; + } + + while (true) + { + uint y; + + if (k >= N) + { + int kk; + for (kk = 0; kk < N - M; kk++) + { + y = (t[kk] & UpperMask) | (t[kk + 1] & LowerMask); + t[kk] = t[kk + M] ^ (y >> 1) ^ Mag01[y & 0x1]; + } + + for (; kk < N - 1; kk++) + { + y = (t[kk] & UpperMask) | (t[kk + 1] & LowerMask); + t[kk] = t[kk + (M - N)] ^ (y >> 1) ^ Mag01[y & 0x1]; + } + + y = (t[N - 1] & UpperMask) | (t[0] & LowerMask); + t[N - 1] = t[M - 1] ^ (y >> 1) ^ Mag01[y & 0x1]; + + k = 0; + } + + y = t[k++]; + + /* Tempering */ + y ^= y >> 11; + y ^= (y << 7) & 0x9d2c5680; + y ^= (y << 15) & 0xefc60000; + y ^= y >> 18; + + yield return y * Reciprocal; + } + } +} diff --git a/MathNet.Numerics/Random/Mrg32k3a.cs b/MathNet.Numerics/Random/Mrg32k3a.cs new file mode 100644 index 0000000..05737c3 --- /dev/null +++ b/MathNet.Numerics/Random/Mrg32k3a.cs @@ -0,0 +1,254 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Runtime.Serialization; + +#if !NETSTANDARD1_3 +using System; +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// A 32-bit combined multiple recursive generator with 2 components of order 3. +/// +/// Based off of P. L'Ecuyer, "Combined Multiple Recursive Random Number Generators," Operations Research, 44, 5 (1996), 816--822. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class Mrg32k3a : RandomSource +{ + const double A12 = 1403580; + const double A13 = 810728; + const double A21 = 527612; + const double A23 = 1370589; + const double Modulus1 = 4294967087; + const double Modulus2 = 4294944443; + + const double Reciprocal = 1.0 / Modulus1; + + [DataMember(Order = 1)] + double _xn1 = 1; + [DataMember(Order = 2)] + double _xn2 = 1; + [DataMember(Order = 3)] + double _xn3; + [DataMember(Order = 4)] + double _yn1 = 1; + [DataMember(Order = 5)] + double _yn2 = 1; + [DataMember(Order = 6)] + double _yn3 = 1; + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public Mrg32k3a() : this(RandomSeed.Robust()) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + public Mrg32k3a(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public Mrg32k3a(int seed) + { + if (seed == 0) + { + seed = 1; + } + + _xn3 = (uint)seed; + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// if set to true, the class is thread safe. + public Mrg32k3a(int seed, bool threadSafe) : base(threadSafe) + { + if (seed == 0) + { + seed = 1; + } + + _xn3 = (uint)seed; + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + double xn = A12 * _xn2 - A13 * _xn3; + double k = (long)(xn / Modulus1); + xn -= k * Modulus1; + if (xn < 0) + { + xn += Modulus1; + } + + double yn = A21 * _yn1 - A23 * _yn3; + k = (long)(yn / Modulus2); + yn -= k * Modulus2; + if (yn < 0) + { + yn += Modulus2; + } + + _xn3 = _xn2; + _xn2 = _xn1; + _xn1 = xn; + _yn3 = _yn2; + _yn2 = _yn1; + _yn1 = yn; + + if (xn <= yn) + { + return (xn - yn + Modulus1) * Reciprocal; + } + + return (xn - yn) * Reciprocal; + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + double x1 = 1; + double x2 = 1; + double x3 = (uint)seed; + double y1 = 1; + double y2 = 1; + double y3 = 1; + + for (int i = 0; i < values.Length; i++) + { + double xn = A12 * x2 - A13 * x3; + double k = (long)(xn / Modulus1); + xn -= k * Modulus1; + if (xn < 0) + { + xn += Modulus1; + } + + double yn = A21 * y1 - A23 * y3; + k = (long)(yn / Modulus2); + yn -= k * Modulus2; + if (yn < 0) + { + yn += Modulus2; + } + + x3 = x2; + x2 = x1; + x1 = xn; + y3 = y2; + y2 = y1; + y1 = yn; + + values[i] = xn <= yn ? (xn - yn + Modulus1) * Reciprocal : (xn - yn) * Reciprocal; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + double x1 = 1; + double x2 = 1; + double x3 = (uint)seed; + double y1 = 1; + double y2 = 1; + double y3 = 1; + + while (true) + { + double xn = A12 * x2 - A13 * x3; + double k = (long)(xn / Modulus1); + xn -= k * Modulus1; + if (xn < 0) + { + xn += Modulus1; + } + + double yn = A21 * y1 - A23 * y3; + k = (long)(yn / Modulus2); + yn -= k * Modulus2; + if (yn < 0) + { + yn += Modulus2; + } + + x3 = x2; + x2 = x1; + x1 = xn; + y3 = y2; + y2 = y1; + y1 = yn; + + yield return xn <= yn ? (xn - yn + Modulus1) * Reciprocal : (xn - yn) * Reciprocal; + } + } +} diff --git a/MathNet.Numerics/Random/Palf.cs b/MathNet.Numerics/Random/Palf.cs new file mode 100644 index 0000000..cf0664c --- /dev/null +++ b/MathNet.Numerics/Random/Palf.cs @@ -0,0 +1,363 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +using MathNet.Numerics.Properties; + +#if !NETSTANDARD1_3 +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// Represents a Parallel Additive Lagged Fibonacci pseudo-random number generator. +/// +/// +/// The type bases upon the implementation in the +/// Boost Random Number Library. +/// It uses the modulus 232 and by default the "lags" 418 and 1279. Some popular pairs are presented on +/// Wikipedia - Lagged Fibonacci generator. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class Palf : RandomSource +{ + /// + /// Default value for the ShortLag + /// + const int DefaultShortLag = 418; + + /// + /// Default value for the LongLag + /// + const int DefaultLongLag = 1279; + + /// + /// The multiplier to compute a double-precision floating point number [0, 1) + /// + const double Reciprocal = 1.0 / 4294967296.0; // 1.0/(uint.MaxValue + 1.0) + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public Palf() : this(RandomSeed.Robust(), Control.ThreadSafeRandomNumberGenerators, DefaultShortLag, DefaultLongLag) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + public Palf(bool threadSafe) : this(RandomSeed.Robust(), threadSafe, DefaultShortLag, DefaultLongLag) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public Palf(int seed) : this(seed, Control.ThreadSafeRandomNumberGenerators, DefaultShortLag, DefaultLongLag) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// if set to true , the class is thread safe. + public Palf(int seed, bool threadSafe) : this(seed, threadSafe, DefaultShortLag, DefaultLongLag) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// if set to true, the class is thread safe. + /// The ShortLag value + /// TheLongLag value + public Palf(int seed, bool threadSafe, int shortLag, int longLag) : base(threadSafe) + { + if (shortLag < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(shortLag)); + } + + if (longLag <= shortLag) + { + throw new ArgumentException(Resources.ArgumentUpperBoundMustBeLargerThanLowerBound, nameof(longLag)); + } + + if (seed == 0) + { + seed = 1; + } + + _threads = Control.MaxDegreeOfParallelism; + ShortLag = shortLag; + + // Align LongLag to number of worker threads. + if (longLag % _threads == 0) + { + LongLag = longLag; + } + else + { + LongLag = ((longLag / _threads) + 1) * _threads; + } + + _x = Generate.Map(MersenneTwister.Doubles(LongLag, seed), uniform => (uint)(uniform * uint.MaxValue)); + _k = LongLag; + } + + /// + /// Gets the short lag of the Lagged Fibonacci pseudo-random number generator. + /// + [DataMember(Order = 1)] + public int ShortLag { get; private set; } + + /// + /// Gets the long lag of the Lagged Fibonacci pseudo-random number generator. + /// + [DataMember(Order = 2)] + public int LongLag { get; private set; } + + /// + /// Stores an array of random numbers + /// + [DataMember(Order = 3)] + readonly uint[] _x; + + [DataMember(Order = 4)] + readonly int _threads; + + /// + /// Stores an index for the random number array element that will be accessed next. + /// + [DataMember(Order = 5)] + int _k; + + /// + /// Fills the array with new unsigned random numbers. + /// + /// + /// Generated random numbers are 32-bit unsigned integers greater than or equal to 0 + /// and less than or equal to . + /// + void Fill() + { + //CommonParallel.For(0, Control.NumberOfParallelWorkerThreads, (u, v) => + //{ + // for (int index = u; index < v; index++) + // { + // // Two loops to avoid costly modulo operations + // for (var j = index; j < ShortLag; j = j + Control.NumberOfParallelWorkerThreads) + // { + // _x[j] += _x[j + (LongLag - ShortLag)]; + // } + + // for (var j = ShortLag + index; j < LongLag; j = j + Control.NumberOfParallelWorkerThreads) + // { + // _x[j] += _x[j - ShortLag - index]; + // } + // } + //}); + + for (int index = 0; index < _threads; index++) + { + // Two loops to avoid costly modulo operations + for (var j = index; j < ShortLag; j = j + _threads) + { + _x[j] += _x[j + (LongLag - ShortLag)]; + } + + for (var j = ShortLag + index; j < LongLag; j = j + _threads) + { + _x[j] += _x[j - ShortLag - index]; + } + } + + _k = 0; + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + if (_k >= LongLag) + { + Fill(); + } + + return _x[_k++] * Reciprocal; + } + + /// + /// Returns a random 32-bit signed integer greater than or equal to zero and less than + /// + protected override int DoSampleInteger() + { + if (_k >= LongLag) + { + Fill(); + } + + uint uint32 = _x[_k++]; + int int31 = (int)(uint32 >> 1); + if (int31 == int.MaxValue) + { + return DoSampleInteger(); + } + + return int31; + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + if (seed == 0) + { + seed = 1; + } + + int threads = Control.MaxDegreeOfParallelism; + const int shortLag = DefaultShortLag; + var longLag = DefaultLongLag; + + // Align LongLag to number of worker threads. + if (longLag % threads != 0) + { + longLag = ((longLag / threads) + 1) * threads; + } + + var x = Generate.Map(MersenneTwister.Doubles(longLag, seed), uniform => (uint)(uniform * uint.MaxValue)); + var k = longLag; + + for (int i = 0; i < values.Length; i++) + { + if (k >= longLag) + { + for (int index = 0; index < threads; index++) + { + // Two loops to avoid costly modulo operations + for (var j = index; j < shortLag; j = j + threads) + { + x[j] += x[j + (longLag - shortLag)]; + } + + for (var j = shortLag + index; j < longLag; j = j + threads) + { + x[j] += x[j - shortLag - index]; + } + } + + k = 0; + } + + values[i] = x[k++] * Reciprocal; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + if (seed == 0) + { + seed = 1; + } + + int threads = Control.MaxDegreeOfParallelism; + const int shortLag = DefaultShortLag; + var longLag = DefaultLongLag; + + // Align LongLag to number of worker threads. + if (longLag % threads != 0) + { + longLag = ((longLag / threads) + 1) * threads; + } + + var x = Generate.Map(MersenneTwister.Doubles(longLag, seed), uniform => (uint)(uniform * uint.MaxValue)); + var k = longLag; + + while (true) + { + if (k >= longLag) + { + for (int index = 0; index < threads; index++) + { + // Two loops to avoid costly modulo operations + for (var j = index; j < shortLag; j = j + threads) + { + x[j] += x[j + (longLag - shortLag)]; + } + + for (var j = shortLag + index; j < longLag; j = j + threads) + { + x[j] += x[j - shortLag - index]; + } + } + + k = 0; + } + + yield return x[k++] * Reciprocal; + } + } +} diff --git a/MathNet.Numerics/Random/RandomExtensions.cs b/MathNet.Numerics/Random/RandomExtensions.cs new file mode 100644 index 0000000..ac303c9 --- /dev/null +++ b/MathNet.Numerics/Random/RandomExtensions.cs @@ -0,0 +1,312 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Random; + +/// +/// This class implements extension methods for the System.Random class. The extension methods generate +/// pseudo-random distributed numbers for types other than double and int32. +/// +public static class RandomExtensions +{ + /// + /// Fills an array with uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// The random number generator. + /// The array to fill with random values. + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static void NextDoubles(this System.Random rnd, double[] values) + { + var rs = rnd as RandomSource; + if (rs != null) + { + rs.NextDoubles(values); + return; + } + + for (var i = 0; i < values.Length; i++) + { + values[i] = rnd.NextDouble(); + } + } + + /// + /// Returns an array of uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// The random number generator. + /// The size of the array to fill. + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static double[] NextDoubles(this System.Random rnd, int count) + { + var values = new double[count]; + NextDoubles(rnd, values); + return values; + } + + /// + /// Returns an infinite sequence of uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static IEnumerable NextDoubleSequence(this System.Random rnd) + { + var rs = rnd as RandomSource; + if (rs != null) + { + return rs.NextDoubleSequence(); + } + + return NextDoubleSequenceEnumerable(rnd); + } + + static IEnumerable NextDoubleSequenceEnumerable(System.Random rnd) + { + while (true) + { + yield return rnd.NextDouble(); + } + } + + /// + /// Returns an array of uniform random bytes. + /// + /// The random number generator. + /// The size of the array to fill. + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static byte[] NextBytes(this System.Random rnd, int count) + { + var values = new byte[count]; + rnd.NextBytes(values); + return values; + } + + /// + /// Fills an array with uniform random 32-bit signed integers greater than or equal to zero and less than . + /// + /// The random number generator. + /// The array to fill with random values. + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static void NextInt32s(this System.Random rnd, int[] values) + { + var rs = rnd as RandomSource; + if (rs != null) + { + rs.NextInt32s(values); + return; + } + + for (var i = 0; i < values.Length; i++) + { + values[i] = rnd.Next(); + } + } + + /// + /// Fills an array with uniform random 32-bit signed integers within the specified range. + /// + /// The random number generator. + /// The array to fill with random values. + /// Lower bound, inclusive. + /// Upper bound, exclusive. + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static void NextInt32s(this System.Random rnd, int[] values, int minInclusive, int maxExclusive) + { + var rs = rnd as RandomSource; + if (rs != null) + { + rs.NextInt32s(values, minInclusive, maxExclusive); + return; + } + + for (var i = 0; i < values.Length; i++) + { + values[i] = rnd.Next(minInclusive, maxExclusive); + } + } + + /// + /// Returns an infinite sequence of uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static IEnumerable NextInt32Sequence(this System.Random rnd, int minInclusive, int maxExclusive) + { + var rs = rnd as RandomSource; + if (rs != null) + { + return rs.NextInt32Sequence(minInclusive, maxExclusive); + } + + return NextInt32SequenceEnumerable(rnd, minInclusive, maxExclusive); + } + + static IEnumerable NextInt32SequenceEnumerable(System.Random rnd, int minInclusive, int maxExclusive) + { + while (true) + { + yield return rnd.Next(minInclusive, maxExclusive); + } + } + + /// + /// Returns a nonnegative random number less than . + /// + /// The random number generator. + /// + /// A 64-bit signed integer greater than or equal to 0, and less than ; that is, + /// the range of return values includes 0 but not . + /// + /// + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static long NextInt64(this System.Random rnd) + { + var buffer = new byte[8]; + + rnd.NextBytes(buffer); + var candidate = BitConverter.ToInt64(buffer, 0); + + // wrap negative numbers around, mapping every negative number to a distinct nonnegative number + // MinValue -> 0, -1 -> MaxValue + candidate &= long.MaxValue; + + // skip candidate if it is MaxValue. Recursive since rare. + return (candidate == long.MaxValue) ? rnd.NextInt64() : candidate; + } + + /// + /// Returns a random number of the full Int32 range. + /// + /// The random number generator. + /// + /// A 32-bit signed integer of the full range, including 0, negative numbers, + /// and . + /// + /// + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static int NextFullRangeInt32(this System.Random rnd) + { + var buffer = new byte[4]; + rnd.NextBytes(buffer); + return BitConverter.ToInt32(buffer, 0); + } + + /// + /// Returns a random number of the full Int64 range. + /// + /// The random number generator. + /// + /// A 64-bit signed integer of the full range, including 0, negative numbers, + /// and . + /// + /// + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static long NextFullRangeInt64(this System.Random rnd) + { + var buffer = new byte[8]; + rnd.NextBytes(buffer); + return BitConverter.ToInt64(buffer, 0); + } + + /// + /// Returns a nonnegative decimal floating point random number less than 1.0. + /// + /// The random number generator. + /// + /// A decimal floating point number greater than or equal to 0.0, and less than 1.0; that is, + /// the range of return values includes 0.0 but not 1.0. + /// + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static decimal NextDecimal(this System.Random rnd) + { + decimal candidate; + + // 50.049 % chance that the number is below 1.0. Try until we have one. + // Guarantees that any decimal in the interval can + // indeed be reached, with uniform probability. + do + { + candidate = new decimal( + rnd.NextFullRangeInt32(), + rnd.NextFullRangeInt32(), + rnd.NextFullRangeInt32(), + false, + 28); + } + while (candidate >= 1.0m); + + return candidate; + } + + /// + /// Returns a random boolean. + /// + /// The random number generator. + /// + /// This extension is thread-safe if and only if called on an random number + /// generator provided by Math.NET Numerics or derived from the RandomSource class. + /// + public static bool NextBoolean(this System.Random rnd) + { + return rnd.NextDouble() >= 0.5; + } +} diff --git a/MathNet.Numerics/Random/RandomSeed.cs b/MathNet.Numerics/Random/RandomSeed.cs new file mode 100644 index 0000000..738cdc5 --- /dev/null +++ b/MathNet.Numerics/Random/RandomSeed.cs @@ -0,0 +1,44 @@ +using System; + +namespace MathNet.Numerics.Random; + +public static class RandomSeed +{ + static readonly object Lock = new object(); + static readonly System.Security.Cryptography.RandomNumberGenerator MasterRng = System.Security.Cryptography.RandomNumberGenerator.Create(); + + /// + /// Provides a time-dependent seed value, matching the default behavior of System.Random. + /// WARNING: There is no randomness in this seed and quick repeated calls can cause + /// the same seed value. Do not use for cryptography! + /// + public static int Time() + { + return Environment.TickCount; + } + + /// + /// Provides a seed based on time and unique GUIDs. + /// WARNING: There is only low randomness in this seed, but at least quick repeated + /// calls will result in different seed values. Do not use for cryptography! + /// + public static int Guid() + { + return Environment.TickCount ^ System.Guid.NewGuid().GetHashCode(); + } + + /// + /// Provides a seed based on an internal random number generator (crypto if available), time and unique GUIDs. + /// WARNING: There is only medium randomness in this seed, but quick repeated + /// calls will result in different seed values. Do not use for cryptography! + /// + public static int Robust() + { + lock (Lock) + { + var bytes = new byte[4]; + MasterRng.GetBytes(bytes); + return BitConverter.ToInt32(bytes, 0); + } + } +} diff --git a/MathNet.Numerics/Random/RandomSource.cs b/MathNet.Numerics/Random/RandomSource.cs new file mode 100644 index 0000000..a4372f9 --- /dev/null +++ b/MathNet.Numerics/Random/RandomSource.cs @@ -0,0 +1,604 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2016 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.Random; + +/// +/// Base class for random number generators. This class introduces a layer between +/// and the Math.Net Numerics random number generators to provide thread safety. +/// When used directly it use the System.Random as random number source. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public abstract class RandomSource : System.Random +{ + readonly bool _threadSafe; + readonly object _lock = new object(); + + /// + /// Initializes a new instance of the class using + /// the value of to set whether + /// the instance is thread safe or not. + /// + protected RandomSource() : base(RandomSeed.Robust()) + { + _threadSafe = Control.ThreadSafeRandomNumberGenerators; + } + + /// + /// Initializes a new instance of the class. + /// + /// if set to true , the class is thread safe. + /// Thread safe instances are two and half times slower than non-thread + /// safe classes. + protected RandomSource(bool threadSafe) : base(RandomSeed.Robust()) + { + _threadSafe = threadSafe; + } + + /// + /// Fills an array with uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// The array to fill with random values. + public void NextDoubles(double[] values) + { + if (_threadSafe) + { + lock (_lock) + { + for (var i = 0; i < values.Length; i++) + { + values[i] = DoSample(); + } + } + } + else + { + for (var i = 0; i < values.Length; i++) + { + values[i] = DoSample(); + } + } + } + + /// + /// Returns an array of uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// The size of the array to fill. + public double[] NextDoubles(int count) + { + var values = new double[count]; + NextDoubles(values); + return values; + } + + /// + /// Returns an infinite sequence of uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// + public IEnumerable NextDoubleSequence() + { + for (int i = 0; i < 64; i++) + { + yield return NextDouble(); + } + + var buffer = new double[64]; + while (true) + { + NextDoubles(buffer); + for (int i = 0; i < buffer.Length; i++) + { + yield return buffer[i]; + } + } + } + + /// + /// Returns a random 32-bit signed integer greater than or equal to zero and less than . + /// + public sealed override int Next() + { + if (_threadSafe) + { + lock (_lock) + { + return DoSampleInteger(); + } + } + else + { + return DoSampleInteger(); + } + } + + /// + /// Returns a random number less then a specified maximum. + /// + /// The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 1. + /// A 32-bit signed integer less than . + /// is zero or negative. + public sealed override int Next(int maxExclusive) + { + // Invalid case: Zero and less are not valid use cases. + if (maxExclusive <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive); + } + + // Fast case: Only zero is allowed to be returned. No sampling is needed. + if (maxExclusive == 1) + { + return 0; + } + + // Simple case: standard range + if (maxExclusive == int.MaxValue) + { + return Next(); + } + + // Sample with maxExclusive ≥ 2 + if (_threadSafe) + { + lock (_lock) + { + return DoSampleInteger(maxExclusive); + } + } + else + { + return DoSampleInteger(maxExclusive); + } + } + + /// + /// Returns a random number within a specified range. + /// + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. Range: maxExclusive > minExclusive. + /// + /// A 32-bit signed integer greater than or equal to and less than ; that is, the range of return values includes but not . If equals , is returned. + /// + /// is greater than . + public sealed override int Next(int minInclusive, int maxExclusive) + { + // Invalid case: empty range. + if (minInclusive >= maxExclusive) + { + throw new ArgumentException(Resources.ArgumentMaxExclusiveMustBeLargerThanMinInclusive); + } + + // Fast case: Only minInclusive is allowed to be returned. No sampling is needed. + if (maxExclusive == minInclusive + 1) + { + return minInclusive; + } + + // Simple case: simple range + if (minInclusive == 0) + { + // Simple case: standard range + if (maxExclusive == int.MaxValue) + { + return Next(); + } + + return Next(maxExclusive); + } + + // Sample with maxExclusive ≥ minExclusive + 2 + if (_threadSafe) + { + lock (_lock) + { + return DoSampleInteger(minInclusive, maxExclusive); + } + } + else + { + return DoSampleInteger(minInclusive, maxExclusive); + } + } + + /// + /// Fills an array with random 32-bit signed integers greater than or equal to zero and less than . + /// + /// The array to fill with random values. + public void NextInt32s(int[] values) + { + if (_threadSafe) + { + lock (_lock) + { + for (var i = 0; i < values.Length; i++) + { + values[i] = DoSampleInteger(); + } + } + } + else + { + for (var i = 0; i < values.Length; i++) + { + values[i] = DoSampleInteger(); + } + } + } + + /// + /// Returns an array with random 32-bit signed integers greater than or equal to zero and less than . + /// + /// The size of the array to fill. + public int[] NextInt32s(int count) + { + var values = new int[count]; + NextInt32s(values); + return values; + } + + /// + /// Fills an array with random numbers within a specified range. + /// + /// The array to fill with random values. + /// The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 1. + public void NextInt32s(int[] values, int maxExclusive) + { + // Invalid case: Zero and less are not valid use cases. + if (maxExclusive <= 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive); + } + + // Fast case: Only zero is allowed to be returned. No sampling is needed. + if (maxExclusive == 1) + { + Array.Clear(values, 0, values.Length); + return; + } + + // Simple case: standard range + if (maxExclusive == int.MaxValue) + { + NextInt32s(values); + return; + } + + // Sample with maxExclusive ≥ 2 + if (_threadSafe) + { + lock (_lock) + { + for (var i = 0; i < values.Length; i++) + { + values[i] = DoSampleInteger(maxExclusive); + } + } + } + else + { + for (var i = 0; i < values.Length; i++) + { + values[i] = DoSampleInteger(maxExclusive); + } + } + } + + /// + /// Returns an array with random 32-bit signed integers within the specified range. + /// + /// The size of the array to fill. + /// The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 1. + public int[] NextInt32s(int count, int maxExclusive) + { + var values = new int[count]; + NextInt32s(values, maxExclusive); + return values; + } + + /// + /// Fills an array with random numbers within a specified range. + /// + /// The array to fill with random values. + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. Range: maxExclusive > minExclusive. + public void NextInt32s(int[] values, int minInclusive, int maxExclusive) + { + // Invalid case: empty range. + if (minInclusive >= maxExclusive) + { + throw new ArgumentException(Resources.ArgumentMaxExclusiveMustBeLargerThanMinInclusive); + } + + // Fast case: Only minInclusive is allowed to be returned. No sampling is needed. + if (maxExclusive == minInclusive + 1) + { + for (var i = 0; i < values.Length; i++) + { + values[i] = minInclusive; + } + + return; + } + + // Simple case: simple range + if (minInclusive == 0) + { + // Simple case: standard range + if (maxExclusive == int.MaxValue) + { + NextInt32s(values); + return; + } + + NextInt32s(values, maxExclusive); + return; + } + + // Sample with maxExclusive ≥ minExclusive + 2 + if (_threadSafe) + { + lock (_lock) + { + for (var i = 0; i < values.Length; i++) + { + values[i] = DoSampleInteger(minInclusive, maxExclusive); + } + } + } + else + { + for (var i = 0; i < values.Length; i++) + { + values[i] = DoSampleInteger(minInclusive, maxExclusive); + } + } + } + + /// + /// Returns an array with random 32-bit signed integers within the specified range. + /// + /// The size of the array to fill. + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. Range: maxExclusive > minExclusive. + public int[] NextInt32s(int count, int minInclusive, int maxExclusive) + { + var values = new int[count]; + NextInt32s(values, minInclusive, maxExclusive); + return values; + } + + /// + /// Returns an infinite sequence of random 32-bit signed integers greater than or equal to zero and less than . + /// + public IEnumerable NextInt32Sequence() + { + for (int i = 0; i < 64; i++) + { + yield return Next(); + } + + var buffer = new int[64]; + while (true) + { + NextInt32s(buffer); + for (int i = 0; i < buffer.Length; i++) + { + yield return buffer[i]; + } + } + } + + /// + /// Returns an infinite sequence of random numbers within a specified range. + /// + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. Range: maxExclusive > minExclusive. + public IEnumerable NextInt32Sequence(int minInclusive, int maxExclusive) + { + if (minInclusive > maxExclusive) + { + throw new ArgumentException(Resources.ArgumentMinValueGreaterThanMaxValue); + } + + for (int i = 0; i < 64; i++) + { + yield return Next(minInclusive, maxExclusive); + } + + var buffer = new int[64]; + while (true) + { + NextInt32s(buffer, minInclusive, maxExclusive); + for (int i = 0; i < buffer.Length; i++) + { + yield return buffer[i]; + } + } + } + + /// + /// Fills the elements of a specified array of bytes with random numbers. + /// + /// An array of bytes to contain random numbers. + /// is null. + public sealed override void NextBytes(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (_threadSafe) + { + lock (_lock) + { + DoSampleBytes(buffer); + } + + return; + } + + DoSampleBytes(buffer); + } + + /// + /// Returns a random number between 0.0 and 1.0. + /// + /// A double-precision floating point number greater than or equal to 0.0, and less than 1.0. + protected sealed override double Sample() + { + if (_threadSafe) + { + lock (_lock) + { + return DoSample(); + } + } + + return DoSample(); + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected abstract double DoSample(); + + /// + /// Returns a random 32-bit signed integer greater than or equal to zero and less than 2147483647 (). + /// + protected virtual int DoSampleInteger() + { + return (int)(DoSample() * int.MaxValue); + } + + /// + /// Fills the elements of a specified array of bytes with random numbers in full range, including zero and 255 (). + /// + protected virtual void DoSampleBytes(byte[] buffer) + { + for (var i = 0; i < buffer.Length; i++) + { + buffer[i] = (byte)(DoSampleInteger() % 256); + } + } + + /// + /// Returns a random N-bit signed integer greater than or equal to zero and less than 2^N. + /// N (bit count) is expected to be greater than zero and less than 32 (not verified). + /// + protected virtual int DoSampleInt32WithNBits(int bitCount) + { + // Fast case: Only 0 is allowed to be returned + // No random call is needed + if (bitCount == 0) + { + return 0; + } + + var bytes = new byte[4]; + DoSampleBytes(bytes); + + // every bit with independent uniform distribution + uint uint32 = BitConverter.ToUInt32(bytes, 0); + + // the least significant N bits with independent uniform distribution and the remaining bits zero + uint uintN = uint32 >> (32 - bitCount); + return (int)uintN; + } + + /// + /// Returns a random N-bit signed long integer greater than or equal to zero and less than 2^N. + /// N (bit count) is expected to be greater than zero and less than 64 (not verified). + /// + protected virtual long DoSampleInt64WithNBits(int bitCount) + { + // Fast case: Only 0 is allowed to be returned + // No random call is needed + if (bitCount == 0) + { + return 0; + } + + var bytes = new byte[8]; + DoSampleBytes(bytes); + + // every bit with independent uniform distribution + ulong uint64 = BitConverter.ToUInt64(bytes, 0); + + // the least significant N bits with independent uniform distribution and the remaining bits zero + ulong uintN = uint64 >> (64 - bitCount); + return (long)uintN; + } + + /// + /// Returns a random 32-bit signed integer within the specified range. + /// + /// The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 2 (not verified, must be ensured by caller). + protected virtual int DoSampleInteger(int maxExclusive) + { + // non-biased implementation + // (biased: return (int)(DoSample() * maxExclusive);) + + int bitCount = Euclid.Log2(maxExclusive); + int range = Euclid.PowerOfTwo(bitCount); + + // Fast case: maxExclusive is a power of two + if (range == maxExclusive) + { + return DoSampleInt32WithNBits(bitCount); + } + + // Rejection case: we need to use rejection to avoid introducing bias + bitCount++; + int sample; + do + { + sample = DoSampleInt32WithNBits(bitCount); + } + while (sample >= maxExclusive); + return sample; + } + + /// + /// Returns a random 32-bit signed integer within the specified range. + /// + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. Range: maxExclusive ≥ minExclusive + 2 (not verified, must be ensured by caller). + protected virtual int DoSampleInteger(int minInclusive, int maxExclusive) + { + // Sample with maxExclusive ≥ 2 + return DoSampleInteger(maxExclusive - minInclusive) + minInclusive; + } +} diff --git a/MathNet.Numerics/Random/SystemRandomSource.cs b/MathNet.Numerics/Random/SystemRandomSource.cs new file mode 100644 index 0000000..e3c5e9e --- /dev/null +++ b/MathNet.Numerics/Random/SystemRandomSource.cs @@ -0,0 +1,233 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Runtime.Serialization; + +using MathNet.Numerics.Threading; + +using System.Threading; + +#if !NETSTANDARD1_3 +using System; +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// A random number generator based on the class in the .NET library. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class SystemRandomSource : RandomSource +{ + [DataMember(Order = 1)] + readonly System.Random _random; + + /// + /// Construct a new random number generator with a random seed. + /// + public SystemRandomSource() : this(RandomSeed.Robust()) + { + } + + /// + /// Construct a new random number generator with random seed. + /// + /// if set to true , the class is thread safe. + public SystemRandomSource(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Construct a new random number generator with random seed. + /// + /// The seed value. + public SystemRandomSource(int seed) + { + _random = new System.Random(seed); + } + + /// + /// Construct a new random number generator with random seed. + /// + /// The seed value. + /// if set to true , the class is thread safe. + public SystemRandomSource(int seed, bool threadSafe) : base(threadSafe) + { + _random = new System.Random(seed); + } + + static readonly ThreadLocal DefaultInstance = new ThreadLocal(() => new SystemRandomSource(RandomSeed.Robust(), true)); + + /// + /// Default instance, thread-safe. + /// + public static SystemRandomSource Default + { + get { return DefaultInstance.Value; } + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + return _random.NextDouble(); + } + + /// + /// Returns a random 32-bit signed integer greater than or equal to zero and less than + /// + protected override int DoSampleInteger() + { + return _random.Next(); + } + + /// + /// Returns a random 32-bit signed integer within the specified range. + /// + /// The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 2 (not verified, must be ensured by caller). + protected override int DoSampleInteger(int maxExclusive) + { + return _random.Next(maxExclusive); + } + + /// + /// Returns a random 32-bit signed integer within the specified range. + /// + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. Range: maxExclusive ≥ minExclusive + 2 (not verified, must be ensured by caller). + protected override int DoSampleInteger(int minInclusive, int maxExclusive) + { + return _random.Next(minInclusive, maxExclusive); + } + + /// + /// Fills the elements of a specified array of bytes with random numbers in full range, including zero and 255 (). + /// + protected override void DoSampleBytes(byte[] buffer) + { + _random.NextBytes(buffer); + } + + /// + /// Fill an array with uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// WARNING: potentially very short random sequence length, can generate repeated partial sequences. + /// + /// Parallelized on large length, but also supports being called in parallel from multiple threads + public static void FastDoubles(double[] values) + { + if (values.Length < 2048) + { + Default.NextDoubles(values); + return; + } + + CommonParallel.For(0, values.Length, values.Length >= 65536 ? 8192 : values.Length >= 16384 ? 2048 : 1024, (a, b) => + { + var rnd = new System.Random(RandomSeed.Robust()); + for (int i = a; i < b; i++) + { + values[i] = rnd.NextDouble(); + } + }); + } + + /// + /// Returns an array of uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// WARNING: potentially very short random sequence length, can generate repeated partial sequences. + /// + /// Parallelized on large length, but also supports being called in parallel from multiple threads + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] FastDoubles(int length) + { + var data = new double[length]; + FastDoubles(data); + return data; + } + + /// + /// Returns an infinite sequence of uniform random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence() + { + var rnd1 = Default; + for (int i = 0; i < 128; i++) + { + yield return rnd1.NextDouble(); + } + + var rnd2 = new System.Random(RandomSeed.Robust()); + while (true) + { + yield return rnd2.NextDouble(); + } + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + var rnd = new System.Random(seed); + for (int i = 0; i < values.Length; i++) + { + values[i] = rnd.NextDouble(); + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + var rnd = new System.Random(seed); + while (true) + { + yield return rnd.NextDouble(); + } + } +} diff --git a/MathNet.Numerics/Random/WH1982.cs b/MathNet.Numerics/Random/WH1982.cs new file mode 100644 index 0000000..d7479de --- /dev/null +++ b/MathNet.Numerics/Random/WH1982.cs @@ -0,0 +1,192 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Runtime.Serialization; + +#if !NETSTANDARD1_3 +using System; +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// Wichmann-Hill’s 1982 combined multiplicative congruential generator. +/// +/// See: Wichmann, B. A. & Hill, I. D. (1982), "Algorithm AS 183: +/// An efficient and portable pseudo-random number generator". Applied Statistics 31 (1982) 188-190 +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class WH1982 : RandomSource +{ + const uint Modx = 30269; + const double ModxRecip = 1.0 / Modx; + const uint Mody = 30307; + const double ModyRecip = 1.0 / Mody; + const uint Modz = 30323; + const double ModzRecip = 1.0 / Modz; + + [DataMember(Order = 1)] + uint _xn; + [DataMember(Order = 2)] + uint _yn = 1; + [DataMember(Order = 3)] + uint _zn = 1; + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + public WH1982() : this(RandomSeed.Robust()) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + public WH1982(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public WH1982(int seed) + { + if (seed == 0) + { + seed = 1; + } + + _xn = (uint)seed % Modx; + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// The seed is set to 1, if the zero is used as the seed. + /// if set to true , the class is thread safe. + public WH1982(int seed, bool threadSafe) + : base(threadSafe) + { + if (seed == 0) + { + seed = 1; + } + + _xn = (uint)seed % Modx; + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + _xn = (171 * _xn) % Modx; + _yn = (172 * _yn) % Mody; + _zn = (170 * _zn) % Modz; + + double w = _xn * ModxRecip + _yn * ModyRecip + _zn * ModzRecip; + w -= (int)w; + return w; + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + if (seed == 0) + { + seed = 1; + } + + uint xn = (uint)seed % Modx; + uint yn = 1; + uint zn = 1; + + for (int i = 0; i < values.Length; i++) + { + xn = (171 * xn) % Modx; + yn = (172 * yn) % Mody; + zn = (170 * zn) % Modz; + + double w = xn * ModxRecip + yn * ModyRecip + zn * ModzRecip; + values[i] = w - (int)w; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + if (seed == 0) + { + seed = 1; + } + + uint xn = (uint)seed % Modx; + uint yn = 1; + uint zn = 1; + + while (true) + { + xn = (171 * xn) % Modx; + yn = (172 * yn) % Mody; + zn = (170 * zn) % Modz; + + double w = xn * ModxRecip + yn * ModyRecip + zn * ModzRecip; + yield return w - (int)w; + } + } +} diff --git a/MathNet.Numerics/Random/WH2006.cs b/MathNet.Numerics/Random/WH2006.cs new file mode 100644 index 0000000..114a8e0 --- /dev/null +++ b/MathNet.Numerics/Random/WH2006.cs @@ -0,0 +1,200 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Runtime.Serialization; + +#if !NETSTANDARD1_3 +using System; +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// Wichmann-Hill’s 2006 combined multiplicative congruential generator. +/// +/// See: Wichmann, B. A. & Hill, I. D. (2006), "Generating good pseudo-random numbers". +/// Computational Statistics & Data Analysis 51:3 (2006) 1614-1622 +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class WH2006 : RandomSource +{ + const uint Modw = 2147483123; + const double ModwRecip = 1.0 / Modw; + const uint Modx = 2147483579; + const double ModxRecip = 1.0 / Modx; + const uint Mody = 2147483543; + const double ModyRecip = 1.0 / Mody; + const uint Modz = 2147483423; + const double ModzRecip = 1.0 / Modz; + + [DataMember(Order = 1)] + ulong _wn = 1; + [DataMember(Order = 2)] + ulong _xn; + [DataMember(Order = 3)] + ulong _yn = 1; + [DataMember(Order = 4)] + ulong _zn = 1; + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + public WH2006() : this(RandomSeed.Robust()) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + public WH2006(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + public WH2006(int seed) + { + if (seed == 0) + { + seed = 1; + } + + _xn = (uint)seed % Modx; + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// The seed is set to 1, if the zero is used as the seed. + /// if set to true , the class is thread safe. + public WH2006(int seed, bool threadSafe) : base(threadSafe) + { + if (seed == 0) + { + seed = 1; + } + + _xn = (uint)seed % Modx; + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + _xn = 11600 * _xn % Modx; + _yn = 47003 * _yn % Mody; + _zn = 23000 * _zn % Modz; + _wn = 33000 * _wn % Modw; + + double u = _xn * ModxRecip + _yn * ModyRecip + _zn * ModzRecip + _wn * ModwRecip; + u -= (int)u; + return u; + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + if (seed == 0) + { + seed = 1; + } + + ulong wn = 1; + ulong xn = (uint)seed % Modx; + ulong yn = 1; + ulong zn = 1; + + for (int i = 0; i < values.Length; i++) + { + xn = 11600 * xn % Modx; + yn = 47003 * yn % Mody; + zn = 23000 * zn % Modz; + wn = 33000 * wn % Modw; + + double u = xn * ModxRecip + yn * ModyRecip + zn * ModzRecip + wn * ModwRecip; + values[i] = u - (int)u; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + if (seed == 0) + { + seed = 1; + } + + ulong wn = 1; + ulong xn = (uint)seed % Modx; + ulong yn = 1; + ulong zn = 1; + + while (true) + { + xn = 11600 * xn % Modx; + yn = 47003 * yn % Mody; + zn = 23000 * zn % Modz; + wn = 33000 * wn % Modw; + + double u = xn * ModxRecip + yn * ModyRecip + zn * ModzRecip + wn * ModwRecip; + yield return u - (int)u; + } + } +} diff --git a/MathNet.Numerics/Random/Xorshift.cs b/MathNet.Numerics/Random/Xorshift.cs new file mode 100644 index 0000000..febd3bb --- /dev/null +++ b/MathNet.Numerics/Random/Xorshift.cs @@ -0,0 +1,383 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +using MathNet.Numerics.Properties; + +#if !NETSTANDARD1_3 +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// Implements a multiply-with-carry Xorshift pseudo random number generator (RNG) specified in Marsaglia, George. (2003). Xorshift RNGs. +/// Xn = a * Xn−3 + c mod 2^32 +/// http://www.jstatsoft.org/v08/i14/paper +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class Xorshift : RandomSource +{ + /// + /// The default value for X1. + /// + const uint YSeed = 362436069; + + /// + /// The default value for X2. + /// + const uint ZSeed = 77465321; + + /// + /// The default value for the multiplier. + /// + const uint ASeed = 916905990; + + /// + /// The default value for the carry over. + /// + const uint CSeed = 13579; + + /// + /// The multiplier to compute a double-precision floating point number [0, 1) + /// + const double UlongToDoubleMultiplier = 1.0 / 4294967296.0; // 1.0/(uint.MaxValue + 1.0) + + /// + /// Seed or last but three unsigned random number. + /// + [DataMember(Order = 1)] + ulong _x; + + /// + /// Last but two unsigned random number. + /// + [DataMember(Order = 2)] + ulong _y; + + /// + /// Last but one unsigned random number. + /// + [DataMember(Order = 3)] + ulong _z; + + /// + /// The value of the carry over. + /// + [DataMember(Order = 4)] + ulong _c; + + /// + /// The multiplier. + /// + [DataMember(Order = 5)] + readonly ulong _a; + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + /// Uses the default values of: + /// + /// a = 916905990 + /// c = 13579 + /// X1 = 77465321 + /// X2 = 362436069 + /// + public Xorshift() : this(RandomSeed.Robust()) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// The multiply value + /// The initial carry value. + /// The initial value if X1. + /// The initial value if X2. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + /// Note: must be less than . + /// + public Xorshift(long a, long c, long x1, long x2) : this(RandomSeed.Robust(), a, c, x1, x2) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + /// + /// Uses the default values of: + /// + /// a = 916905990 + /// c = 13579 + /// X1 = 77465321 + /// X2 = 362436069 + /// + public Xorshift(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Initializes a new instance of the class using + /// a seed based on time and unique GUIDs. + /// + /// if set to true , the class is thread safe. + /// The multiply value + /// The initial carry value. + /// The initial value if X1. + /// The initial value if X2. + /// must be less than . + public Xorshift(bool threadSafe, long a, long c, long x1, long x2) : this(RandomSeed.Robust(), threadSafe, a, c, x1, x2) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + /// Uses the default values of: + /// + /// a = 916905990 + /// c = 13579 + /// X1 = 77465321 + /// X2 = 362436069 + /// + public Xorshift(int seed) : this(seed, Control.ThreadSafeRandomNumberGenerators) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// If the seed value is zero, it is set to one. Uses the + /// value of to + /// set whether the instance is thread safe. + /// The multiply value + /// The initial carry value. + /// The initial value if X1. + /// The initial value if X2. + /// must be less than . + public Xorshift(int seed, long a, long c, long x1, long x2) : this(seed, Control.ThreadSafeRandomNumberGenerators, a, c, x1, x2) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// if set to true, the class is thread safe. + /// + /// Uses the default values of: + /// + /// a = 916905990 + /// c = 13579 + /// X1 = 77465321 + /// X2 = 362436069 + /// + public Xorshift(int seed, bool threadSafe) : base(threadSafe) + { + if (seed == 0) + { + seed = 1; + } + + _x = (uint)seed; + _y = YSeed; + _z = ZSeed; + _c = CSeed; + _a = ASeed; + } + + /// + /// Initializes a new instance of the class. + /// + /// The seed value. + /// if set to true, the class is thread safe. + /// The multiply value + /// The initial carry value. + /// The initial value if X1. + /// The initial value if X2. + /// must be less than . + public Xorshift(int seed, bool threadSafe, long a, long c, long x1, long x2) : base(threadSafe) + { + if (seed == 0) + { + seed = 1; + } + + if (a <= c) + { + throw new ArgumentException(string.Format(Resources.ArgumentOutOfRangeGreater, "a", "c"), nameof(a)); + } + + _x = (uint)seed; + _y = (ulong)x1; + _z = (ulong)x2; + _a = (ulong)a; + _c = (ulong)c; + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + var t = (_a * _x) + _c; + _x = _y; + _y = _z; + _c = t >> 32; + _z = t & 0xffffffff; + return _z * UlongToDoubleMultiplier; + } + + /// + /// Returns a random 32-bit signed integer greater than or equal to zero and less than + /// + protected sealed override int DoSampleInteger() + { + var t = (_a * _x) + _c; + _x = _y; + _y = _z; + _c = t >> 32; + _z = t & 0xffffffff; + uint uint32 = (uint)_z; + int int31 = (int)(uint32 >> 1); + if (int31 == int.MaxValue) + { + return DoSampleInteger(); + } + + return int31; + } + + /// + /// Fills the elements of a specified array of bytes with random numbers in full range, including zero and 255 (). + /// + protected sealed override void DoSampleBytes(byte[] buffer) + { + for (var i = 0; i < buffer.Length; i++) + { + var t = (_a * _x) + _c; + _x = _y; + _y = _z; + _c = t >> 32; + _z = t & 0xffffffff; + buffer[i] = (byte)(_z % 256); + } + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [CLSCompliant(false)] + public static void Doubles(double[] values, int seed, ulong a = ASeed, ulong c = CSeed, ulong x1 = YSeed, ulong x2 = ZSeed) + { + if (a <= c) + { + throw new ArgumentException(string.Format(Resources.ArgumentOutOfRangeGreater, "a", "c"), nameof(a)); + } + + if (seed == 0) + { + seed = 1; + } + + ulong x = (uint)seed; + + for (int i = 0; i < values.Length; i++) + { + var t = (a * x) + c; + x = x1; + x1 = x2; + c = t >> 32; + x2 = t & 0xffffffff; + values[i] = x2 * UlongToDoubleMultiplier; + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [CLSCompliant(false)] + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed, ulong a = ASeed, ulong c = CSeed, ulong x1 = YSeed, ulong x2 = ZSeed) + { + var data = new double[length]; + Doubles(data, seed, a, c, x1, x2); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + [CLSCompliant(false)] + public static IEnumerable DoubleSequence(int seed, ulong a = ASeed, ulong c = CSeed, ulong x1 = YSeed, ulong x2 = ZSeed) + { + if (a <= c) + { + throw new ArgumentException(string.Format(Resources.ArgumentOutOfRangeGreater, "a", "c"), nameof(a)); + } + + if (seed == 0) + { + seed = 1; + } + + ulong x = (uint)seed; + + while (true) + { + var t = (a * x) + c; + x = x1; + x1 = x2; + c = t >> 32; + x2 = t & 0xffffffff; + yield return x2 * UlongToDoubleMultiplier; + } + } +} diff --git a/MathNet.Numerics/Random/Xoshiro256StarStar.cs b/MathNet.Numerics/Random/Xoshiro256StarStar.cs new file mode 100644 index 0000000..64406bb --- /dev/null +++ b/MathNet.Numerics/Random/Xoshiro256StarStar.cs @@ -0,0 +1,375 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +/* + Original code's copyright and license: + Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + + To the extent possible under law, the author has dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + See . +*/ + +using System.Collections.Generic; +using System.Runtime.Serialization; + +#if !NETSTANDARD1_3 +using System; +using System.Runtime; +#endif + +namespace MathNet.Numerics.Random; + +/// +/// Xoshiro256** pseudo random number generator. +/// A random number generator based on the class in the .NET library. +/// +/// +/// This is xoshiro256** 1.0, our all-purpose, rock-solid generator. It has +/// excellent(sub-ns) speed, a state space(256 bits) that is large enough +/// for any parallel application, and it passes all tests we are aware of. +/// +/// For generating just floating-point numbers, xoshiro256+ is even faster. +/// +/// The state must be seeded so that it is not everywhere zero.If you have +/// a 64-bit seed, we suggest to seed a splitmix64 generator and use its +/// output to fill s. +/// +/// For further details see: +/// David Blackman & Sebastiano Vigna (2018), "Scrambled Linear Pseudorandom Number Generators". +/// https://arxiv.org/abs/1805.01407 +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics/Random")] +public class Xoshiro256StarStar : RandomSource +{ + // Constants. + const double REAL_UNIT_UINT = 1.0 / (1UL << 53); + + // RNG state. + [DataMember(Order = 1)] + ulong _s0; + [DataMember(Order = 2)] + ulong _s1; + [DataMember(Order = 3)] + ulong _s2; + [DataMember(Order = 4)] + ulong _s3; + + /// + /// Construct a new random number generator with a random seed. + /// + public Xoshiro256StarStar() : this(RandomSeed.Robust()) + { + } + + /// + /// Construct a new random number generator with random seed. + /// + /// if set to true , the class is thread safe. + public Xoshiro256StarStar(bool threadSafe) : this(RandomSeed.Robust(), threadSafe) + { + } + + /// + /// Construct a new random number generator with random seed. + /// + /// The seed value. + public Xoshiro256StarStar(int seed) + { + Initialise(seed); + } + + /// + /// Construct a new random number generator with random seed. + /// + /// The seed value. + /// if set to true , the class is thread safe. + public Xoshiro256StarStar(int seed, bool threadSafe) : base(threadSafe) + { + Initialise(seed); + } + + /// + /// Returns a random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + /// + protected sealed override double DoSample() + { + // Note. Here we generate a random integer between 0 and 2^53-1 (i.e. 53 binary 1s) and multiply + // by the fractional unit value 1.0 / 2^53, thus the result has a max value of + // 1.0 - (1.0 / 2^53), or 0.99999999999999989 in decimal. + return (NextInnerULong() >> 11) * REAL_UNIT_UINT; + } + + /// + /// Returns a random 32-bit signed integer greater than or equal to zero and less than + /// + protected override int DoSampleInteger() + { + retry: + // Handle the special case where the value int.MaxValue is generated; this is outside + // the range of permitted return values for this method. + ulong rtn = NextInnerULong() & 0x7fff_ffffUL; + if (rtn == 0x7fff_ffffUL) + { + goto retry; + } + + return (int)rtn; + } + + /// + /// Fills the elements of a specified array of bytes with random numbers in full range, including zero and 255 (). + /// + protected override void DoSampleBytes(byte[] buffer) + { + // For improved performance the below loop operates on these stack allocated copies of the heap variables. + // Notes. doing this means that these heavily used variables are located near to other local/stack variables, + // thus they will very likely be cached in the same CPU cache line. + ulong s0 = _s0; + ulong s1 = _s1; + ulong s2 = _s2; + ulong s3 = _s3; + + int i = 0; + + // Fill up the bulk of the buffer in chunks of 8 bytes at a time. + for (int bound = buffer.Length - 3; i < bound;) + { + // Generate 64 random bits. + ulong x = RotateLeft(s1 * 5, 7) * 9; + + // Update PRNG state. + ulong t = s1 << 17; + s2 ^= s0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + s2 ^= t; + s3 = RotateLeft(s3, 45); + + // Assign bits to a segment of 8 bytes. + buffer[i++] = (byte)x; + buffer[i++] = (byte)(x >> 8); + buffer[i++] = (byte)(x >> 16); + buffer[i++] = (byte)(x >> 24); + buffer[i++] = (byte)(x >> 32); + buffer[i++] = (byte)(x >> 40); + buffer[i++] = (byte)(x >> 48); + buffer[i++] = (byte)(x >> 56); + } + + // Fill up any remaining bytes in the buffer. + if (i < buffer.Length) + { + // Generate 64 random bits. + ulong x = RotateLeft(s1 * 5, 7) * 9; + + // Update PRNG state. + ulong t = s1 << 17; + s2 ^= s0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + s2 ^= t; + s3 = RotateLeft(s3, 45); + + // Allocate one byte at a time until we reach the end of the buffer. + while (i < buffer.Length) + { + buffer[i++] = (byte)x; + x >>= 8; + } + } + + // Update the state variables on the heap. + _s0 = s0; + _s1 = s1; + _s2 = s2; + _s3 = s3; + } + + /// + /// Returns a random N-bit signed integer greater than or equal to zero and less than 2^N. + /// N (bit count) is expected to be greater than zero and less than 32 (not verified). + /// + protected override int DoSampleInt32WithNBits(int bitCount) + { + return (int)(NextInnerULong() >> (64 - bitCount)); + } + + /// + /// Returns a random N-bit signed long integer greater than or equal to zero and less than 2^N. + /// N (bit count) is expected to be greater than zero and less than 64 (not verified). + /// + protected override long DoSampleInt64WithNBits(int bitCount) + { + return (long)(NextInnerULong() >> (64 - bitCount)); + } + + private void Initialise(int seed) + { + // Notes. + // xoroshiro256** requires that at least one of the state variable be non-zero, use of splitmix64 + // satisfies that requirement because its outputs are equidistributed, i.e. if a zero is output + // then the next zero will be after a further 2^64 outputs. + // Splitmix will also accept a zero input, thus all possible seeds can be accepted here and will + // all generate good initial state for xoshiro256**. + ulong longSeed = (ulong)seed; + + // Use the splitmix64 RNG to hash the seed. + _s0 = Splitmix64(ref longSeed); + _s1 = Splitmix64(ref longSeed); + _s2 = Splitmix64(ref longSeed); + _s3 = Splitmix64(ref longSeed); + } + + private ulong NextInnerULong() + { + ulong s0 = _s0; + ulong s1 = _s1; + ulong s2 = _s2; + ulong s3 = _s3; + + ulong result = RotateLeft(s1 * 5, 7) * 9; + + ulong t = s1 << 17; + s2 ^= s0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + s2 ^= t; + s3 = RotateLeft(s3, 45); + + _s0 = s0; + _s1 = s1; + _s2 = s2; + _s3 = s3; + + return result; + } + + /// + /// Fills an array with random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + public static void Doubles(double[] values, int seed) + { + // Init state. + ulong longSeed = (ulong)seed; + ulong s0 = Splitmix64(ref longSeed); + ulong s1 = Splitmix64(ref longSeed); + ulong s2 = Splitmix64(ref longSeed); + ulong s3 = Splitmix64(ref longSeed); + + for (int i = 0; i < values.Length; i++) + { + // Generate sample. + values[i] = ((RotateLeft(s1 * 5, 7) * 9) >> 11) * REAL_UNIT_UINT; + + // Update PRNG state. + ulong t = s1 << 17; + s2 ^= s0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + s2 ^= t; + s3 = RotateLeft(s3, 45); + } + } + + /// + /// Returns an array of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads. + [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] + public static double[] Doubles(int length, int seed) + { + var data = new double[length]; + Doubles(data, seed); + return data; + } + + /// + /// Returns an infinite sequence of random numbers greater than or equal to 0.0 and less than 1.0. + /// + /// Supports being called in parallel from multiple threads, but the result must be enumerated from a single thread each. + public static IEnumerable DoubleSequence(int seed) + { + // Init state. + ulong longSeed = (ulong)seed; + ulong s0 = Splitmix64(ref longSeed); + ulong s1 = Splitmix64(ref longSeed); + ulong s2 = Splitmix64(ref longSeed); + ulong s3 = Splitmix64(ref longSeed); + + while (true) + { + // Generate sample. + double x = ((RotateLeft(s1 * 5, 7) * 9) >> 11) * REAL_UNIT_UINT; + + // Update PRNG state. + ulong t = s1 << 17; + s2 ^= s0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + s2 ^= t; + s3 = RotateLeft(s3, 45); + + // Yield sample. + yield return x; + } + } + + /// + /// Splitmix64 RNG. + /// + /// RNG state. This can take any value, including zero. + /// A new random UInt64. + /// + /// Splitmix64 produces equidistributed outputs, thus if a zero is generated then the + /// next zero will be after a further 2^64 outputs. + /// + private static ulong Splitmix64(ref ulong x) + { + ulong z = (x += 0x9E3779B97F4A7C15UL); + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL; + return z ^ (z >> 31); + } + + private static ulong RotateLeft(ulong x, int k) + { + // Note. RyuJIT will compile this to a single rotate CPU instruction (as of about .NET 4.6.1 and dotnet core 2.0). + return (x << k) | (x >> (64 - k)); + } +} diff --git a/MathNet.Numerics/RootFinding/Bisection.cs b/MathNet.Numerics/RootFinding/Bisection.cs new file mode 100644 index 0000000..39af34c --- /dev/null +++ b/MathNet.Numerics/RootFinding/Bisection.cs @@ -0,0 +1,150 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.RootFinding; + +/// +/// Bisection root-finding algorithm. +/// +public static class Bisection +{ + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// Guess for the low value of the range where the root is supposed to be. Will be expanded if needed. + /// Guess for the high value of the range where the root is supposed to be. Will be expanded if needed. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// Factor at which to expand the bounds, if needed. Default 1.6. + /// Maximum number of expand iterations. Default 100. + /// Returns the root with the specified accuracy. + /// + public static double FindRootExpand(Func f, double guessLowerBound, double guessUpperBound, double accuracy = 1e-8, int maxIterations = 100, double expandFactor = 1.6, int maxExpandIteratons = 100) + { + ZeroCrossingBracketing.ExpandReduce(f, ref guessLowerBound, ref guessUpperBound, expandFactor, maxExpandIteratons, maxExpandIteratons * 10); + return FindRoot(f, guessLowerBound, guessUpperBound, accuracy, maxIterations); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The low value of the range where the root is supposed to be. + /// The high value of the range where the root is supposed to be. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// Returns the root with the specified accuracy. + /// + public static double FindRoot(Func f, double lowerBound, double upperBound, double accuracy = 1e-14, int maxIterations = 100) + { + double root; + if (TryFindRoot(f, lowerBound, upperBound, accuracy, maxIterations, out root)) + { + return root; + } + + throw new NonConvergenceException(Resources.RootFindingFailed); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The low value of the range where the root is supposed to be. + /// The high value of the range where the root is supposed to be. + /// Desired accuracy for both the root and the function value at the root. The root will be refined until the accuracy or the maximum number of iterations is reached. + /// Maximum number of iterations. Usually 100. + /// The root that was found, if any. Undefined if the function returns false. + /// True if a root with the specified accuracy was found, else false. + public static bool TryFindRoot(Func f, double lowerBound, double upperBound, double accuracy, int maxIterations, out double root) + { + if (upperBound < lowerBound) + { + var t = upperBound; + upperBound = lowerBound; + lowerBound = t; + } + + double fmin = f(lowerBound); + if (Math.Sign(fmin) == 0) + { + root = lowerBound; + return true; + } + + double fmax = f(upperBound); + if (Math.Sign(fmax) == 0) + { + root = upperBound; + return true; + } + + root = 0.5 * (lowerBound + upperBound); + + // bad bracketing? + if (Math.Sign(fmin) == Math.Sign(fmax)) + { + return false; + } + + for (int i = 0; i <= maxIterations; i++) + { + double froot = f(root); + + if (upperBound - lowerBound <= 2 * accuracy && Math.Abs(froot) <= accuracy) + { + return true; + } + + if ((lowerBound == root) || (upperBound == root)) + { + // accuracy not sufficient, but cannot be improved further + return false; + } + + if (Math.Sign(froot) == Math.Sign(fmin)) + { + lowerBound = root; + fmin = froot; + } + else if (Math.Sign(froot) == Math.Sign(fmax)) + { + upperBound = root; + fmax = froot; + } + else // Math.Sign(froot) == 0 + { + return true; + } + + root = 0.5 * (lowerBound + upperBound); + } + + return false; + } +} diff --git a/MathNet.Numerics/RootFinding/Brent.cs b/MathNet.Numerics/RootFinding/Brent.cs new file mode 100644 index 0000000..26e8fe9 --- /dev/null +++ b/MathNet.Numerics/RootFinding/Brent.cs @@ -0,0 +1,206 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.RootFinding; + +/// +/// Algorithm by Brent, Van Wijngaarden, Dekker et al. +/// Implementation inspired by Press, Teukolsky, Vetterling, and Flannery, "Numerical Recipes in C", 2nd edition, Cambridge University Press +/// +public static class Brent +{ + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// Guess for the low value of the range where the root is supposed to be. Will be expanded if needed. + /// Guess for the high value of the range where the root is supposed to be. Will be expanded if needed. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// Factor at which to expand the bounds, if needed. Default 1.6. + /// Maximum number of expand iterations. Default 100. + /// Returns the root with the specified accuracy. + /// + public static double FindRootExpand(Func f, double guessLowerBound, double guessUpperBound, double accuracy = 1e-8, int maxIterations = 100, double expandFactor = 1.6, int maxExpandIteratons = 100) + { + ZeroCrossingBracketing.ExpandReduce(f, ref guessLowerBound, ref guessUpperBound, expandFactor, maxExpandIteratons, maxExpandIteratons * 10); + return FindRoot(f, guessLowerBound, guessUpperBound, accuracy, maxIterations); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The low value of the range where the root is supposed to be. + /// The high value of the range where the root is supposed to be. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// Returns the root with the specified accuracy. + /// + public static double FindRoot(Func f, double lowerBound, double upperBound, double accuracy = 1e-8, int maxIterations = 100) + { + double root; + if (TryFindRoot(f, lowerBound, upperBound, accuracy, maxIterations, out root)) + { + return root; + } + + throw new NonConvergenceException(Resources.RootFindingFailed); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The low value of the range where the root is supposed to be. + /// The high value of the range where the root is supposed to be. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. + /// Maximum number of iterations. Usually 100. + /// The root that was found, if any. Undefined if the function returns false. + /// True if a root with the specified accuracy was found, else false. + public static bool TryFindRoot(Func f, double lowerBound, double upperBound, double accuracy, int maxIterations, out double root) + { + double fmin = f(lowerBound); + double fmax = f(upperBound); + double froot = fmax; + double d = 0.0, e = 0.0; + + root = upperBound; + double xMid = double.NaN; + + // Root must be bracketed. + if (Math.Sign(fmin) == Math.Sign(fmax)) + { + return false; + } + + for (int i = 0; i <= maxIterations; i++) + { + // adjust bounds + if (Math.Sign(froot) == Math.Sign(fmax)) + { + upperBound = lowerBound; + fmax = fmin; + e = d = root - lowerBound; + } + + if (Math.Abs(fmax) < Math.Abs(froot)) + { + lowerBound = root; + root = upperBound; + upperBound = lowerBound; + fmin = froot; + froot = fmax; + fmax = fmin; + } + + // convergence check + double xAcc1 = Precision.PositiveDoublePrecision * Math.Abs(root) + 0.5 * accuracy; + double xMidOld = xMid; + xMid = (upperBound - root) / 2.0; + + if (Math.Abs(xMid) <= xAcc1 || froot.AlmostEqualNormRelative(0, froot, accuracy)) + { + return true; + } + + if (xMid == xMidOld) + { + // accuracy not sufficient, but cannot be improved further + return false; + } + + if (Math.Abs(e) >= xAcc1 && Math.Abs(fmin) > Math.Abs(froot)) + { + // Attempt inverse quadratic interpolation + double s = froot / fmin; + double p; + double q; + if (lowerBound.AlmostEqualRelative(upperBound)) + { + p = 2.0 * xMid * s; + q = 1.0 - s; + } + else + { + q = fmin / fmax; + double r = froot / fmax; + p = s * (2.0 * xMid * q * (q - r) - (root - lowerBound) * (r - 1.0)); + q = (q - 1.0) * (r - 1.0) * (s - 1.0); + } + + if (p > 0.0) + { + // Check whether in bounds + q = -q; + } + + p = Math.Abs(p); + if (2.0 * p < Math.Min(3.0 * xMid * q - Math.Abs(xAcc1 * q), Math.Abs(e * q))) + { + // Accept interpolation + e = d; + d = p / q; + } + else + { + // Interpolation failed, use bisection + d = xMid; + e = d; + } + } + else + { + // Bounds decreasing too slowly, use bisection + d = xMid; + e = d; + } + + lowerBound = root; + fmin = froot; + if (Math.Abs(d) > xAcc1) + { + root += d; + } + else + { + root += Sign(xAcc1, xMid); + } + + froot = f(root); + } + + return false; + } + + /// Helper method useful for preventing rounding errors. + /// a*sign(b) + static double Sign(double a, double b) + { + return b >= 0 ? (a >= 0 ? a : -a) : (a >= 0 ? -a : a); + } +} diff --git a/MathNet.Numerics/RootFinding/Broyden.cs b/MathNet.Numerics/RootFinding/Broyden.cs new file mode 100644 index 0000000..a836e7c --- /dev/null +++ b/MathNet.Numerics/RootFinding/Broyden.cs @@ -0,0 +1,166 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Double; +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.RootFinding; + +/// +/// Algorithm by Broyden. +/// Implementation inspired by Press, Teukolsky, Vetterling, and Flannery, "Numerical Recipes in C", 2nd edition, Cambridge University Press +/// +public static class Broyden +{ + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// Initial guess of the root. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// Relative step size for calculating the Jacobian matrix at first step. Default 1.0e-4 + /// Returns the root with the specified accuracy. + /// + public static double[] FindRoot(Func f, double[] initialGuess, double accuracy = 1e-8, int maxIterations = 100, double jacobianStepSize = 1.0e-4) + { + double[] root; + if (TryFindRootWithJacobianStep(f, initialGuess, accuracy, maxIterations, jacobianStepSize, out root)) + { + return root; + } + + throw new NonConvergenceException(Resources.RootFindingFailed); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// Initial guess of the root. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. + /// Maximum number of iterations. Usually 100. + /// Relative step size for calculating the Jacobian matrix at first step. + /// The root that was found, if any. Undefined if the function returns false. + /// True if a root with the specified accuracy was found, else false. + public static bool TryFindRootWithJacobianStep(Func f, double[] initialGuess, double accuracy, int maxIterations, double jacobianStepSize, out double[] root) + { + var x = new DenseVector(initialGuess); + + double[] y0 = f(initialGuess); + var y = new DenseVector(y0); + double g = y.L2Norm(); + + Matrix B = CalculateApproximateJacobian(f, initialGuess, y0, jacobianStepSize); + + for (int i = 0; i <= maxIterations; i++) + { + var dx = (DenseVector)(-B.LU().Solve(y)); + var xnew = x + dx; + var ynew = new DenseVector(f(xnew.Values)); + double gnew = ynew.L2Norm(); + + if (gnew > g) + { + double g2 = g * g; + double scale = g2 / (g2 + gnew * gnew); + if (scale == 0.0) + { + scale = 1.0e-4; + } + + dx = scale * dx; + xnew = x + dx; + ynew = new DenseVector(f(xnew.Values)); + gnew = ynew.L2Norm(); + } + + if (gnew < accuracy) + { + root = xnew.Values; + return true; + } + + // update Jacobian B + DenseVector dF = ynew - y; + Matrix dB = (dF - B.Multiply(dx)).ToColumnMatrix() * dx.Multiply(1.0 / Math.Pow(dx.L2Norm(), 2)).ToRowMatrix(); + B = B + dB; + + x = xnew; + y = ynew; + g = gnew; + } + + root = null; + return false; + } + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// Initial guess of the root. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. + /// Maximum number of iterations. Usually 100. + /// The root that was found, if any. Undefined if the function returns false. + /// True if a root with the specified accuracy was found, else false. + public static bool TryFindRoot(Func f, double[] initialGuess, double accuracy, int maxIterations, out double[] root) + { + return TryFindRootWithJacobianStep(f, initialGuess, accuracy, maxIterations, 1.0e-4, out root); + } + + /// + /// Helper method to calculate an approximation of the Jacobian. + /// + /// The function. + /// The argument (initial guess). + /// The result (of initial guess). + /// Relative step size for calculating the Jacobian. + static Matrix CalculateApproximateJacobian(Func f, double[] x0, double[] y0, double jacobianStepSize) + { + int dim = x0.Length; + var B = new DenseMatrix(dim); + + var x = new double[dim]; + Array.Copy(x0, 0, x, 0, dim); + + for (int j = 0; j < dim; j++) + { + double h = (1.0 + Math.Abs(x0[j])) * jacobianStepSize; + + var xj = x[j]; + x[j] = xj + h; + double[] y = f(x); + x[j] = xj; + + for (int i = 0; i < dim; i++) + { + B.At(i, j, (y[i] - y0[i]) / h); + } + } + + return B; + } +} diff --git a/MathNet.Numerics/RootFinding/Cubic.cs b/MathNet.Numerics/RootFinding/Cubic.cs new file mode 100644 index 0000000..0eeeabc --- /dev/null +++ b/MathNet.Numerics/RootFinding/Cubic.cs @@ -0,0 +1,138 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.RootFinding; + +/// +/// Finds roots to the cubic equation x^3 + a2*x^2 + a1*x + a0 = 0 +/// Implements the cubic formula in http://mathworld.wolfram.com/CubicFormula.html +/// +public static class Cubic +{ + // D = Q^3 + R^2 is the polynomial discriminant. + // D > 0, 1 real root + // D = 0, 3 real roots, at least two are equal + // D < 0, 3 real and unequal roots + + /// + /// Q and R are transformed variables. + /// + static void QR(double a2, double a1, double a0, out double Q, out double R) + { + Q = (3 * a1 - a2 * a2) / 9.0; + R = (9.0 * a2 * a1 - 27 * a0 - 2 * a2 * a2 * a2) / 54.0; + } + + /// + /// n^(1/3) - work around a negative double raised to (1/3) + /// + static double PowThird(double n) + { + return Math.Pow(Math.Abs(n), 1d / 3d) * Math.Sign(n); + } + + /// + /// Find all real-valued roots of the cubic equation a0 + a1*x + a2*x^2 + x^3 = 0. + /// Note the special coefficient order ascending by exponent (consistent with polynomials). + /// + public static Tuple RealRoots(double a0, double a1, double a2) + { + double Q, R; + QR(a2, a1, a0, out Q, out R); + + var Q3 = Q * Q * Q; + var D = Q3 + R * R; + var shift = -a2 / 3d; + + double x1; + double x2 = double.NaN; + double x3 = double.NaN; + + if (D >= 0) + { + // when D >= 0, use eqn (54)-(56) where S and T are real + double sqrtD = Math.Pow(D, 0.5); + double S = PowThird(R + sqrtD); + double T = PowThird(R - sqrtD); + x1 = shift + (S + T); + if (D == 0) + { + x2 = shift - S; + } + } + else + { + // 3 real roots, use eqn (70)-(73) to calculate the real roots + double theta = Math.Acos(R / Math.Sqrt(-Q3)); + x1 = 2d * Math.Sqrt(-Q) * Math.Cos(theta / 3.0) + shift; + x2 = 2d * Math.Sqrt(-Q) * Math.Cos((theta + 2.0 * Constants.Pi) / 3d) + shift; + x3 = 2d * Math.Sqrt(-Q) * Math.Cos((theta - 2.0 * Constants.Pi) / 3d) + shift; + } + + return new Tuple(x1, x2, x3); + } + + /// + /// Find all three complex roots of the cubic equation d + c*x + b*x^2 + a*x^3 = 0. + /// Note the special coefficient order ascending by exponent (consistent with polynomials). + /// + public static Tuple Roots(double d, double c, double b, double a) + { + double A = b * b - 3 * a * c; + double B = 2 * b * b * b - 9 * a * b * c + 27 * a * a * d; + double s = -1 / (3 * a); + + double D = (B * B - 4 * A * A * A) / (-27 * a * a); + if (D == 0d) + { + if (A == 0d) + { + var u = new Complex(s * b, 0d); + return new Tuple(u, u, u); + } + + var v = new Complex((9 * a * d - b * c) / (2 * A), 0d); + var w = new Complex((4 * a * b * c - 9 * a * a * d - b * b * b) / (a * A), 0d); + return new Tuple(v, v, w); + } + + var C = (A == 0) + ? new Complex(B, 0d).CubicRoots() + : ((B + Complex.Sqrt(B * B - 4 * A * A * A)) / 2).CubicRoots(); + + return new Tuple( + s * (b + C.Item1 + A / C.Item1), + s * (b + C.Item2 + A / C.Item2), + s * (b + C.Item3 + A / C.Item3)); + } +} diff --git a/MathNet.Numerics/RootFinding/NewtonRaphson.cs b/MathNet.Numerics/RootFinding/NewtonRaphson.cs new file mode 100644 index 0000000..4ecfff3 --- /dev/null +++ b/MathNet.Numerics/RootFinding/NewtonRaphson.cs @@ -0,0 +1,115 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.RootFinding; + +/// +/// Pure Newton-Raphson root-finding algorithm without any recovery measures in cases it behaves badly. +/// The algorithm aborts immediately if the root leaves the bound interval. +/// +/// +public static class NewtonRaphson +{ + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The first derivative of the function to find roots from. + /// The low value of the range where the root is supposed to be. Aborts if it leaves the interval. + /// The high value of the range where the root is supposed to be. Aborts if it leaves the interval. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// Returns the root with the specified accuracy. + /// + public static double FindRoot(Func f, Func df, double lowerBound, double upperBound, double accuracy = 1e-8, int maxIterations = 100) + { + double root; + if (TryFindRoot(f, df, 0.5 * (lowerBound + upperBound), lowerBound, upperBound, accuracy, maxIterations, out root)) + { + return root; + } + + throw new NonConvergenceException(Resources.RootFindingFailedRecommendRobustNewtonRaphson); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The first derivative of the function to find roots from. + /// Initial guess of the root. + /// The low value of the range where the root is supposed to be. Aborts if it leaves the interval. Default MinValue. + /// The high value of the range where the root is supposed to be. Aborts if it leaves the interval. Default MaxValue. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// Returns the root with the specified accuracy. + /// + public static double FindRootNearGuess(Func f, Func df, double initialGuess, double lowerBound = double.MinValue, double upperBound = double.MaxValue, double accuracy = 1e-8, int maxIterations = 100) + { + double root; + if (TryFindRoot(f, df, initialGuess, lowerBound, upperBound, accuracy, maxIterations, out root)) + { + return root; + } + + throw new NonConvergenceException(Resources.RootFindingFailedRecommendRobustNewtonRaphson); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The first derivative of the function to find roots from. + /// Initial guess of the root. + /// The low value of the range where the root is supposed to be. Aborts if it leaves the interval. + /// The high value of the range where the root is supposed to be. Aborts if it leaves the interval. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Example: 1e-14. + /// Maximum number of iterations. Example: 100. + /// The root that was found, if any. Undefined if the function returns false. + /// True if a root with the specified accuracy was found, else false. + public static bool TryFindRoot(Func f, Func df, double initialGuess, double lowerBound, double upperBound, double accuracy, int maxIterations, out double root) + { + root = initialGuess; + for (int i = 0; i < maxIterations && root >= lowerBound && root <= upperBound; i++) + { + // Evaluation + double fx = f(root); + double dfx = df(root); + + // Netwon-Raphson step + double step = fx / dfx; + root -= step; + + if (Math.Abs(step) < accuracy && Math.Abs(fx) < accuracy) + { + return true; + } + } + + return false; + } +} diff --git a/MathNet.Numerics/RootFinding/RobustNewtonRaphson.cs b/MathNet.Numerics/RootFinding/RobustNewtonRaphson.cs new file mode 100644 index 0000000..0b8521f --- /dev/null +++ b/MathNet.Numerics/RootFinding/RobustNewtonRaphson.cs @@ -0,0 +1,183 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.RootFinding; + +/// +/// Robust Newton-Raphson root-finding algorithm that falls back to bisection when overshooting or converging too slow, or to subdivision on lacking bracketing. +/// +/// +public static class RobustNewtonRaphson +{ + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The first derivative of the function to find roots from. + /// The low value of the range where the root is supposed to be. + /// The high value of the range where the root is supposed to be. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// How many parts an interval should be split into for zero crossing scanning in case of lacking bracketing. Default 20. + /// Returns the root with the specified accuracy. + /// + public static double FindRoot(Func f, Func df, double lowerBound, double upperBound, double accuracy = 1e-8, int maxIterations = 100, int subdivision = 20) + { + double root; + if (TryFindRoot(f, df, lowerBound, upperBound, accuracy, maxIterations, subdivision, out root)) + { + return root; + } + + throw new NonConvergenceException(Resources.RootFindingFailed); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The first derivative of the function to find roots from. + /// The low value of the range where the root is supposed to be. + /// The high value of the range where the root is supposed to be. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Example: 1e-14. + /// Maximum number of iterations. Example: 100. + /// How many parts an interval should be split into for zero crossing scanning in case of lacking bracketing. Example: 20. + /// The root that was found, if any. Undefined if the function returns false. + /// True if a root with the specified accuracy was found, else false. + public static bool TryFindRoot(Func f, Func df, double lowerBound, double upperBound, double accuracy, int maxIterations, int subdivision, out double root) + { + double fmin = f(lowerBound); + double fmax = f(upperBound); + + if (Math.Abs(fmin) < accuracy) + { + root = lowerBound; + return true; + } + + if (Math.Abs(fmax) < accuracy) + { + root = upperBound; + return true; + } + + root = 0.5 * (lowerBound + upperBound); + double fx = f(root); + double lastStep = Math.Abs(upperBound - lowerBound); + for (int i = 0; i < maxIterations; i++) + { + double dfx = df(root); + + // Netwon-Raphson step + double step = fx / dfx; + root -= step; + + if (Math.Abs(step) < accuracy && Math.Abs(fx) < accuracy) + { + return true; + } + + bool overshoot = root > upperBound, undershoot = root < lowerBound; + if (overshoot || undershoot || Math.Abs(2 * fx) > Math.Abs(lastStep * dfx)) + { + // Newton-Raphson step failed + + // If same signs, try subdivision to scan for zero crossing intervals + if (Math.Sign(fmin) == Math.Sign(fmax) && TryScanForCrossingsWithRoots(f, df, lowerBound, upperBound, accuracy, maxIterations - i - 1, subdivision, out root)) + { + return true; + } + + // Bisection + root = 0.5 * (upperBound + lowerBound); + fx = f(root); + lastStep = 0.5 * Math.Abs(upperBound - lowerBound); + if (Math.Sign(fx) == Math.Sign(fmin)) + { + lowerBound = root; + fmin = fx; + if (overshoot) + { + root = upperBound; + fx = fmax; + } + } + else + { + upperBound = root; + fmax = fx; + if (undershoot) + { + root = lowerBound; + fx = fmin; + } + } + + continue; + } + + // Evaluation + fx = f(root); + lastStep = step; + + // Update bounds + if (Math.Sign(fx) != Math.Sign(fmin)) + { + upperBound = root; + fmax = fx; + } + else if (Math.Sign(fx) != Math.Sign(fmax)) + { + lowerBound = root; + fmin = fx; + } + else if (Math.Sign(fmin) != Math.Sign(fmax) && Math.Abs(fx) < accuracy) + { + return true; + } + } + + return false; + } + + static bool TryScanForCrossingsWithRoots(Func f, Func df, double lowerBound, double upperBound, double accuracy, int maxIterations, int subdivision, out double root) + { + var zeroCrossings = ZeroCrossingBracketing.FindIntervalsWithin(f, lowerBound, upperBound, subdivision); + foreach (Tuple bounds in zeroCrossings) + { + if (TryFindRoot(f, df, bounds.Item1, bounds.Item2, accuracy, maxIterations, subdivision, out root)) + { + return true; + } + } + + root = double.NaN; + return false; + } +} diff --git a/MathNet.Numerics/RootFinding/Secant.cs b/MathNet.Numerics/RootFinding/Secant.cs new file mode 100644 index 0000000..60bc78f --- /dev/null +++ b/MathNet.Numerics/RootFinding/Secant.cs @@ -0,0 +1,107 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.RootFinding; + +/// +/// Pure Secant root-finding algorithm without any recovery measures in cases it behaves badly. +/// The algorithm aborts immediately if the root leaves the bound interval. +/// +/// +public static class Secant +{ + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The first guess of the root within the bounds specified. + /// The second guess of the root within the bounds specified. + /// The low value of the range where the root is supposed to be. Aborts if it leaves the interval. Default MinValue. + /// The high value of the range where the root is supposed to be. Aborts if it leaves the interval. Default MaxValue. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Default 1e-8. + /// Maximum number of iterations. Default 100. + /// Returns the root with the specified accuracy. + /// + public static double FindRoot(Func f, double guess, double secondGuess, double lowerBound = double.MinValue, double upperBound = double.MaxValue, double accuracy = 1e-8, int maxIterations = 100) + { + double root; + if (TryFindRoot(f, guess, secondGuess, lowerBound, upperBound, accuracy, maxIterations, out root)) + { + return root; + } + + throw new NonConvergenceException(Resources.RootFindingFailed); + } + + /// Find a solution of the equation f(x)=0. + /// The function to find roots from. + /// The first guess of the root within the bounds specified. + /// The second guess of the root within the bounds specified. + /// The low value of the range where the root is supposed to be. Aborts if it leaves the interval. + /// The low value of the range where the root is supposed to be. Aborts if it leaves the interval. + /// Desired accuracy. The root will be refined until the accuracy or the maximum number of iterations is reached. Example: 1e-14. + /// Maximum number of iterations. Example: 100. + /// The root that was found, if any. Undefined if the function returns false. + /// True if a root with the specified accuracy was found, else false + public static bool TryFindRoot(Func f, double guess, double secondGuess, double lowerBound, double upperBound, double accuracy, int maxIterations, out double root) + { + root = secondGuess; + + // Either guess is outside of bounds + if (guess <= lowerBound || guess >= upperBound || secondGuess <= lowerBound || secondGuess >= upperBound) + { + return false; + } + + // Evaluation + double fguess = f(guess); + double froot = f(root); + + for (int i = 0; i <= maxIterations && root >= lowerBound && root <= upperBound; i++) + { + // Secant step + double step = froot * (root - guess) / (froot - fguess); + + guess = root; + fguess = froot; + + root -= step; + froot = f(root); + + if (Math.Abs(step) < accuracy && Math.Abs(froot) < accuracy) + { + return true; + } + } + + return false; + } +} diff --git a/MathNet.Numerics/RootFinding/ZeroCrossingBracketing.cs b/MathNet.Numerics/RootFinding/ZeroCrossingBracketing.cs new file mode 100644 index 0000000..361a6b1 --- /dev/null +++ b/MathNet.Numerics/RootFinding/ZeroCrossingBracketing.cs @@ -0,0 +1,174 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.RootFinding; + +public static class ZeroCrossingBracketing +{ + public static IEnumerable> FindIntervalsWithin(Func f, double lowerBound, double upperBound, int subdivisions) + { + // TODO: Consider binary-style search instead of linear scan + double fmin = f(lowerBound); + double fmax = f(upperBound); + + if (Math.Sign(fmin) != Math.Sign(fmax)) + { + yield return new Tuple(lowerBound, upperBound); + yield break; + } + + double subdiv = (upperBound - lowerBound) / subdivisions; + double smin = lowerBound; + int sign = Math.Sign(fmin); + + for (int k = 0; k < subdivisions; k++) + { + double smax = smin + subdiv; + double sfmax = f(smax); + if (double.IsInfinity(sfmax)) + { + // expand interval to include pole + smin = smax; + continue; + } + + if (Math.Sign(sfmax) != sign) + { + yield return new Tuple(smin, smax); + sign = Math.Sign(sfmax); + } + + smin = smax; + } + } + + /// Detect a range containing at least one root. + /// The function to detect roots from. + /// Lower value of the range. + /// Upper value of the range + /// The growing factor of research. Usually 1.6. + /// Maximum number of iterations. Usually 50. + /// True if the bracketing operation succeeded, false otherwise. + /// This iterative methods stops when two values with opposite signs are found. + public static bool Expand(Func f, ref double lowerBound, ref double upperBound, double factor = 1.6, int maxIterations = 50) + { + double originalLowerBound = lowerBound; + double originalUpperBound = upperBound; + + if (lowerBound >= upperBound) + { + throw new ArgumentOutOfRangeException(nameof(upperBound), string.Format(Resources.ArgumentOutOfRangeGreater, "xmax", "xmin")); + } + + double fmin = f(lowerBound); + double fmax = f(upperBound); + + for (int i = 0; i < maxIterations; i++) + { + if (Math.Sign(fmin) != Math.Sign(fmax)) + { + return true; + } + + if (Math.Abs(fmin) < Math.Abs(fmax)) + { + lowerBound += factor * (lowerBound - upperBound); + fmin = f(lowerBound); + } + else + { + upperBound += factor * (upperBound - lowerBound); + fmax = f(upperBound); + } + } + + lowerBound = originalLowerBound; + upperBound = originalUpperBound; + return false; + } + + public static bool Reduce(Func f, ref double lowerBound, ref double upperBound, int subdivisions = 1000) + { + double originalLowerBound = lowerBound; + double originalUpperBound = upperBound; + + if (lowerBound >= upperBound) + { + throw new ArgumentOutOfRangeException(nameof(upperBound), string.Format(Resources.ArgumentOutOfRangeGreater, "xmax", "xmin")); + } + + // TODO: Consider binary-style search instead of linear scan + double fmin = f(lowerBound); + double fmax = f(upperBound); + + if (Math.Sign(fmin) != Math.Sign(fmax)) + { + return true; + } + + double subdiv = (upperBound - lowerBound) / subdivisions; + double smin = lowerBound; + int sign = Math.Sign(fmin); + + for (int k = 0; k < subdivisions; k++) + { + double smax = smin + subdiv; + double sfmax = f(smax); + if (double.IsInfinity(sfmax)) + { + // expand interval to include pole + smin = smax; + continue; + } + + if (Math.Sign(sfmax) != sign) + { + lowerBound = smin; + upperBound = smax; + return true; + } + + smin = smax; + } + + lowerBound = originalLowerBound; + upperBound = originalUpperBound; + return false; + } + + public static bool ExpandReduce(Func f, ref double lowerBound, ref double upperBound, double expansionFactor = 1.6, int expansionMaxIterations = 50, int reduceSubdivisions = 100) + { + return Expand(f, ref lowerBound, ref upperBound, expansionFactor, expansionMaxIterations) || Reduce(f, ref lowerBound, ref upperBound, reduceSubdivisions); + } +} diff --git a/MathNet.Numerics/Settings.StyleCop b/MathNet.Numerics/Settings.StyleCop new file mode 100644 index 0000000..28b815f --- /dev/null +++ b/MathNet.Numerics/Settings.StyleCop @@ -0,0 +1,405 @@ + + + NoMerge + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + False + + + + + False + + + + + + x + y + z + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + True + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + True + + + + + True + + + + + True + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + + + diff --git a/MathNet.Numerics/Sorting.cs b/MathNet.Numerics/Sorting.cs new file mode 100644 index 0000000..6257a8f --- /dev/null +++ b/MathNet.Numerics/Sorting.cs @@ -0,0 +1,890 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics; + +/// +/// Sorting algorithms for single, tuple and triple lists. +/// +public static class Sorting +{ + /// + /// Sort a list of keys, in place using the quick sort algorithm using the quick sort algorithm. + /// + /// The type of elements in the key list. + /// List to sort. + /// Comparison, defining the sort order. + public static void Sort(IList keys, IComparer comparer = null) + { + int count = keys.Count; + if (count <= 1) + { + return; + } + + if (null == comparer) + { + comparer = Comparer.Default; + } + + if (count == 2) + { + if (comparer.Compare(keys[0], keys[1]) > 0) + { + Swap(keys, 0, 1); + } + + return; + } + + // insertion sort + if (count <= 10) + { + for (int i = 1; i < count; i++) + { + var key = keys[i]; + int j = i - 1; + while (j >= 0 && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + j--; + } + + keys[j + 1] = key; + } + + return; + } + + // array case + var keysArray = keys as T[]; + if (null != keysArray) + { + Array.Sort(keysArray, comparer); + return; + } + + // generic list case + var keysList = keys as List; + if (null != keysList) + { + keysList.Sort(comparer); + return; + } + + // local sort implementation + QuickSort(keys, comparer, 0, count - 1); + } + + /// + /// Sort a list of keys and items with respect to the keys, in place using the quick sort algorithm. + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// List to sort. + /// List to permute the same way as the key list. + /// Comparison, defining the sort order. + public static void Sort(IList keys, IList items, IComparer comparer = null) + { + int count = keys.Count; + if (count <= 1) + { + return; + } + + if (null == comparer) + { + comparer = Comparer.Default; + } + + if (count == 2) + { + if (comparer.Compare(keys[0], keys[1]) > 0) + { + Swap(keys, 0, 1); + Swap(items, 0, 1); + } + + return; + } + + // insertion sort + if (count <= 10) + { + for (int i = 1; i < count; i++) + { + var key = keys[i]; + var item = items[i]; + int j = i - 1; + while (j >= 0 && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + items[j + 1] = items[j]; + j--; + } + + keys[j + 1] = key; + items[j + 1] = item; + } + + return; + } + + // array case + var keysArray = keys as TKey[]; + var itemsArray = items as TItem[]; + if ((null != keysArray) && (null != itemsArray)) + { + Array.Sort(keysArray, itemsArray, comparer); + return; + } + + // local sort implementation + QuickSort(keys, items, comparer, 0, count - 1); + } + + /// + /// Sort a list of keys, items1 and items2 with respect to the keys, in place using the quick sort algorithm. + /// + /// The type of elements in the key list. + /// The type of elements in the first item list. + /// The type of elements in the second item list. + /// List to sort. + /// First list to permute the same way as the key list. + /// Second list to permute the same way as the key list. + /// Comparison, defining the sort order. + public static void Sort(IList keys, IList items1, IList items2, IComparer comparer = null) + { + int count = keys.Count; + if (count <= 1) + { + return; + } + + if (null == comparer) + { + comparer = Comparer.Default; + } + + if (count == 2) + { + if (comparer.Compare(keys[0], keys[1]) > 0) + { + Swap(keys, 0, 1); + Swap(items1, 0, 1); + Swap(items2, 0, 1); + } + + return; + } + + // insertion sort + if (count <= 10) + { + for (int i = 1; i < count; i++) + { + var key = keys[i]; + var item1 = items1[i]; + var item2 = items2[i]; + int j = i - 1; + while (j >= 0 && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + items1[j + 1] = items1[j]; + items2[j + 1] = items2[j]; + j--; + } + + keys[j + 1] = key; + items1[j + 1] = item1; + items2[j + 1] = item2; + } + + return; + } + + // local sort implementation + QuickSort(keys, items1, items2, comparer, 0, count - 1); + } + + /// + /// Sort a range of a list of keys, in place using the quick sort algorithm. + /// + /// The type of element in the list. + /// List to sort. + /// The zero-based starting index of the range to sort. + /// The length of the range to sort. + /// Comparison, defining the sort order. + public static void Sort(IList keys, int index, int count, IComparer comparer = null) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || index + count > keys.Count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (count <= 1) + { + return; + } + + if (null == comparer) + { + comparer = Comparer.Default; + } + + if (count == 2) + { + if (comparer.Compare(keys[index], keys[index + 1]) > 0) + { + Swap(keys, index, index + 1); + } + + return; + } + + // insertion sort + if (count <= 10) + { + int to = index + count; + for (int i = index + 1; i < to; i++) + { + var key = keys[i]; + int j = i - 1; + while (j >= index && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + j--; + } + + keys[j + 1] = key; + } + + return; + } + + // array case + var keysArray = keys as T[]; + if (null != keysArray) + { + Array.Sort(keysArray, index, count, comparer); + return; + } + + // generic list case + var keysList = keys as List; + if (null != keysList) + { + keysList.Sort(index, count, comparer); + return; + } + + // fall back: local sort implementation + QuickSort(keys, comparer, index, count - 1); + } + + /// + /// Sort a list of keys and items with respect to the keys, in place using the quick sort algorithm. + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// List to sort. + /// List to permute the same way as the key list. + /// The zero-based starting index of the range to sort. + /// The length of the range to sort. + /// Comparison, defining the sort order. + public static void Sort(IList keys, IList items, int index, int count, IComparer comparer = null) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || index + count > keys.Count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (count <= 1) + { + return; + } + + if (null == comparer) + { + comparer = Comparer.Default; + } + + if (count == 2) + { + if (comparer.Compare(keys[index], keys[index + 1]) > 0) + { + Swap(keys, index, index + 1); + Swap(items, index, index + 1); + } + + return; + } + + // insertion sort + if (count <= 10) + { + int to = index + count; + for (int i = index + 1; i < to; i++) + { + var key = keys[i]; + var item = items[i]; + int j = i - 1; + while (j >= index && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + items[j + 1] = items[j]; + j--; + } + + keys[j + 1] = key; + items[j + 1] = item; + } + + return; + } + + // array case + var keysArray = keys as TKey[]; + var itemsArray = items as TItem[]; + if ((null != keysArray) && (null != itemsArray)) + { + Array.Sort(keysArray, itemsArray, index, count, comparer); + return; + } + + // fall back: local sort implementation + QuickSort(keys, items, comparer, index, count - 1); + } + + /// + /// Sort a list of keys, items1 and items2 with respect to the keys, in place using the quick sort algorithm. + /// + /// The type of elements in the key list. + /// The type of elements in the first item list. + /// The type of elements in the second item list. + /// List to sort. + /// First list to permute the same way as the key list. + /// Second list to permute the same way as the key list. + /// The zero-based starting index of the range to sort. + /// The length of the range to sort. + /// Comparison, defining the sort order. + public static void Sort(IList keys, IList items1, IList items2, int index, int count, IComparer comparer = null) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0 || index + count > keys.Count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (count <= 1) + { + return; + } + + if (null == comparer) + { + comparer = Comparer.Default; + } + + if (count == 2) + { + if (comparer.Compare(keys[index], keys[index + 1]) > 0) + { + Swap(keys, index, index + 1); + Swap(items1, index, index + 1); + Swap(items2, index, index + 1); + } + + return; + } + + // insertion sort + if (count <= 10) + { + int to = index + count; + for (int i = index + 1; i < to; i++) + { + var key = keys[i]; + var item1 = items1[i]; + var item2 = items2[i]; + int j = i - 1; + while (j >= index && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + items1[j + 1] = items1[j]; + items2[j + 1] = items2[j]; + j--; + } + + keys[j + 1] = key; + items1[j + 1] = item1; + items2[j + 1] = item2; + } + + return; + } + + // fall back: local sort implementation + QuickSort(keys, items1, items2, comparer, index, count - 1); + } + + /// + /// Sort a list of keys and items with respect to the keys, in place using the quick sort algorithm. + /// + /// The type of elements in the primary list. + /// The type of elements in the secondary list. + /// List to sort. + /// List to sort on duplicate primary items, and permute the same way as the key list. + /// Comparison, defining the primary sort order. + /// Comparison, defining the secondary sort order. + public static void SortAll(IList primary, IList secondary, IComparer primaryComparer = null, IComparer secondaryComparer = null) + { + if (null == primaryComparer) + { + primaryComparer = Comparer.Default; + } + + if (null == secondaryComparer) + { + secondaryComparer = Comparer.Default; + } + + // local sort implementation + QuickSortAll(primary, secondary, primaryComparer, secondaryComparer, 0, primary.Count - 1); + } + + /// + /// Recursive implementation for an in place quick sort on a list. + /// + /// The type of the list on which the quick sort is performed. + /// The list which is sorted using quick sort. + /// The method with which to compare two elements of the quick sort. + /// The left boundary of the quick sort. + /// The right boundary of the quick sort. + static void QuickSort(IList keys, IComparer comparer, int left, int right) + { + do + { + // Pivoting + int a = left; + int b = right; + int p = a + ((b - a) >> 1); // midpoint + + if (comparer.Compare(keys[a], keys[p]) > 0) + { + Swap(keys, a, p); + } + + if (comparer.Compare(keys[a], keys[b]) > 0) + { + Swap(keys, a, b); + } + + if (comparer.Compare(keys[p], keys[b]) > 0) + { + Swap(keys, p, b); + } + + T pivot = keys[p]; + + // Hoare Partitioning + do + { + while (comparer.Compare(keys[a], pivot) < 0) + { + a++; + } + + while (comparer.Compare(pivot, keys[b]) < 0) + { + b--; + } + + if (a > b) + { + break; + } + + if (a < b) + { + Swap(keys, a, b); + } + + a++; + b--; + } while (a <= b); + + // In order to limit the recursion depth to log(n), we sort the + // shorter partition recursively and the longer partition iteratively. + if ((b - left) <= (right - a)) + { + if (left < b) + { + QuickSort(keys, comparer, left, b); + } + + left = a; + } + else + { + if (a < right) + { + QuickSort(keys, comparer, a, right); + } + + right = b; + } + } while (left < right); + } + + /// + /// Recursive implementation for an in place quick sort on a list while reordering one other list accordingly. + /// + /// The type of the list on which the quick sort is performed. + /// The type of the list which is automatically reordered accordingly. + /// The list which is sorted using quick sort. + /// The list which is automatically reordered accordingly. + /// The method with which to compare two elements of the quick sort. + /// The left boundary of the quick sort. + /// The right boundary of the quick sort. + static void QuickSort(IList keys, IList items, IComparer comparer, int left, int right) + { + do + { + // Pivoting + int a = left; + int b = right; + int p = a + ((b - a) >> 1); // midpoint + + if (comparer.Compare(keys[a], keys[p]) > 0) + { + Swap(keys, a, p); + Swap(items, a, p); + } + + if (comparer.Compare(keys[a], keys[b]) > 0) + { + Swap(keys, a, b); + Swap(items, a, b); + } + + if (comparer.Compare(keys[p], keys[b]) > 0) + { + Swap(keys, p, b); + Swap(items, p, b); + } + + T pivot = keys[p]; + + // Hoare Partitioning + do + { + while (comparer.Compare(keys[a], pivot) < 0) + { + a++; + } + + while (comparer.Compare(pivot, keys[b]) < 0) + { + b--; + } + + if (a > b) + { + break; + } + + if (a < b) + { + Swap(keys, a, b); + Swap(items, a, b); + } + + a++; + b--; + } while (a <= b); + + // In order to limit the recursion depth to log(n), we sort the + // shorter partition recursively and the longer partition iteratively. + if ((b - left) <= (right - a)) + { + if (left < b) + { + QuickSort(keys, items, comparer, left, b); + } + + left = a; + } + else + { + if (a < right) + { + QuickSort(keys, items, comparer, a, right); + } + + right = b; + } + } while (left < right); + } + + /// + /// Recursive implementation for an in place quick sort on one list while reordering two other lists accordingly. + /// + /// The type of the list on which the quick sort is performed. + /// The type of the first list which is automatically reordered accordingly. + /// The type of the second list which is automatically reordered accordingly. + /// The list which is sorted using quick sort. + /// The first list which is automatically reordered accordingly. + /// The second list which is automatically reordered accordingly. + /// The method with which to compare two elements of the quick sort. + /// The left boundary of the quick sort. + /// The right boundary of the quick sort. + static void QuickSort( + IList keys, IList items1, IList items2, + IComparer comparer, + int left, int right) + { + do + { + // Pivoting + int a = left; + int b = right; + int p = a + ((b - a) >> 1); // midpoint + + if (comparer.Compare(keys[a], keys[p]) > 0) + { + Swap(keys, a, p); + Swap(items1, a, p); + Swap(items2, a, p); + } + + if (comparer.Compare(keys[a], keys[b]) > 0) + { + Swap(keys, a, b); + Swap(items1, a, b); + Swap(items2, a, b); + } + + if (comparer.Compare(keys[p], keys[b]) > 0) + { + Swap(keys, p, b); + Swap(items1, p, b); + Swap(items2, p, b); + } + + T pivot = keys[p]; + + // Hoare Partitioning + do + { + while (comparer.Compare(keys[a], pivot) < 0) + { + a++; + } + + while (comparer.Compare(pivot, keys[b]) < 0) + { + b--; + } + + if (a > b) + { + break; + } + + if (a < b) + { + Swap(keys, a, b); + Swap(items1, a, b); + Swap(items2, a, b); + } + + a++; + b--; + } while (a <= b); + + // In order to limit the recursion depth to log(n), we sort the + // shorter partition recursively and the longer partition iteratively. + if ((b - left) <= (right - a)) + { + if (left < b) + { + QuickSort(keys, items1, items2, comparer, left, b); + } + + left = a; + } + else + { + if (a < right) + { + QuickSort(keys, items1, items2, comparer, a, right); + } + + right = b; + } + } while (left < right); + } + + /// + /// Recursive implementation for an in place quick sort on the primary and then by the secondary list while reordering one secondary list accordingly. + /// + /// The type of the primary list. + /// The type of the secondary list. + /// The list which is sorted using quick sort. + /// The list which is sorted secondarily (on primary duplicates) and automatically reordered accordingly. + /// The method with which to compare two elements of the primary list. + /// The method with which to compare two elements of the secondary list. + /// The left boundary of the quick sort. + /// The right boundary of the quick sort. + static void QuickSortAll( + IList primary, IList secondary, + IComparer primaryComparer, IComparer secondaryComparer, + int left, int right) + { + do + { + // Pivoting + int a = left; + int b = right; + int p = a + ((b - a) >> 1); // midpoint + + int ap = primaryComparer.Compare(primary[a], primary[p]); + if (ap > 0 || ap == 0 && secondaryComparer.Compare(secondary[a], secondary[p]) > 0) + { + Swap(primary, a, p); + Swap(secondary, a, p); + } + + int ab = primaryComparer.Compare(primary[a], primary[b]); + if (ab > 0 || ab == 0 && secondaryComparer.Compare(secondary[a], secondary[b]) > 0) + { + Swap(primary, a, b); + Swap(secondary, a, b); + } + + int pb = primaryComparer.Compare(primary[p], primary[b]); + if (pb > 0 || pb == 0 && secondaryComparer.Compare(secondary[p], secondary[b]) > 0) + { + Swap(primary, p, b); + Swap(secondary, p, b); + } + + T1 pivot1 = primary[p]; + T2 pivot2 = secondary[p]; + + // Hoare Partitioning + do + { + int ax; + while ((ax = primaryComparer.Compare(primary[a], pivot1)) < 0 || ax == 0 && secondaryComparer.Compare(secondary[a], pivot2) < 0) + { + a++; + } + + int xb; + while ((xb = primaryComparer.Compare(pivot1, primary[b])) < 0 || xb == 0 && secondaryComparer.Compare(pivot2, secondary[b]) < 0) + { + b--; + } + + if (a > b) + { + break; + } + + if (a < b) + { + Swap(primary, a, b); + Swap(secondary, a, b); + } + + a++; + b--; + } while (a <= b); + + // In order to limit the recursion depth to log(n), we sort the + // shorter partition recursively and the longer partition iteratively. + if ((b - left) <= (right - a)) + { + if (left < b) + { + QuickSortAll(primary, secondary, primaryComparer, secondaryComparer, left, b); + } + + left = a; + } + else + { + if (a < right) + { + QuickSortAll(primary, secondary, primaryComparer, secondaryComparer, a, right); + } + + right = b; + } + } while (left < right); + } + + /// + /// Performs an in place swap of two elements in a list. + /// + /// The type of elements stored in the list. + /// The list in which the elements are stored. + /// The index of the first element of the swap. + /// The index of the second element of the swap. + static void Swap(IList keys, int a, int b) + { + if (a == b) + { + return; + } + + T local = keys[a]; + keys[a] = keys[b]; + keys[b] = local; + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Airy.cs b/MathNet.Numerics/SpecialFunctions/Airy.cs new file mode 100644 index 0000000..1c354fd --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Airy.cs @@ -0,0 +1,185 @@ +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the Airy functions. +/// +public static partial class SpecialFunctions +{ + /// + /// Returns the Airy function Ai. + /// AiryAi(z) is a solution to the Airy equation, y'' - y * z = 0. + /// + /// The value to compute the Airy function of. + /// The Airy function Ai. + public static Complex AiryAi(Complex z) + { + return Amos.Cairy(z); + } + + /// + /// Returns the exponentially scaled Airy function Ai. + /// ScaledAiryAi(z) is given by Exp(zta) * AiryAi(z), where zta = (2/3) * z * Sqrt(z). + /// + /// The value to compute the Airy function of. + /// The exponentially scaled Airy function Ai. + public static Complex AiryAiScaled(Complex z) + { + return Amos.ScaledCairy(z); + } + + /// + /// Returns the Airy function Ai. + /// AiryAi(z) is a solution to the Airy equation, y'' - y * z = 0. + /// + /// The value to compute the Airy function of. + /// The Airy function Ai. + public static double AiryAi(double z) + { + return AiryAi(new Complex(z, 0)).Real; + } + + /// + /// Returns the exponentially scaled Airy function Ai. + /// ScaledAiryAi(z) is given by Exp(zta) * AiryAi(z), where zta = (2/3) * z * Sqrt(z). + /// + /// The value to compute the Airy function of. + /// The exponentially scaled Airy function Ai. + public static double AiryAiScaled(double z) + { + return Amos.ScaledCairy(z); + } + + /// + /// Returns the derivative of the Airy function Ai. + /// AiryAiPrime(z) is defined as d/dz AiryAi(z). + /// + /// The value to compute the derivative of the Airy function of. + /// The derivative of the Airy function Ai. + public static Complex AiryAiPrime(Complex z) + { + return Amos.CairyPrime(z); + } + + /// + /// Returns the exponentially scaled derivative of Airy function Ai + /// ScaledAiryAiPrime(z) is given by Exp(zta) * AiryAiPrime(z), where zta = (2/3) * z * Sqrt(z). + /// + /// The value to compute the derivative of the Airy function of. + /// The exponentially scaled derivative of Airy function Ai. + public static Complex AiryAiPrimeScaled(Complex z) + { + return Amos.ScaledCairyPrime(z); + } + + /// + /// Returns the derivative of the Airy function Ai. + /// AiryAiPrime(z) is defined as d/dz AiryAi(z). + /// + /// The value to compute the derivative of the Airy function of. + /// The derivative of the Airy function Ai. + public static double AiryAiPrime(double z) + { + return AiryAiPrime(new Complex(z, 0)).Real; + } + + /// + /// Returns the exponentially scaled derivative of the Airy function Ai. + /// ScaledAiryAiPrime(z) is given by Exp(zta) * AiryAiPrime(z), where zta = (2/3) * z * Sqrt(z). + /// + /// The value to compute the derivative of the Airy function of. + /// The exponentially scaled derivative of the Airy function Ai. + public static double AiryAiPrimeScaled(double z) + { + return Amos.ScaledCairyPrime(z); + } + + /// + /// Returns the Airy function Bi. + /// AiryBi(z) is a solution to the Airy equation, y'' - y * z = 0. + /// + /// The value to compute the Airy function of. + /// The Airy function Bi. + public static Complex AiryBi(Complex z) + { + return Amos.Cbiry(z); + } + + /// + /// Returns the exponentially scaled Airy function Bi. + /// ScaledAiryBi(z) is given by Exp(-Abs(zta.Real)) * AiryBi(z) where zta = (2 / 3) * z * Sqrt(z). + /// + /// The value to compute the Airy function of. + /// The exponentially scaled Airy function Bi(z). + public static Complex AiryBiScaled(Complex z) + { + return Amos.ScaledCbiry(z); + } + + /// + /// Returns the Airy function Bi. + /// AiryBi(z) is a solution to the Airy equation, y'' - y * z = 0. + /// + /// The value to compute the Airy function of. + /// The Airy function Bi. + public static double AiryBi(double z) + { + return AiryBi(new Complex(z, 0)).Real; + } + + /// + /// Returns the exponentially scaled Airy function Bi. + /// ScaledAiryBi(z) is given by Exp(-Abs(zta.Real)) * AiryBi(z) where zta = (2 / 3) * z * Sqrt(z). + /// + /// The value to compute the Airy function of. + /// The exponentially scaled Airy function Bi. + public static double AiryBiScaled(double z) + { + return AiryBiScaled(new Complex(z, 0)).Real; + } + + /// + /// Returns the derivative of the Airy function Bi. + /// AiryBiPrime(z) is defined as d/dz AiryBi(z). + /// + /// The value to compute the derivative of the Airy function of. + /// The derivative of the Airy function Bi. + public static Complex AiryBiPrime(Complex z) + { + return Amos.CbiryPrime(z); + } + + /// + /// Returns the exponentially scaled derivative of the Airy function Bi. + /// ScaledAiryBiPrime(z) is given by Exp(-Abs(zta.Real)) * AiryBiPrime(z) where zta = (2 / 3) * z * Sqrt(z). + /// + /// The value to compute the derivative of the Airy function of. + /// The exponentially scaled derivative of the Airy function Bi. + public static Complex AiryBiPrimeScaled(Complex z) + { + return Amos.ScaledCbiryPrime(z); + } + + /// + /// Returns the derivative of the Airy function Bi. + /// AiryBiPrime(z) is defined as d/dz AiryBi(z). + /// + /// The value to compute the derivative of the Airy function of. + /// The derivative of the Airy function Bi. + public static double AiryBiPrime(double z) + { + return AiryBiPrime(new Complex(z, 0)).Real; + } + + /// + /// Returns the exponentially scaled derivative of the Airy function Bi. + /// ScaledAiryBiPrime(z) is given by Exp(-Abs(zta.Real)) * AiryBiPrime(z) where zta = (2 / 3) * z * Sqrt(z). + /// + /// The value to compute the derivative of the Airy function of. + /// The exponentially scaled derivative of the Airy function Bi. + public static double AiryBiPrimeScaled(double z) + { + return AiryBiPrimeScaled(new Complex(z, 0)).Real; + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Amos/Amos.cs b/MathNet.Numerics/SpecialFunctions/Amos/Amos.cs new file mode 100644 index 0000000..fadc524 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Amos/Amos.cs @@ -0,0 +1,1026 @@ +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +// References: +// [1] https://github.com/scipy/scipy/blob/master/scipy/special/amos_wrappers.c +public static partial class SpecialFunctions +{ + private static class Amos + { + #region AiryAi + + // Returns Ai(z) + public static Complex Cairy(Complex z) + { + int id = 0; + int kode = 1; + int nz = 0; + int ierr = 0; + + double air = double.NaN; + double aii = double.NaN; + + AmosHelper.zairy(z.Real, z.Imaginary, id, kode, ref air, ref aii, ref nz, ref ierr); + return new Complex(air, aii); + } + + // Returns Exp(zta) * Ai(z) where zta = (2/3) * z * Sqrt(z) + public static Complex ScaledCairy(Complex z) + { + int id = 0; + int kode = 2; + int nz = 0; + int ierr = 0; + + double air = double.NaN; + double aii = double.NaN; + + AmosHelper.zairy(z.Real, z.Imaginary, id, kode, ref air, ref aii, ref nz, ref ierr); + return new Complex(air, aii); + } + + // Returns Exp(zta) * Ai(z) where zta = (2/3) * z * Sqrt(z) + public static double ScaledCairy(double z) + { + if (z < 0) + { + return double.NaN; + } + + int id = 0; + int kode = 2; + int nz = 0; + int ierr = 0; + + double air = double.NaN; + double aii = double.NaN; + + AmosHelper.zairy(z, 0.0, id, kode, ref air, ref aii, ref nz, ref ierr); + return air; + } + + // Returns d/dz Ai(z) + public static Complex CairyPrime(Complex z) + { + int id = 1; + int kode = 1; + int nz = 0; + int ierr = 0; + + double air = double.NaN; + double aii = double.NaN; + + AmosHelper.zairy(z.Real, z.Imaginary, id, kode, ref air, ref aii, ref nz, ref ierr); + return new Complex(air, aii); + } + + // Returns Exp(zta) * d/dz Ai(z) where zta = (2/3) * z * Sqrt(z) + public static Complex ScaledCairyPrime(Complex z) + { + int id = 1; + int kode = 2; + int nz = 0; + int ierr = 0; + + double air = double.NaN; + double aii = double.NaN; + + AmosHelper.zairy(z.Real, z.Imaginary, id, kode, ref air, ref aii, ref nz, ref ierr); + return new Complex(air, aii); + } + + // Returns Exp(zta) * d/dz Ai(z) where zta = (2/3) * z * Sqrt(z) + public static double ScaledCairyPrime(double z) + { + if (z < 0) + { + return double.NaN; + } + + int id = 1; + int kode = 2; + int nz = 0; + int ierr = 0; + + double air = double.NaN; + double aii = double.NaN; + + AmosHelper.zairy(z, 0.0, id, kode, ref air, ref aii, ref nz, ref ierr); + return air; + } + + #endregion + + #region AiryBi + + // Returns Bi(z) + public static Complex Cbiry(Complex z) + { + int id = 0; + int kode = 1; + int nz = 0; + int ierr = 0; + + double bir = double.NaN; + double bii = double.NaN; + + AmosHelper.zbiry(z.Real, z.Imaginary, id, kode, ref bir, ref bii, ref nz, ref ierr); + return new Complex(bir, bii); + } + + // Returns Exp(-axzta) * Bi(z) where zta = (2 / 3) * z * Sqrt(z) and axzta = Abs(zta.Real) + public static Complex ScaledCbiry(Complex z) + { + int id = 0; + int kode = 2; + int nz = 0; + int ierr = 0; + + double bir = double.NaN; + double bii = double.NaN; + + AmosHelper.zbiry(z.Real, z.Imaginary, id, kode, ref bir, ref bii, ref nz, ref ierr); + return new Complex(bir, bii); + } + + // Returns d/dz Bi(z) + public static Complex CbiryPrime(Complex z) + { + int id = 1; + int kode = 1; + int nz = 0; + int ierr = 0; + + double bipr = double.NaN; + double bipi = double.NaN; + + AmosHelper.zbiry(z.Real, z.Imaginary, id, kode, ref bipr, ref bipi, ref nz, ref ierr); + return new Complex(bipr, bipi); + } + + // Returns Exp(-axzta) * d/dz Bi(z) where zta = (2 / 3) * z * Sqrt(z) and axzta = Abs(zta.Real) + public static Complex ScaledCbiryPrime(Complex z) + { + int id = 1; + int kode = 2; + int nz = 0; + int ierr = 0; + + double bipr = double.NaN; + double bipi = double.NaN; + + AmosHelper.zbiry(z.Real, z.Imaginary, id, kode, ref bipr, ref bipi, ref nz, ref ierr); + return new Complex(bipr, bipi); + } + + #endregion + + #region BesselJ + + // Return J(v, z) + public static Complex Cbesj(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + int n = 1; + int kode = 1; + int nz = 0; + int ierr = 0; + double[] cyjr = new double[n]; + double[] cyji = new double[n]; + for (int i = 0; i < n; i++) + { + cyjr[i] = double.NaN; + cyji[i] = double.NaN; + } + + AmosHelper.zbesj(z.Real, z.Imaginary, v, kode, n, cyjr, cyji, ref nz, ref ierr); + Complex cyj = new Complex(cyjr[0], cyji[0]); + + if (ierr == 2) + { + //overflow + cyj = ScaledCbesj(v, z); + cyj = new Complex(cyj.Real * double.PositiveInfinity, cyj.Imaginary * double.PositiveInfinity); + } + + if (sign == -1) + { + if (!ReflectJY(ref cyj, v)) + { + double[] cyyr = new double[n]; + double[] cyyi = new double[n]; + double[] cwrkr = new double[n]; + double[] cwrki = new double[n]; + + for (int i = 0; i < n; i++) + { + cyyr[i] = double.NaN; + cyyi[i] = double.NaN; + cwrkr[i] = double.NaN; + cwrki[i] = double.NaN; + } + + AmosHelper.zbesy(z.Real, z.Imaginary, v, kode, n, cyyr, cyyi, ref nz, cwrkr, cwrki, ref ierr); + Complex cyy = new Complex(cyyr[0], cyyi[0]); + + cyj = RotateJY(cyj, cyy, v); + } + } + + return cyj; + } + + // Return J(v, z) + public static double Cbesj(double v, double z) + { + if (z < 0 && v != (int)v) + { + return double.NaN; + } + + return Cbesj(v, new Complex(z, 0)).Real; + } + + // Return Exp(-Abs(y)) * J(v, z) where y = z.Imaginary + public static Complex ScaledCbesj(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + int n = 1; + int kode = 2; + int nz = 0; + int ierr = 0; + + double[] cyjr = new double[n]; + double[] cyji = new double[n]; + for (int i = 0; i < n; i++) + { + cyjr[i] = double.NaN; + cyji[i] = double.NaN; + } + + AmosHelper.zbesj(z.Real, z.Imaginary, v, kode, n, cyjr, cyji, ref nz, ref ierr); + Complex cyj = new Complex(cyjr[0], cyji[0]); + + if (sign == -1) + { + if (!ReflectJY(ref cyj, v)) + { + double[] cyyr = new double[n]; + double[] cyyi = new double[n]; + double[] cworkr = new double[n]; + double[] cworki = new double[n]; + for (int i = 0; i < n; i++) + { + cyyr[i] = double.NaN; + cyyi[i] = double.NaN; + cworkr[i] = double.NaN; + cworki[i] = double.NaN; + } + + AmosHelper.zbesy(z.Real, z.Imaginary, v, kode, n, cyyr, cyyi, ref nz, cworkr, cworki, ref ierr); + Complex cyy = new Complex(cyyr[0], cyyi[0]); + + cyj = RotateJY(cyj, cyy, v); + } + } + + return cyj; + } + + // Return Exp(-Abs(y)) * J(v, z) where y = z.Imaginary + public static double ScaledCbesj(double v, double z) + { + if (z < 0 && v != (int)v) + { + return double.NaN; + } + + return ScaledCbesj(v, new Complex(z, 0)).Real; + } + + #endregion + + #region BesselY + + // Return Y(v, z) + public static Complex Cbesy(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + int n = 1; + int kode = 1; + int nz = 0; + int ierr = 0; + Complex cyy; + + if (z.Real == 0 && z.Imaginary == 0) + { + //overflow + cyy = new Complex(double.NegativeInfinity, 0); + } + else + { + double[] cyyr = new double[n]; + double[] cyyi = new double[n]; + double[] cworkr = new double[n]; + double[] cworki = new double[n]; + for (int i = 0; i < n; i++) + { + cyyr[i] = double.NaN; + cyyi[i] = double.NaN; + cworkr[i] = double.NaN; + cworki[i] = double.NaN; + } + + AmosHelper.zbesy(z.Real, z.Imaginary, v, kode, n, cyyr, cyyi, ref nz, cworkr, cworki, ref ierr); + cyy = new Complex(cyyr[0], cyyi[0]); + + if (ierr == 2) + { + if (z.Real >= 0 && z.Imaginary == 0) + { + //overflow + cyy = new Complex(double.NegativeInfinity, 0); + } + } + } + + if (sign == -1) + { + if (!ReflectJY(ref cyy, v)) + { + double[] cyjr = new double[n]; + double[] cyji = new double[n]; + for (int i = 0; i < n; i++) + { + cyjr[i] = double.NaN; + cyji[i] = double.NaN; + } + + AmosHelper.zbesj(z.Real, z.Imaginary, v, kode, n, cyjr, cyji, ref nz, ref ierr); + Complex cyj = new Complex(cyjr[0], cyji[0]); + + cyy = RotateJY(cyy, cyj, -v); + } + } + + return cyy; + } + + // Return Y(v, z) + public static double Cbesy(double v, double x) + { + if (x < 0.0) + { + return double.NaN; + } + + Complex z = new Complex(x, 0.0); + return Cbesy(v, z).Real; + } + + // Return Exp(-Abs(y)) * Y(v, z) where y = z.Imaginary + public static Complex ScaledCbesy(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + int n = 1; + int kode = 2; + int nz = 0; + int ierr = 0; + + double[] cyyr = new double[n]; + double[] cyyi = new double[n]; + double[] cwrkr = new double[n]; + double[] cwrki = new double[n]; + for (int i = 0; i < n; i++) + { + cyyr[i] = double.NaN; + cyyi[i] = double.NaN; + cwrkr[i] = double.NaN; + cwrki[i] = double.NaN; + } + + AmosHelper.zbesy(z.Real, z.Imaginary, v, kode, n, cyyr, cyyi, ref nz, cwrkr, cwrki, ref ierr); + Complex cyy = new Complex(cyyr[0], cyyi[0]); + + if (ierr == 2) + { + if (z.Real >= 0 && z.Imaginary == 0) + { + //overflow + cyy = new Complex(double.PositiveInfinity, 0); + } + } + + if (sign == -1) + { + if (!ReflectJY(ref cyy, v)) + { + double[] cyjr = new double[n]; + double[] cyji = new double[n]; + for (int i = 0; i < n; i++) + { + cyjr[i] = double.NaN; + cyji[i] = double.NaN; + } + + AmosHelper.zbesj(z.Real, z.Imaginary, v, kode, n, cyjr, cyji, ref nz, ref ierr); + Complex cyj = new Complex(cyjr[0], cyji[0]); + cyy = RotateJY(cyy, cyj, -v); + } + } + + return cyy; + } + + // Return Exp(-Abs(y)) * Y(v, z) where y = z.Imaginary + public static double ScaledCbesy(double v, double x) + { + if (x < 0) + { + return double.NaN; + } + + return ScaledCbesy(v, new Complex(x, 0)).Real; + } + + #endregion + + #region BesselI + + // Returns I(v, z) + public static Complex Cbesi(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + int n = 1; + int kode = 1; + int nz = 0; + int ierr = 0; + + double[] cyir = new double[n]; + double[] cyii = new double[n]; + for (int i = 0; i < n; i++) + { + cyir[i] = double.NaN; + cyii[i] = double.NaN; + } + + AmosHelper.zbesi(z.Real, z.Imaginary, v, kode, n, cyir, cyii, ref nz, ref ierr); + Complex cyi = new Complex(cyir[0], cyii[0]); + + if (ierr == 2) + { + //overflow + if (z.Imaginary == 0 && (z.Real >= 0 || v == Math.Floor(v))) + { + if (z.Real < 0 && v / 2 != Math.Floor(v / 2)) + cyi = new Complex(double.NegativeInfinity, 0); + else + cyi = new Complex(double.PositiveInfinity, 0); + } + else + { + cyi = ScaledCbesi(v * sign, z); + cyi = new Complex(cyi.Real * double.PositiveInfinity, cyi.Imaginary * double.PositiveInfinity); + } + } + + if (sign == -1) + { + if (!ReflectI(cyi, v)) + { + double[] cykr = new double[n]; + double[] cyki = new double[n]; + AmosHelper.zbesk(z.Real, z.Imaginary, v, kode, n, cykr, cyki, ref nz, ref ierr); + Complex cyk = new Complex(cykr[0], cyki[0]); + + cyi = RotateI(cyi, cyk, v); + } + } + + return cyi; + } + + // Return Exp(-Abs(x)) * I(v, z) where x = z.Real + public static Complex ScaledCbesi(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + int n = 1; + int kode = 2; + int nz = 0; + int ierr = 0; + + double[] cyir = new double[n]; + double[] cyii = new double[n]; + for (int i = 0; i < n; i++) + { + cyir[i] = double.NaN; + cyii[i] = double.NaN; + } + + AmosHelper.zbesi(z.Real, z.Imaginary, v, kode, n, cyir, cyii, ref nz, ref ierr); + Complex cyi = new Complex(cyir[0], cyii[0]); + + if (sign == -1) + { + if (!ReflectI(cyi, v)) + { + double[] cykr = new double[n]; + double[] cyki = new double[n]; + AmosHelper.zbesk(z.Real, z.Imaginary, v, kode, n, cykr, cyki, ref nz, ref ierr); + Complex cyk = new Complex(cykr[0], cyki[0]); + + //adjust scaling to match zbesi + cyk = Rotate(cyk, -z.Imaginary / Math.PI); + if (z.Real > 0) + { + cyk = new Complex(cyk.Real * Math.Exp(-2 * z.Real), cyk.Imaginary * Math.Exp(-2 * z.Real)); + } + //v -> -v + cyi = RotateI(cyi, cyk, v); + } + } + + return cyi; + } + + // Return Exp(-Abs(x)) * I(v, z) where x = z.Real + public static double ScaledCbesi(double v, double x) + { + if (v != Math.Floor(v) && x < 0) + { + return double.NaN; + } + + return ScaledCbesi(v, new Complex(x, 0)).Real; + } + + #endregion + + #region BesselK + + // Returns K(v, z) + public static Complex Cbesk(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + if (v < 0) + { + //K_v == K_{-v} even for non-integer v + v = -v; + } + + int n = 1; + int kode = 1; + int nz = 0; + int ierr = 0; + + double[] cykr = new double[n]; + double[] cyki = new double[n]; + for (int i = 0; i < n; i++) + { + cykr[i] = double.NaN; + cyki[i] = double.NaN; + } + + AmosHelper.zbesk(z.Real, z.Imaginary, v, kode, n, cykr, cyki, ref nz, ref ierr); + Complex cyk = new Complex(cykr[0], cyki[0]); + + if (ierr == 1) + { + if (z.Real == 0.0 && z.Imaginary == 0.0) + { + cyk = new Complex(double.PositiveInfinity, 0); + } + } + else if (ierr == 2) + { + if (z.Real >= 0 && z.Imaginary == 0) + { + //overflow + cyk = new Complex(double.PositiveInfinity, 0); + } + } + + return cyk; + } + + // Returns K(v, z) + public static double Cbesk(double v, double z) + { + if (z < 0) + { + return double.NaN; + } + else if (z == 0) + { + return double.PositiveInfinity; + } + else if (z > 710 * (1 + Math.Abs(v))) + { + // Underflow. See uniform expansion https://dlmf.nist.gov/10.41 + // This condition is not a strict bound (it can underflow earlier), + // rather, we are here working around a restriction in AMOS. + + return 0; + } + else + { + Complex w = new Complex(z, 0); + return Cbesk(v, w).Real; + } + } + + // returns Exp(z) * K(v, z) + public static Complex ScaledCbesk(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + if (v < 0) + { + //K_v == K_{-v} even for non-integer v + v = -v; + } + + int n = 1; + int kode = 2; + int nz = 0; + int ierr = 0; + + double[] cykr = new double[n]; + double[] cyki = new double[n]; + for (int i = 0; i < n; i++) + { + cykr[i] = double.NaN; + cyki[i] = double.NaN; + } + + AmosHelper.zbesk(z.Real, z.Imaginary, v, kode, n, cykr, cyki, ref nz, ref ierr); + Complex cyk = new Complex(cykr[0], cyki[0]); + + if (ierr == 2) + { + if (z.Real >= 0 && z.Imaginary == 0) + { + //overflow + cyk = new Complex(double.PositiveInfinity, 0); + } + } + + return cyk; + } + + // returns Exp(z) * K(v, z) + public static double ScaledCbesk(double v, double z) + { + if (z < 0) + { + return double.NaN; + } + else if (z == 0) + { + return double.PositiveInfinity; + } + else + { + Complex w = new Complex(z, 0); + return ScaledCbesk(v, w).Real; + } + } + + #endregion + + #region HankelH1 + + // Returns H1(v, z) + public static Complex Cbesh1(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + ; + } + + int n = 1; + int kode = 1; + int m = 1; + int nz = 0; + int ierr = 0; + + double[] cyhr = new double[n]; + double[] cyhi = new double[n]; + for (int i = 0; i < n; i++) + { + cyhr[i] = double.NaN; + cyhi[i] = double.NaN; + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + AmosHelper.zbesh(z.Real, z.Imaginary, v, kode, m, n, cyhr, cyhi, ref nz, ref ierr); + Complex cyh = new Complex(cyhr[0], cyhi[0]); + + if (sign == -1) + { + cyh = Rotate(cyh, v); + } + + return cyh; + } + + // Returns Exp(-z * j) * H1(n, z) where j = Sqrt(-1) + public static Complex ScaledCbesh1(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + ; + } + + int n = 1; + int kode = 2; + int m = 1; + int nz = 0; + int ierr = 0; + + double[] cyhr = new double[n]; + double[] cyhi = new double[n]; + for (int i = 0; i < n; i++) + { + cyhr[i] = double.NaN; + cyhi[i] = double.NaN; + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + AmosHelper.zbesh(z.Real, z.Imaginary, v, kode, m, n, cyhr, cyhi, ref nz, ref ierr); + Complex cyh = new Complex(cyhr[0], cyhi[0]); + + if (sign == -1) + { + cyh = Rotate(cyh, v); + } + + return cyh; + } + + #endregion + + #region HankelH2 + + // Returns H2(v, z) + public static Complex Cbesh2(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + if (v == 0 && z.Real == 0 && z.Imaginary == 0) + { + return new Complex(double.NaN, double.NaN); // ComplexInfinity + } + + int n = 1; + int kode = 1; + int m = 2; + int nz = 0; + int ierr = 0; + + double[] cyhr = new double[n]; + double[] cyhi = new double[n]; + for (int i = 0; i < n; i++) + { + cyhr[i] = double.NaN; + cyhi[i] = double.NaN; + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + AmosHelper.zbesh(z.Real, z.Imaginary, v, kode, m, n, cyhr, cyhi, ref nz, ref ierr); + Complex cyh = new Complex(cyhr[0], cyhi[0]); + + if (sign == -1) + { + cyh = Rotate(cyh, -v); + } + + return cyh; + } + + // Returns Exp(z * j) * H2(n, z) where j = Sqrt(-1) + public static Complex ScaledCbesh2(double v, Complex z) + { + if (double.IsNaN(v) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + if (v == 0 && z.Real == 0 && z.Imaginary == 0) + { + return new Complex(double.NaN, double.NaN); // ComplexInfinity + } + + int n = 1; + int kode = 2; + int m = 2; + int nz = 0; + int ierr = 0; + + double[] cyhr = new double[n]; + double[] cyhi = new double[n]; + for (int i = 0; i < n; i++) + { + cyhr[i] = double.NaN; + cyhi[i] = double.NaN; + } + + int sign = 1; + if (v < 0) + { + v = -v; + sign = -1; + } + + AmosHelper.zbesh(z.Real, z.Imaginary, v, kode, m, n, cyhr, cyhi, ref nz, ref ierr); + Complex cyh = new Complex(cyhr[0], cyhi[0]); + + if (sign == -1) + { + cyh = Rotate(cyh, -v); + } + + return cyh; + } + + #endregion + + #region utilities + + private static double SinPi(double x) + { + if (Math.Floor(x) == x && Math.Abs(x) < 1.0e14) + { + //Return 0 when at exact zero, as long as the floating point number is + //small enough to distinguish integer points from other points. + + return 0; + } + + return Math.Sin(Math.PI * x); + } + + private static double CosPi(double x) + { + if (Math.Floor(x + 0.5) == x + 0.5 && Math.Abs(x) < 1.0E14) + { + //Return 0 when at exact zero, as long as the floating point number is + //small enough to distinguish integer points from other points. + + return 0; + } + + return Math.Cos(Math.PI * x); + } + + private static Complex Rotate(Complex z, double v) + { + double c = CosPi(v); + double s = SinPi(v); + return new Complex(z.Real * c - z.Imaginary * s, z.Real * s + z.Imaginary * c); + } + + private static Complex RotateJY(Complex j, Complex y, double v) + { + double c = CosPi(v); + double s = SinPi(v); + return new Complex(j.Real * c - y.Real * s, j.Imaginary * c - y.Imaginary * s); + } + + private static bool ReflectJY(ref Complex jy, double v) + { + //NB: Y_v may be huge near negative integers -- so handle exact + // integers carefully + + if (v != Math.Floor(v)) + { + return false; + } + + int i = (int)(v - 16384.0 * Math.Floor(v / 16384.0)); + if (i % 2 == 1) + { + jy = new Complex(-jy.Real, -jy.Imaginary); + } + + return true; + } + + private static bool ReflectI(Complex ik, double v) + { + if (v != Math.Floor(v)) + { + return false; + } + + return true; //I is symmetric for integer v + } + + private static Complex RotateI(Complex i, Complex k, double v) + { + double s = Math.Sin(v * Math.PI) * (2.0 / Math.PI); + return new Complex(i.Real + s * k.Real, i.Imaginary + s * k.Imaginary); + } + + #endregion + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Amos/AmosHelper.cs b/MathNet.Numerics/SpecialFunctions/Amos/AmosHelper.cs new file mode 100644 index 0000000..a95e32a --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Amos/AmosHelper.cs @@ -0,0 +1,8320 @@ +using System; + +namespace MathNet.Numerics; + +public static partial class SpecialFunctions +{ + // Translated from AMOS fortran codes by hand + // + // References: + // [1] Amos package in netlib. http://www.netlib.org/amos + private static class AmosHelper + { + #region Bessel- related functions + + // The Airy function Ai(z) and derivative + public static int zairy(double zr, double zi, int id, int kode, ref double air, ref double aii, ref int nz, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE ZAIRY + //***DATE WRITTEN 830501 (YYMMDD) + //***REVISION DATE 890801 (YYMMDD) + //***CATEGORY NO. B5K + //***KEYWORDS AIRY FUNCTION,BESSEL FUNCTIONS OF ORDER ONE THIRD + //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES + //***PURPOSE TO COMPUTE AIRY FUNCTIONS AI(Z) AND DAI(Z) FOR COMPLEX Z + //***DESCRIPTION + // + // ***A DOUBLE PRECISION ROUTINE*** + // ON KODE=1, ZAIRY COMPUTES THE COMPLEX AIRY FUNCTION AI(Z) OR + // ITS DERIVATIVE DAI(Z)/DZ ON ID=0 OR ID=1 RESPECTIVELY. ON + // KODE=2, A SCALING OPTION CEXP(ZTA)*AI(Z) OR CEXP(ZTA)* + // DAI(Z)/DZ IS PROVIDED TO REMOVE THE EXPONENTIAL DECAY IN + // -PI/3.LT.ARG(Z).LT.PI/3 AND THE EXPONENTIAL GROWTH IN + // PI/3.LT.ABS(ARG(Z)).LT.PI WHERE ZTA=(2/3)*Z*CSQRT(Z). + // + // WHILE THE AIRY FUNCTIONS AI(Z) AND DAI(Z)/DZ ARE ANALYTIC IN + // THE WHOLE Z PLANE, THE CORRESPONDING SCALED FUNCTIONS DEFINED + // FOR KODE=2 HAVE A CUT ALONG THE NEGATIVE REAL AXIS. + // DEFINTIONS AND NOTATION ARE FOUND IN THE NBS HANDBOOK OF + // MATHEMATICAL FUNCTIONS (REF. 1). + // + // INPUT ZR,ZI ARE DOUBLE PRECISION + // ZR,ZI - Z=CMPLX(ZR,ZI) + // ID - ORDER OF DERIVATIVE, ID=0 OR ID=1 + // KODE - A PARAMETER TO INDICATE THE SCALING OPTION + // KODE= 1 RETURNS + // AI=AI(Z) ON ID=0 OR + // AI=DAI(Z)/DZ ON ID=1 + // = 2 RETURNS + // AI=CEXP(ZTA)*AI(Z) ON ID=0 OR + // AI=CEXP(ZTA)*DAI(Z)/DZ ON ID=1 WHERE + // ZTA=(2/3)*Z*CSQRT(Z) + // + // OUTPUT AIR,AII ARE DOUBLE PRECISION + // AIR,AII- COMPLEX ANSWER DEPENDING ON THE CHOICES FOR ID AND + // KODE + // NZ - UNDERFLOW INDICATOR + // NZ= 0 , NORMAL RETURN + // NZ= 1 , AI=CMPLX(0.0D0,0.0D0) DUE TO UNDERFLOW IN + // -PI/3.LT.ARG(Z).LT.PI/3 ON KODE=1 + // IERR - ERROR FLAG + // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED + // IERR=1, INPUT ERROR - NO COMPUTATION + // IERR=2, OVERFLOW - NO COMPUTATION, REAL(ZTA) + // TOO LARGE ON KODE=1 + // IERR=3, CABS(Z) LARGE - COMPUTATION COMPLETED + // LOSSES OF SIGNIFCANCE BY ARGUMENT REDUCTION + // PRODUCE LESS THAN HALF OF MACHINE ACCURACY + // IERR=4, CABS(Z) TOO LARGE - NO COMPUTATION + // COMPLETE LOSS OF ACCURACY BY ARGUMENT + // REDUCTION + // IERR=5, ERROR - NO COMPUTATION, + // ALGORITHM TERMINATION CONDITION NOT MET + // + //***LONG DESCRIPTION + // + // AI AND DAI ARE COMPUTED FOR CABS(Z).GT.1.0 FROM THE K BESSEL + // FUNCTIONS BY + // + // AI(Z)=C*SQRT(Z)*K(1/3,ZTA) , DAI(Z)=-C*Z*K(2/3,ZTA) + // C=1.0/(PI*SQRT(3.0)) + // ZTA=(2/3)*Z**(3/2) + // + // WITH THE POWER SERIES FOR CABS(Z).LE.1.0. + // + // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- + // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z IS LARGE, LOSSES + // OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. CONSEQUENTLY, IF + // THE MAGNITUDE OF ZETA=(2/3)*Z**1.5 EXCEEDS U1=SQRT(0.5/UR), + // THEN LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR + // FLAG IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS + // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. + // ALSO, IF THE MAGNITUDE OF ZETA IS LARGER THAN U2=0.5/UR, THEN + // ALL SIGNIFICANCE IS LOST AND IERR=4. IN ORDER TO USE THE INT + // FUNCTION, ZETA MUST BE FURTHER RESTRICTED NOT TO EXCEED THE + // LARGEST INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF ZETA + // MUST BE RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, + // AND U3 ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE + // PRECISION ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE + // PRECISION ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMIT- + // ING IN THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT THE MAG- + // NITUDE OF Z CANNOT EXCEED 3.1E+4 IN SINGLE AND 2.1E+6 IN + // DOUBLE PRECISION ARITHMETIC. THIS ALSO MEANS THAT ONE CAN + // EXPECT TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, + // NO DIGITS IN SINGLE PRECISION AND ONLY 7 DIGITS IN DOUBLE + // PRECISION ARITHMETIC. SIMILAR CONSIDERATIONS HOLD FOR OTHER + // MACHINES. + // + // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX + // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT + // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- + // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE + // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), + // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF + // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY + // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN + // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY + // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER + // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, + // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS + // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER + // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY + // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER + // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE + // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, + // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, + // OR -PI/2+P. + // + //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ + // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF + // COMMERCE, 1955. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 + // + // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- + // 1018, MAY, 1985 + // + // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. + // MATH. SOFTWARE, 1986 + // + //***ROUTINES CALLED ZACAI,ZBKNU,ZEXP,ZSQRT,I1MACH,D1MACH + //***END PROLOGUE ZAIRY + + #endregion + + const double tth = 0.666666666666666667; + const double c1 = 0.35502805388781724; + const double c2 = 0.258819403792806799; + const double coef = 0.183776298473930683; + const double zeror = 0.0; + const double zeroi = 0.0; + const double coner = 1.0; + const double conei = 0.0; + + double aa, ad, ak, alim, atrm, az, az3, bk; + double cc, ck, csqi = 0, csqr = 0, dig; + double dk, d1, d2, elim, fid, fnu, ptr, rl, r1m5, sfac, sti = 0, str = 0; + double s1i, s1r, s2i, s2r, tol, trm1i, trm1r, trm2i, trm2r; + double ztai, ztar, z3i, z3r, alaz, bb; + int iflag, k, k1, k2, mr, nn = 0; + + double[] cyi = new double[1]; + double[] cyr = new double[1]; + + air = 0; + aii = 0; + ierr = 0; + nz = 0; + if (id < 0 || id > 1) + ierr = 1; + if (kode < 1 || kode > 2) + ierr = 1; + if (ierr != 0) + return 0; + az = zabs(zr, zi); + tol = Math.Max(d1mach(4), 1.0E-18); + fid = (double)id; + if (az > 1.0) + goto L70; + // ----------------------------------------------------------------------- + // POWER SERIES FOR ABS(Z).LE.1. + // ----------------------------------------------------------------------- + s1r = coner; + s1i = conei; + s2r = coner; + s2i = conei; + if (az < tol) + goto L170; + aa = az * az; + if (aa < tol / az) + goto L40; + trm1r = coner; + trm1i = conei; + trm2r = coner; + trm2i = conei; + atrm = 1.0; + str = zr * zr - zi * zi; + sti = zr * zi + zi * zr; + z3r = str * zr - sti * zi; + z3i = str * zi + sti * zr; + az3 = az * aa; + ak = 2.0 + fid; + bk = 3.0 - fid - fid; + ck = 4.0 - fid; + dk = 3.0 + fid + fid; + d1 = ak * dk; + d2 = bk * ck; + ad = Math.Min(d1, d2); + ak = 24.0 + 9.0 * fid; + bk = 30.0 - 9.0 * fid; + for (k = 1; k <= 25; k++) + { + str = (trm1r * z3r - trm1i * z3i) / d1; + trm1i = (trm1r * z3i + trm1i * z3r) / d1; + trm1r = str; + s1r += trm1r; + s1i += trm1i; + str = (trm2r * z3r - trm2i * z3i) / d2; + trm2i = (trm2r * z3i + trm2i * z3r) / d2; + trm2r = str; + s2r += trm2r; + s2i += trm2i; + atrm = atrm * az3 / ad; + d1 += ak; + d2 += bk; + ad = Math.Min(d1, d2); + if (atrm < tol * ad) + goto L40; + ak += 18.0; + bk += 18.0; + } + + L40: + if (id == 1) + goto L50; + air = s1r * c1 - c2 * (zr * s2r - zi * s2i); + aii = s1i * c1 - c2 * (zr * s2i + zi * s2r); + if (kode == 1) + return 0; + zsqrt(zr, zi, ref str, ref sti); + ztar = tth * (zr * str - zi * sti); + ztai = tth * (zr * sti + zi * str); + zexp(ztar, ztai, ref str, ref sti); + ptr = air * str - aii * sti; + aii = air * sti + aii * str; + air = ptr; + return 0; + L50: + air = -s2r * c2; + aii = -s2i * c2; + if (az <= tol) + goto L60; + str = zr * s1r - zi * s1i; + sti = zr * s1i + zi * s1r; + cc = c1 / (fid + 1.0); + air += cc * (str * zr - sti * zi); + aii += cc * (str * zi + sti * zr); + L60: + if (kode == 1) + return 0; + zsqrt(zr, zi, ref str, ref sti); + ztar = tth * (zr * str - zi * sti); + ztai = tth * (zr * sti + zi * str); + zexp(ztar, ztai, ref str, ref sti); + ptr = str * air - sti * aii; + aii = str * aii + sti * air; + air = ptr; + return 0; + // ----------------------------------------------------------------------- + // CASE FOR ABS(Z).GT.1.0 + // ----------------------------------------------------------------------- + L70: + fnu = (1.0 + fid) / 3.0; + // ----------------------------------------------------------------------- + // SET PARAMETERS RELATED TO MACHINE CONSTANTS. + // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0D-18. + // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. + // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND + // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR + // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. + // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. + // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). + // ----------------------------------------------------------------------- + k1 = i1mach(15); + k2 = i1mach(16); + r1m5 = d1mach(5); + k = Math.Min(Math.Abs(k1), Math.Abs(k2)); + elim = (k * r1m5 - 3.0) * 2.303; + k1 = i1mach(14) - 1; + aa = r1m5 * k1; + dig = Math.Min(aa, 18.0); + aa *= 2.303; + alim = elim + Math.Max(-aa, -41.45); + rl = 1.2 * dig + 3.0; + alaz = Math.Log(az); + // ----------------------------------------------------------------------- + // TEST FOR PROPER RANGE + // ----------------------------------------------------------------------- + aa = 0.5 / tol; + bb = i1mach(9) * 0.5; + aa = Math.Min(aa, bb); + aa = Math.Pow(aa, tth); + if (az > aa) + goto L260; + aa = Math.Sqrt(aa); + if (az > aa) + ierr = 3; + zsqrt(zr, zi, ref csqr, ref csqi); + ztar = tth * (zr * csqr - zi * csqi); + ztai = tth * (zr * csqi + zi * csqr); + // ----------------------------------------------------------------------- + // RE(ZTA).LE.0 WHEN RE(Z).LT.0, ESPECIALLY WHEN IM(Z) IS SMALL + // ----------------------------------------------------------------------- + iflag = 0; + sfac = 1.0; + ak = ztai; + if (zr >= 0.0) + goto L80; + bk = ztar; + ck = -Math.Abs(bk); + ztar = ck; + ztai = ak; + L80: + if (zi != 0.0) + goto L90; + if (zr > 0.0) + goto L90; + ztar = 0.0; + ztai = ak; + L90: + aa = ztar; + if (aa >= 0.0 && zr > 0.0) + goto L110; + if (kode == 2) + goto L100; + // ----------------------------------------------------------------------- + // OVERFLOW TEST + // ----------------------------------------------------------------------- + if (aa > -alim) + goto L100; + aa = -aa + alaz * 0.25; + iflag = 1; + sfac = tol; + if (aa > elim) + goto L270; + L100: + // ----------------------------------------------------------------------- + // CBKNU AND CACON RETURN EXP(ZTA)*K(FNU,ZTA) ON KODE=2 + // ----------------------------------------------------------------------- + mr = 1; + if (zi < 0.0) + mr = -1; + zacai(ztar, ztai, fnu, kode, mr, 1, cyr, cyi, ref nn, rl, tol, elim, alim); + if (nn < 0) + goto L280; + nz += nn; + goto L130; + L110: + if (kode == 2) + goto L120; + // ----------------------------------------------------------------------- + // UNDERFLOW TEST + // ----------------------------------------------------------------------- + if (aa < alim) + goto L120; + aa = -aa - 0.25 * alaz; + iflag = 2; + sfac = 1.0 / tol; + if (aa < -elim) + goto L210; + L120: + zbknu(ztar, ztai, fnu, kode, 1, cyr, cyi, ref nz, tol, elim, alim); + L130: + s1r = cyr[0] * coef; + s1i = cyi[0] * coef; + if (iflag != 0) + goto L150; + if (id == 1) + goto L140; + air = csqr * s1r - csqi * s1i; + aii = csqr * s1i + csqi * s1r; + return 0; + L140: + air = -(zr * s1r - zi * s1i); + aii = -(zr * s1i + zi * s1r); + return 0; + L150: + s1r *= sfac; + s1i *= sfac; + if (id == 1) + goto L160; + str = s1r * csqr - s1i * csqi; + s1i = s1r * csqi + s1i * csqr; + s1r = str; + air = s1r / sfac; + aii = s1i / sfac; + return 0; + L160: + str = -(s1r * zr - s1i * zi); + s1i = -(s1r * zi + s1i * zr); + s1r = str; + air = s1r / sfac; + aii = s1i / sfac; + return 0; + L170: + aa = 1.0E3 * d1mach(1); + s1r = zeror; + s1i = zeroi; + if (id == 1) + goto L190; + if (az <= aa) + goto L180; + s1r = c2 * zr; + s1i = c2 * zi; + L180: + air = c1 - s1r; + aii = -s1i; + return 0; + L190: + air = -c2; + aii = 0.0; + aa = Math.Sqrt(aa); + if (az <= aa) + goto L200; + s1r = (zr * zr - zi * zi) * 0.5; + s1i = zr * zi; + L200: + air += c1 * s1r; + aii += c1 * s1i; + return 0; + L210: + nz = 1; + air = zeror; + aii = zeroi; + return 0; + L270: + nz = 0; + ierr = 2; + return 0; + L280: + if (nn == -1) + goto L270; + nz = 0; + ierr = 5; + return 0; + L260: + ierr = 4; + nz = 0; + return 0; + } + + // The Airy function Bi(z) and derivative + public static int zbiry(double zr, double zi, int id, int kode, ref double bir, ref double bii, ref int nz, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE ZBIRY + //***DATE WRITTEN 830501 (YYMMDD) + //***REVISION DATE 890801 (YYMMDD) + //***CATEGORY NO. B5K + //***KEYWORDS AIRY FUNCTION,BESSEL FUNCTIONS OF ORDER ONE THIRD + //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES + //***PURPOSE TO COMPUTE AIRY FUNCTIONS BI(Z) AND DBI(Z) FOR COMPLEX Z + //***DESCRIPTION + // + // ***A DOUBLE PRECISION ROUTINE*** + // ON KODE=1, CBIRY COMPUTES THE COMPLEX AIRY FUNCTION BI(Z) OR + // ITS DERIVATIVE DBI(Z)/DZ ON ID=0 OR ID=1 RESPECTIVELY. ON + // KODE=2, A SCALING OPTION CEXP(-AXZTA)*BI(Z) OR CEXP(-AXZTA)* + // DBI(Z)/DZ IS PROVIDED TO REMOVE THE EXPONENTIAL BEHAVIOR IN + // BOTH THE LEFT AND RIGHT HALF PLANES WHERE + // ZTA=(2/3)*Z*CSQRT(Z)=CMPLX(XZTA,YZTA) AND AXZTA=ABS(XZTA). + // DEFINTIONS AND NOTATION ARE FOUND IN THE NBS HANDBOOK OF + // MATHEMATICAL FUNCTIONS (REF. 1). + // + // INPUT ZR,ZI ARE DOUBLE PRECISION + // ZR,ZI - Z=CMPLX(ZR,ZI) + // ID - ORDER OF DERIVATIVE, ID=0 OR ID=1 + // KODE - A PARAMETER TO INDICATE THE SCALING OPTION + // KODE= 1 RETURNS + // BI=BI(Z) ON ID=0 OR + // BI=DBI(Z)/DZ ON ID=1 + // = 2 RETURNS + // BI=CEXP(-AXZTA)*BI(Z) ON ID=0 OR + // BI=CEXP(-AXZTA)*DBI(Z)/DZ ON ID=1 WHERE + // ZTA=(2/3)*Z*CSQRT(Z)=CMPLX(XZTA,YZTA) + // AND AXZTA=ABS(XZTA) + // + // OUTPUT BIR,BII ARE DOUBLE PRECISION + // BIR,BII- COMPLEX ANSWER DEPENDING ON THE CHOICES FOR ID AND + // KODE + // IERR - ERROR FLAG + // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED + // IERR=1, INPUT ERROR - NO COMPUTATION + // IERR=2, OVERFLOW - NO COMPUTATION, REAL(Z) + // TOO LARGE ON KODE=1 + // IERR=3, CABS(Z) LARGE - COMPUTATION COMPLETED + // LOSSES OF SIGNIFCANCE BY ARGUMENT REDUCTION + // PRODUCE LESS THAN HALF OF MACHINE ACCURACY + // IERR=4, CABS(Z) TOO LARGE - NO COMPUTATION + // COMPLETE LOSS OF ACCURACY BY ARGUMENT + // REDUCTION + // IERR=5, ERROR - NO COMPUTATION, + // ALGORITHM TERMINATION CONDITION NOT MET + // + //***LONG DESCRIPTION + // + // BI AND DBI ARE COMPUTED FOR CABS(Z).GT.1.0 FROM THE I BESSEL + // FUNCTIONS BY + // + // BI(Z)=C*SQRT(Z)*( I(-1/3,ZTA) + I(1/3,ZTA) ) + // DBI(Z)=C * Z * ( I(-2/3,ZTA) + I(2/3,ZTA) ) + // C=1.0/SQRT(3.0) + // ZTA=(2/3)*Z**(3/2) + // + // WITH THE POWER SERIES FOR CABS(Z).LE.1.0. + // + // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- + // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z IS LARGE, LOSSES + // OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. CONSEQUENTLY, IF + // THE MAGNITUDE OF ZETA=(2/3)*Z**1.5 EXCEEDS U1=SQRT(0.5/UR), + // THEN LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR + // FLAG IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS + // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. + // ALSO, IF THE MAGNITUDE OF ZETA IS LARGER THAN U2=0.5/UR, THEN + // ALL SIGNIFICANCE IS LOST AND IERR=4. IN ORDER TO USE THE INT + // FUNCTION, ZETA MUST BE FURTHER RESTRICTED NOT TO EXCEED THE + // LARGEST INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF ZETA + // MUST BE RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, + // AND U3 ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE + // PRECISION ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE + // PRECISION ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMIT- + // ING IN THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT THE MAG- + // NITUDE OF Z CANNOT EXCEED 3.1E+4 IN SINGLE AND 2.1E+6 IN + // DOUBLE PRECISION ARITHMETIC. THIS ALSO MEANS THAT ONE CAN + // EXPECT TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, + // NO DIGITS IN SINGLE PRECISION AND ONLY 7 DIGITS IN DOUBLE + // PRECISION ARITHMETIC. SIMILAR CONSIDERATIONS HOLD FOR OTHER + // MACHINES. + // + // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX + // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT + // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- + // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE + // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), + // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF + // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY + // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN + // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY + // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER + // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, + // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS + // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER + // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY + // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER + // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE + // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, + // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, + // OR -PI/2+P. + // + //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ + // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF + // COMMERCE, 1955. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 + // + // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- + // 1018, MAY, 1985 + // + // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. + // MATH. SOFTWARE, 1986 + // + //***ROUTINES CALLED ZBINU,ZABS,ZDIV,ZSQRT,D1MACH,I1MACH + //***END PROLOGUE ZBIRY + + #endregion + + const double coef = 5.77350269189625765E-01; + const double coner = 1.0; + const double conei = 0.0; + const double c1 = 6.14926627446000736E-01; + const double c2 = 4.48288357353826359E-01; + const double pi = 3.14159265358979323846264338327950; + const double tth = 6.66666666666666667E-01; + + double aa, ad, ak, alim, atrm, az, az3, bb; + double bk, cc, ck, csqi = 0, csqr = 0; + double dig, dk, d1, d2, eaa, elim, fid, fmr, fnu, fnul, rl, r1m5; + double sfac, sti = 0, str = 0, s1i, s1r, s2i, s2r, tol, trm1i, trm1r, trm2i; + double trm2r, ztai, ztar, z3i, z3r; + int k, k1, k2; + + double[] cyr = new double[2]; + double[] cyi = new double[2]; + + ierr = 0; + nz = 0; + if (id < 0 || id > 1) + ierr = 1; + if (kode < 1 || kode > 2) + ierr = 1; + if (ierr != 0) + return 0; + az = zabs(zr, zi); + tol = Math.Max(d1mach(4), 1.0E-18); + fid = (double)id; + if (az > 1.0) + goto L70; + //----------------------------------------------------------------------- + // POWER SERIES FOR CABS(Z).LE.1. + //----------------------------------------------------------------------- + s1r = coner; + s1i = conei; + s2r = coner; + s2i = conei; + if (az < tol) + goto L130; + aa = az * az; + if (aa < tol / az) + goto L40; + trm1r = coner; + trm1i = conei; + trm2r = coner; + trm2i = conei; + atrm = 1.0; + str = zr * zr - zi * zi; + sti = zr * zi + zi * zr; + z3r = str * zr - sti * zi; + z3i = str * zi + sti * zr; + az3 = az * aa; + ak = 2.0 + fid; + bk = 3.0 - fid - fid; + ck = 4.0 - fid; + dk = 3.0 + fid + fid; + d1 = ak * dk; + d2 = bk * ck; + ad = Math.Min(d1, d2); + ak = 24.0 + 9.0 * fid; + bk = 30.0 - 9.0 * fid; + for (k = 1; k <= 25; k++) + { + str = (trm1r * z3r - trm1i * z3i) / d1; + trm1i = (trm1r * z3i + trm1i * z3r) / d1; + trm1r = str; + s1r = s1r + trm1r; + s1i = s1i + trm1i; + str = (trm2r * z3r - trm2i * z3i) / d2; + trm2i = (trm2r * z3i + trm2i * z3r) / d2; + trm2r = str; + s2r = s2r + trm2r; + s2i = s2i + trm2i; + atrm = atrm * az3 / ad; + d1 = d1 + ak; + d2 = d2 + bk; + ad = Math.Min(d1, d2); + if (atrm < tol * ad) + goto L40; + ak = ak + 18.0; + bk = bk + 18.0; + } + + L40: + if (id == 1) + goto L50; + bir = c1 * s1r + c2 * (zr * s2r - zi * s2i); + bii = c1 * s1i + c2 * (zr * s2i + zi * s2r); + if (kode == 1) + return 0; + zsqrt(zr, zi, ref str, ref sti); + ztar = tth * (zr * str - zi * sti); + ztai = tth * (zr * sti + zi * str); + aa = ztar; + aa = -Math.Abs(aa); + eaa = Math.Exp(aa); + bir = bir * eaa; + bii = bii * eaa; + return 0; + L50: + bir = s2r * c2; + bii = s2i * c2; + if (az <= tol) + goto L60; + cc = c1 / (1.0 + fid); + str = s1r * zr - s1i * zi; + sti = s1r * zi + s1i * zr; + bir = bir + cc * (str * zr - sti * zi); + bii = bii + cc * (str * zi + sti * zr); + L60: + if (kode == 1) + return 0; + zsqrt(zr, zi, ref str, ref sti); + ztar = tth * (zr * str - zi * sti); + ztai = tth * (zr * sti + zi * str); + aa = ztar; + aa = -Math.Abs(aa); + eaa = Math.Exp(aa); + bir = bir * eaa; + bii = bii * eaa; + return 0; + //----------------------------------------------------------------------- + // CASE FOR CABS(Z).GT.1.0 + //----------------------------------------------------------------------- + L70: + fnu = (1.0 + fid) / 3.0; + //----------------------------------------------------------------------- + // SET PARAMETERS RELATED TO MACHINE CONSTANTS. + // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. + // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. + // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND + // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR + // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. + // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. + // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). + // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU. + //----------------------------------------------------------------------- + k1 = i1mach(15); + k2 = i1mach(16); + r1m5 = d1mach(5); + k = Math.Min(Math.Abs(k1), Math.Abs(k2)); + elim = 2.303 * ((double)k * r1m5 - 3.0); + k1 = i1mach(14) - 1; + aa = r1m5 * (double)k1; + dig = Math.Min(aa, 18.0); + aa = aa * 2.303; + alim = elim + Math.Max(-aa, -41.45); + rl = 1.2 * dig + 3.0; + fnul = 10.0 + 6.0d * (dig - 3.0); + //----------------------------------------------------------------------- + // TEST FOR RANGE + //----------------------------------------------------------------------- + aa = 0.5 / tol; + bb = (double)i1mach(9) * 0.5; + aa = Math.Min(aa, bb); + aa = Math.Pow(aa, tth); + if (az > aa) + goto L260; + aa = Math.Sqrt(aa); + if (az > aa) + ierr = 3; + zsqrt(zr, zi, ref csqr, ref csqi); + ztar = tth * (zr * csqr - zi * csqi); + ztai = tth * (zr * csqi + zi * csqr); + //---------------------------------------------------------------------- + // RE(ZTA).LE.0 WHEN RE(Z).LT.0, ESPECIALLY WHEN IM(Z) IS SMALL + //----------------------------------------------------------------------- + sfac = 1.0; + ak = ztai; + if (zr >= 0.0) + goto L80; + bk = ztar; + ck = -Math.Abs(bk); + ztar = ck; + ztai = ak; + L80: + if (zi != 0.0 || zr > 0.0) + goto L90; + ztar = 0.0; + ztai = ak; + L90: + aa = ztar; + if (kode == 2) + goto L100; + //----------------------------------------------------------------------- + // OVERFLOW TEST + //----------------------------------------------------------------------- + bb = Math.Abs(aa); + if (bb < alim) + goto L100; + bb = bb + 0.25 * Math.Log(az); + sfac = tol; + if (bb > elim) + goto L190; + L100: + fmr = 0.0; + if (aa >= 0.0 && zr > 0.0) + goto L110; + fmr = pi; + if (zi < 0.0) + fmr = -pi; + ztar = -ztar; + ztai = -ztai; + L110: + //----------------------------------------------------------------------- + // AA=FACTOR FOR ANALYTIC CONTINUATION OF I(FNU,ZTA) + // KODE=2 RETURNS EXP(-ABS(XZTA))*I(FNU,ZTA) FROM CBESI + //----------------------------------------------------------------------- + zbinu(ztar, ztai, fnu, kode, 1, cyr, cyi, ref nz, rl, fnul, tol, elim, alim); + if (nz < 0) + goto L200; + aa = fmr * fnu; + z3r = sfac; + str = Math.Cos(aa); + sti = Math.Sin(aa); + s1r = (str * cyr[0] - sti * cyi[0]) * z3r; + s1i = (str * cyi[0] + sti * cyr[0]) * z3r; + fnu = (2.0 - fid) / 3.0; + zbinu(ztar, ztai, fnu, kode, 2, cyr, cyi, ref nz, rl, fnul, tol, elim, alim); + cyr[0] = cyr[0] * z3r; + cyi[0] = cyi[0] * z3r; + cyr[1] = cyr[1] * z3r; + cyi[1] = cyi[1] * z3r; + //----------------------------------------------------------------------- + // BACKWARD RECUR ONE STEP FOR ORDERS -1/3 OR -2/3 + //----------------------------------------------------------------------- + zdiv(cyr[0], cyi[0], ztar, ztai, ref str, ref sti); + s2r = (fnu + fnu) * str + cyr[1]; + s2i = (fnu + fnu) * sti + cyi[1]; + aa = fmr * (fnu - 1.0); + str = Math.Cos(aa); + sti = Math.Sin(aa); + s1r = coef * (s1r + s2r * str - s2i * sti); + s1i = coef * (s1i + s2r * sti + s2i * str); + if (id == 1) + goto L120; + str = csqr * s1r - csqi * s1i; + s1i = csqr * s1i + csqi * s1r; + s1r = str; + bir = s1r / sfac; + bii = s1i / sfac; + return 0; + L120: + str = zr * s1r - zi * s1i; + s1i = zr * s1i + zi * s1r; + s1r = str; + bir = s1r / sfac; + bii = s1i / sfac; + return 0; + L130: + aa = c1 * (1.0 - fid) + fid * c2; + bir = aa; + bii = 0.0; + return 0; + L190: + ierr = 2; + nz = 0; + return 0; + L200: + if (nz == -1) + goto L190; + nz = 0; + ierr = 5; + return 0; + L260: + ierr = 4; + nz = 0; + return 0; + } + + // The Bessel function of the first kind and derivative + public static int zbesj(double zr, double zi, double fnu, int kode, int n, double[] cyr, double[] cyi, ref int nz, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE ZBESJ + //***DATE WRITTEN 830501 (YYMMDD) + //***REVISION DATE 890801 (YYMMDD) + //***CATEGORY NO. B5K + //***KEYWORDS J-BESSEL FUNCTION,BESSEL FUNCTION OF COMPLEX ARGUMENT, + // BESSEL FUNCTION OF FIRST KIND + //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES + //***PURPOSE TO COMPUTE THE J-BESSEL FUNCTION OF A COMPLEX ARGUMENT + //***DESCRIPTION + // + // ***A DOUBLE PRECISION ROUTINE*** + // ON KODE=1, CBESJ COMPUTES AN N MEMBER SEQUENCE OF COMPLEX + // BESSEL FUNCTIONS CY(I)=J(FNU+I-1,Z) FOR REAL, NONNEGATIVE + // ORDERS FNU+I-1, I=1,...,N AND COMPLEX Z IN THE CUT PLANE + // -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESJ RETURNS THE SCALED + // FUNCTIONS + // + // CY(I)=EXP(-ABS(Y))*J(FNU+I-1,Z) I = 1,...,N , Y=AIMAG(Z) + // + // WHICH REMOVE THE EXPONENTIAL GROWTH IN BOTH THE UPPER AND + // LOWER HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION + // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS + // (REF. 1). + // + // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION + // ZR,ZI - Z=CMPLX(ZR,ZI), -PI.LT.ARG(Z).LE.PI + // FNU - ORDER OF INITIAL J FUNCTION, FNU.GE.0.0D0 + // KODE - A PARAMETER TO INDICATE THE SCALING OPTION + // KODE= 1 RETURNS + // CY(I)=J(FNU+I-1,Z), I=1,...,N + // = 2 RETURNS + // CY(I)=J(FNU+I-1,Z)EXP(-ABS(Y)), I=1,...,N + // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 + // + // OUTPUT CYR,CYI ARE DOUBLE PRECISION + // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS + // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE + // CY(I)=J(FNU+I-1,Z) OR + // CY(I)=J(FNU+I-1,Z)EXP(-ABS(Y)) I=1,...,N + // DEPENDING ON KODE, Y=AIMAG(Z). + // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, + // NZ= 0 , NORMAL RETURN + // NZ.GT.0 , LAST NZ COMPONENTS OF CY SET ZERO DUE + // TO UNDERFLOW, CY(I)=CMPLX(0.0D0,0.0D0), + // I = N-NZ+1,...,N + // IERR - ERROR FLAG + // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED + // IERR=1, INPUT ERROR - NO COMPUTATION + // IERR=2, OVERFLOW - NO COMPUTATION, AIMAG(Z) + // TOO LARGE ON KODE=1 + // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE + // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT + // REDUCTION PRODUCE LESS THAN HALF OF MACHINE + // ACCURACY + // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- + // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- + // CANCE BY ARGUMENT REDUCTION + // IERR=5, ERROR - NO COMPUTATION, + // ALGORITHM TERMINATION CONDITION NOT MET + // + //***LONG DESCRIPTION + // + // THE COMPUTATION IS CARRIED OUT BY THE FORMULA + // + // J(FNU,Z)=EXP( FNU*PI*I/2)*I(FNU,-I*Z) AIMAG(Z).GE.0.0 + // + // J(FNU,Z)=EXP(-FNU*PI*I/2)*I(FNU, I*Z) AIMAG(Z).LT.0.0 + // + // WHERE I**2 = -1 AND I(FNU,Z) IS THE I BESSEL FUNCTION. + // + // FOR NEGATIVE ORDERS,THE FORMULA + // + // J(-FNU,Z) = J(FNU,Z)*COS(PI*FNU) - Y(FNU,Z)*SIN(PI*FNU) + // + // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO INTEGERS, THE + // THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE POSITIVE + // INTEGER,THE MAGNITUDE OF J(-FNU,Z)=J(FNU,Z)*COS(PI*FNU) IS A + // LARGE NEGATIVE POWER OF TEN. BUT WHEN FNU IS NOT AN INTEGER, + // Y(FNU,Z) DOMINATES IN MAGNITUDE WITH A LARGE POSITIVE POWER OF + // TEN AND THE MOST THAT THE SECOND TERM CAN BE REDUCED IS BY + // UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, WIDE CHANGES CAN + // OCCUR WITHIN UNIT ROUNDOFF OF A LARGE INTEGER FOR FNU. HERE, + // LARGE MEANS FNU.GT.CABS(Z). + // + // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- + // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS + // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. + // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN + // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG + // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS + // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. + // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS + // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS + // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE + // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS + // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 + // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION + // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION + // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN + // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT + // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS + // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. + // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. + // + // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX + // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT + // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- + // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE + // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), + // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF + // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY + // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN + // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY + // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER + // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, + // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS + // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER + // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY + // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER + // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE + // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, + // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, + // OR -PI/2+P. + // + //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ + // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF + // COMMERCE, 1955. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // BY D. E. AMOS, SAND83-0083, MAY, 1983. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 + // + // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- + // 1018, MAY, 1985 + // + // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. + // MATH. SOFTWARE, 1986 + // + //***ROUTINES CALLED ZBINU,I1MACH,D1MACH + //***END PROLOGUE ZBESJ + + #endregion + + const double hpi = 1.570796326794896619231321696; + + double aa, alim, arg, cii, csgni, csgnr, dig; + double elim, fnul, rl, r1m5, str, tol, zni, znr; + double bb, fn, az, ascle, rtol, atol, sti; + int i, inu, inuh, ir, k, k1, k2, nl; + + ierr = 0; + nz = 0; + if (fnu < 0.0) + ierr = 1; + if (kode < 1 || kode > 2) + ierr = 1; + if (n < 1) + ierr = 1; + if (ierr != 0) + return 0; + //----------------------------------------------------------------------- + // SET PARAMETERS RELATED TO MACHINE CONSTANTS. + // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. + // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. + // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND + // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR + // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. + // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. + // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). + // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU. + //----------------------------------------------------------------------- + tol = Math.Max(d1mach(4), 1.0E-18); + k1 = i1mach(15); + k2 = i1mach(16); + r1m5 = d1mach(5); + k = Math.Min(Math.Abs(k1), Math.Abs(k2)); + elim = (k * r1m5 - 3.0) * 2.303; + k1 = i1mach(14) - 1; + aa = r1m5 * k1; + dig = Math.Min(aa, 18.0); + aa *= 2.303; + alim = elim + Math.Max(-aa, -41.45); + rl = dig * 1.2 + 3.0; + fnul = (dig - 3.0) * 6.0 + 10.0; + //----------------------------------------------------------------------- + // TEST FOR PROPER RANGE + //----------------------------------------------------------------------- + az = zabs(zr, zi); + fn = fnu + (double)(n - 1); + aa = 0.5 / tol; + bb = 0.5 * (double)i1mach(9); + aa = Math.Min(aa, bb); + if (az > aa) + goto L260; + if (fn > aa) + goto L260; + aa = Math.Sqrt(aa); + if (az > aa) + ierr = 3; + if (fn > aa) + ierr = 3; + //----------------------------------------------------------------------- + // CALCULATE CSGN=EXP(FNU*HPI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE + // WHEN FNU IS LARGE + //----------------------------------------------------------------------- + cii = 1.0; + inu = (int)fnu; + inuh = inu / 2; + ir = inu - (inuh << 1); + arg = (fnu - (inu - ir)) * hpi; + csgnr = Math.Cos(arg); + csgni = Math.Sin(arg); + if (inuh % 2 == 0) + goto L40; + csgnr = -csgnr; + csgni = -csgni; + L40: + //----------------------------------------------------------------------- + // ZN IS IN THE RIGHT HALF PLANE + //----------------------------------------------------------------------- + znr = zi; + zni = -zr; + if (zi >= 0.0) + goto L50; + znr = -znr; + zni = -zni; + csgni = -csgni; + cii = -cii; + L50: + zbinu(znr, zni, fnu, kode, n, cyr, cyi, ref nz, rl, fnul, tol, elim, alim); + if (nz < 0) + goto L130; + nl = n - nz; + if (nl == 0) + return 0; + rtol = 1.0 / tol; + ascle = d1mach(1) * rtol * 1.0E3; + for (i = 1; i <= nl; i++) + { + // STR = CYR(I)*CSGNR - CYI(I)*CSGNI + // CYI(I) = CYR(I)*CSGNI + CYI(I)*CSGNR + // CYR(I) = STR + aa = cyr[i - 1]; + bb = cyi[i - 1]; + atol = 1.0; + if (Math.Max(Math.Abs(aa), Math.Abs(bb)) > ascle) + goto L55; + aa *= rtol; + bb *= rtol; + atol = tol; + L55: + str = aa * csgnr - bb * csgni; + sti = aa * csgni + bb * csgnr; + cyr[i - 1] = str * atol; + cyi[i - 1] = sti * atol; + str = -csgni * cii; + csgni = csgnr * cii; + csgnr = str; + } + + return 0; + L130: + if (nz == -2) + goto L140; + nz = 0; + ierr = 2; + return 0; + L140: + nz = 0; + ierr = 5; + return 0; + L260: + nz = 0; + ierr = 4; + return 0; + } + + // The Bessel function of the second kind and derivative + public static int zbesy(double zr, double zi, double fnu, int kode, int n, double[] cyr, double[] cyi, ref int nz, double[] cwrkr, double[] cwrki, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE ZBESY + //***DATE WRITTEN 830501 (YYMMDD) + //***REVISION DATE 890801 (YYMMDD) + //***CATEGORY NO. B5K + //***KEYWORDS Y-BESSEL FUNCTION,BESSEL FUNCTION OF COMPLEX ARGUMENT, + // BESSEL FUNCTION OF SECOND KIND + //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES + //***PURPOSE TO COMPUTE THE Y-BESSEL FUNCTION OF A COMPLEX ARGUMENT + //***DESCRIPTION + // + // ***A DOUBLE PRECISION ROUTINE*** + // + // ON KODE=1, CBESY COMPUTES AN N MEMBER SEQUENCE OF COMPLEX + // BESSEL FUNCTIONS CY(I)=Y(FNU+I-1,Z) FOR REAL, NONNEGATIVE + // ORDERS FNU+I-1, I=1,...,N AND COMPLEX Z IN THE CUT PLANE + // -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESY RETURNS THE SCALED + // FUNCTIONS + // + // CY(I)=EXP(-ABS(Y))*Y(FNU+I-1,Z) I = 1,...,N , Y=AIMAG(Z) + // + // WHICH REMOVE THE EXPONENTIAL GROWTH IN BOTH THE UPPER AND + // LOWER HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION + // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS + // (REF. 1). + // + // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION + // ZR,ZI - Z=CMPLX(ZR,ZI), Z.NE.CMPLX(0.0D0,0.0D0), + // -PI.LT.ARG(Z).LE.PI + // FNU - ORDER OF INITIAL Y FUNCTION, FNU.GE.0.0D0 + // KODE - A PARAMETER TO INDICATE THE SCALING OPTION + // KODE= 1 RETURNS + // CY(I)=Y(FNU+I-1,Z), I=1,...,N + // = 2 RETURNS + // CY(I)=Y(FNU+I-1,Z)*EXP(-ABS(Y)), I=1,...,N + // WHERE Y=AIMAG(Z) + // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 + // CWRKR, - DOUBLE PRECISION WORK VECTORS OF DIMENSION AT + // CWRKI AT LEAST N + // + // OUTPUT CYR,CYI ARE DOUBLE PRECISION + // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS + // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE + // CY(I)=Y(FNU+I-1,Z) OR + // CY(I)=Y(FNU+I-1,Z)*EXP(-ABS(Y)) I=1,...,N + // DEPENDING ON KODE. + // NZ - NZ=0 , A NORMAL RETURN + // NZ.GT.0 , NZ COMPONENTS OF CY SET TO ZERO DUE TO + // UNDERFLOW (GENERALLY ON KODE=2) + // IERR - ERROR FLAG + // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED + // IERR=1, INPUT ERROR - NO COMPUTATION + // IERR=2, OVERFLOW - NO COMPUTATION, FNU IS + // TOO LARGE OR CABS(Z) IS TOO SMALL OR BOTH + // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE + // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT + // REDUCTION PRODUCE LESS THAN HALF OF MACHINE + // ACCURACY + // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- + // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- + // CANCE BY ARGUMENT REDUCTION + // IERR=5, ERROR - NO COMPUTATION, + // ALGORITHM TERMINATION CONDITION NOT MET + // + //***LONG DESCRIPTION + // + // THE COMPUTATION IS CARRIED OUT BY THE FORMULA + // + // Y(FNU,Z)=0.5*(H(1,FNU,Z)-H(2,FNU,Z))/I + // + // WHERE I**2 = -1 AND THE HANKEL BESSEL FUNCTIONS H(1,FNU,Z) + // AND H(2,FNU,Z) ARE CALCULATED IN CBESH. + // + // FOR NEGATIVE ORDERS,THE FORMULA + // + // Y(-FNU,Z) = Y(FNU,Z)*COS(PI*FNU) + J(FNU,Z)*SIN(PI*FNU) + // + // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO HALF ODD + // INTEGERS THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE + // POSITIVE HALF ODD INTEGER,THE MAGNITUDE OF Y(-FNU,Z)=J(FNU,Z)* + // SIN(PI*FNU) IS A LARGE NEGATIVE POWER OF TEN. BUT WHEN FNU IS + // NOT A HALF ODD INTEGER, Y(FNU,Z) DOMINATES IN MAGNITUDE WITH A + // LARGE POSITIVE POWER OF TEN AND THE MOST THAT THE SECOND TERM + // CAN BE REDUCED IS BY UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, + // WIDE CHANGES CAN OCCUR WITHIN UNIT ROUNDOFF OF A LARGE HALF + // ODD INTEGER. HERE, LARGE MEANS FNU.GT.CABS(Z). + // + // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- + // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS + // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. + // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN + // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG + // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS + // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. + // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS + // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS + // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE + // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS + // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 + // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION + // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION + // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN + // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT + // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS + // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. + // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. + // + // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX + // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT + // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- + // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE + // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), + // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF + // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY + // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN + // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY + // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER + // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, + // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS + // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER + // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY + // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER + // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE + // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, + // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, + // OR -PI/2+P. + // + //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ + // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF + // COMMERCE, 1955. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // BY D. E. AMOS, SAND83-0083, MAY, 1983. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 + // + // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- + // 1018, MAY, 1985 + // + // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. + // MATH. SOFTWARE, 1986 + // + //***ROUTINES CALLED ZBESH,I1MACH,D1MACH + //***END PROLOGUE ZBESY + + #endregion + + double c1i, c1r, c2i, c2r; + double elim, exi, exr, ey, hcii, sti, str, tay; + double ascle, rtol, atol, aa, bb, tol, r1m5; + int i, k, k1, k2, nz1 = 0, nz2 = 0; + + ierr = 0; + nz = 0; + if (zr == 0.0 && zi == 0.0) + ierr = 1; + if (fnu < 0.0) + ierr = 1; + if (kode < 1 || kode > 2) + ierr = 1; + if (n < 1) + ierr = 1; + if (ierr != 0) + return 0; + hcii = 0.5; + zbesh(zr, zi, fnu, kode, 1, n, cyr, cyi, ref nz1, ref ierr); + if (ierr != 0 && ierr != 3) + goto L170; + zbesh(zr, zi, fnu, kode, 2, n, cwrkr, cwrki, ref nz2, ref ierr); + if (ierr != 0 && ierr != 3) + goto L170; + nz = Math.Min(nz1, nz2); + if (kode == 2) + goto L60; + for (i = 1; i <= n; i++) + { + str = cwrkr[i - 1] - cyr[i - 1]; + sti = cwrki[i - 1] - cyi[i - 1]; + cyr[i - 1] = -sti * hcii; + cyi[i - 1] = str * hcii; + } + + return 0; + L60: + tol = Math.Max(d1mach(4), 1.0E-18); + k1 = i1mach(15); + k2 = i1mach(16); + k = Math.Min(Math.Abs(k1), Math.Abs(k2)); + r1m5 = d1mach(5); + //----------------------------------------------------------------------- + // ELIM IS THE APPROXIMATE EXPONENTIAL UNDER- AND OVERFLOW LIMIT + //----------------------------------------------------------------------- + elim = 2.303 * ((double)k * r1m5 - 3.0); + exr = Math.Cos(zr); + exi = Math.Sin(zr); + ey = 0.0; + tay = Math.Abs(zi + zi); + if (tay < elim) + ey = Math.Exp(-tay); + if (zi < 0.0) + goto L90; + c1r = exr * ey; + c1i = exi * ey; + c2r = exr; + c2i = -exi; + L70: + nz = 0; + rtol = 1.0 / tol; + ascle = d1mach(1) * rtol * 1.0E3; + for (i = 1; i <= n; i++) + { + // STR = C1R*CYR(I) - C1I*CYI(I) + // STI = C1R*CYI(I) + C1I*CYR(I) + // STR = -STR + C2R*CWRKR(I) - C2I*CWRKI(I) + // STI = -STI + C2R*CWRKI(I) + C2I*CWRKR(I) + // CYR(I) = -STI*HCII + // CYI(I) = STR*HCII + aa = cwrkr[i - 1]; + bb = cwrki[i - 1]; + atol = 1.0; + if (Math.Max(Math.Abs(aa), Math.Abs(bb)) > ascle) + goto L75; + aa *= rtol; + bb *= rtol; + atol = tol; + L75: + str = (aa * c2r - bb * c2i) * atol; + sti = (aa * c2i + bb * c2r) * atol; + aa = cyr[i - 1]; + bb = cyi[i - 1]; + atol = 1.0; + if (Math.Max(Math.Abs(aa), Math.Abs(bb)) > ascle) + goto L85; + aa *= rtol; + bb *= rtol; + atol = tol; + L85: + str -= (aa * c1r - bb * c1i) * atol; + sti -= (aa * c1i + bb * c1r) * atol; + cyr[i - 1] = -sti * hcii; + cyi[i - 1] = str * hcii; + if (str == 0.0 && sti == 0.0 && ey == 0.0) + nz++; + } + + return 0; + L90: + c1r = exr; + c1i = exi; + c2r = exr * ey; + c2i = -exi * ey; + goto L70; + L170: + nz = 0; + return 0; + } + + // The modified Bessel function of the first kind and derivative + public static int zbesi(double zr, double zi, double fnu, int kode, int n, double[] cyr, double[] cyi, ref int nz, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE ZBESI + //***DATE WRITTEN 830501 (YYMMDD) + //***REVISION DATE 890801 (YYMMDD) + //***CATEGORY NO. B5K + //***KEYWORDS I-BESSEL FUNCTION,COMPLEX BESSEL FUNCTION, + // MODIFIED BESSEL FUNCTION OF THE FIRST KIND + //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES + //***PURPOSE TO COMPUTE I-BESSEL FUNCTIONS OF COMPLEX ARGUMENT + //***DESCRIPTION + // + // ***A DOUBLE PRECISION ROUTINE*** + // ON KODE=1, ZBESI COMPUTES AN N MEMBER SEQUENCE OF COMPLEX + // BESSEL FUNCTIONS CY(J)=I(FNU+J-1,Z) FOR REAL, NONNEGATIVE + // ORDERS FNU+J-1, J=1,...,N AND COMPLEX Z IN THE CUT PLANE + // -PI.LT.ARG(Z).LE.PI. ON KODE=2, ZBESI RETURNS THE SCALED + // FUNCTIONS + // + // CY(J)=EXP(-ABS(X))*I(FNU+J-1,Z) J = 1,...,N , X=REAL(Z) + // + // WITH THE EXPONENTIAL GROWTH REMOVED IN BOTH THE LEFT AND + // RIGHT HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION + // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS + // (REF. 1). + // + // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION + // ZR,ZI - Z=CMPLX(ZR,ZI), -PI.LT.ARG(Z).LE.PI + // FNU - ORDER OF INITIAL I FUNCTION, FNU.GE.0.0D0 + // KODE - A PARAMETER TO INDICATE THE SCALING OPTION + // KODE= 1 RETURNS + // CY(J)=I(FNU+J-1,Z), J=1,...,N + // = 2 RETURNS + // CY(J)=I(FNU+J-1,Z)*EXP(-ABS(X)), J=1,...,N + // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 + // + // OUTPUT CYR,CYI ARE DOUBLE PRECISION + // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS + // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE + // CY(J)=I(FNU+J-1,Z) OR + // CY(J)=I(FNU+J-1,Z)*EXP(-ABS(X)) J=1,...,N + // DEPENDING ON KODE, X=REAL(Z) + // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, + // NZ= 0 , NORMAL RETURN + // NZ.GT.0 , LAST NZ COMPONENTS OF CY SET TO ZERO + // TO UNDERFLOW, CY(J)=CMPLX(0.0D0,0.0D0) + // J = N-NZ+1,...,N + // IERR - ERROR FLAG + // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED + // IERR=1, INPUT ERROR - NO COMPUTATION + // IERR=2, OVERFLOW - NO COMPUTATION, REAL(Z) TOO + // LARGE ON KODE=1 + // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE + // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT + // REDUCTION PRODUCE LESS THAN HALF OF MACHINE + // ACCURACY + // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- + // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- + // CANCE BY ARGUMENT REDUCTION + // IERR=5, ERROR - NO COMPUTATION, + // ALGORITHM TERMINATION CONDITION NOT MET + // + //***LONG DESCRIPTION + // + // THE COMPUTATION IS CARRIED OUT BY THE POWER SERIES FOR + // SMALL CABS(Z), THE ASYMPTOTIC EXPANSION FOR LARGE CABS(Z), + // THE MILLER ALGORITHM NORMALIZED BY THE WRONSKIAN AND A + // NEUMANN SERIES FOR IMTERMEDIATE MAGNITUDES, AND THE + // UNIFORM ASYMPTOTIC EXPANSIONS FOR I(FNU,Z) AND J(FNU,Z) + // FOR LARGE ORDERS. BACKWARD RECURRENCE IS USED TO GENERATE + // SEQUENCES OR REDUCE ORDERS WHEN NECESSARY. + // + // THE CALCULATIONS ABOVE ARE DONE IN THE RIGHT HALF PLANE AND + // CONTINUED INTO THE LEFT HALF PLANE BY THE FORMULA + // + // I(FNU,Z*EXP(M*PI)) = EXP(M*PI*FNU)*I(FNU,Z) REAL(Z).GT.0.0 + // M = +I OR -I, I**2=-1 + // + // FOR NEGATIVE ORDERS,THE FORMULA + // + // I(-FNU,Z) = I(FNU,Z) + (2/PI)*SIN(PI*FNU)*K(FNU,Z) + // + // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO INTEGERS, THE + // THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE POSITIVE + // INTEGER,THE MAGNITUDE OF I(-FNU,Z)=I(FNU,Z) IS A LARGE + // NEGATIVE POWER OF TEN. BUT WHEN FNU IS NOT AN INTEGER, + // K(FNU,Z) DOMINATES IN MAGNITUDE WITH A LARGE POSITIVE POWER OF + // TEN AND THE MOST THAT THE SECOND TERM CAN BE REDUCED IS BY + // UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, WIDE CHANGES CAN + // OCCUR WITHIN UNIT ROUNDOFF OF A LARGE INTEGER FOR FNU. HERE, + // LARGE MEANS FNU.GT.CABS(Z). + // + // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- + // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS + // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. + // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN + // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG + // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS + // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. + // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS + // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS + // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE + // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS + // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 + // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION + // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION + // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN + // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT + // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS + // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. + // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. + // + // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX + // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT + // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- + // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE + // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), + // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF + // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY + // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN + // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY + // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER + // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, + // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS + // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER + // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY + // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER + // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE + // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, + // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, + // OR -PI/2+P. + // + //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ + // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF + // COMMERCE, 1955. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // BY D. E. AMOS, SAND83-0083, MAY, 1983. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 + // + // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- + // 1018, MAY, 1985 + // + // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. + // MATH. SOFTWARE, 1986 + // + //***ROUTINES CALLED ZBINU,I1MACH,D1MACH + //***END PROLOGUE ZBESI + + #endregion + + const double coner = 1.0; + const double conei = 0.0; + const double pi = 3.14159265358979323846264338327950; + + double aa, alim, arg, csgni, csgnr; + double dig, elim, fnul, rl, r1m5, str, tol, zni, znr; + double az, bb, fn, ascle, rtol, atol, sti; + int i, inu, k, k1, k2, nn; + + ierr = 0; + nz = 0; + if (fnu < 0.0) + ierr = 1; + if (kode < 1 || kode > 2) + ierr = 1; + if (n < 1) + ierr = 1; + if (ierr != 0) + return 0; + //----------------------------------------------------------------------- + // SET PARAMETERS RELATED TO MACHINE CONSTANTS. + // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. + // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. + // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND + // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR + // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. + // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. + // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). + // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU. + //----------------------------------------------------------------------- + tol = Math.Max(d1mach(4), 1.0E-18); + k1 = i1mach(15); + k2 = i1mach(16); + r1m5 = d1mach(5); + k = Math.Min(Math.Abs(k1), Math.Abs(k2)); + elim = 2.303 * ((double)k * r1m5 - 3.0); + k1 = i1mach(14) - 1; + aa = r1m5 * (double)k1; + dig = Math.Min(aa, 18.0); + aa = aa * 2.303; + alim = elim + Math.Max(-aa, -41.45); + rl = dig * 1.2 + 3.0; + fnul = 10.0 + 6.0 * (dig - 3.0); + //----------------------------------------------------------------------- + // TEST FOR PROPER RANGE + //----------------------------------------------------------------------- + az = zabs(zr, zi); + fn = fnu + (double)(n - 1); + aa = 0.5 / tol; + bb = (double)i1mach(9) * 0.5; + aa = Math.Min(aa, bb); + if (az > aa) + goto L260; + if (fn > aa) + goto L260; + aa = Math.Sqrt(aa); + if (az > aa) + ierr = 3; + if (fn > aa) + ierr = 3; + znr = zr; + zni = zi; + csgnr = coner; + csgni = conei; + if (zr >= 0.0) + goto L40; + znr = -zr; + zni = -zi; + //----------------------------------------------------------------------- + // CALCULATE CSGN=EXP(FNU*PI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE + // WHEN FNU IS LARGE + //----------------------------------------------------------------------- + inu = (int)fnu; + arg = (fnu - (double)inu) * pi; + if (zi < 0.0) + arg = -arg; + csgnr = Math.Cos(arg); + csgni = Math.Sin(arg); + if (inu % 2 == 0) + goto L40; + csgnr = -csgnr; + csgni = -csgni; + L40: + zbinu(znr, zni, fnu, kode, n, cyr, cyi, ref nz, rl, fnul, tol, elim, alim); + if (nz < 0) + goto L120; + if (zr >= 0.0) + return 0; + //----------------------------------------------------------------------- + // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE + //----------------------------------------------------------------------- + nn = n - nz; + if (nn == 0) + return 0; + rtol = 1.0 / tol; + ascle = d1mach(1) * rtol * 1.0E3; + for (i = 1; i <= nn; i++) + { + // STR = CYR(I) * CSGNR - CYI(I) * CSGNI + // CYI(I) = CYR(I) * CSGNI + CYI(I) * CSGNR + // CYR(I) = STR + aa = cyr[i - 1]; + bb = cyi[i - 1]; + atol = 1.0; + if (Math.Max(Math.Abs(aa), Math.Abs(bb)) > ascle) + goto L55; + aa = aa * rtol; + bb = bb * rtol; + atol = tol; + L55: + str = aa * csgnr - bb * csgni; + sti = aa * csgni + bb * csgnr; + cyr[i - 1] = str * atol; + cyi[i - 1] = sti * atol; + csgnr = -csgnr; + csgni = -csgni; + } + + return 0; + L120: + if (nz == -2) + goto L130; + nz = 0; + ierr = 2; + return 0; + L130: + nz = 0; + ierr = 5; + return 0; + L260: + nz = 0; + ierr = 4; + return 0; + } + + // The modified Bessel function of the second kind and derivative + public static int zbesk(double zr, double zi, double fnu, int kode, int n, double[] cyr, double[] cyi, ref int nz, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE ZBESK + //***DATE WRITTEN 830501 (YYMMDD) + //***REVISION DATE 890801 (YYMMDD) + //***CATEGORY NO. B5K + //***KEYWORDS K-BESSEL FUNCTION,COMPLEX BESSEL FUNCTION, + // MODIFIED BESSEL FUNCTION OF THE SECOND KIND, + // BESSEL FUNCTION OF THE THIRD KIND + //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES + //***PURPOSE TO COMPUTE K-BESSEL FUNCTIONS OF COMPLEX ARGUMENT + //***DESCRIPTION + // + // ***A DOUBLE PRECISION ROUTINE*** + // + // ON KODE=1, CBESK COMPUTES AN N MEMBER SEQUENCE OF COMPLEX + // BESSEL FUNCTIONS CY(J)=K(FNU+J-1,Z) FOR REAL, NONNEGATIVE + // ORDERS FNU+J-1, J=1,...,N AND COMPLEX Z.NE.CMPLX(0.0,0.0) + // IN THE CUT PLANE -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESK + // RETURNS THE SCALED K FUNCTIONS, + // + // CY(J)=EXP(Z)*K(FNU+J-1,Z) , J=1,...,N, + // + // WHICH REMOVE THE EXPONENTIAL BEHAVIOR IN BOTH THE LEFT AND + // RIGHT HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND + // NOTATION ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL + // FUNCTIONS (REF. 1). + // + // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION + // ZR,ZI - Z=CMPLX(ZR,ZI), Z.NE.CMPLX(0.0D0,0.0D0), + // -PI.LT.ARG(Z).LE.PI + // FNU - ORDER OF INITIAL K FUNCTION, FNU.GE.0.0D0 + // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 + // KODE - A PARAMETER TO INDICATE THE SCALING OPTION + // KODE= 1 RETURNS + // CY(I)=K(FNU+I-1,Z), I=1,...,N + // = 2 RETURNS + // CY(I)=K(FNU+I-1,Z)*EXP(Z), I=1,...,N + // + // OUTPUT CYR,CYI ARE DOUBLE PRECISION + // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS + // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE + // CY(I)=K(FNU+I-1,Z), I=1,...,N OR + // CY(I)=K(FNU+I-1,Z)*EXP(Z), I=1,...,N + // DEPENDING ON KODE + // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW. + // NZ= 0 , NORMAL RETURN + // NZ.GT.0 , FIRST NZ COMPONENTS OF CY SET TO ZERO DUE + // TO UNDERFLOW, CY(I)=CMPLX(0.0D0,0.0D0), + // I=1,...,N WHEN X.GE.0.0. WHEN X.LT.0.0 + // NZ STATES ONLY THE NUMBER OF UNDERFLOWS + // IN THE SEQUENCE. + // + // IERR - ERROR FLAG + // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED + // IERR=1, INPUT ERROR - NO COMPUTATION + // IERR=2, OVERFLOW - NO COMPUTATION, FNU IS + // TOO LARGE OR CABS(Z) IS TOO SMALL OR BOTH + // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE + // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT + // REDUCTION PRODUCE LESS THAN HALF OF MACHINE + // ACCURACY + // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- + // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- + // CANCE BY ARGUMENT REDUCTION + // IERR=5, ERROR - NO COMPUTATION, + // ALGORITHM TERMINATION CONDITION NOT MET + // + //***LONG DESCRIPTION + // + // EQUATIONS OF THE REFERENCE ARE IMPLEMENTED FOR SMALL ORDERS + // DNU AND DNU+1.0 IN THE RIGHT HALF PLANE X.GE.0.0. FORWARD + // RECURRENCE GENERATES HIGHER ORDERS. K IS CONTINUED TO THE LEFT + // HALF PLANE BY THE RELATION + // + // K(FNU,Z*EXP(MP)) = EXP(-MP*FNU)*K(FNU,Z)-MP*I(FNU,Z) + // MP=MR*PI*I, MR=+1 OR -1, RE(Z).GT.0, I**2=-1 + // + // WHERE I(FNU,Z) IS THE I BESSEL FUNCTION. + // + // FOR LARGE ORDERS, FNU.GT.FNUL, THE K FUNCTION IS COMPUTED + // BY MEANS OF ITS UNIFORM ASYMPTOTIC EXPANSIONS. + // + // FOR NEGATIVE ORDERS, THE FORMULA + // + // K(-FNU,Z) = K(FNU,Z) + // + // CAN BE USED. + // + // CBESK ASSUMES THAT A SIGNIFICANT DIGIT SINH(X) FUNCTION IS + // AVAILABLE. + // + // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- + // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS + // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. + // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN + // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG + // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS + // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. + // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS + // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS + // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE + // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS + // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 + // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION + // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION + // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN + // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT + // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS + // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. + // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. + // + // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX + // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT + // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- + // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE + // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), + // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF + // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY + // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN + // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY + // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER + // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, + // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS + // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER + // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY + // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER + // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE + // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, + // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, + // OR -PI/2+P. + // + //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ + // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF + // COMMERCE, 1955. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // BY D. E. AMOS, SAND83-0083, MAY, 1983. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983. + // + // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- + // 1018, MAY, 1985 + // + // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. + // MATH. SOFTWARE, 1986 + // + //***ROUTINES CALLED ZACON,ZBKNU,ZBUNK,ZUOIK,ZABS,I1MACH,D1MACH + //***END PROLOGUE ZBESK + + #endregion + + double aa, alim, aln, arg, az, dig, elim, fn; + double fnul, rl, r1m5, tol, ufl, bb; + int k, k1, k2, mr, nn, nuf = 0, nw = 0; + + ierr = 0; + nz = 0; + if (zi == 0.0 && zr == 0.0) + ierr = 1; + if (fnu < 0.0) + ierr = 1; + if (kode < 1 || kode > 2) + ierr = 1; + if (n < 1) + ierr = 1; + if (ierr != 0) + return 0; + nn = n; + // ----------------------------------------------------------------------- + // SET PARAMETERS RELATED TO MACHINE CONSTANTS. + // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. + // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. + // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND + // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR + // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. + // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. + // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). + // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU + // ----------------------------------------------------------------------- + tol = Math.Max(d1mach(4), 1.0E-18); + k1 = i1mach(15); + k2 = i1mach(16); + r1m5 = d1mach(5); + k = Math.Min(Math.Abs(k1), Math.Abs(k2)); + elim = 2.303 * ((double)k * r1m5 - 3.0); + k1 = i1mach(14) - 1; + aa = r1m5 * (double)k1; + dig = Math.Min(aa, 18.0); + aa *= 2.303; + alim = elim + Math.Max(-aa, -41.45); + fnul = (dig - 3.0) * 6.0 + 10.0; + rl = 1.2 * dig + 3.0; + // ----------------------------------------------------------------------- + // TEST FOR PROPER RANGE + // ----------------------------------------------------------------------- + az = zabs(zr, zi); + fn = fnu + (nn - 1); + aa = 0.5 / tol; + bb = i1mach(9) * 0.5; + aa = Math.Min(aa, bb); + if (az > aa) + goto L260; + if (fn > aa) + goto L260; + aa = Math.Sqrt(aa); + if (az > aa) + ierr = 3; + if (fn > aa) + ierr = 3; + // ----------------------------------------------------------------------- + // OVERFLOW TEST ON THE LAST MEMBER OF THE SEQUENCE + // ----------------------------------------------------------------------- + // UFL = EXP(-ELIM) + ufl = d1mach(1) * 1.0E3; + if (az < ufl) + goto L180; + if (fnu > fnul) + goto L80; + if (fn <= 1.0) + goto L60; + if (fn > 2.0) + goto L50; + if (az > tol) + goto L60; + arg = az * 0.5; + aln = -fn * Math.Log(arg); + if (aln > elim) + goto L180; + goto L60; + L50: + zuoik(zr, zi, fnu, kode, 2, nn, cyr, cyi, ref nuf, tol, elim, alim); + if (nuf < 0) + goto L180; + nz += nuf; + nn -= nuf; + // ----------------------------------------------------------------------- + // HERE NN=N OR NN=0 SINCE NUF=0,NN, OR -1 ON RETURN FROM CUOIK + // IF NUF=NN, THEN CY(I)=CZERO FOR ALL I + // ----------------------------------------------------------------------- + if (nn == 0) + goto L100; + L60: + if (zr < 0.0) + goto L70; + // ----------------------------------------------------------------------- + // RIGHT HALF PLANE COMPUTATION, REAL(Z).GE.0. + // ----------------------------------------------------------------------- + zbknu(zr, zi, fnu, kode, nn, cyr, cyi, ref nw, tol, elim, alim); + if (nw < 0) + goto L200; + nz = nw; + return 0; + // ----------------------------------------------------------------------- + // LEFT HALF PLANE COMPUTATION + // PI/2.LT.ARG(Z).LE.PI AND -PI.LT.ARG(Z).LT.-PI/2. + // ----------------------------------------------------------------------- + L70: + if (nz != 0) + goto L180; + mr = 1; + if (zi < 0.0) + mr = -1; + zacon(zr, zi, fnu, kode, mr, nn, cyr, cyi, ref nw, rl, fnul, tol, elim, alim); + if (nw < 0) + goto L200; + nz = nw; + return 0; + // ----------------------------------------------------------------------- + // UNIFORM ASYMPTOTIC EXPANSIONS FOR FNU.GT.FNUL + // ----------------------------------------------------------------------- + L80: + mr = 0; + if (zr >= 0.0) + goto L90; + mr = 1; + if (zi < 0.0) + mr = -1; + L90: + zbunk(zr, zi, fnu, kode, mr, nn, cyr, cyi, ref nw, tol, elim, alim); + if (nw < 0) + goto L200; + nz += nw; + return 0; + L100: + if (zr < 0.0) + goto L180; + return 0; + L180: + nz = 0; + ierr = 2; + return 0; + L200: + if (nw == -1) + goto L180; + nz = 0; + ierr = 5; + return 0; + L260: + nz = 0; + ierr = 4; + return 0; + } + + // The Hankel functions or Bessel functions of third kind and derivative + public static int zbesh(double zr, double zi, double fnu, int kode, int m, int n, double[] cyr, double[] cyi, ref int nz, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE ZBESH + //***DATE WRITTEN 830501 (YYMMDD) + //***REVISION DATE 890801 (YYMMDD) + //***CATEGORY NO. B5K + //***KEYWORDS H-BESSEL FUNCTIONS,BESSEL FUNCTIONS OF COMPLEX ARGUMENT, + // BESSEL FUNCTIONS OF THIRD KIND,HANKEL FUNCTIONS + //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES + //***PURPOSE TO COMPUTE THE H-BESSEL FUNCTIONS OF A COMPLEX ARGUMENT + //***DESCRIPTION + // + // ***A DOUBLE PRECISION ROUTINE*** + // ON KODE=1, ZBESH COMPUTES AN N MEMBER SEQUENCE OF COMPLEX + // HANKEL (BESSEL) FUNCTIONS CY(J)=H(M,FNU+J-1,Z) FOR KINDS M=1 + // OR 2, REAL, NONNEGATIVE ORDERS FNU+J-1, J=1,...,N, AND COMPLEX + // Z.NE.CMPLX(0.0,0.0) IN THE CUT PLANE -PI.LT.ARG(Z).LE.PI. + // ON KODE=2, ZBESH RETURNS THE SCALED HANKEL FUNCTIONS + // + // CY(I)=EXP(-MM*Z*I)*H(M,FNU+J-1,Z) MM=3-2*M, I**2=-1. + // + // WHICH REMOVES THE EXPONENTIAL BEHAVIOR IN BOTH THE UPPER AND + // LOWER HALF PLANES. DEFINITIONS AND NOTATION ARE FOUND IN THE + // NBS HANDBOOK OF MATHEMATICAL FUNCTIONS (REF. 1). + // + // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION + // ZR,ZI - Z=CMPLX(ZR,ZI), Z.NE.CMPLX(0.0D0,0.0D0), + // -PT.LT.ARG(Z).LE.PI + // FNU - ORDER OF INITIAL H FUNCTION, FNU.GE.0.0D0 + // KODE - A PARAMETER TO INDICATE THE SCALING OPTION + // KODE= 1 RETURNS + // CY(J)=H(M,FNU+J-1,Z), J=1,...,N + // = 2 RETURNS + // CY(J)=H(M,FNU+J-1,Z)*EXP(-I*Z*(3-2M)) + // J=1,...,N , I**2=-1 + // M - KIND OF HANKEL FUNCTION, M=1 OR 2 + // N - NUMBER OF MEMBERS IN THE SEQUENCE, N.GE.1 + // + // OUTPUT CYR,CYI ARE DOUBLE PRECISION + // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS + // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE + // CY(J)=H(M,FNU+J-1,Z) OR + // CY(J)=H(M,FNU+J-1,Z)*EXP(-I*Z*(3-2M)) J=1,...,N + // DEPENDING ON KODE, I**2=-1. + // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, + // NZ= 0 , NORMAL RETURN + // NZ.GT.0 , FIRST NZ COMPONENTS OF CY SET TO ZERO DUE + // TO UNDERFLOW, CY(J)=CMPLX(0.0D0,0.0D0) + // J=1,...,NZ WHEN Y.GT.0.0 AND M=1 OR + // Y.LT.0.0 AND M=2. FOR THE COMPLMENTARY + // HALF PLANES, NZ STATES ONLY THE NUMBER + // OF UNDERFLOWS. + // IERR - ERROR FLAG + // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED + // IERR=1, INPUT ERROR - NO COMPUTATION + // IERR=2, OVERFLOW - NO COMPUTATION, FNU TOO + // LARGE OR CABS(Z) TOO SMALL OR BOTH + // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE + // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT + // REDUCTION PRODUCE LESS THAN HALF OF MACHINE + // ACCURACY + // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- + // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- + // CANCE BY ARGUMENT REDUCTION + // IERR=5, ERROR - NO COMPUTATION, + // ALGORITHM TERMINATION CONDITION NOT MET + // + //***LONG DESCRIPTION + // + // THE COMPUTATION IS CARRIED OUT BY THE RELATION + // + // H(M,FNU,Z)=(1/MP)*EXP(-MP*FNU)*K(FNU,Z*EXP(-MP)) + // MP=MM*HPI*I, MM=3-2*M, HPI=PI/2, I**2=-1 + // + // FOR M=1 OR 2 WHERE THE K BESSEL FUNCTION IS COMPUTED FOR THE + // RIGHT HALF PLANE RE(Z).GE.0.0. THE K FUNCTION IS CONTINUED + // TO THE LEFT HALF PLANE BY THE RELATION + // + // K(FNU,Z*EXP(MP)) = EXP(-MP*FNU)*K(FNU,Z)-MP*I(FNU,Z) + // MP=MR*PI*I, MR=+1 OR -1, RE(Z).GT.0, I**2=-1 + // + // WHERE I(FNU,Z) IS THE I BESSEL FUNCTION. + // + // EXPONENTIAL DECAY OF H(M,FNU,Z) OCCURS IN THE UPPER HALF Z + // PLANE FOR M=1 AND THE LOWER HALF Z PLANE FOR M=2. EXPONENTIAL + // GROWTH OCCURS IN THE COMPLEMENTARY HALF PLANES. SCALING + // BY EXP(-MM*Z*I) REMOVES THE EXPONENTIAL BEHAVIOR IN THE + // WHOLE Z PLANE FOR Z TO INFINITY. + // + // FOR NEGATIVE ORDERS,THE FORMULAE + // + // H(1,-FNU,Z) = H(1,FNU,Z)*CEXP( PI*FNU*I) + // H(2,-FNU,Z) = H(2,FNU,Z)*CEXP(-PI*FNU*I) + // I**2=-1 + // + // CAN BE USED. + // + // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- + // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS + // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. + // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN + // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG + // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS + // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. + // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS + // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS + // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE + // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS + // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 + // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION + // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION + // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN + // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT + // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS + // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. + // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. + // + // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX + // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT + // ROUNDOFF,1.0D-18) IS THE NOMINAL PRECISION AND 10**S REPRE- + // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE + // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), + // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF + // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY + // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN + // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY + // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER + // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, + // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS + // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER + // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY + // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER + // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE + // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, + // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, + // OR -PI/2+P. + // + //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ + // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF + // COMMERCE, 1955. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // BY D. E. AMOS, SAND83-0083, MAY, 1983. + // + // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 + // + // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- + // 1018, MAY, 1985 + // + // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX + // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. + // MATH. SOFTWARE, 1986 + // + //***ROUTINES CALLED ZACON,ZBKNU,ZBUNK,ZUOIK,ZABS,I1MACH,D1MACH + //***END PROLOGUE ZBESH + + #endregion + + const double hpi = 1.570796326794896619231321696; + + double aa, alim, aln, arg, az, dig, elim; + double fmm, fn, fnul, rhpi, rl, r1m5, sgn, str, tol, ufl; + double zni, znr, zti, bb, ascle, rtol, atol, sti; + double csgnr, csgni; + int i, inu, inuh, ir, k, k1, k2; + int mm, mr, nn, nuf = 0, nw = 0; + + ierr = 0; + nz = 0; + if (zr == 0.0 && zi == 0.0) + ierr = 1; + if (fnu < 0.0) + ierr = 1; + if (m < 1 || m > 2) + ierr = 1; + if (kode < 1 || kode > 2) + ierr = 1; + if (n < 1) + ierr = 1; + if (ierr != 0) + return 0; + nn = n; + //----------------------------------------------------------------------- + // SET PARAMETERS RELATED TO MACHINE CONSTANTS. + // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. + // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. + // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND + // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR + // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. + // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. + // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). + // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU + //----------------------------------------------------------------------- + tol = Math.Max(d1mach(4), 1.0E-18); + k1 = i1mach(15); + k2 = i1mach(16); + r1m5 = d1mach(5); + k = Math.Min(Math.Abs(k1), Math.Abs(k2)); + elim = 2.303 * (k * r1m5 - 3.0); + k1 = i1mach(14) - 1; + aa = r1m5 * (double)k1; + dig = Math.Min(aa, 18.0); + aa *= 2.303; + alim = elim + Math.Max(-aa, -41.45); + fnul = (dig - 3.0) * 6.0 + 10.0; + rl = dig * 1.2 + 3.0; + fn = fnu + (nn - 1); + mm = 3 - m - m; + fmm = (double)mm; + znr = fmm * zi; + zni = -fmm * zr; + //----------------------------------------------------------------------- + // TEST FOR PROPER RANGE + //----------------------------------------------------------------------- + az = zabs(zr, zi); + aa = 0.5 / tol; + bb = i1mach(9) * 0.5; + aa = Math.Min(aa, bb); + if (az > aa) + goto L260; + if (fn > aa) + goto L260; + aa = Math.Sqrt(aa); + if (az > aa) + ierr = 3; + if (fn > aa) + ierr = 3; + //----------------------------------------------------------------------- + // OVERFLOW TEST ON THE LAST MEMBER OF THE SEQUENCE + //----------------------------------------------------------------------- + ufl = d1mach(1) * 1.0E3; + if (az < ufl) + goto L230; + if (fnu > fnul) + goto L90; + if (fn <= 1.0) + goto L70; + if (fn > 2.0) + goto L60; + if (az > tol) + goto L70; + arg = 0.5 * az; + aln = -fn * Math.Log(arg); + if (aln > elim) + goto L230; + goto L70; + L60: + zuoik(znr, zni, fnu, kode, 2, nn, cyr, cyi, ref nuf, tol, elim, alim); + if (nuf < 0) + goto L230; + nz += nuf; + nn -= nuf; + //----------------------------------------------------------------------- + // HERE NN=N OR NN=0 SINCE NUF=0,NN, OR -1 ON RETURN FROM CUOIK + // IF NUF=NN, THEN CY(I)=CZERO FOR ALL I + //----------------------------------------------------------------------- + if (nn == 0) + goto L140; + L70: + if (znr < 0.0 || znr == 0.0 && zni < 0.0 && m == 2) + goto L80; + //----------------------------------------------------------------------- + // RIGHT HALF PLANE COMPUTATION, XN.GE.0. .AND. (XN.NE.0. .OR. + // YN.GE.0. .OR. M=1) + //----------------------------------------------------------------------- + zbknu(znr, zni, fnu, kode, nn, cyr, cyi, ref nz, tol, elim, alim); + goto L110; + //----------------------------------------------------------------------- + // LEFT HALF PLANE COMPUTATION + //----------------------------------------------------------------------- + L80: + mr = -mm; + zacon(znr, zni, fnu, kode, mr, nn, cyr, cyi, ref nw, rl, fnul, tol, elim, alim); + if (nw < 0) + goto L240; + nz = nw; + goto L110; + L90: + //----------------------------------------------------------------------- + // UNIFORM ASYMPTOTIC EXPANSIONS FOR FNU.GT.FNUL + //----------------------------------------------------------------------- + mr = 0; + if (znr >= 0.0 && (znr != 0.0 || zni >= 0.0 || m != 2)) + goto L100; + mr = -mm; + if (znr != 0.0 || zni >= 0.0) + goto L100; + znr = -znr; + zni = -zni; + L100: + zbunk(znr, zni, fnu, kode, mr, nn, cyr, cyi, ref nw, tol, elim, alim); + if (nw < 0) + goto L240; + nz += nw; + L110: + //----------------------------------------------------------------------- + // H(M,FNU,Z) = -FMM*(I/HPI)*(ZT**FNU)*K(FNU,-Z*ZT) + + // ZT=EXP(-FMM*HPI*I) = CMPLX(0.0,-FMM), FMM=3-2*M, M=1,2 + //----------------------------------------------------------------------- + sgn = dsign(hpi, -fmm); + //----------------------------------------------------------------------- + // CALCULATE EXP(FNU*HPI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE + // WHEN FNU IS LARGE + //----------------------------------------------------------------------- + inu = (int)fnu; + inuh = inu / 2; + ir = inu - 2 * inuh; + arg = (fnu - (inu - ir)) * sgn; + rhpi = 1.0 / sgn; + // ZNI = RHPI*COS(ARG) + // ZNR = -RHPI*SIN(ARG) + csgni = rhpi * Math.Cos(arg); + csgnr = -rhpi * Math.Sin(arg); + if (inuh % 2 == 0) + goto L120; + // ZNR = -ZNR + // ZNI = -ZNI + csgnr = -csgnr; + csgni = -csgni; + L120: + zti = -fmm; + rtol = 1.0 / tol; + ascle = ufl * rtol; + for (i = 1; i <= nn; i++) + { + // STR = CYR(I)*ZNR - CYI(I)*ZNI + // CYI(I) = CYR(I)*ZNI + CYI(I)*ZNR + // CYR(I) = STR + // STR = -ZNI*ZTI + // ZNI = ZNR*ZTI + // ZNR = STR + aa = cyr[i - 1]; + bb = cyi[i - 1]; + atol = 1.0; + //Computing MAX + if (Math.Max(Math.Abs(aa), Math.Abs(bb)) > ascle) + goto L135; + aa *= rtol; + bb *= rtol; + atol = tol; + L135: + str = aa * csgnr - bb * csgni; + sti = aa * csgni + bb * csgnr; + cyr[i - 1] = str * atol; + cyi[i - 1] = sti * atol; + str = -csgni * zti; + csgni = csgnr * zti; + csgnr = str; + } + + return 0; + L140: + if (znr < 0.0) + goto L230; + return 0; + L230: + nz = 0; + ierr = 2; + return 0; + L240: + if (nw == -1) + goto L230; + nz = 0; + ierr = 5; + return 0; + L260: + nz = 0; + ierr = 4; + return 0; + } + + #endregion + + #region LnGamma functions + + // The logarithm of the gamma function + public static double dgamln(double z, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE DGAMLN + //***DATE WRITTEN 830501 (YYMMDD) + //***REVISION DATE 830501 (YYMMDD) + //***CATEGORY NO. B5F + //***KEYWORDS GAMMA FUNCTION,LOGARITHM OF GAMMA FUNCTION + //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES + //***PURPOSE TO COMPUTE THE LOGARITHM OF THE GAMMA FUNCTION + //***DESCRIPTION + // + // **** A DOUBLE PRECISION ROUTINE **** + // DGAMLN COMPUTES THE NATURAL LOG OF THE GAMMA FUNCTION FOR + // Z.GT.0. THE ASYMPTOTIC EXPANSION IS USED TO GENERATE VALUES + // GREATER THAN ZMIN WHICH ARE ADJUSTED BY THE RECURSION + // G(Z+1)=Z*G(Z) FOR Z.LE.ZMIN. THE FUNCTION WAS MADE AS + // PORTABLE AS POSSIBLE BY COMPUTIMG ZMIN FROM THE NUMBER OF BASE + // 10 DIGITS IN A WORD, RLN=AMAX1(-ALOG10(R1MACH(4)),0.5E-18) + // LIMITED TO 18 DIGITS OF (RELATIVE) ACCURACY. + // + // SINCE INTEGER ARGUMENTS ARE COMMON, A TABLE LOOK UP ON 100 + // VALUES IS USED FOR SPEED OF EXECUTION. + // + // DESCRIPTION OF ARGUMENTS + // + // INPUT Z IS D0UBLE PRECISION + // Z - ARGUMENT, Z.GT.0.0D0 + // + // OUTPUT DGAMLN IS DOUBLE PRECISION + // DGAMLN - NATURAL LOG OF THE GAMMA FUNCTION AT Z.NE.0.0D0 + // IERR - ERROR FLAG + // IERR=0, NORMAL RETURN, COMPUTATION COMPLETED + // IERR=1, Z.LE.0.0D0, NO COMPUTATION + // + // + //***REFERENCES COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT + // BY D. E. AMOS, SAND83-0083, MAY, 1983. + //***ROUTINES CALLED I1MACH,D1MACH + //***END PROLOGUE DGAMLN + + #endregion + + const double con = 1.83787706640934548; + + double[] gln = { + 0.0,0.0,0.693147180559945309, + 1.791759469228055,3.17805383034794562,4.78749174278204599, + 6.579251212010101,8.5251613610654143,10.6046029027452502, + 12.8018274800814696,15.1044125730755153,17.5023078458738858, + 19.9872144956618861,22.5521638531234229,25.1912211827386815, + 27.8992713838408916,30.6718601060806728,33.5050734501368889, + 36.3954452080330536,39.339884187199494,42.335616460753485, + 45.380138898476908,48.4711813518352239,51.6066755677643736, + 54.7847293981123192,58.0036052229805199,61.261701761002002, + 64.5575386270063311,67.889743137181535,71.257038967168009, + 74.6582363488301644,78.0922235533153106,81.5579594561150372, + 85.0544670175815174,88.5808275421976788,92.1361756036870925, + 95.7196945421432025,99.3306124547874269,102.968198614513813, + 106.631760260643459,110.320639714757395,114.034211781461703, + 117.771881399745072,121.533081515438634,125.317271149356895, + 129.123933639127215,132.95257503561631,136.802722637326368, + 140.673923648234259,144.565743946344886,148.477766951773032, + 152.409592584497358,156.360836303078785,160.331128216630907, + 164.320112263195181,168.327445448427652,172.352797139162802, + 176.395848406997352,180.456291417543771,184.533828861449491, + 188.628173423671591,192.739047287844902,196.866181672889994, + 201.009316399281527,205.168199482641199,209.342586752536836, + 213.532241494563261,217.736934113954227,221.956441819130334, + 226.190548323727593,230.439043565776952,234.701723442818268, + 238.978389561834323,243.268849002982714,247.572914096186884, + 251.890402209723194,256.221135550009525,260.564940971863209, + 264.921649798552801,269.291097651019823,273.673124285693704, + 278.067573440366143,282.474292687630396,286.893133295426994, + 291.323950094270308,295.766601350760624,300.220948647014132, + 304.686856765668715,309.164193580146922,313.652829949879062, + 318.152639620209327,322.663499126726177,327.185287703775217, + 331.717887196928473,336.261181979198477,340.815058870799018, + 345.379407062266854,349.954118040770237,354.539085519440809, + 359.134205369575399 }; + double[] cf = { + .0833333333333333333,-.00277777777777777778, + 7.93650793650793651e-4,-5.95238095238095238e-4, + 8.41750841750841751e-4,-.00191752691752691753, + .00641025641025641026,-.0295506535947712418,.179644372368830573, + -1.39243221690590112,13.402864044168392,-156.848284626002017, + 2193.10333333333333,-36108.7712537249894,691472.268851313067, + -15238221.5394074162,382900751.391414141,-10882266035.7843911, + 347320283765.002252,-12369602142269.2745,488788064793079.335, + -21320333960919373.9 }; + + double fln, fz, rln, s, tlg, trm, tst; + double t1, wdtol, zdmy, zinc, zm, zmin, zp, zsq; + int i, i1m, k, mz, nz = 0; + + ierr = 0; + if (z <= 0.0) + goto L70; + if (z > 101.0) + goto L10; + nz = (int)z; + fz = z - (double)nz; + if (fz > 0.0) + goto L10; + if (nz > 100) + goto L10; + return gln[nz - 1]; + L10: + wdtol = d1mach(4); + wdtol = Math.Max(wdtol, 5e-19); + i1m = i1mach(14); + rln = d1mach(5) * (double)i1m; + fln = Math.Min(rln, 20.0); + fln = Math.Max(fln, 3.0); + fln += -3.0; + zm = 1.8 + 0.3875 * fln; + mz = (int)zm + 1; + zmin = (double)mz; + zdmy = z; + zinc = 0.0; + if (z >= zmin) + goto L20; + zinc = zmin - nz; + zdmy = z + zinc; + L20: + zp = 1.0 / zdmy; + t1 = cf[0] * zp; + s = t1; + if (zp < wdtol) + goto L40; + zsq = zp * zp; + tst = t1 * wdtol; + for (k = 2; k <= 22; k++) + { + zp *= zsq; + trm = cf[k - 1] * zp; + if (Math.Abs(trm) < tst) + goto L40; + s += trm; + } + + L40: + if (zinc != 0.0) + goto L50; + tlg = Math.Log(z); + return z * (tlg - 1.0) + (con - tlg) * 0.5 + s; + L50: + zp = 1.0; + nz = (int)zinc; + for (i = 1; i <= nz; i++) + { + zp *= z + (i - 1); + } + + tlg = Math.Log(zdmy); + return zdmy * (tlg - 1.0) - Math.Log(zp) + (con - tlg) * 0.5 + s; + L70: + ierr = 1; + return d1mach(2); + } + + #endregion + + #region Fortran utilities + + private static double d1mach(int i) + { + #region Description + + //***BEGIN PROLOGUE D1MACH + //***DATE WRITTEN 750101 (YYMMDD) + //***REVISION DATE 890213 (YYMMDD) + //***CATEGORY NO. R1 + //***KEYWORDS LIBRARY=SLATEC,TYPE=DOUBLE PRECISION(R1MACH-S D1MACH-D), + // MACHINE CONSTANTS + //***AUTHOR FOX, P. A., (BELL LABS) + // HALL, A. D., (BELL LABS) + // SCHRYER, N. L., (BELL LABS) + //***PURPOSE Returns double precision machine dependent constants + //***DESCRIPTION + // + // D1MACH can be used to obtain machine-dependent parameters + // for the local machine environment. It is a function + // subprogram with one (input) argument, and can be called + // as follows, for example + // + // D = D1MACH(I) + // + // where I=1,...,5. The (output) value of D above is + // determined by the (input) value of I. The results for + // various values of I are discussed below. + // + // D1MACH( 1) = B**(EMIN-1), the smallest positive magnitude. + // D1MACH( 2) = B**EMAX*(1 - B**(-T)), the largest magnitude. + // D1MACH( 3) = B**(-T), the smallest relative spacing. + // D1MACH( 4) = B**(1-T), the largest relative spacing. + // D1MACH( 5) = LOG10(B) + // + // Assume double precision numbers are represented in the T-digit, + // base-B form + // + // sign (B**E)*( (X(1)/B) + ... + (X(T)/B**T) ) + // + // where 0 .LE. X(I) .LT. B for I=1,...,T, 0 .LT. X(1), and + // EMIN .LE. E .LE. EMAX. + // + // The values of B, T, EMIN and EMAX are provided in I1MACH as + // follows: + // I1MACH(10) = B, the base. + // I1MACH(14) = T, the number of base-B digits. + // I1MACH(15) = EMIN, the smallest exponent E. + // I1MACH(16) = EMAX, the largest exponent E. + // + // To alter this function for a particular environment, + // the desired set of DATA statements should be activated by + // removing the C from column 1. Also, the values of + // D1MACH(1) - D1MACH(4) should be checked for consistency + // with the local operating system. + // + //***REFERENCES FOX P.A., HALL A.D., SCHRYER N.L.,*FRAMEWORK FOR A + // PORTABLE LIBRARY*, ACM TRANSACTIONS ON MATHEMATICAL + // SOFTWARE, VOL. 4, NO. 2, JUNE 1978, PP. 177-188. + //***ROUTINES CALLED XERROR + //***END PROLOGUE D1MACH + + #endregion + + const int FLT_RADIX = 2; // the radix used by the representation of all floating-point types + const double DBL_EPSILON = 2.2204460492503130808E-16; // 2^(1 - 53) + const double DBL_MAX = double.MaxValue; // 2^1024 * (1 - 2^(-53)) + const double DBL_MIN = 2.2250738585072013831E-308; // 2^(-1021 - 1) + + switch (i) + { + case 1: + return DBL_MIN; // the smallest positive magnitude. + case 2: + return DBL_MAX; // the largest magnitude. + case 3: + return DBL_EPSILON / FLT_RADIX; // return Precision.DoublePrecision; // the smallest relative spacing. + case 4: + return DBL_EPSILON; // return Precision.PositiveDoublePrecision; // the largest relative spacing. + case 5: + return Math.Log10(FLT_RADIX); + } + + return 0; + } + + private static int i1mach(int i) + { + #region Description + + //***BEGIN PROLOGUE I1MACH + //***DATE WRITTEN 750101 (YYMMDD) + //***REVISION DATE 890213 (YYMMDD) + //***CATEGORY NO. R1 + //***KEYWORDS LIBRARY=SLATEC,TYPE=INTEGER(I1MACH-I),MACHINE CONSTANTS + //***AUTHOR FOX, P. A., (BELL LABS) + // HALL, A. D., (BELL LABS) + // SCHRYER, N. L., (BELL LABS) + //***PURPOSE Returns integer machine dependent constants + //***DESCRIPTION + // + // I1MACH can be used to obtain machine-dependent parameters + // for the local machine environment. It is a function + // subroutine with one (input) argument, and can be called + // as follows, for example + // + // K = I1MACH(I) + // + // where I=1,...,16. The (output) value of K above is + // determined by the (input) value of I. The results for + // various values of I are discussed below. + // + // I/O unit numbers. + // I1MACH( 1) = the standard input unit. + // I1MACH( 2) = the standard output unit. + // I1MACH( 3) = the standard punch unit. + // I1MACH( 4) = the standard error message unit. + // + // Words. + // I1MACH( 5) = the number of bits per integer storage unit. + // I1MACH( 6) = the number of characters per integer storage unit. + // + // Integers. + // assume integers are represented in the S-digit, base-A form + // + // sign ( X(S-1)*A**(S-1) + ... + X(1)*A + X(0) ) + // + // where 0 .LE. X(I) .LT. A for I=0,...,S-1. + // I1MACH( 7) = A, the base. + // I1MACH( 8) = S, the number of base-A digits. + // I1MACH( 9) = A**S - 1, the largest magnitude. + // + // Floating-Point Numbers. + // Assume floating-point numbers are represented in the T-digit, + // base-B form + // sign (B**E)*( (X(1)/B) + ... + (X(T)/B**T) ) + // + // where 0 .LE. X(I) .LT. B for I=1,...,T, + // 0 .LT. X(1), and EMIN .LE. E .LE. EMAX. + // I1MACH(10) = B, the base. + // + // Single-Precision + // I1MACH(11) = T, the number of base-B digits. + // I1MACH(12) = EMIN, the smallest exponent E. + // I1MACH(13) = EMAX, the largest exponent E. + // + // Double-Precision + // I1MACH(14) = T, the number of base-B digits. + // I1MACH(15) = EMIN, the smallest exponent E. + // I1MACH(16) = EMAX, the largest exponent E. + // + // To alter this function for a particular environment, + // the desired set of DATA statements should be activated by + // removing the C from column 1. Also, the values of + // I1MACH(1) - I1MACH(4) should be checked for consistency + // with the local operating system. + // + //***REFERENCES FOX P.A., HALL A.D., SCHRYER N.L.,*FRAMEWORK FOR A + // PORTABLE LIBRARY*, ACM TRANSACTIONS ON MATHEMATICAL + // SOFTWARE, VOL. 4, NO. 2, JUNE 1978, PP. 177-188. + //***ROUTINES CALLED (NONE) + //***END PROLOGUE I1MACH + + #endregion + + switch (i) + { + case 9: + return Int32.MaxValue; // the largest magnitude of integer = 2^31 - 1 = 2147483647 + case 14: + return 53; // return Precision.DoubleWidth; // the number of base-2 digits. + case 15: + return -1021; // EMIN, the smallest exponent E. + case 16: + return 1024; // EMAX, the largest exponent E = 2^10 + } + + return 0; + } + + private static double dsign(double a, double b) + { + // Returns the absolute value of A times the sign of B + double x = (a >= 0 ? a : -a); + return (b >= 0 ? x : -x); + } + + private static double zabs(double zr, double zi) + { + #region Description + + //***BEGIN PROLOGUE ZABS + //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZBESY,ZAIRY,ZBIRY + // + // ZABS COMPUTES THE ABSOLUTE VALUE OR MAGNITUDE OF A DOUBLE + // PRECISION COMPLEX VARIABLE CMPLX(ZR,ZI) + // + //***ROUTINES CALLED (NONE) + //***END PROLOGUE ZABS + + #endregion + + double u, v, q, s; + + u = Math.Abs(zr); + v = Math.Abs(zi); + s = u + v; + //---------------------------------------------------------------------- - + // S * 1.0D0 MAKES AN UNNORMALIZED UNDERFLOW ON CDC MACHINES INTO A + // TRUE FLOATING ZERO + //----------------------------------------------------------------------- + s = s * 1.0; + if (s == 0.0) + goto L20; + if (u > v) + goto L10; + q = u / v; + return v * Math.Sqrt(1.0 + q * q); + L10: + q = v / u; + return u * Math.Sqrt(1.0 + q * q); + L20: + return 0.0; + } + + private static int zdiv(double ar, double ai, double br, double bi, ref double cr, ref double ci) + { + #region Description + + //***BEGIN PROLOGUE ZDIV + //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZBESY,ZAIRY,ZBIRY + // + // DOUBLE PRECISION COMPLEX DIVIDE C=A/B. + // + //***ROUTINES CALLED ZABS + //***END PROLOGUE ZDIV + + #endregion + + double bm, ca, cb, cc, cd; + + bm = 1.0 / zabs(br, bi); + cc = br * bm; + cd = bi * bm; + ca = (ar * cc + ai * cd) * bm; + cb = (ai * cc - ar * cd) * bm; + cr = ca; + ci = cb; + return 0; + } + + private static int zexp(double ar, double ai, ref double br, ref double bi) + { + #region Description + + //***BEGIN PROLOGUE ZEXP + //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZBESY,ZAIRY,ZBIRY + // + // DOUBLE PRECISION COMPLEX EXPONENTIAL FUNCTION B=EXP(A) + // + //***ROUTINES CALLED (NONE) + //***END PROLOGUE ZEXP + + #endregion + + double zm, ca, cb; + + zm = Math.Exp(ar); + ca = zm * Math.Cos(ai); + cb = zm * Math.Sin(ai); + br = ca; + bi = cb; + return 0; + } + + private static int zlog(double ar, double ai, ref double br, ref double bi, ref int ierr) + { + #region Description + + //***BEGIN PROLOGUE ZLOG + //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZBESY,ZAIRY,ZBIRY + // + // DOUBLE PRECISION COMPLEX LOGARITHM B=CLOG(A) + // IERR=0,NORMAL RETURN IERR=1, Z=CMPLX(0.0,0.0) + //***ROUTINES CALLED ZABS + //***END PROLOGUE ZLOG + + #endregion + + double dhpi = 1.570796326794896619231321696; + double dpi = 3.141592653589793238462643383; + double zm, dtheta; + + ierr = 0; + if (ar == 0.0) + goto L10; + if (ai == 0.0) + goto L20; + dtheta = Math.Atan(ai / ar); + if (dtheta <= 0.0) + goto L40; + if (ar < 0.0) + dtheta = dtheta - dpi; + goto L50; + L10: + if (ai == 0.0) + goto L60; + bi = dhpi; + br = Math.Log(Math.Abs(ai)); + if (ai < 0.0) + bi = -bi; + return 0; + L20: + if (ar > 0.0) + goto L30; + br = Math.Log(Math.Abs(ar)); + bi = dpi; + return 0; + L30: + br = Math.Log(ar); + bi = 0.0; + return 0; + L40: + if (ar < 0.0) + dtheta = dtheta + dpi; + L50: + zm = zabs(ar, ai); + br = Math.Log(zm); + bi = dtheta; + return 0; + L60: + ierr = 1; + return 0; + } + + private static int zmlt(double ar, double ai, double br, double bi, ref double cr, ref double ci) + { + #region Description + + //***BEGIN PROLOGUE ZMLT + //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZBESY,ZAIRY,ZBIRY + // + // DOUBLE PRECISION COMPLEX MULTIPLY, C=A*B. + // + //***ROUTINES CALLED (NONE) + //***END PROLOGUE ZMLT + + #endregion + + double ca, cb; + + ca = ar * br - ai * bi; + cb = ar * bi + ai * br; + cr = ca; + ci = cb; + return 0; + } + + private static int zsqrt(double ar, double ai, ref double br, ref double bi) + { + #region Description + + //***BEGIN PROLOGUE ZSQRT + //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZBESY,ZAIRY,ZBIRY + // + // DOUBLE PRECISION COMPLEX SQUARE ROOT, B=CSQRT(A) + // + //***ROUTINES CALLED ZABS + //***END PROLOGUE ZSQRT + + #endregion + + const double drt = 0.7071067811865475244008443621; // sqrt(2) + const double dpi = 3.141592653589793238462643383; + double zm, dtheta; + + zm = zabs(ar, ai); + zm = Math.Sqrt(zm); + if (ar == 0.0) + goto L10; + if (ai == 0.0) + goto L20; + dtheta = Math.Atan(ai / ar); + if (dtheta <= 0.0) + goto L40; + if (ar < 0.0) + dtheta = dtheta - dpi; + goto L50; + L10: + if (ai > 0.0) + goto L60; + if (ai < 0.0) + goto L70; + br = 0.0; + bi = 0.0; + return 0; + L20: + if (ar > 0.0) + goto L30; + br = 0.0; + bi = Math.Sqrt(Math.Abs(ar)); + return 0; + L30: + br = Math.Sqrt(ar); + bi = 0.0; + return 0; + L40: + if (ar < 0.0) + dtheta = dtheta + dpi; + L50: + dtheta = dtheta * 0.5; + br = zm * Math.Cos(dtheta); + bi = zm * Math.Sin(dtheta); + return 0; + L60: + br = zm * drt; + bi = zm * drt; + return 0; + L70: + br = zm * drt; + bi = -zm * drt; + return 0; + } + + #endregion + + #region Subroutines to calculate the Bessel functions + + private static int zacai(double zr, double zi, double fnu, int kode, int mr, int n, double[] yr, double[] yi, ref int nz, double rl, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZACAI + //***REFER TO ZAIRY + // + // ZACAI APPLIES THE ANALYTIC CONTINUATION FORMULA + // + // K(FNU,ZN*EXP(MP))=K(FNU,ZN)*EXP(-MP*FNU) - MP*I(FNU,ZN) + // MP=PI*MR*CMPLX(0.0,1.0) + // + // TO CONTINUE THE K FUNCTION FROM THE RIGHT HALF TO THE LEFT + // HALF Z PLANE FOR USE WITH ZAIRY WHERE FNU=1/3 OR 2/3 AND N=1. + // ZACAI IS THE SAME AS ZACON WITH THE PARTS FOR LARGER ORDERS AND + // RECURRENCE REMOVED. A RECURSIVE CALL TO ZACON CAN RESULT IF ZACON + // IS CALLED FROM ZAIRY. + // + //***ROUTINES CALLED ZASYI,ZBKNU,ZMLRI,ZSERI,ZS1S2,D1MACH,ZABS + //***END PROLOGUE ZACAI + + #endregion + + const double pi = 3.14159265358979323846264338327950; + double arg, ascle, az, csgnr, csgni, cspnr; + double cspni, c1r, c1i, c2r, c2i, dfnu, fmr; + double sgn, yy, znr, zni; + int inu, iuf, nn, nw = 0; + + double[] cyr = new double[2]; + double[] cyi = new double[2]; + + nz = 0; + znr = -zr; + zni = -zi; + az = zabs(zr, zi); + nn = n; + dfnu = fnu + (double)(n - 1); + if (az <= 2.0) + goto L10; + if (az * az * 0.25 > dfnu + 1.0) + goto L20; + L10: + // ----------------------------------------------------------------------- + // POWER SERIES FOR THE I FUNCTION + // ----------------------------------------------------------------------- + zseri(znr, zni, fnu, kode, nn, yr, yi, ref nw, tol, elim, alim); + goto L40; + L20: + if (az < rl) + goto L30; + // ----------------------------------------------------------------------- + // ASYMPTOTIC EXPANSION FOR LARGE Z FOR THE I FUNCTION + // ----------------------------------------------------------------------- + zasyi(znr, zni, fnu, kode, nn, yr, yi, ref nw, rl, tol, elim, alim); + if (nw < 0) + goto L80; + goto L40; + L30: + // ----------------------------------------------------------------------- + // MILLER ALGORITHM NORMALIZED BY THE SERIES FOR THE I FUNCTION + // ----------------------------------------------------------------------- + zmlri(znr, zni, fnu, kode, nn, yr, yi, ref nw, tol); + if (nw < 0) + goto L80; + L40: + // ----------------------------------------------------------------------- + // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE FOR THE K FUNCTION + // ----------------------------------------------------------------------- + zbknu(znr, zni, fnu, kode, 1, cyr, cyi, ref nw, tol, elim, alim); + if (nw != 0) + goto L80; + fmr = (double)mr; + sgn = -dsign(pi, fmr); + csgnr = 0.0; + csgni = sgn; + if (kode == 1) + goto L50; + yy = -zni; + csgnr = -csgni * Math.Sin(yy); + csgni = csgni * Math.Cos(yy); + L50: + // ----------------------------------------------------------------------- + // CALCULATE CSPN=EXP(FNU*PI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE + // WHEN FNU IS LARGE + // ----------------------------------------------------------------------- + inu = (int)fnu; + arg = (fnu - (double)inu) * sgn; + cspnr = Math.Cos(arg); + cspni = Math.Sin(arg); + if (inu % 2 == 0) + goto L60; + cspnr = -cspnr; + cspni = -cspni; + L60: + c1r = cyr[0]; + c1i = cyi[0]; + c2r = yr[0]; + c2i = yi[0]; + if (kode == 1) + goto L70; + iuf = 0; + ascle = d1mach(1) * 1.0E3 / tol; + zs1s2(znr, zni, ref c1r, ref c1i, ref c2r, ref c2i, ref nw, ascle, alim, ref iuf); + nz += nw; + L70: + yr[0] = cspnr * c1r - cspni * c1i + csgnr * c2r - csgni * c2i; + yi[0] = cspnr * c1i + cspni * c1r + csgnr * c2i + csgni * c2r; + return 0; + L80: + nz = -1; + if (nw == -2) + nz = -2; + return 0; + } + + private static int zacon(double zr, double zi, double fnu, int kode, int mr, int n, double[] yr, double[] yi, ref int nz, double rl, double fnul, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZACON + //***REFER TO ZBESK,ZBESH + // + // ZACON APPLIES THE ANALYTIC CONTINUATION FORMULA + // + // K(FNU,ZN*EXP(MP))=K(FNU,ZN)*EXP(-MP*FNU) - MP*I(FNU,ZN) + // MP=PI*MR*CMPLX(0.0,1.0) + // + // TO CONTINUE THE K FUNCTION FROM THE RIGHT HALF TO THE LEFT + // HALF Z PLANE + // + //***ROUTINES CALLED ZBINU,ZBKNU,ZS1S2,D1MACH,ZABS,ZMLT + //***END PROLOGUE ZACON + + #endregion + + const double coner = 1.0; + const double pi = 3.14159265358979323846264338327950; + const double zeror = 0.0; + + double arg, ascle, as2, azn, bscle, cki; + double ckr, cpn, cscl, cscr, csgni, csgnr, cspni, cspnr; + double csr, c1i, c1m, c1r, c2i, c2r, fmr; + double fn, pti = 0, ptr = 0, razn, rzi, rzr, sc1i, sc1r; + double sc2i = 0, sc2r = 0, sgn, spn, sti = 0, str = 0, s1i, s1r, s2i, s2r; + double yy, zni, znr; + int i, inu, iuf, kflag, nn, nw = 0; + + double[] bry = new double[3]; + double[] csrr = new double[3]; + double[] cssr = new double[3]; + double[] cyi = new double[2]; + double[] cyr = new double[2]; + + nz = 0; + znr = -zr; + zni = -zi; + nn = n; + zbinu(znr, zni, fnu, kode, nn, yr, yi, ref nw, rl, fnul, tol, elim, alim); + if (nw < 0) + goto L90; + // ----------------------------------------------------------------------- + // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE FOR THE K FUNCTION + // ----------------------------------------------------------------------- + nn = Math.Min(2, n); + zbknu(znr, zni, fnu, kode, nn, cyr, cyi, ref nw, tol, elim, alim); + if (nw != 0) + goto L90; + s1r = cyr[0]; + s1i = cyi[0]; + fmr = (double)mr; + sgn = -dsign(pi, fmr); + csgnr = zeror; + csgni = sgn; + if (kode == 1) + goto L10; + yy = -zni; + cpn = Math.Cos(yy); + spn = Math.Sin(yy); + zmlt(csgnr, csgni, cpn, spn, ref csgnr, ref csgni); + L10: + // ----------------------------------------------------------------------- + // CALCULATE CSPN=EXP(FNU*PI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE + // WHEN FNU IS LARGE + // ----------------------------------------------------------------------- + inu = (int)fnu; + arg = (fnu - (double)inu) * sgn; + cpn = Math.Cos(arg); + spn = Math.Sin(arg); + cspnr = cpn; + cspni = spn; + if (inu % 2 == 0) + goto L20; + cspnr = -cspnr; + cspni = -cspni; + L20: + iuf = 0; + c1r = s1r; + c1i = s1i; + c2r = yr[0]; + c2i = yi[0]; + ascle = 1.0E3 * d1mach(1) / tol; + if (kode == 1) + goto L30; + zs1s2(znr, zni, ref c1r, ref c1i, ref c2r, ref c2i, ref nw, ascle, alim, ref iuf); + nz += nw; + sc1r = c1r; + sc1i = c1i; + L30: + zmlt(cspnr, cspni, c1r, c1i, ref str, ref sti); + zmlt(csgnr, csgni, c2r, c2i, ref ptr, ref pti); + yr[0] = str + ptr; + yi[0] = sti + pti; + if (n == 1) + return 0; + cspnr = -cspnr; + cspni = -cspni; + s2r = cyr[1]; + s2i = cyi[1]; + c1r = s2r; + c1i = s2i; + c2r = yr[1]; + c2i = yi[1]; + if (kode == 1) + goto L40; + zs1s2(znr, zni, ref c1r, ref c1i, ref c2r, ref c2i, ref nw, ascle, alim, ref iuf); + nz += nw; + sc2r = c1r; + sc2i = c1i; + L40: + zmlt(cspnr, cspni, c1r, c1i, ref str, ref sti); + zmlt(csgnr, csgni, c2r, c2i, ref ptr, ref pti); + yr[1] = str + ptr; + yi[1] = sti + pti; + if (n == 2) + return 0; + cspnr = -cspnr; + cspni = -cspni; + azn = zabs(znr, zni); + razn = 1.0 / azn; + str = znr * razn; + sti = -zni * razn; + rzr = (str + str) * razn; + rzi = (sti + sti) * razn; + fn = fnu + 1.0; + ckr = fn * rzr; + cki = fn * rzi; + // ----------------------------------------------------------------------- + // SCALE NEAR EXPONENT EXTREMES DURING RECURRENCE ON K FUNCTIONS + // ----------------------------------------------------------------------- + cscl = 1.0 / tol; + cscr = tol; + cssr[0] = cscl; + cssr[1] = coner; + cssr[2] = cscr; + csrr[0] = cscr; + csrr[1] = coner; + csrr[2] = cscl; + bry[0] = ascle; + bry[1] = 1.0 / ascle; + bry[2] = d1mach(2); + as2 = zabs(s2r, s2i); + kflag = 2; + if (as2 > bry[0]) + goto L50; + kflag = 1; + goto L60; + L50: + if (as2 < bry[1]) + goto L60; + kflag = 3; + L60: + bscle = bry[kflag - 1]; + s1r *= cssr[kflag - 1]; + s1i *= cssr[kflag - 1]; + s2r *= cssr[kflag - 1]; + s2i *= cssr[kflag - 1]; + csr = csrr[kflag - 1]; + for (i = 3; i <= n; i++) + { + str = s2r; + sti = s2i; + s2r = ckr * str - cki * sti + s1r; + s2i = ckr * sti + cki * str + s1i; + s1r = str; + s1i = sti; + c1r = s2r * csr; + c1i = s2i * csr; + str = c1r; + sti = c1i; + c2r = yr[i - 1]; + c2i = yi[i - 1]; + if (kode == 1) + goto L70; + if (iuf < 0) + goto L70; + zs1s2(znr, zni, ref c1r, ref c1i, ref c2r, ref c2i, ref nw, ascle, alim, ref iuf); + nz += nw; + sc1r = sc2r; + sc1i = sc2i; + sc2r = c1r; + sc2i = c1i; + if (iuf != 3) + goto L70; + iuf = -4; + s1r = sc1r * cssr[kflag - 1]; + s1i = sc1i * cssr[kflag - 1]; + s2r = sc2r * cssr[kflag - 1]; + s2i = sc2i * cssr[kflag - 1]; + str = sc2r; + sti = sc2i; + L70: + ptr = cspnr * c1r - cspni * c1i; + pti = cspnr * c1i + cspni * c1r; + yr[i - 1] = ptr + csgnr * c2r - csgni * c2i; + yi[i - 1] = pti + csgnr * c2i + csgni * c2r; + ckr += rzr; + cki += rzi; + cspnr = -cspnr; + cspni = -cspni; + if (kflag >= 3) + goto L80; + ptr = Math.Abs(c1r); + pti = Math.Abs(c1i); + c1m = Math.Max(ptr, pti); + if (c1m <= bscle) + goto L80; + kflag++; + bscle = bry[kflag - 1]; + s1r *= csr; + s1i *= csr; + s2r = str; + s2i = sti; + s1r *= cssr[kflag - 1]; + s1i *= cssr[kflag - 1]; + s2r *= cssr[kflag - 1]; + s2i *= cssr[kflag - 1]; + csr = csrr[kflag - 1]; + L80: + ; + } + + return 0; + L90: + nz = -1; + if (nw == -2) + nz = -2; + return 0; + } + + private static int zasyi(double zr, double zi, double fnu, int kode, int n, double[] yr, double[] yi, ref int nz, double rl, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZASYI + //***REFER TO ZBESI,ZBESK + // + // ZASYI COMPUTES THE I BESSEL FUNCTION FOR REAL(Z).GE.0.0 BY + // MEANS OF THE ASYMPTOTIC EXPANSION FOR LARGE CABS(Z) IN THE + // REGION CABS(Z).GT.MAX(RL,FNU*FNU/2). NZ=0 IS A NORMAL RETURN. + // NZ.LT.0 INDICATES AN OVERFLOW ON KODE=1. + // + //***ROUTINES CALLED D1MACH,ZABS,ZDIV,ZEXP,ZMLT,ZSQRT + //***END PROLOGUE ZASYI + + #endregion + + const double rtpi = .159154943091895336; + const double zeror = 0.0; + const double zeroi = 0.0; + const double coner = 1.0; + const double conei = 0.0; + const double pi = 3.14159265358979323846264338327950; + + double aa, aez, ak, ak1i, ak1r, arg, arm, atol; + double az, bb, bk, cki = 0, ckr = 0, cs1i, cs1r, cs2i, cs2r, czi; + double czr, dfnu, dki, dkr, dnu2, ezi, ezr, fdn, p1i; + double p1r, raz, rtr1, rzi, rzr, s, sgn, sqk, sti, str, s2i; + double s2r, tzi, tzr; + int i, ib, il, inu, j, jl, k, koded, m, nn; + + nz = 0; + az = zabs(zr, zi); + arm = d1mach(1) * 1.0E3; + rtr1 = Math.Sqrt(arm); + il = Math.Min(2, n); + dfnu = fnu + (n - il); + // ----------------------------------------------------------------------- + // OVERFLOW TEST + // ----------------------------------------------------------------------- + raz = 1.0 / az; + str = zr * raz; + sti = -zi * raz; + ak1r = rtpi * str * raz; + ak1i = rtpi * sti * raz; + zsqrt(ak1r, ak1i, ref ak1r, ref ak1i); + czr = zr; + czi = zi; + if (kode != 2) + goto L10; + czr = zeror; + czi = zi; + L10: + if (Math.Abs(czr) > elim) + goto L100; + dnu2 = dfnu + dfnu; + koded = 1; + if (Math.Abs(czr) > alim && n > 2) + goto L20; + koded = 0; + zexp(czr, czi, ref str, ref sti); + zmlt(ak1r, ak1i, str, sti, ref ak1r, ref ak1i); + L20: + fdn = 0.0; + if (dnu2 > rtr1) + fdn = dnu2 * dnu2; + ezr = zr * 8.0; + ezi = zi * 8.0; + // ----------------------------------------------------------------------- + // WHEN Z IS IMAGINARY, THE ERROR TEST MUST BE MADE RELATIVE TO THE + // FIRST RECIPROCAL POWER SINCE THIS IS THE LEADING TERM OF THE + // EXPANSION FOR THE IMAGINARY PART. + // ----------------------------------------------------------------------- + aez = 8.0 * az; + s = tol / aez; + jl = (int)(rl + rl) + 2; + p1r = zeror; + p1i = zeroi; + if (zi == 0.0) + goto L30; + // ----------------------------------------------------------------------- + // CALCULATE EXP(PI*(0.5+FNU+N-IL)*I) TO MINIMIZE LOSSES OF + // SIGNIFICANCE WHEN FNU OR N IS LARGE + // ----------------------------------------------------------------------- + inu = (int)fnu; + arg = (fnu - (double)inu) * pi; + inu = inu + n - il; + ak = -Math.Sin(arg); + bk = Math.Cos(arg); + if (zi < 0.0) + bk = -bk; + p1r = ak; + p1i = bk; + if (inu % 2 == 0) + goto L30; + p1r = -p1r; + p1i = -p1i; + L30: + for (k = 1; k <= il; k++) + { + sqk = fdn - 1.0; + atol = s * Math.Abs(sqk); + sgn = 1.0; + cs1r = coner; + cs1i = conei; + cs2r = coner; + cs2i = conei; + ckr = coner; + cki = conei; + ak = 0.0; + aa = 1.0; + bb = aez; + dkr = ezr; + dki = ezi; + for (j = 1; j <= jl; j++) + { + zdiv(ckr, cki, dkr, dki, ref str, ref sti); + ckr = str * sqk; + cki = sti * sqk; + cs2r += ckr; + cs2i += cki; + sgn = -sgn; + cs1r += ckr * sgn; + cs1i += cki * sgn; + dkr += ezr; + dki += ezi; + aa = aa * Math.Abs(sqk) / bb; + bb += aez; + ak += 8.0; + sqk -= ak; + if (aa <= atol) + goto L50; + } + + goto L110; + L50: + s2r = cs1r; + s2i = cs1i; + if (zr + zr >= elim) + goto L60; + tzr = zr + zr; + tzi = zi + zi; + zexp(-tzr, -tzi, ref str, ref sti); + zmlt(str, sti, p1r, p1i, ref str, ref sti); + zmlt(str, sti, cs2r, cs2i, ref str, ref sti); + s2r += str; + s2i += sti; + L60: + fdn = fdn + dfnu * 8.0 + 4.0; + p1r = -p1r; + p1i = -p1i; + m = n - il + k; + yr[m - 1] = s2r * ak1r - s2i * ak1i; + yi[m - 1] = s2r * ak1i + s2i * ak1r; + } + + if (n <= 2) + return 0; + nn = n; + k = nn - 2; + ak = (double)k; + str = zr * raz; + sti = -zi * raz; + rzr = (str + str) * raz; + rzi = (sti + sti) * raz; + ib = 3; + for (i = ib; i <= nn; i++) + { + yr[k - 1] = (ak + fnu) * (rzr * yr[k] - rzi * yi[k]) + yr[k + 1]; + yi[k - 1] = (ak + fnu) * (rzr * yi[k] + rzi * yr[k]) + yi[k + 1]; + ak = ak - 1.0; + k--; + } + + if (koded == 0) + return 0; + zexp(czr, czi, ref ckr, ref cki); + for (i = 1; i <= nn; i++) + { + str = yr[i - 1] * ckr - yi[i - 1] * cki; + yi[i - 1] = yr[i] * cki + yi[i - 1] * ckr; + yr[i - 1] = str; + } + + return 0; + L100: + nz = -1; + return 0; + L110: + nz = -2; + return 0; + } + + private static int zbinu(double zr, double zi, double fnu, int kode, int n, double[] cyr, double[] cyi, ref int nz, double rl, double fnul, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZBINU + //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZAIRY,ZBIRY + // + // ZBINU COMPUTES THE I FUNCTION IN THE RIGHT HALF Z PLANE + // + //***ROUTINES CALLED ZABS,ZASYI,ZBUNI,ZMLRI,ZSERI,ZUOIK,ZWRSK + //***END PROLOGUE ZBINU + + #endregion + + const double zeror = 0.0; + const double zeroi = 0.0; + + double az, dfnu; + int i, inw, nlast = 0, nn, nui = 0, nw = 0; + + double[] cwi = new double[2]; + double[] cwr = new double[2]; + + nz = 0; + az = zabs(zr, zi); + nn = n; + dfnu = fnu + (double)(n - 1); + if (az <= 2.0) + goto L10; + if (az * az * 0.25 > dfnu + 1.0) + goto L20; + L10: + // ----------------------------------------------------------------------- + // POWER SERIES + // ----------------------------------------------------------------------- + zseri(zr, zi, fnu, kode, nn, cyr, cyi, ref nw, tol, elim, alim); + inw = Math.Abs(nw); + nz = nz + inw; + nn = nn - inw; + if (nn == 0) + return 0; + if (nw >= 0) + goto L120; + dfnu = fnu + (double)(nn - 1); + L20: + if (az < rl) + goto L40; + if (dfnu <= 1.0) + goto L30; + if (az + az < dfnu * dfnu) + goto L50; + // ----------------------------------------------------------------------- + // ASYMPTOTIC EXPANSION FOR LARGE Z + // ----------------------------------------------------------------------- + L30: + zasyi(zr, zi, fnu, kode, nn, cyr, cyi, ref nw, rl, tol, elim, alim); + if (nw < 0) + goto L130; + goto L120; + L40: + if (dfnu <= 1.0) + goto L70; + L50: + // ----------------------------------------------------------------------- + // OVERFLOW AND UNDERFLOW TEST ON I SEQUENCE FOR MILLER ALGORITHM + // ----------------------------------------------------------------------- + zuoik(zr, zi, fnu, kode, 1, nn, cyr, cyi, ref nw, tol, elim, alim); + if (nw < 0) + goto L130; + nz = nz + nw; + nn = nn - nw; + if (nn == 0) + return 0; + dfnu = fnu + (double)(nn - 1); + if (dfnu > fnul) + goto L110; + if (az > fnul) + goto L110; + L60: + if (az > rl) + goto L80; + L70: + // ----------------------------------------------------------------------- + // MILLER ALGORITHM NORMALIZED BY THE SERIES + // ----------------------------------------------------------------------- + zmlri(zr, zi, fnu, kode, nn, cyr, cyi, ref nw, tol); + if (nw < 0) + goto L130; + goto L120; + L80: + // ----------------------------------------------------------------------- + // MILLER ALGORITHM NORMALIZED BY THE WRONSKIAN + // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + // OVERFLOW TEST ON K FUNCTIONS USED IN WRONSKIAN + // ----------------------------------------------------------------------- + zuoik(zr, zi, fnu, kode, 2, 2, cwr, cwi, ref nw, tol, elim, alim); + if (nw >= 0) + goto L100; + nz = nn; + for (i = 1; i <= nn; i++) + { + cyr[i - 1] = zeror; + cyi[i - 1] = zeroi; + } + + return 0; + L100: + if (nw > 0) + goto L130; + zwrsk(zr, zi, fnu, kode, nn, cyr, cyi, ref nw, cwr, cwi, tol, elim, alim); + if (nw < 0) + goto L130; + goto L120; + L110: + // ----------------------------------------------------------------------- + // INCREMENT FNU+NN-1 UP TO FNUL, COMPUTE AND RECUR BACKWARD + // ----------------------------------------------------------------------- + nui = (int)(fnul - dfnu) + 1; + nui = Math.Max(nui, 0); + zbuni(zr, zi, fnu, kode, nn, cyr, cyi, ref nw, nui, ref nlast, fnul, tol, elim, alim); + if (nw < 0) + goto L130; + nz = nz + nw; + if (nlast == 0) + goto L120; + nn = nlast; + goto L60; + L120: + return 0; + L130: + nz = -1; + if (nw == -2) + nz = -2; + return 0; + } + + private static int zbknu(double zr, double zi, double fnu, int kode, int n, double[] yr, double[] yi, ref int nz, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZBKNU + //***REFER TO ZBESI,ZBESK,ZAIRY,ZBESH + // + // ZBKNU COMPUTES THE K BESSEL FUNCTION IN THE RIGHT HALF Z PLANE. + // + //***ROUTINES CALLED DGAMLN,I1MACH,D1MACH,ZKSCL,ZSHCH,ZUCHK,ZABS,ZDIV, + // ZEXP,ZLOG,ZMLT,ZSQRT + //***END PROLOGUE ZBKNU + + #endregion + + const int kmax = 30; + const double czeror = 0.0; + const double czeroi = 0.0; + const double coner = 1.0; + const double conei = 0.0; + const double ctwor = 2.0; + const double dpi = 3.14159265358979323846264338327950; + const double r1 = 2.0; + const double rthpi = 1.25331413731550025; + const double spi = 1.90985931710274403; // 6/pi + const double hpi = 1.570796326794896619231321696; + const double fpi = 1.89769999331517738; + const double tth = .666666666666666666; + + double[] cc = { + 0.577215664901532861, + -0.0420026350340952355, + -0.0421977345555443367, + 0.00721894324666309954, + -2.15241674114950973e-4, + -2.01348547807882387e-5, + 1.13302723198169588e-6, + 6.11609510448141582e-9 }; + + double aa, ak, ascle, a1, a2, bb, bk, caz; + double cbi, cbr, cchi = 0, cchr = 0, cki = 0, ckr = 0, coefi = 0, coefr = 0; + double crscr, csclr, cshi = 0, cshr = 0, csi, csr; + double czi = 0, czr = 0, dnu, dnu2 = 0, etest, fc, fhs; + double fi, fk, fks, fmui, fmur, fr, g1, g2, pi, pr, pti = 0; + double ptr = 0, p1i, p1r, p2i, p2m, p2r, qi, qr, rak, rcaz, rzi; + double rzr, s, smui = 0, smur = 0, sti, str, s1i, s1r, s2i = 0, s2r = 0, tm; + double t1, t2, elm; + double celmr, zdr, zdi, alas, helim; + int i, idum = 0, iflag, inu, k, kflag, kk, koded; + int j, ic, inub, nw = 0; + + double[] cyr = new double[2]; + double[] cyi = new double[2]; + double[] cssr = new double[3]; + double[] csrr = new double[3]; + double[] bry = new double[3]; + + caz = zabs(zr, zi); + csclr = 1.0 / tol; + crscr = tol; + cssr[0] = csclr; + cssr[1] = 1.0; + cssr[2] = crscr; + csrr[0] = crscr; + csrr[1] = 1.0; + csrr[2] = csclr; + bry[0] = 1.0E3 * d1mach(1) / tol; + bry[1] = 1.0 / bry[0]; + bry[2] = d1mach(2); + nz = 0; + iflag = 0; + koded = kode; + rcaz = 1.0 / caz; + str = zr * rcaz; + sti = -zi * rcaz; + rzr = (str + str) * rcaz; + rzi = (sti + sti) * rcaz; + inu = (int)(fnu + 0.5); + dnu = fnu - (double)inu; + if (Math.Abs(dnu) == 0.5) + goto L110; + dnu2 = 0.0; + if (Math.Abs(dnu) > tol) + dnu2 = dnu * dnu; + if (caz > r1) + goto L110; + //----------------------------------------------------------------------- + // SERIES FOR ABS(Z).LE.R1 + //----------------------------------------------------------------------- + fc = 1.0; + zlog(rzr, rzi, ref smur, ref smui, ref idum); + fmur = smur * dnu; + fmui = smui * dnu; + zshch(fmur, fmui, ref cshr, ref cshi, ref cchr, ref cchi); + if (dnu == 0.0) + goto L10; + fc = dnu * dpi; + fc = fc / Math.Sin(fc); + smur = cshr / dnu; + smui = cshi / dnu; + L10: + a2 = 1.0 + dnu; + //----------------------------------------------------------------------- + // GAM(1-Z)*GAM(1+Z)=PI*Z/SIN(PI*Z), T1=1/GAM(1-DNU), T2=1/GAM(1+DNU) + //----------------------------------------------------------------------- + t2 = Math.Exp(-dgamln(a2, ref idum)); + t1 = 1.0 / (t2 * fc); + if (Math.Abs(dnu) > 0.1) + goto L40; + //----------------------------------------------------------------------- + // SERIES FOR F0 TO RESOLVE INDETERMINACY FOR SMALL ABS(DNU) + //----------------------------------------------------------------------- + ak = 1.0; + s = cc[0]; + for (k = 2; k <= 8; k++) + { + ak = ak * dnu2; + tm = cc[k - 1] * ak; + s = s + tm; + if (Math.Abs(tm) < tol) + goto L30; + } + + L30: + g1 = -s; + goto L50; + L40: + g1 = (t1 - t2) / (dnu + dnu); + L50: + g2 = (t1 + t2) * 0.5; + fr = fc * (cchr * g1 + smur * g2); + fi = fc * (cchi * g1 + smui * g2); + zexp(fmur, fmui, ref str, ref sti); + pr = 0.5 * str / t2; + pi = 0.5 * sti / t2; + zdiv(0.5, 0.0, str, sti, ref ptr, ref pti); + qr = ptr / t1; + qi = pti / t1; + s1r = fr; + s1i = fi; + s2r = pr; + s2i = pi; + ak = 1.0; + a1 = 1.0; + ckr = coner; + cki = conei; + bk = 1.0 - dnu2; + if (inu > 0 || n > 1) + goto L80; + //----------------------------------------------------------------------- + // GENERATE K(FNU,Z), 0.0D0 .LE. FNU .LT. 0.5D0 AND N=1 + //----------------------------------------------------------------------- + if (caz < tol) + goto L70; + zmlt(zr, zi, zr, zi, ref czr, ref czi); + czr = 0.25 * czr; + czi = 0.25 * czi; + t1 = 0.25 * caz * caz; + L60: + fr = (fr * ak + pr + qr) / bk; + fi = (fi * ak + pi + qi) / bk; + str = 1.0 / (ak - dnu); + pr = pr * str; + pi = pi * str; + str = 1.0 / (ak + dnu); + qr = qr * str; + qi = qi * str; + str = ckr * czr - cki * czi; + rak = 1.0 / ak; + cki = (ckr * czi + cki * czr) * rak; + ckr = str * rak; + s1r = ckr * fr - cki * fi + s1r; + s1i = ckr * fi + cki * fr + s1i; + a1 = a1 * t1 * rak; + bk = bk + ak + ak + 1.0; + ak = ak + 1.0; + if (a1 > tol) + goto L60; + L70: + yr[0] = s1r; + yi[0] = s1i; + if (koded == 1) + return 0; + zexp(zr, zi, ref str, ref sti); + zmlt(s1r, s1i, str, sti, ref yr[0], ref yi[0]); + return 0; + //----------------------------------------------------------------------- + // GENERATE K(DNU,Z) AND K(DNU+1,Z) FOR FORWARD RECURRENCE + //----------------------------------------------------------------------- + L80: + if (caz < tol) + goto L100; + zmlt(zr, zi, zr, zi, ref czr, ref czi); + czr = 0.25 * czr; + czi = 0.25 * czi; + t1 = 0.25 * caz * caz; + L90: + fr = (fr * ak + pr + qr) / bk; + fi = (fi * ak + pi + qi) / bk; + str = 1.0 / (ak - dnu); + pr = pr * str; + pi = pi * str; + str = 1.0 / (ak + dnu); + qr = qr * str; + qi = qi * str; + str = ckr * czr - cki * czi; + rak = 1.0 / ak; + cki = (ckr * czi + cki * czr) * rak; + ckr = str * rak; + s1r = ckr * fr - cki * fi + s1r; + s1i = ckr * fi + cki * fr + s1i; + str = pr - fr * ak; + sti = pi - fi * ak; + s2r = ckr * str - cki * sti + s2r; + s2i = ckr * sti + cki * str + s2i; + a1 = a1 * t1 * rak; + bk = bk + ak + ak + 1.0; + ak = ak + 1.0; + if (a1 > tol) + goto L90; + L100: + kflag = 2; + a1 = fnu + 1.0; + ak = a1 * Math.Abs(smur); + if (ak > alim) + kflag = 3; + str = cssr[kflag - 1]; + p2r = s2r * str; + p2i = s2i * str; + zmlt(p2r, p2i, rzr, rzi, ref s2r, ref s2i); + s1r = s1r * str; + s1i = s1i * str; + if (koded == 1) + goto L210; + zexp(zr, zi, ref fr, ref fi); + zmlt(s1r, s1i, fr, fi, ref s1r, ref s1i); + zmlt(s2r, s2i, fr, fi, ref s2r, ref s2i); + goto L210; + //----------------------------------------------------------------------- + // IFLAG=0 MEANS NO UNDERFLOW OCCURRED + // IFLAG=1 MEANS AN UNDERFLOW OCCURRED- COMPUTATION PROCEEDS WITH + // KODED=2 AND A TEST FOR ON SCALE VALUES IS MADE DURING FORWARD + // RECURSION + //----------------------------------------------------------------------- + L110: + zsqrt(zr, zi, ref str, ref sti); + zdiv(rthpi, czeroi, str, sti, ref coefr, ref coefi); + kflag = 2; + if (koded == 2) + goto L120; + if (zr > alim) + goto L290; + // BLANK LINE + str = Math.Exp(-zr) * cssr[kflag - 1]; + sti = -str * Math.Sin(zi); + str = str * Math.Cos(zi); + zmlt(coefr, coefi, str, sti, ref coefr, ref coefi); + L120: + if (Math.Abs(dnu) == 0.5) + goto L300; + //----------------------------------------------------------------------- + // MILLER ALGORITHM FOR ABS(Z).GT.R1 + //----------------------------------------------------------------------- + ak = Math.Cos(dpi * dnu); + ak = Math.Abs(ak); + if (ak == czeror) + goto L300; + fhs = Math.Abs(0.25 - dnu2); + if (fhs == czeror) + goto L300; + //----------------------------------------------------------------------- + // COMPUTE R2=F(E). IF ABS(Z).GE.R2, USE FORWARD RECURRENCE TO + // DETERMINE THE BACKWARD INDEX K. R2=F(E) IS A STRAIGHT LINE ON + // 12.LE.E.LE.60. E IS COMPUTED FROM 2**(-E)=B**(1-I1MACH(14))= + // TOL WHERE B IS THE BASE OF THE ARITHMETIC. + //----------------------------------------------------------------------- + t1 = (double)(i1mach(14) - 1); + t1 = t1 * d1mach(5) * 3.321928094; + t1 = Math.Max(t1, 12.0); + t1 = Math.Min(t1, 60.0); + t2 = tth * t1 - 6.0; + if (zr != 0.0) + goto L130; + t1 = hpi; + goto L140; + L130: + t1 = Math.Atan(zi / zr); + t1 = Math.Abs(t1); + L140: + if (t2 > caz) + goto L170; + //----------------------------------------------------------------------- + // FORWARD RECURRENCE LOOP WHEN ABS(Z).GE.R2 + //----------------------------------------------------------------------- + etest = ak / (dpi * caz * tol); + fk = coner; + if (etest < coner) + goto L180; + fks = ctwor; + ckr = caz + caz + ctwor; + p1r = czeror; + p2r = coner; + for (i = 1; i <= kmax; i++) + { + ak = fhs / fks; + cbr = ckr / (fk + coner); + ptr = p2r; + p2r = cbr * p2r - p1r * ak; + p1r = ptr; + ckr = ckr + ctwor; + fks = fks + fk + fk + ctwor; + fhs = fhs + fk + fk; + fk = fk + coner; + str = Math.Abs(p2r) * fk; + if (etest < str) + goto L160; + } + + goto L310; + L160: + fk = fk + spi * t1 * Math.Sqrt(t2 / caz); + fhs = Math.Abs(0.25 - dnu2); + goto L180; + L170: + //----------------------------------------------------------------------- + // COMPUTE BACKWARD INDEX K FOR ABS(Z).LT.R2 + //----------------------------------------------------------------------- + a2 = Math.Sqrt(caz); + ak = fpi * ak / (tol * Math.Sqrt(a2)); + aa = 3.0 * t1 / (1.0 + caz); + bb = 14.7 * t1 / (28.0 + caz); + ak = (Math.Log(ak) + caz * Math.Cos(aa) / (1.0 + 0.008 * caz)) / Math.Cos(bb); + fk = 0.12125 * ak * ak / caz + 1.5; + L180: + //----------------------------------------------------------------------- + // BACKWARD RECURRENCE LOOP FOR MILLER ALGORITHM + //----------------------------------------------------------------------- + k = (int)fk; + fk = (double)k; + fks = fk * fk; + p1r = czeror; + p1i = czeroi; + p2r = tol; + p2i = czeroi; + csr = p2r; + csi = p2i; + for (i = 1; i <= k; i++) + { + a1 = fks - fk; + ak = (fks + fk) / (a1 + fhs); + rak = 2.0 / (fk + coner); + cbr = (fk + zr) * rak; + cbi = zi * rak; + ptr = p2r; + pti = p2i; + p2r = (ptr * cbr - pti * cbi - p1r) * ak; + p2i = (pti * cbr + ptr * cbi - p1i) * ak; + p1r = ptr; + p1i = pti; + csr = csr + p2r; + csi = csi + p2i; + fks = a1 - fk + coner; + fk = fk - coner; + } + //----------------------------------------------------------------------- + // COMPUTE (P2/CS)=(P2/ABS(CS))*(CONJG(CS)/ABS(CS)) FOR BETTER + // SCALING + //----------------------------------------------------------------------- + tm = zabs(csr, csi); + ptr = 1.0 / tm; + s1r = p2r * ptr; + s1i = p2i * ptr; + csr = csr * ptr; + csi = -csi * ptr; + zmlt(coefr, coefi, s1r, s1i, ref str, ref sti); + zmlt(str, sti, csr, csi, ref s1r, ref s1i); + if (inu > 0 || n > 1) + goto L200; + zdr = zr; + zdi = zi; + if (iflag == 1) + goto L270; + goto L240; + L200: + //----------------------------------------------------------------------- + // COMPUTE P1/P2=(P1/ABS(P2)*CONJG(P2)/ABS(P2) FOR SCALING + //----------------------------------------------------------------------- + tm = zabs(p2r, p2i); + ptr = 1.0 / tm; + p1r = p1r * ptr; + p1i = p1i * ptr; + p2r = p2r * ptr; + p2i = -p2i * ptr; + zmlt(p1r, p1i, p2r, p2i, ref ptr, ref pti); + str = dnu + 0.5 - ptr; + sti = -pti; + zdiv(str, sti, zr, zi, ref str, ref sti); + str = str + 1.0; + zmlt(str, sti, s1r, s1i, ref s2r, ref s2i); + //----------------------------------------------------------------------- + // FORWARD RECURSION ON THE THREE TERM RECURSION WITH RELATION WITH + // SCALING NEAR EXPONENT EXTREMES ON KFLAG=1 OR KFLAG=3 + //----------------------------------------------------------------------- + L210: + str = dnu + 1.0; + ckr = str * rzr; + cki = str * rzi; + if (n == 1) + inu = inu - 1; + if (inu > 0) + goto L220; + if (n > 1) + goto L215; + s1r = s2r; + s1i = s2i; + L215: + zdr = zr; + zdi = zi; + if (iflag == 1) + goto L270; + goto L240; + L220: + inub = 1; + if (iflag == 1) + goto L261; + L225: + p1r = csrr[kflag - 1]; + ascle = bry[kflag - 1]; + for (i = inub; i <= inu; i++) + { + str = s2r; + sti = s2i; + s2r = ckr * str - cki * sti + s1r; + s2i = ckr * sti + cki * str + s1i; + s1r = str; + s1i = sti; + ckr = ckr + rzr; + cki = cki + rzi; + if (kflag >= 3) + goto L230; + p2r = s2r * p1r; + p2i = s2i * p1r; + str = Math.Abs(p2r); + sti = Math.Abs(p2i); + p2m = Math.Max(str, sti); + if (p2m <= ascle) + goto L230; + kflag = kflag + 1; + ascle = bry[kflag - 1]; + s1r = s1r * p1r; + s1i = s1i * p1r; + s2r = p2r; + s2i = p2i; + str = cssr[kflag - 1]; + s1r = s1r * str; + s1i = s1i * str; + s2r = s2r * str; + s2i = s2i * str; + p1r = csrr[kflag - 1]; + L230: + ; + } + + if (n != 1) + goto L240; + s1r = s2r; + s1i = s2i; + L240: + str = csrr[kflag - 1]; + yr[0] = s1r * str; + yi[0] = s1i * str; + if (n == 1) + return 0; + yr[1] = s2r * str; + yi[1] = s2i * str; + if (n == 2) + return 0; + kk = 2; + L250: + kk = kk + 1; + if (kk > n) + return 0; + p1r = csrr[kflag - 1]; + ascle = bry[kflag - 1]; + for (i = kk; i <= n; i++) + { + p2r = s2r; + p2i = s2i; + s2r = ckr * p2r - cki * p2i + s1r; + s2i = cki * p2r + ckr * p2i + s1i; + s1r = p2r; + s1i = p2i; + ckr = ckr + rzr; + cki = cki + rzi; + p2r = s2r * p1r; + p2i = s2i * p1r; + yr[i - 1] = p2r; + yi[i - 1] = p2i; + if (kflag >= 3) + goto L260; + str = Math.Abs(p2r); + sti = Math.Abs(p2i); + p2m = Math.Max(str, sti); + if (p2m <= ascle) + goto L260; + kflag = kflag + 1; + ascle = bry[kflag - 1]; + s1r = s1r * p1r; + s1i = s1i * p1r; + s2r = p2r; + s2i = p2i; + str = cssr[kflag - 1]; + s1r = s1r * str; + s1i = s1i * str; + s2r = s2r * str; + s2i = s2i * str; + p1r = csrr[kflag - 1]; + L260: + ; + } + + return 0; + //----------------------------------------------------------------------- + // IFLAG=1 CASES, FORWARD RECURRENCE ON SCALED VALUES ON UNDERFLOW + //----------------------------------------------------------------------- + L261: + helim = 0.5 * elim; + elm = Math.Exp(-elim); + celmr = elm; + ascle = bry[0]; + zdr = zr; + zdi = zi; + ic = -1; + j = 2; + for (i = 1; i <= inu; i++) + { + str = s2r; + sti = s2i; + s2r = str * ckr - sti * cki + s1r; + s2i = sti * ckr + str * cki + s1i; + s1r = str; + s1i = sti; + ckr = ckr + rzr; + cki = cki + rzi; + alas = Math.Log(zabs(s2r, s2i)); //as = zabs(s2r, s2i); alas = Math.Log(as); + p2r = -zdr + alas; + if (p2r < -elim) + goto L263; + zlog(s2r, s2i, ref str, ref sti, ref idum); + p2r = -zdr + str; + p2i = -zdi + sti; + p2m = Math.Exp(p2r) / tol; + p1r = p2m * Math.Cos(p2i); + p1i = p2m * Math.Sin(p2i); + zuchk(p1r, p1i, ref nw, ascle, tol); + if (nw != 0) + goto L263; + j = 3 - j; + cyr[j - 1] = p1r; + cyi[j - 1] = p1i; + if (ic == i - 1) + goto L264; + ic = i; + goto L262; + L263: + if (alas < helim) + goto L262; + zdr = zdr - elim; + s1r = s1r * celmr; + s1i = s1i * celmr; + s2r = s2r * celmr; + s2i = s2i * celmr; + L262: + ; + } + + if (n != 1) + goto L270; + s1r = s2r; + s1i = s2i; + goto L270; + L264: + kflag = 1; + inub = i + 1; + s2r = cyr[j - 1]; + s2i = cyi[j - 1]; + j = 3 - j; + s1r = cyr[j - 1]; + s1i = cyi[j - 1]; + if (inub <= inu) + goto L225; + if (n != 1) + goto L240; + s1r = s2r; + s1i = s2i; + goto L240; + L270: + yr[0] = s1r; + yi[0] = s1i; + if (n == 1) + goto L280; + yr[1] = s2r; + yi[1] = s2i; + L280: + ascle = bry[0]; + zkscl(zdr, zdi, fnu, n, yr, yi, ref nz, rzr, rzi, ascle, tol, elim); + inu = n - nz; + if (inu <= 0) + return 0; + kk = nz + 1; + s1r = yr[kk - 1]; + s1i = yi[kk - 1]; + yr[kk - 1] = s1r * csrr[0]; + yi[kk - 1] = s1i * csrr[0]; + if (inu == 1) + return 0; + kk = nz + 2; + s2r = yr[kk - 1]; + s2i = yi[kk - 1]; + yr[kk - 1] = s2r * csrr[0]; + yi[kk - 1] = s2i * csrr[0]; + if (inu == 2) + return 0; + t2 = fnu + (double)(kk - 1); + ckr = t2 * rzr; + cki = t2 * rzi; + kflag = 1; + goto L250; + L290: + //----------------------------------------------------------------------- + // SCALE BY EXP(Z), IFLAG = 1 CASES + //----------------------------------------------------------------------- + koded = 2; + iflag = 1; + kflag = 2; + goto L120; + //----------------------------------------------------------------------- + // FNU=HALF ODD INTEGER CASE, DNU=-0.5 + //----------------------------------------------------------------------- + L300: + s1r = coefr; + s1i = coefi; + s2r = coefr; + s2i = coefi; + goto L210; + L310: + nz = -2; + return 0; + } + + private static int zbuni(double zr, double zi, double fnu, int kode, int n, double[] yr, double[] yi, ref int nz, int nui, ref int nlast, double fnul, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZBUNI + //***REFER TO ZBESI,ZBESK + // + // ZBUNI COMPUTES THE I BESSEL FUNCTION FOR LARGE CABS(Z).GT. + // FNUL AND FNU+N-1.LT.FNUL. THE ORDER IS INCREASED FROM + // FNU+N-1 GREATER THAN FNUL BY ADDING NUI AND COMPUTING + // ACCORDING TO THE UNIFORM ASYMPTOTIC EXPANSION FOR I(FNU,Z) + // ON IFORM=1 AND THE EXPANSION FOR J(FNU,Z) ON IFORM=2 + // + //***ROUTINES CALLED ZUNI1,ZUNI2,ZABS,D1MACH + //***END PROLOGUE ZBUNI + + #endregion + + double ax, ay, csclr, cscrr, dfnu; + double fnui, gnu, raz, rzi, rzr, sti, str, s1i, s1r; + double s2i, s2r, ascle, c1r, c1i, c1m; + int i, iflag, iform, k, nl, nw = 0; + + double[] cyi = new double[2]; + double[] cyr = new double[2]; + double[] bry = new double[3]; + + nz = 0; + ax = Math.Abs(zr) * 1.7321; + ay = Math.Abs(zi); + iform = 1; + if (ay > ax) + iform = 2; + if (nui == 0) + goto L60; + fnui = (double)nui; + dfnu = fnu + (double)(n - 1); + gnu = dfnu + fnui; + if (iform == 2) + goto L10; + // ----------------------------------------------------------------------- + // ASYMPTOTIC EXPANSION FOR I(FNU,Z) FOR LARGE FNU APPLIED IN + // -PI/3.LE.ARG(Z).LE.PI/3 + // ----------------------------------------------------------------------- + zuni1(zr, zi, gnu, kode, 2, cyr, cyi, ref nw, ref nlast, fnul, tol, elim, alim); + goto L20; + L10: + // ----------------------------------------------------------------------- + // ASYMPTOTIC EXPANSION FOR J(FNU,Z*EXP(M*HPI)) FOR LARGE FNU + // APPLIED IN PI/3.LT.ABS(ARG(Z)).LE.PI/2 WHERE M=+I OR -I + // AND HPI=PI/2 + // ----------------------------------------------------------------------- + zuni2(zr, zi, gnu, kode, 2, cyr, cyi, ref nw, ref nlast, fnul, tol, elim, alim); + L20: + if (nw < 0) + goto L50; + if (nw != 0) + goto L90; + str = zabs(cyr[0], cyi[0]); + // ---------------------------------------------------------------------- + // SCALE BACKWARD RECURRENCE, BRY(3) IS DEFINED BUT NEVER USED + // ---------------------------------------------------------------------- + bry[0] = d1mach(1) * 1.0E3 / tol; + bry[1] = 1.0 / bry[0]; + bry[2] = bry[1]; + iflag = 2; + ascle = bry[1]; + csclr = 1.0; + if (str > bry[0]) + goto L21; + iflag = 1; + ascle = bry[0]; + csclr = 1.0 / tol; + goto L25; + L21: + if (str < bry[1]) + goto L25; + iflag = 3; + ascle = bry[2]; + csclr = tol; + L25: + cscrr = 1.0 / csclr; + s1r = cyr[1] * csclr; + s1i = cyi[1] * csclr; + s2r = cyr[0] * csclr; + s2i = cyi[0] * csclr; + raz = 1.0 / zabs(zr, zi); + str = zr * raz; + sti = -zi * raz; + rzr = (str + str) * raz; + rzi = (sti + sti) * raz; + for (i = 1; i <= nui; i++) + { + str = s2r; + sti = s2i; + s2r = (dfnu + fnui) * (rzr * str - rzi * sti) + s1r; + s2i = (dfnu + fnui) * (rzr * sti + rzi * str) + s1i; + s1r = str; + s1i = sti; + fnui += -1.0; + if (iflag >= 3) + goto L30; + str = s2r * cscrr; + sti = s2i * cscrr; + c1r = Math.Abs(str); + c1i = Math.Abs(sti); + c1m = Math.Max(c1r, c1i); + if (c1m <= ascle) + goto L30; + iflag++; + ascle = bry[iflag - 1]; + s1r *= cscrr; + s1i *= cscrr; + s2r = str; + s2i = sti; + csclr *= tol; + cscrr = 1.0 / csclr; + s1r *= csclr; + s1i *= csclr; + s2r *= csclr; + s2i *= csclr; + L30: + ; + } + + yr[n - 1] = s2r * cscrr; + yi[n - 1] = s2i * cscrr; + if (n == 1) + return 0; + nl = n - 1; + fnui = (double)nl; + k = nl; + for (i = 1; i <= nl; i++) + { + str = s2r; + sti = s2i; + s2r = (fnu + fnui) * (rzr * str - rzi * sti) + s1r; + s2i = (fnu + fnui) * (rzr * sti + rzi * str) + s1i; + s1r = str; + s1i = sti; + str = s2r * cscrr; + sti = s2i * cscrr; + yr[k - 1] = str; + yi[k - 1] = sti; + fnui += -1.0; + k--; + if (iflag >= 3) + goto L40; + c1r = Math.Abs(str); + c1i = Math.Abs(sti); + c1m = Math.Max(c1r, c1i); + if (c1m <= ascle) + goto L40; + iflag++; + ascle = bry[iflag - 1]; + s1r *= cscrr; + s1i *= cscrr; + s2r = str; + s2i = sti; + csclr *= tol; + cscrr = 1.0 / csclr; + s1r *= csclr; + s1i *= csclr; + s2r *= csclr; + s2i *= csclr; + L40: + ; + } + + return 0; + L50: + nz = -1; + if (nw == -2) + nz = -2; + return 0; + L60: + if (iform == 2) + goto L70; + // ----------------------------------------------------------------------- + // ASYMPTOTIC EXPANSION FOR I(FNU,Z) FOR LARGE FNU APPLIED IN + // -PI/3.LE.ARG(Z).LE.PI/3 + // ----------------------------------------------------------------------- + zuni1(zr, zi, fnu, kode, n, yr, yi, ref nw, ref nlast, fnul, tol, elim, alim); + goto L80; + L70: + // ----------------------------------------------------------------------- + // ASYMPTOTIC EXPANSION FOR J(FNU,Z*EXP(M*HPI)) FOR LARGE FNU + // APPLIED IN PI/3.LT.ABS(ARG(Z)).LE.PI/2 WHERE M=+I OR -I + // AND HPI=PI/2 + // ----------------------------------------------------------------------- + zuni2(zr, zi, fnu, kode, n, yr, yi, ref nw, ref nlast, fnul, tol, elim, alim); + L80: + if (nw < 0) + goto L50; + nz = nw; + return 0; + L90: + nlast = n; + return 0; + } + + private static int zbunk(double zr, double zi, double fnu, int kode, int mr, int n, double[] yr, double[] yi, ref int nz, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZBUNK + //***REFER TO ZBESK,ZBESH + // + // ZBUNK COMPUTES THE K BESSEL FUNCTION FOR FNU.GT.FNUL. + // ACCORDING TO THE UNIFORM ASYMPTOTIC EXPANSION FOR K(FNU,Z) + // IN ZUNK1 AND THE EXPANSION FOR H(2,FNU,Z) IN ZUNK2 + // + //***ROUTINES CALLED ZUNK1,ZUNK2 + //***END PROLOGUE ZBUNK + + #endregion + + double ax, ay; + + nz = 0; + ax = Math.Abs(zr) * 1.7321; + ay = Math.Abs(zi); + if (ay > ax) + goto L10; + //----------------------------------------------------------------------- + // ASYMPTOTIC EXPANSION FOR K(FNU,Z) FOR LARGE FNU APPLIED IN + // -PI/3.LE.ARG(Z).LE.PI/3 + //----------------------------------------------------------------------- + zunk1(zr, zi, fnu, kode, mr, n, yr, yi, ref nz, tol, elim, alim); + goto L20; + L10: + //----------------------------------------------------------------------- + // ASYMPTOTIC EXPANSION FOR H(2,FNU,Z*EXP(M*HPI)) FOR LARGE FNU + // APPLIED IN PI/3.LT.ABS(ARG(Z)).LE.PI/2 WHERE M=+I OR -I + // AND HPI=PI/2 + //----------------------------------------------------------------------- + zunk2(zr, zi, fnu, kode, mr, n, yr, yi, ref nz, tol, elim, alim); + L20: + return 0; + } + + private static int zkscl(double zrr, double zri, double fnu, int n, double[] yr, double[] yi, ref int nz, double rzr, double rzi, double ascle, double tol, double elim) + { + #region Description + + //***BEGIN PROLOGUE ZKSCL + //***REFER TO ZBESK + // + // SET K FUNCTIONS TO ZERO ON UNDERFLOW, CONTINUE RECURRENCE + // ON SCALED FUNCTIONS UNTIL TWO MEMBERS COME ON SCALE, THEN + // RETURN WITH MIN(NZ+2,N) VALUES SCALED BY 1/TOL. + // + //***ROUTINES CALLED ZUCHK,ZABS,ZLOG + //***END PROLOGUE ZKSCL + + #endregion + + const double zeror = 0.0; + const double zeroi = 0.0; + + double acs, as_, cki, ckr, csi = 0, csr = 0; + double fn, str, s1i, s1r, s2i; + double s2r; + double zdr, zdi, celmr, elm, helim, alas; + int i, ic, idum = 0, kk, nn, nw = 0; + + double[] cyi = new double[2]; + double[] cyr = new double[2]; + + nz = 0; + ic = 0; + nn = Math.Min(2, n); + for (i = 1; i <= nn; i++) + { + s1r = yr[i - 1]; + s1i = yi[i - 1]; + cyr[i - 1] = s1r; + cyi[i - 1] = s1i; + as_ = zabs(s1r, s1i); + acs = -zrr + Math.Log(as_); + nz++; + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + if (acs < -elim) + goto L10; + zlog(s1r, s1i, ref csr, ref csi, ref idum); + csr -= zrr; + csi -= zri; + str = Math.Exp(csr) / tol; + csr = str * Math.Cos(csi); + csi = str * Math.Sin(csi); + zuchk(csr, csi, ref nw, ascle, tol); + if (nw != 0) + goto L10; + yr[i - 1] = csr; + yi[i - 1] = csi; + ic = i; + nz--; + L10: + ; + } + + if (n == 1) + return 0; + if (ic > 1) + goto L20; + yr[0] = zeror; + yi[0] = zeroi; + nz = 2; + L20: + if (n == 2) + return 0; + if (nz == 0) + return 0; + fn = fnu + 1.0; + ckr = fn * rzr; + cki = fn * rzi; + s1r = cyr[0]; + s1i = cyi[0]; + s2r = cyr[1]; + s2i = cyi[1]; + helim = elim * .5; + elm = Math.Exp(-(elim)); + celmr = elm; + zdr = zrr; + zdi = zri; + //----------------------------------------------------------------------- + // FIND TWO CONSECUTIVE Y VALUES ON SCALE. SCALE RECURRENCE IF + // S2 GETS LARGER THAN EXP(ELIM/2) + //----------------------------------------------------------------------- + for (i = 3; i <= n; i++) + { + kk = i; + csr = s2r; + csi = s2i; + s2r = ckr * csr - cki * csi + s1r; + s2i = cki * csr + ckr * csi + s1i; + s1r = csr; + s1i = csi; + ckr += rzr; + cki += rzi; + as_ = zabs(s2r, s2i); + alas = Math.Log(as_); + acs = -zdr + alas; + nz++; + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + if (acs < -elim) + goto L25; + zlog(s2r, s2i, ref csr, ref csi, ref idum); + csr -= zdr; + csi -= zdi; + str = Math.Exp(csr) / tol; + csr = str * Math.Cos(csi); + csi = str * Math.Sin(csi); + zuchk(csr, csi, ref nw, ascle, tol); + if (nw != 0) + goto L25; + yr[i - 1] = csr; + yi[i - 1] = csi; + nz--; + if (ic == kk - 1) + goto L40; + ic = kk; + goto L30; + L25: + if (alas < helim) + goto L30; + zdr -= elim; + s1r *= celmr; + s1i *= celmr; + s2r *= celmr; + s2i *= celmr; + L30: + ; + } + + nz = n; + if (ic == n) + nz = n - 1; + goto L45; + L40: + nz = kk - 2; + L45: + for (i = 1; i <= nz; i++) + { + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + } + + return 0; + } + + private static int zmlri(double zr, double zi, double fnu, int kode, int n, double[] yr, double[] yi, ref int nz, double tol) + { + #region Description + + //***BEGIN PROLOGUE ZMLRI + //***REFER TO ZBESI,ZBESK + // + // ZMLRI COMPUTES THE I BESSEL FUNCTION FOR RE(Z).GE.0.0 BY THE + // MILLER ALGORITHM NORMALIZED BY A NEUMANN SERIES. + // + //***ROUTINES CALLED DGAMLN,D1MACH,ZABS,ZEXP,ZLOG,ZMLT + //***END PROLOGUE ZMLRI + + #endregion + + const double zeror = 0.0; + const double zeroi = 0.0; + const double coner = 1.0; + const double conei = 0.0; + + double ack, ak, ap, at, az, bk, cki, ckr, cnormi = 0; + double cnormr = 0, fkap, fkk, flam, fnf, pti, ptr, p1i; + double p1r, p2i, p2r, raz, rho, rho2, rzi, rzr, scle, sti, str, sumi; + double sumr, tfnf, tst; + int i, iaz, idum = 0, ifnu, inu, itime, k, kk, km, m; + + scle = d1mach(1) / tol; + nz = 0; + az = zabs(zr, zi); + iaz = (int)az; + ifnu = (int)fnu; + inu = ifnu + n - 1; + at = (double)iaz + 1.0; + raz = 1.0 / az; + str = zr * raz; + sti = -zi * raz; + ckr = str * at * raz; + cki = sti * at * raz; + rzr = (str + str) * raz; + rzi = (sti + sti) * raz; + p1r = zeror; + p1i = zeroi; + p2r = coner; + p2i = conei; + ack = (at + 1.0) * raz; + rho = ack + Math.Sqrt(ack * ack - 1.0); + rho2 = rho * rho; + tst = (rho2 + rho2) / ((rho2 - 1.0) * (rho - 1.0)); + tst /= tol; + //----------------------------------------------------------------------- + // COMPUTE RELATIVE TRUNCATION ERROR INDEX FOR SERIES + //----------------------------------------------------------------------- + ak = at; + for (i = 1; i <= 80; i++) + { + ptr = p2r; + pti = p2i; + p2r = p1r - (ckr * ptr - cki * pti); + p2i = p1i - (cki * ptr + ckr * pti); + p1r = ptr; + p1i = pti; + ckr += rzr; + cki += rzi; + ap = zabs(p2r, p2i); + if (ap > tst * ak * ak) + goto L20; + ak += 1.0; + } + + goto L110; + L20: + i++; + k = 0; + if (inu < iaz) + goto L40; + //----------------------------------------------------------------------- + // COMPUTE RELATIVE TRUNCATION ERROR FOR RATIOS + //----------------------------------------------------------------------- + p1r = zeror; + p1i = zeroi; + p2r = coner; + p2i = conei; + at = inu + 1.0; + str = zr * raz; + sti = -zi * raz; + ckr = str * at * raz; + cki = sti * at * raz; + ack = at * raz; + tst = Math.Sqrt(ack / tol); + itime = 1; + for (k = 1; k <= 80; k++) + { + ptr = p2r; + pti = p2i; + p2r = p1r - (ckr * ptr - cki * pti); + p2i = p1i - (ckr * pti + cki * ptr); + p1r = ptr; + p1i = pti; + ckr += rzr; + cki += rzi; + ap = zabs(p2r, p2i); + if (ap < tst) + goto L30; + if (itime == 2) + goto L40; + ack = zabs(ckr, cki); + flam = ack + Math.Sqrt(ack * ack - 1.0); + fkap = ap / zabs(p1r, p1i); + rho = Math.Min(flam, fkap); + tst *= Math.Sqrt(rho / (rho * rho - 1.0)); + itime = 2; + L30: + ; + } + + goto L110; + L40: + //----------------------------------------------------------------------- + // BACKWARD RECURRENCE AND SUM NORMALIZING RELATION + //----------------------------------------------------------------------- + k++; + kk = Math.Max(i + iaz, k + inu); + fkk = (double)kk; + p1r = zeror; + p1i = zeroi; + //----------------------------------------------------------------------- + // SCALE P2 AND SUM BY SCLE + //----------------------------------------------------------------------- + p2r = scle; + p2i = zeroi; + fnf = fnu - (double)ifnu; + tfnf = fnf + fnf; + bk = dgamln(fkk + tfnf + 1.0, ref idum) - dgamln(fkk + 1.0, ref idum) - dgamln(tfnf + 1.0, ref idum); + bk = Math.Exp(bk); + sumr = zeror; + sumi = zeroi; + km = kk - inu; + for (i = 1; i <= km; i++) + { + ptr = p2r; + pti = p2i; + p2r = p1r + (fkk + fnf) * (rzr * ptr - rzi * pti); + p2i = p1i + (fkk + fnf) * (rzi * ptr + rzr * pti); + p1r = ptr; + p1i = pti; + ak = 1.0 - tfnf / (fkk + tfnf); + ack = bk * ak; + sumr += (ack + bk) * p1r; + sumi += (ack + bk) * p1i; + bk = ack; + fkk += -1.0; + } + + yr[n - 1] = p2r; + yi[n - 1] = p2i; + if (n == 1) + goto L70; + for (i = 2; i <= n; i++) + { + ptr = p2r; + pti = p2i; + p2r = p1r + (fkk + fnf) * (rzr * ptr - rzi * pti); + p2i = p1i + (fkk + fnf) * (rzi * ptr + rzr * pti); + p1r = ptr; + p1i = pti; + ak = 1.0 - tfnf / (fkk + tfnf); + ack = bk * ak; + sumr += (ack + bk) * p1r; + sumi += (ack + bk) * p1i; + bk = ack; + fkk += -1.0; + m = n - i + 1; + yr[m - 1] = p2r; + yi[m - 1] = p2i; + } + + L70: + if (ifnu <= 0) + goto L90; + for (i = 1; i <= ifnu; i++) + { + ptr = p2r; + pti = p2i; + p2r = p1r + (fkk + fnf) * (rzr * ptr - rzi * pti); + p2i = p1i + (fkk + fnf) * (rzr * pti + rzi * ptr); + p1r = ptr; + p1i = pti; + ak = 1.0 - tfnf / (fkk + tfnf); + ack = bk * ak; + sumr += (ack + bk) * p1r; + sumi += (ack + bk) * p1i; + bk = ack; + fkk += -1.0; + } + + L90: + ptr = zr; + pti = zi; + if (kode == 2) + ptr = zeror; + zlog(rzr, rzi, ref str, ref sti, ref idum); + p1r = -fnf * str + ptr; + p1i = -fnf * sti + pti; + ap = dgamln(fnf + 1.0, ref idum); + ptr = p1r - ap; + pti = p1i; + //----------------------------------------------------------------------- + // THE DIVISION CEXP(PT)/(SUM+P2) IS ALTERED TO AVOID OVERFLOW + // IN THE DENOMINATOR BY SQUARING LARGE QUANTITIES + //----------------------------------------------------------------------- + p2r += sumr; + p2i += sumi; + ap = zabs(p2r, p2i); + p1r = 1.0 / ap; + zexp(ptr, pti, ref str, ref sti); + ckr = str * p1r; + cki = sti * p1r; + ptr = p2r * p1r; + pti = -p2i * p1r; + zmlt(ckr, cki, ptr, pti, ref cnormr, ref cnormi); + for (i = 1; i <= n; i++) + { + str = yr[i - 1] * cnormr - yi[i - 1] * cnormi; + yi[i - 1] = yr[i - 1] * cnormi + yi[i - 1] * cnormr; + yr[i - 1] = str; + } + + return 0; + L110: + nz = -2; + return 0; + } + + private static int zrati(double zr, double zi, double fnu, int n, double[] cyr, double[] cyi, double tol) + { + #region Description + + //***BEGIN PROLOGUE ZRATI + //***REFER TO ZBESI,ZBESK,ZBESH + // + // ZRATI COMPUTES RATIOS OF I BESSEL FUNCTIONS BY BACKWARD + // RECURRENCE. THE STARTING INDEX IS DETERMINED BY FORWARD + // RECURRENCE AS DESCRIBED IN J. RES. OF NAT. BUR. OF STANDARDS-B, + // MATHEMATICAL SCIENCES, VOL 77B, P111-114, SEPTEMBER, 1973, + // BESSEL FUNCTIONS I AND J OF COMPLEX ARGUMENT AND INTEGER ORDER, + // BY D. J. SOOKNE. + // + //***ROUTINES CALLED ZABS,ZDIV + //***END PROLOGUE ZRATI + + #endregion + + const double czeror = 0.0; + const double czeroi = 0.0; + const double coner = 1.0; + const double conei = 0.0; + const double rt2 = 1.41421356237309505; // sqrt(2) + + double ak, amagz, ap1, ap2, arg, az, cdfnui, cdfnur; + double dfnu, fdnu, flam; + double fnup, pti, ptr, p1i, p1r, p2i, p2r, rak, rap1, rho, rzi; + double rzr, test, test1, tti, ttr, t1i, t1r; + int i, id, idnu, inu, itime, k, kk, magz; + + az = zabs(zr, zi); + inu = (int)fnu; + idnu = inu + n - 1; + magz = (int)az; + amagz = (double)(magz + 1); + fdnu = (double)idnu; + fnup = Math.Max(amagz, fdnu); + id = idnu - magz - 1; + itime = 1; + k = 1; + ptr = 1.0 / az; + rzr = ptr * (zr + zr) * ptr; + rzi = -ptr * (zi + zi) * ptr; + t1r = rzr * fnup; + t1i = rzi * fnup; + p2r = -t1r; + p2i = -t1i; + p1r = coner; + p1i = conei; + t1r += rzr; + t1i += rzi; + if (id > 0) + id = 0; + ap2 = zabs(p2r, p2i); + ap1 = zabs(p1r, p1i); + //----------------------------------------------------------------------- + // THE OVERFLOW TEST ON K(FNU+I-1,Z) BEFORE THE CALL TO CBKNU + // GUARANTEES THAT P2 IS ON SCALE. SCALE TEST1 AND ALL SUBSEQUENT + // P2 VALUES BY AP1 TO ENSURE THAT AN OVERFLOW DOES NOT OCCUR + // PREMATURELY. + //----------------------------------------------------------------------- + arg = (ap2 + ap2) / (ap1 * tol); + test1 = Math.Sqrt(arg); + test = test1; + rap1 = 1.0 / ap1; + p1r *= rap1; + p1i *= rap1; + p2r *= rap1; + p2i *= rap1; + ap2 *= rap1; + L10: + k++; + ap1 = ap2; + ptr = p2r; + pti = p2i; + p2r = p1r - (t1r * ptr - t1i * pti); + p2i = p1i - (t1r * pti + t1i * ptr); + p1r = ptr; + p1i = pti; + t1r += rzr; + t1i += rzi; + ap2 = zabs(p2r, p2i); + if (ap1 <= test) + goto L10; + if (itime == 2) + goto L20; + ak = zabs(t1r, t1i) * 0.5; + flam = ak + Math.Sqrt(ak * ak - 1.0); + rho = Math.Min(ap2 / ap1, flam); + test = test1 * Math.Sqrt(rho / (rho * rho - 1.0)); + itime = 2; + goto L10; + L20: + kk = k + 1 - id; + ak = (double)kk; + t1r = ak; + t1i = czeroi; + dfnu = fnu + (double)(n - 1); + p1r = 1.0 / ap2; + p1i = czeroi; + p2r = czeror; + p2i = czeroi; + for (i = 1; i <= kk; i++) + { + ptr = p1r; + pti = p1i; + rap1 = dfnu + t1r; + ttr = rzr * rap1; + tti = rzi * rap1; + p1r = ptr * ttr - pti * tti + p2r; + p1i = ptr * tti + pti * ttr + p2i; + p2r = ptr; + p2i = pti; + t1r -= coner; + } + + if (p1r != czeror || p1i != czeroi) + goto L40; + p1r = tol; + p1i = tol; + L40: + zdiv(p2r, p2i, p1r, p1i, ref cyr[n - 1], ref cyi[n - 1]); + if (n == 1) + return 0; + k = n - 1; + ak = (double)k; + t1r = ak; + t1i = czeroi; + cdfnur = fnu * rzr; + cdfnui = fnu * rzi; + for (i = 2; i <= n; i++) + { + ptr = cdfnur + (t1r * rzr - t1i * rzi) + cyr[k]; + pti = cdfnui + (t1r * rzi + t1i * rzr) + cyi[k]; + ak = zabs(ptr, pti); + if (ak != czeror) + goto L50; + ptr = tol; + pti = tol; + ak = tol * rt2; + L50: + rak = coner / ak; + cyr[k - 1] = rak * ptr * rak; + cyi[k - 1] = -rak * pti * rak; + t1r -= coner; + k--; + } + + return 0; + } + + private static int zs1s2(double zrr, double zri, ref double s1r, ref double s1i, ref double s2r, ref double s2i, ref int nz, double ascle, double alim, ref int iuf) + { + #region Description + + //*** BEGIN PROLOGUE ZS1S2 + //*** REFER TO ZBESK, ZAIRY + // + // ZS1S2 TESTS FOR A POSSIBLE UNDERFLOW RESULTING FROM THE + // ADDITION OF THE I AND K FUNCTIONS IN THE ANALYTIC CON - + // TINUATION FORMULA WHERE S1 = K FUNCTION AND S2 = I FUNCTION. + // ON KODE = 1 THE I AND K FUNCTIONS ARE DIFFERENT ORDERS OF + // MAGNITUDE, BUT FOR KODE = 2 THEY CAN BE OF THE SAME ORDER + // OF MAGNITUDE AND THE MAXIMUM MUST BE AT LEAST ONE + // PRECISION ABOVE THE UNDERFLOW LIMIT. + // + //*** ROUTINES CALLED ZABS, ZEXP, ZLOG + //*** END PROLOGUE ZS1S2 + + #endregion + + const double zeror = 0.0; + const double zeroi = 0.0; + + double aa, aln, as1, as2, c1i = 0, c1r = 0, s1di; + double s1dr; + int idum = 0; + + nz = 0; + as1 = zabs(s1r, s1i); + as2 = zabs(s2r, s2i); + if (s1r == 0.0 && s1i == 0.0) + goto L10; + if (as1 == 0.0) + goto L10; + aln = -zrr - zrr + Math.Log(as1); + s1dr = s1r; + s1di = s1i; + s1r = zeror; + s1i = zeroi; + as1 = zeror; + if (aln < -alim) + goto L10; + zlog(s1dr, s1di, ref c1r, ref c1i, ref idum); + c1r = c1r - zrr - zrr; + c1i = c1i - zri - zri; + zexp(c1r, c1i, ref s1r, ref s1i); + as1 = zabs(s1r, s1i); + iuf++; + L10: + aa = Math.Max(as1, as2); + if (aa > ascle) + return 0; + s1r = zeror; + s1i = zeroi; + s2r = zeror; + s2i = zeroi; + nz = 1; + iuf = 0; + return 0; + } + + private static int zseri(double zr, double zi, double fnu, int kode, int n, double[] yr, double[] yi, ref int nz, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZSERI + //***REFER TO ZBESI,ZBESK + // + // ZSERI COMPUTES THE I BESSEL FUNCTION FOR REAL(Z).GE.0.0 BY + // MEANS OF THE POWER SERIES FOR LARGE CABS(Z) IN THE + // REGION CABS(Z).LE.2*SQRT(FNU+1). NZ=0 IS A NORMAL RETURN. + // NZ.GT.0 MEANS THAT THE LAST NZ COMPONENTS WERE SET TO ZERO + // DUE TO UNDERFLOW. NZ.LT.0 MEANS UNDERFLOW OCCURRED, BUT THE + // CONDITION CABS(Z).LE.2*SQRT(FNU+1) WAS VIOLATED AND THE + // COMPUTATION MUST BE COMPLETED IN ANOTHER ROUTINE WITH N=N-ABS(NZ). + // + //***ROUTINES CALLED DGAMLN,D1MACH,ZUCHK,ZABS,ZDIV,ZLOG,ZMLT + //***END PROLOGUE ZSERI + + #endregion + + const double zeror = 0.0; + const double zeroi = 0.0; + const double coner = 1.0; + const double conei = 0.0; + + double aa, acz, ak, ak1i, ak1r, arm, ascle = 0, atol; + double az, cki = 0, ckr = 0, coefi, coefr, crscr, czi, czr, dfnu; + double fnup, hzi, hzr, raz, rs, rtr1, rzi, rzr, s, ss = 0, sti = 0; + double str = 0, s1i = 0, s1r, s2i, s2r; + int i, ib, idum = 0, iflag, il, k, l, m, nn, nw = 0; + + double[] wi = new double[2]; + double[] wr = new double[2]; + + nz = 0; + az = zabs(zr, zi); + if (az == 0.0) + goto L160; + arm = d1mach(1) * 1.0E3; + rtr1 = Math.Sqrt(arm); + crscr = 1.0; + iflag = 0; + if (az < arm) + goto L150; + hzr = zr * 0.5; + hzi = zi * 0.5; + czr = zeror; + czi = zeroi; + if (az <= rtr1) + goto L10; + zmlt(hzr, hzi, hzr, hzi, ref czr, ref czi); + L10: + acz = zabs(czr, czi); + nn = n; + zlog(hzr, hzi, ref ckr, ref cki, ref idum); + L20: + dfnu = fnu + (double)(nn - 1); + fnup = dfnu + 1.0; + // ----------------------------------------------------------------------- + // UNDERFLOW TEST + // ----------------------------------------------------------------------- + ak1r = ckr * dfnu; + ak1i = cki * dfnu; + ak = dgamln(fnup, ref idum); + ak1r -= ak; + if (kode == 2) + ak1r -= zr; + if (ak1r > -elim) + goto L40; + L30: + nz++; + yr[nn - 1] = zeror; + yi[nn - 1] = zeroi; + if (acz > dfnu) + goto L190; + nn--; + if (nn == 0) + return 0; + goto L20; + L40: + if (ak1r > -alim) + goto L50; + iflag = 1; + ss = 1.0 / tol; + crscr = tol; + ascle = arm * ss; + L50: + aa = Math.Exp(ak1r); + if (iflag == 1) + aa *= ss; + coefr = aa * Math.Cos(ak1i); + coefi = aa * Math.Sin(ak1i); + atol = tol * acz / fnup; + il = Math.Min(2, nn); + for (i = 1; i <= il; i++) + { + dfnu = fnu + (nn - i); + fnup = dfnu + 1.0; + s1r = coner; + s1i = conei; + if (acz < tol * fnup) + goto L70; + ak1r = coner; + ak1i = conei; + ak = fnup + 2.0; + s = fnup; + aa = 2.0; + L60: + rs = 1.0 / s; + str = ak1r * czr - ak1i * czi; + sti = ak1r * czi + ak1i * czr; + ak1r = str * rs; + ak1i = sti * rs; + s1r += ak1r; + s1i += ak1i; + s += ak; + ak += 2.0; + aa = aa * acz * rs; + if (aa > atol) + goto L60; + L70: + s2r = s1r * coefr - s1i * coefi; + s2i = s1r * coefi + s1i * coefr; + wr[i - 1] = s2r; + wi[i - 1] = s2i; + if (iflag == 0) + goto L80; + zuchk(s2r, s2i, ref nw, ascle, tol); + if (nw != 0) + goto L30; + L80: + m = nn - i + 1; + yr[m - 1] = s2r * crscr; + yi[m - 1] = s2i * crscr; + if (i == il) + goto L90; + zdiv(coefr, coefi, hzr, hzi, ref str, ref sti); + coefr = str * dfnu; + coefi = sti * dfnu; + L90: + ; + } + + if (nn <= 2) + return 0; + k = nn - 2; + ak = (double)k; + raz = 1.0 / az; + str = zr * raz; + sti = -zi * raz; + rzr = (str + str) * raz; + rzi = (sti + sti) * raz; + if (iflag == 1) + goto L120; + ib = 3; + L100: + for (i = ib; i <= nn; i++) + { + yr[k - 1] = (ak + fnu) * (rzr * yr[k] - rzi * yi[k]) + yr[k + 1]; + yi[k - 1] = (ak + fnu) * (rzr * yi[k] + rzi * yr[k]) + yi[k + 1]; + ak += -1.0; + k--; + } + + return 0; + // ----------------------------------------------------------------------- + // RECUR BACKWARD WITH SCALED VALUES + // ----------------------------------------------------------------------- + L120: + // ----------------------------------------------------------------------- + // EXP(-ALIM)=EXP(-ELIM)/TOL=APPROX. ONE PRECISION ABOVE THE + // UNDERFLOW LIMIT = ASCLE = D1MACH(1)*SS*1.0D+3 + // ----------------------------------------------------------------------- + s1r = wr[0]; + s1i = wi[0]; + s2r = wr[1]; + s2i = wi[1]; + for (l = 3; l <= nn; l++) + { + ckr = s2r; + cki = s2i; + s2r = s1r + (ak + fnu) * (rzr * ckr - rzi * cki); + s2i = s1i + (ak + fnu) * (rzr * cki + rzi * ckr); + s1r = ckr; + s1i = cki; + ckr = s2r * crscr; + cki = s2i * crscr; + yr[k - 1] = ckr; + yi[k - 1] = cki; + ak += -1.0; + k--; + if (zabs(ckr, cki) > ascle) + goto L140; + } + + return 0; + L140: + ib = l + 1; + if (ib > nn) + return 0; + goto L100; + L150: + nz = n; + if (fnu == 0.0) + nz--; + L160: + yr[0] = zeror; + yi[0] = zeroi; + if (fnu != 0.0) + goto L170; + yr[0] = coner; + yi[0] = conei; + L170: + if (n == 1) + return 0; + for (i = 2; i <= n; i++) + { + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + } + + return 0; + // ----------------------------------------------------------------------- + // RETURN WITH NZ.LT.0 IF ABS(Z*Z/4).GT.FNU+N-NZ-1 COMPLETE + // THE CALCULATION IN CBINU WITH N=N-ABS(NZ) + // ----------------------------------------------------------------------- + L190: + nz = -nz; + return 0; + } + + private static int zshch(double zr, double zi, ref double cshr, ref double cshi, ref double cchr, ref double cchi) + { + #region Description + + //*** BEGIN PROLOGUE ZSHCH + //*** REFER TO ZBESK, ZBESH + // + // ZSHCH COMPUTES THE COMPLEX HYPERBOLIC FUNCTIONS CSH = SINH(X + I * Y) + // AND CCH = COSH(X + I * Y), WHERE I**2 = -1. + // + //*** ROUTINES CALLED(NONE) + //*** END PROLOGUE ZSHCH + + #endregion + + double sh = Math.Sinh(zr); + double ch = Math.Cosh(zr); + double sn = Math.Sin(zi); + double cn = Math.Cos(zi); + cshr = sh * cn; + cshi = ch * sn; + cchr = ch * cn; + cchi = sh * sn; + return 0; + } + + private static int zuchk(double yr, double yi, ref int nz, double ascle, double tol) + { + #region Description + + //***BEGIN PROLOGUE ZUCHK + //***REFER TO ZSERI,ZUOIK,ZUNK1,ZUNK2,ZUNI1,ZUNI2,ZKSCL + // + // Y ENTERS AS A SCALED QUANTITY WHOSE MAGNITUDE IS GREATER THAN + // EXP(-ALIM)=ASCLE=1.0E+3*D1MACH(1)/TOL. THE TEST IS MADE TO SEE + // IF THE MAGNITUDE OF THE REAL OR IMAGINARY PART WOULD UNDERFLOW + // WHEN Y IS SCALED (BY TOL) TO ITS PROPER VALUE. Y IS ACCEPTED + // IF THE UNDERFLOW IS AT LEAST ONE PRECISION BELOW THE MAGNITUDE + // OF THE LARGEST COMPONENT; OTHERWISE THE PHASE ANGLE DOES NOT HAVE + // ABSOLUTE ACCURACY AND AN UNDERFLOW IS ASSUMED. + // + //***ROUTINES CALLED (NONE) + //***END PROLOGUE ZUCHK + + #endregion + + double ss, st, wr, wi; + + nz = 0; + wr = Math.Abs(yr); + wi = Math.Abs(yi); + st = Math.Min(wr, wi); + if (st > ascle) + return 0; + ss = Math.Max(wr, wi); + st /= tol; + if (ss < st) + nz = 1; + return 0; + } + + private static int zunhj(double zr, double zi, double fnu, int ipmtr, double tol, ref double phir, ref double phii, ref double argr, ref double argi, ref double zeta1r, ref double zeta1i, ref double zeta2r, ref double zeta2i, ref double asumr, ref double asumi, ref double bsumr, ref double bsumi) + { + #region Description + + //***BEGIN PROLOGUE ZUNHJ + //***REFER TO ZBESI,ZBESK + // + // REFERENCES + // HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ AND I.A. + // STEGUN, AMS55, NATIONAL BUREAU OF STANDARDS, 1965, CHAPTER 9. + // + // ASYMPTOTICS AND SPECIAL FUNCTIONS BY F.W.J. OLVER, ACADEMIC + // PRESS, N.Y., 1974, PAGE 420 + // + // ABSTRACT + // ZUNHJ COMPUTES PARAMETERS FOR BESSEL FUNCTIONS C(FNU,Z) = + // J(FNU,Z), Y(FNU,Z) OR H(I,FNU,Z) I=1,2 FOR LARGE ORDERS FNU + // BY MEANS OF THE UNIFORM ASYMPTOTIC EXPANSION + // + // C(FNU,Z)=C1*PHI*( ASUM*AIRY(ARG) + C2*BSUM*DAIRY(ARG) ) + // + // FOR PROPER CHOICES OF C1, C2, AIRY AND DAIRY WHERE AIRY IS + // AN AIRY FUNCTION AND DAIRY IS ITS DERIVATIVE. + // + // (2/3)*FNU*ZETA**1.5 = ZETA1-ZETA2, + // + // ZETA1=0.5*FNU*CLOG((1+W)/(1-W)), ZETA2=FNU*W FOR SCALING + // PURPOSES IN AIRY FUNCTIONS FROM CAIRY OR CBIRY. + // + // MCONJ=SIGN OF AIMAG(Z), BUT IS AMBIGUOUS WHEN Z IS REAL AND + // MUST BE SPECIFIED. IPMTR=0 RETURNS ALL PARAMETERS. IPMTR= + // 1 COMPUTES ALL EXCEPT ASUM AND BSUM. + // + //***ROUTINES CALLED ZABS,ZDIV,ZLOG,ZSQRT,D1MACH + //***END PROLOGUE ZUNHJ + + #endregion + + const double coner = 1.0; + const double conei = 0.0; + const double ex1 = .333333333333333333; + const double ex2 = .666666666666666667; + const double gpi = 3.14159265358979323846264338327950; + const double hpi = 1.570796326794896619231321696; + const double thpi = 4.71238898038468986; + const double zeror = 0.0; + const double zeroi = 0.0; + + double[] ar = { + 1.0,.104166666666666667,.0835503472222222222, + .12822657455632716,.291849026464140464,.881627267443757652, + 3.32140828186276754,14.9957629868625547,78.9230130115865181, + 474.451538868264323,3207.49009089066193,24086.5496408740049, + 198923.119169509794,1791902.00777534383 }; + double[] br = { 1.0,-.145833333333333333, + -.0987413194444444444,-.143312053915895062,-.317227202678413548, + -.942429147957120249,-3.51120304082635426,-15.7272636203680451, + -82.2814390971859444,-492.355370523670524,-3316.21856854797251, + -24827.6742452085896,-204526.587315129788,-1838444.9170682099 }; + double[] c = { 1.0,-.208333333333333333,.125, + .334201388888888889,-.401041666666666667,.0703125, + -1.02581259645061728,1.84646267361111111,-.8912109375,.0732421875, + 4.66958442342624743,-11.2070026162229938,8.78912353515625, + -2.3640869140625,.112152099609375,-28.2120725582002449, + 84.6362176746007346,-91.8182415432400174,42.5349987453884549, + -7.3687943594796317,.227108001708984375,212.570130039217123, + -765.252468141181642,1059.99045252799988,-699.579627376132541, + 218.19051174421159,-26.4914304869515555,.572501420974731445, + -1919.457662318407,8061.72218173730938,-13586.5500064341374, + 11655.3933368645332,-5305.64697861340311,1200.90291321635246, + -108.090919788394656,1.7277275025844574,20204.2913309661486, + -96980.5983886375135,192547.001232531532,-203400.177280415534, + 122200.46498301746,-41192.6549688975513,7109.51430248936372, + -493.915304773088012,6.07404200127348304,-242919.187900551333, + 1311763.6146629772,-2998015.91853810675,3763271.297656404, + -2813563.22658653411,1268365.27332162478,-331645.172484563578, + 45218.7689813627263,-2499.83048181120962,24.3805296995560639, + 3284469.85307203782,-19706819.1184322269,50952602.4926646422, + -74105148.2115326577,66344512.2747290267,-37567176.6607633513, + 13288767.1664218183,-2785618.12808645469,308186.404612662398, + -13886.0897537170405,110.017140269246738,-49329253.664509962, + 325573074.185765749,-939462359.681578403,1553596899.57058006, + -1621080552.10833708,1106842816.82301447,-495889784.275030309, + 142062907.797533095,-24474062.7257387285,2243768.17792244943, + -84005.4336030240853,551.335896122020586,814789096.118312115, + -5866481492.05184723,18688207509.2958249,-34632043388.1587779, + 41280185579.753974,-33026599749.8007231,17954213731.1556001, + -6563293792.61928433,1559279864.87925751,-225105661.889415278, + 17395107.5539781645,-549842.327572288687,3038.09051092238427, + -14679261247.6956167,114498237732.02581,-399096175224.466498, + 819218669548.577329,-1098375156081.22331,1008158106865.38209, + -645364869245.376503,287900649906.150589,-87867072178.0232657, + 17634730606.8349694,-2167164983.22379509,143157876.718888981, + -3871833.44257261262,18257.7554742931747 }; + double[] alfa = { -.00444444444444444444, + -9.22077922077922078e-4,-8.84892884892884893e-5, + 1.65927687832449737e-4,2.4669137274179291e-4, + 2.6599558934625478e-4,2.61824297061500945e-4, + 2.48730437344655609e-4,2.32721040083232098e-4, + 2.16362485712365082e-4,2.00738858762752355e-4, + 1.86267636637545172e-4,1.73060775917876493e-4, + 1.61091705929015752e-4,1.50274774160908134e-4, + 1.40503497391269794e-4,1.31668816545922806e-4, + 1.23667445598253261e-4,1.16405271474737902e-4, + 1.09798298372713369e-4,1.03772410422992823e-4, + 9.82626078369363448e-5,9.32120517249503256e-5, + 8.85710852478711718e-5,8.42963105715700223e-5, + 8.03497548407791151e-5,7.66981345359207388e-5, + 7.33122157481777809e-5,7.01662625163141333e-5, + 6.72375633790160292e-5,6.93735541354588974e-4, + 2.32241745182921654e-4,-1.41986273556691197e-5, + -1.1644493167204864e-4,-1.50803558053048762e-4, + -1.55121924918096223e-4,-1.46809756646465549e-4, + -1.33815503867491367e-4,-1.19744975684254051e-4, + -1.0618431920797402e-4,-9.37699549891194492e-5, + -8.26923045588193274e-5,-7.29374348155221211e-5, + -6.44042357721016283e-5,-5.69611566009369048e-5, + -5.04731044303561628e-5,-4.48134868008882786e-5, + -3.98688727717598864e-5,-3.55400532972042498e-5, + -3.1741425660902248e-5,-2.83996793904174811e-5, + -2.54522720634870566e-5,-2.28459297164724555e-5, + -2.05352753106480604e-5,-1.84816217627666085e-5, + -1.66519330021393806e-5,-1.50179412980119482e-5, + -1.35554031379040526e-5,-1.22434746473858131e-5, + -1.10641884811308169e-5,-3.54211971457743841e-4, + -1.56161263945159416e-4,3.0446550359493641e-5, + 1.30198655773242693e-4,1.67471106699712269e-4, + 1.70222587683592569e-4,1.56501427608594704e-4, + 1.3633917097744512e-4,1.14886692029825128e-4, + 9.45869093034688111e-5,7.64498419250898258e-5, + 6.07570334965197354e-5,4.74394299290508799e-5, + 3.62757512005344297e-5,2.69939714979224901e-5, + 1.93210938247939253e-5,1.30056674793963203e-5, + 7.82620866744496661e-6,3.59257485819351583e-6, + 1.44040049814251817e-7,-2.65396769697939116e-6, + -4.9134686709848591e-6,-6.72739296091248287e-6, + -8.17269379678657923e-6,-9.31304715093561232e-6, + -1.02011418798016441e-5,-1.0880596251059288e-5, + -1.13875481509603555e-5,-1.17519675674556414e-5, + -1.19987364870944141e-5,3.78194199201772914e-4, + 2.02471952761816167e-4,-6.37938506318862408e-5, + -2.38598230603005903e-4,-3.10916256027361568e-4, + -3.13680115247576316e-4,-2.78950273791323387e-4, + -2.28564082619141374e-4,-1.75245280340846749e-4, + -1.25544063060690348e-4,-8.22982872820208365e-5, + -4.62860730588116458e-5,-1.72334302366962267e-5, + 5.60690482304602267e-6,2.313954431482868e-5, + 3.62642745856793957e-5,4.58006124490188752e-5, + 5.2459529495911405e-5,5.68396208545815266e-5, + 5.94349820393104052e-5,6.06478527578421742e-5, + 6.08023907788436497e-5,6.01577894539460388e-5, + 5.891996573446985e-5,5.72515823777593053e-5, + 5.52804375585852577e-5,5.3106377380288017e-5, + 5.08069302012325706e-5,4.84418647620094842e-5, + 4.6056858160747537e-5,-6.91141397288294174e-4, + -4.29976633058871912e-4,1.83067735980039018e-4, + 6.60088147542014144e-4,8.75964969951185931e-4, + 8.77335235958235514e-4,7.49369585378990637e-4, + 5.63832329756980918e-4,3.68059319971443156e-4, + 1.88464535514455599e-4,3.70663057664904149e-5, + -8.28520220232137023e-5,-1.72751952869172998e-4, + -2.36314873605872983e-4,-2.77966150694906658e-4, + -3.02079514155456919e-4,-3.12594712643820127e-4, + -3.12872558758067163e-4,-3.05678038466324377e-4, + -2.93226470614557331e-4,-2.77255655582934777e-4, + -2.59103928467031709e-4,-2.39784014396480342e-4, + -2.20048260045422848e-4,-2.00443911094971498e-4, + -1.81358692210970687e-4,-1.63057674478657464e-4, + -1.45712672175205844e-4,-1.29425421983924587e-4, + -1.14245691942445952e-4,.00192821964248775885, + .00135592576302022234,-7.17858090421302995e-4, + -.00258084802575270346,-.00349271130826168475, + -.00346986299340960628,-.00282285233351310182, + -.00188103076404891354,-8.895317183839476e-4, + 3.87912102631035228e-6,7.28688540119691412e-4, + .00126566373053457758,.00162518158372674427,.00183203153216373172, + .00191588388990527909,.00190588846755546138,.00182798982421825727, + .0017038950642112153,.00155097127171097686,.00138261421852276159, + .00120881424230064774,.00103676532638344962, + 8.71437918068619115e-4,7.16080155297701002e-4, + 5.72637002558129372e-4,4.42089819465802277e-4, + 3.24724948503090564e-4,2.20342042730246599e-4, + 1.28412898401353882e-4,4.82005924552095464e-5 }; + double[] beta = { .0179988721413553309, + .00559964911064388073,.00288501402231132779,.00180096606761053941, + .00124753110589199202,9.22878876572938311e-4, + 7.14430421727287357e-4,5.71787281789704872e-4, + 4.69431007606481533e-4,3.93232835462916638e-4, + 3.34818889318297664e-4,2.88952148495751517e-4, + 2.52211615549573284e-4,2.22280580798883327e-4, + 1.97541838033062524e-4,1.76836855019718004e-4, + 1.59316899661821081e-4,1.44347930197333986e-4, + 1.31448068119965379e-4,1.20245444949302884e-4, + 1.10449144504599392e-4,1.01828770740567258e-4, + 9.41998224204237509e-5,8.74130545753834437e-5, + 8.13466262162801467e-5,7.59002269646219339e-5, + 7.09906300634153481e-5,6.65482874842468183e-5, + 6.25146958969275078e-5,5.88403394426251749e-5, + -.00149282953213429172,-8.78204709546389328e-4, + -5.02916549572034614e-4,-2.94822138512746025e-4, + -1.75463996970782828e-4,-1.04008550460816434e-4, + -5.96141953046457895e-5,-3.1203892907609834e-5, + -1.26089735980230047e-5,-2.42892608575730389e-7, + 8.05996165414273571e-6,1.36507009262147391e-5, + 1.73964125472926261e-5,1.9867297884213378e-5, + 2.14463263790822639e-5,2.23954659232456514e-5, + 2.28967783814712629e-5,2.30785389811177817e-5, + 2.30321976080909144e-5,2.28236073720348722e-5, + 2.25005881105292418e-5,2.20981015361991429e-5, + 2.16418427448103905e-5,2.11507649256220843e-5, + 2.06388749782170737e-5,2.01165241997081666e-5, + 1.95913450141179244e-5,1.9068936791043674e-5, + 1.85533719641636667e-5,1.80475722259674218e-5, + 5.5221307672129279e-4,4.47932581552384646e-4, + 2.79520653992020589e-4,1.52468156198446602e-4, + 6.93271105657043598e-5,1.76258683069991397e-5, + -1.35744996343269136e-5,-3.17972413350427135e-5, + -4.18861861696693365e-5,-4.69004889379141029e-5, + -4.87665447413787352e-5,-4.87010031186735069e-5, + -4.74755620890086638e-5,-4.55813058138628452e-5, + -4.33309644511266036e-5,-4.09230193157750364e-5, + -3.84822638603221274e-5,-3.60857167535410501e-5, + -3.37793306123367417e-5,-3.15888560772109621e-5, + -2.95269561750807315e-5,-2.75978914828335759e-5, + -2.58006174666883713e-5,-2.413083567612802e-5, + -2.25823509518346033e-5,-2.11479656768912971e-5, + -1.98200638885294927e-5,-1.85909870801065077e-5, + -1.74532699844210224e-5,-1.63997823854497997e-5, + -4.74617796559959808e-4,-4.77864567147321487e-4, + -3.20390228067037603e-4,-1.61105016119962282e-4, + -4.25778101285435204e-5,3.44571294294967503e-5, + 7.97092684075674924e-5,1.031382367082722e-4, + 1.12466775262204158e-4,1.13103642108481389e-4, + 1.08651634848774268e-4,1.01437951597661973e-4, + 9.29298396593363896e-5,8.40293133016089978e-5, + 7.52727991349134062e-5,6.69632521975730872e-5, + 5.92564547323194704e-5,5.22169308826975567e-5, + 4.58539485165360646e-5,4.01445513891486808e-5, + 3.50481730031328081e-5,3.05157995034346659e-5, + 2.64956119950516039e-5,2.29363633690998152e-5, + 1.97893056664021636e-5,1.70091984636412623e-5, + 1.45547428261524004e-5,1.23886640995878413e-5, + 1.04775876076583236e-5,8.79179954978479373e-6, + 7.36465810572578444e-4,8.72790805146193976e-4, + 6.22614862573135066e-4,2.85998154194304147e-4, + 3.84737672879366102e-6,-1.87906003636971558e-4, + -2.97603646594554535e-4,-3.45998126832656348e-4, + -3.53382470916037712e-4,-3.35715635775048757e-4, + -3.04321124789039809e-4,-2.66722723047612821e-4, + -2.27654214122819527e-4,-1.89922611854562356e-4, + -1.5505891859909387e-4,-1.2377824076187363e-4, + -9.62926147717644187e-5,-7.25178327714425337e-5, + -5.22070028895633801e-5,-3.50347750511900522e-5, + -2.06489761035551757e-5,-8.70106096849767054e-6, + 1.1369868667510029e-6,9.16426474122778849e-6, + 1.5647778542887262e-5,2.08223629482466847e-5, + 2.48923381004595156e-5,2.80340509574146325e-5, + 3.03987774629861915e-5,3.21156731406700616e-5, + -.00180182191963885708,-.00243402962938042533, + -.00183422663549856802,-7.62204596354009765e-4, + 2.39079475256927218e-4,9.49266117176881141e-4, + .00134467449701540359,.00148457495259449178,.00144732339830617591, + .00130268261285657186,.00110351597375642682, + 8.86047440419791759e-4,6.73073208165665473e-4, + 4.77603872856582378e-4,3.05991926358789362e-4, + 1.6031569459472163e-4,4.00749555270613286e-5, + -5.66607461635251611e-5,-1.32506186772982638e-4, + -1.90296187989614057e-4,-2.32811450376937408e-4, + -2.62628811464668841e-4,-2.82050469867598672e-4, + -2.93081563192861167e-4,-2.97435962176316616e-4, + -2.96557334239348078e-4,-2.91647363312090861e-4, + -2.83696203837734166e-4,-2.73512317095673346e-4, + -2.6175015580676858e-4,.00638585891212050914, + .00962374215806377941,.00761878061207001043,.00283219055545628054, + -.0020984135201272009,-.00573826764216626498, + -.0077080424449541462,-.00821011692264844401, + -.00765824520346905413,-.00647209729391045177, + -.00499132412004966473,-.0034561228971313328, + -.00201785580014170775,-7.59430686781961401e-4, + 2.84173631523859138e-4,.00110891667586337403, + .00172901493872728771,.00216812590802684701,.00245357710494539735, + .00261281821058334862,.00267141039656276912,.0026520307339598043, + .00257411652877287315,.00245389126236094427,.00230460058071795494, + .00213684837686712662,.00195896528478870911,.00177737008679454412, + .00159690280765839059,.00142111975664438546 }; + double[] gama = { .629960524947436582,.251984209978974633, + .154790300415655846,.110713062416159013,.0857309395527394825, + .0697161316958684292,.0586085671893713576,.0504698873536310685, + .0442600580689154809,.0393720661543509966,.0354283195924455368, + .0321818857502098231,.0294646240791157679,.0271581677112934479, + .0251768272973861779,.0234570755306078891,.0219508390134907203, + .020621082823564624,.0194388240897880846,.0183810633800683158, + .0174293213231963172,.0165685837786612353,.0157865285987918445, + .0150729501494095594,.0144193250839954639,.0138184805735341786, + .0132643378994276568,.0127517121970498651,.0122761545318762767, + .0118338262398482403 }; + + double ang; + double atol, aw2, azth, btol; + double fn13, fn23; + double pp, przthi, przthr, ptfni, ptfnr, raw, raw2; + double razth, rfnu, rfnu2, rfn13, rtzti = 0, rtztr = 0, rzthi, rzthr, sti = 0, str = 0; + double sumai, sumar, sumbi, sumbr, test, tfni, tfnr, tzai; + double tzar, t2i, t2r, wi = 0, wr = 0, w2i, w2r, zai = 0, zar = 0, zbi, zbr; + double zci = 0, zcr = 0, zetai, zetar; + double zthi, zthr, ac; + int ias, ibs, is1, j, jr, ju, k, kmax, kp1, ks, l, lr; + int lrp1, l1, l2, m, idum = 0; + + double[] ap = new double[30]; + double[] cri = new double[14]; + double[] crr = new double[14]; + double[] dri = new double[14]; + double[] drr = new double[14]; + double[] pi = new double[30]; + double[] pr = new double[30]; + double[] upi = new double[14]; + double[] upr = new double[14]; + + rfnu = 1.0 / fnu; + //----------------------------------------------------------------------- + // OVERFLOW TEST (Z/FNU TOO SMALL) + //----------------------------------------------------------------------- + test = d1mach(1) * 1.0E3; + ac = fnu * test; + if (Math.Abs(zr) > ac || Math.Abs(zi) > ac) + goto L15; + zeta1r = Math.Abs(Math.Log(test)) * 2.0 + fnu; + zeta1i = 0.0; + zeta2r = fnu; + zeta2i = 0.0; + phir = 1.0; + phii = 0.0; + argr = 1.0; + argi = 0.0; + return 0; + L15: + zbr = zr * rfnu; + zbi = zi * rfnu; + rfnu2 = rfnu * rfnu; + //----------------------------------------------------------------------- + // COMPUTE IN THE FOURTH QUADRANT + //----------------------------------------------------------------------- + fn13 = Math.Pow(fnu, ex1); + fn23 = fn13 * fn13; + rfn13 = 1.0 / fn13; + w2r = coner - zbr * zbr + zbi * zbi; + w2i = conei - zbr * zbi - zbr * zbi; + aw2 = zabs(w2r, w2i); + if (aw2 > 0.25) + goto L130; + //----------------------------------------------------------------------- + // POWER SERIES FOR ABS(W2).LE.0.25D0 + //----------------------------------------------------------------------- + k = 1; + pr[0] = coner; + pi[0] = conei; + sumar = gama[0]; + sumai = zeroi; + ap[0] = 1.0; + if (aw2 < tol) + goto L20; + for (k = 2; k <= 30; k++) + { + pr[k - 1] = pr[k - 2] * w2r - pi[k - 2] * w2i; + pi[k - 1] = pr[k - 2] * w2i + pi[k - 2] * w2r; + sumar += pr[k - 1] * gama[k - 1]; + sumai += pi[k - 1] * gama[k - 1]; + ap[k - 1] = ap[k - 2] * aw2; + if (ap[k - 1] < tol) + goto L20; + } + + k = 30; + L20: + kmax = k; + zetar = w2r * sumar - w2i * sumai; + zetai = w2r * sumai + w2i * sumar; + argr = zetar * fn23; + argi = zetai * fn23; + zsqrt(sumar, sumai, ref zar, ref zai); + zsqrt(w2r, w2i, ref str, ref sti); + zeta2r = str * fnu; + zeta2i = sti * fnu; + str = coner + ex2 * (zetar * zar - zetai * zai); + sti = conei + ex2 * (zetar * zai + zetai * zar); + zeta1r = str * zeta2r - sti * zeta2i; + zeta1i = str * zeta2i + sti * zeta2r; + zar = zar + zar; + zai = zai + zai; + zsqrt(zar, zai, ref str, ref sti); + phir = str * rfn13; + phii = sti * rfn13; + if (ipmtr == 1) + goto L120; + //----------------------------------------------------------------------- + // SUM SERIES FOR ASUM AND BSUM + //----------------------------------------------------------------------- + sumbr = zeror; + sumbi = zeroi; + for (k = 1; k <= kmax; k++) + { + sumbr += pr[k - 1] * beta[k - 1]; + sumbi += pi[k - 1] * beta[k - 1]; + } + + asumr = zeror; + asumi = zeroi; + bsumr = sumbr; + bsumi = sumbi; + l1 = 0; + l2 = 30; + btol = tol * (Math.Abs(bsumr) + Math.Abs(bsumi)); + atol = tol; + pp = 1.0; + ias = 0; + ibs = 0; + if (rfnu2 < tol) + goto L110; + for (is1 = 2; is1 <= 7; is1++) + { + atol /= rfnu2; + pp *= rfnu2; + if (ias == 1) + goto L60; + sumar = zeror; + sumai = zeroi; + for (k = 1; k <= kmax; k++) + { + m = l1 + k; + sumar += pr[k - 1] * alfa[m - 1]; + sumai += pi[k - 1] * alfa[m - 1]; + if (ap[k - 1] < atol) + goto L50; + } + + L50: + asumr += sumar * pp; + asumi += sumai * pp; + if (pp < tol) + ias = 1; + L60: + if (ibs == 1) + goto L90; + sumbr = zeror; + sumbi = zeroi; + for (k = 1; k <= kmax; k++) + { + m = l2 + k; + sumbr += pr[k - 1] * beta[m - 1]; + sumbi += pi[k - 1] * beta[m - 1]; + if (ap[k - 1] < atol) + goto L80; + } + + L80: + bsumr += sumbr * pp; + bsumi += sumbi * pp; + if (pp < btol) + ibs = 1; + L90: + if (ias == 1 && ibs == 1) + goto L110; + l1 += 30; + l2 += 30; + } + + L110: + asumr += coner; + pp = rfnu * rfn13; + bsumr *= pp; + bsumi *= pp; + L120: + return 0; + //----------------------------------------------------------------------- + // ABS(W2).GT.0.25D0 + //----------------------------------------------------------------------- + L130: + zsqrt(w2r, w2i, ref wr, ref wi); + if (wr < 0.0) + wr = 0.0; + if (wi < 0.0) + wi = 0.0; + str = coner + wr; + sti = wi; + zdiv(str, sti, zbr, zbi, ref zar, ref zai); + zlog(zar, zai, ref zcr, ref zci, ref idum); + if (zci < 0.0) + zci = 0.0; + if (zci > hpi) + zci = hpi; + if (zcr < 0.0) + zcr = 0.0; + zthr = (zcr - wr) * 1.5; + zthi = (zci - wi) * 1.5; + zeta1r = zcr * fnu; + zeta1i = zci * fnu; + zeta2r = wr * fnu; + zeta2i = wi * fnu; + azth = zabs(zthr, zthi); + ang = thpi; + if (zthr >= 0.0 && zthi < 0.0) + goto L140; + ang = hpi; + if (zthr == 0.0) + goto L140; + ang = Math.Atan(zthi / zthr); + if (zthr < 0.0) + ang += gpi; + L140: + pp = Math.Pow(azth, ex2); + ang *= ex2; + zetar = pp * Math.Cos(ang); + zetai = pp * Math.Sin(ang); + if (zetai < 0.0) + zetai = 0.0; + argr = zetar * fn23; + argi = zetai * fn23; + zdiv(zthr, zthi, zetar, zetai, ref rtztr, ref rtzti); + zdiv(rtztr, rtzti, wr, wi, ref zar, ref zai); + tzar = zar + zar; + tzai = zai + zai; + zsqrt(tzar, tzai, ref str, ref sti); + phir = str * rfn13; + phii = sti * rfn13; + if (ipmtr == 1) + goto L120; + raw = 1.0 / Math.Sqrt(aw2); + str = wr * raw; + sti = -wi * raw; + tfnr = str * rfnu * raw; + tfni = sti * rfnu * raw; + razth = 1.0 / azth; + str = zthr * razth; + sti = -zthi * razth; + rzthr = str * razth * rfnu; + rzthi = sti * razth * rfnu; + zcr = rzthr * ar[1]; + zci = rzthi * ar[1]; + raw2 = 1.0 / aw2; + str = w2r * raw2; + sti = -w2i * raw2; + t2r = str * raw2; + t2i = sti * raw2; + str = t2r * c[1] + c[2]; + sti = t2i * c[1]; + upr[1] = str * tfnr - sti * tfni; + upi[1] = str * tfni + sti * tfnr; + bsumr = upr[1] + zcr; + bsumi = upi[1] + zci; + asumr = zeror; + asumi = zeroi; + if (rfnu < tol) + goto L220; + przthr = rzthr; + przthi = rzthi; + ptfnr = tfnr; + ptfni = tfni; + upr[0] = coner; + upi[0] = conei; + pp = 1.0; + btol = tol * (Math.Abs(bsumr) + Math.Abs(bsumi)); + ks = 0; + kp1 = 2; + l = 3; + ias = 0; + ibs = 0; + for (lr = 2; lr <= 12; lr += 2) + { + lrp1 = lr + 1; + //----------------------------------------------------------------------- + // COMPUTE TWO ADDITIONAL CR, DR, AND UP FOR TWO MORE TERMS IN + // NEXT SUMA AND SUMB + //----------------------------------------------------------------------- + for (k = lr; k <= lrp1; k++) + { + ks++; + kp1++; + l++; + zar = c[l - 1]; + zai = zeroi; + for (j = 2; j <= kp1; j++) + { + l++; + str = zar * t2r - t2i * zai + c[l - 1]; + zai = zar * t2i + zai * t2r; + zar = str; + } + + str = ptfnr * tfnr - ptfni * tfni; + ptfni = ptfnr * tfni + ptfni * tfnr; + ptfnr = str; + upr[kp1 - 1] = ptfnr * zar - ptfni * zai; + upi[kp1 - 1] = ptfni * zar + ptfnr * zai; + crr[ks - 1] = przthr * br[ks]; + cri[ks - 1] = przthi * br[ks]; + str = przthr * rzthr - przthi * rzthi; + przthi = przthr * rzthi + przthi * rzthr; + przthr = str; + drr[ks - 1] = przthr * ar[ks + 1]; + dri[ks - 1] = przthi * ar[ks + 1]; + } + + pp *= rfnu2; + if (ias == 1) + goto L180; + sumar = upr[lrp1 - 1]; + sumai = upi[lrp1 - 1]; + ju = lrp1; + for (jr = 1; jr <= lr; jr++) + { + ju--; + sumar = sumar + crr[jr - 1] * upr[ju - 1] - cri[jr - 1] * upi[ju - 1]; + sumai = sumai + crr[jr - 1] * upi[ju - 1] + cri[jr - 1] * upr[ju - 1]; + } + + asumr += sumar; + asumi += sumai; + test = Math.Abs(sumar) + Math.Abs(sumai); + if (pp < tol && test < tol) + ias = 1; + L180: + if (ibs == 1) + goto L200; + sumbr = upr[lr + 1] + upr[lrp1 - 1] * zcr - upi[lrp1 - 1] * zci; + sumbi = upi[lr + 1] + upr[lrp1 - 1] * zci + upi[lrp1 - 1] * zcr; + ju = lrp1; + for (jr = 1; jr <= lr; jr++) + { + ju--; + sumbr = sumbr + drr[jr - 1] * upr[ju - 1] - dri[jr - 1] * upi[ju - 1]; + sumbi = sumbi + drr[jr - 1] * upi[ju - 1] + dri[jr - 1] * upr[ju - 1]; + } + + bsumr += sumbr; + bsumi += sumbi; + test = Math.Abs(sumbr) + Math.Abs(sumbi); + if (pp < btol && test < btol) + ibs = 1; + L200: + if (ias == 1 && ibs == 1) + goto L220; + } + + L220: + asumr += coner; + str = -(bsumr) * rfn13; + sti = -(bsumi) * rfn13; + zdiv(str, sti, rtztr, rtzti, ref bsumr, ref bsumi); + goto L120; + } + + private static int zuni1(double zr, double zi, double fnu, int kode, int n, double[] yr, double[] yi, ref int nz, ref int nlast, double fnul, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZUNI1 + //***REFER TO ZBESI,ZBESK + // + // ZUNI1 COMPUTES I(FNU,Z) BY MEANS OF THE UNIFORM ASYMPTOTIC + // EXPANSION FOR I(FNU,Z) IN -PI/3.LE.ARG Z.LE.PI/3. + // + // FNUL IS THE SMALLEST ORDER PERMITTED FOR THE ASYMPTOTIC + // EXPANSION. NLAST=0 MEANS ALL OF THE Y VALUES WERE SET. + // NLAST.NE.0 IS THE NUMBER LEFT TO BE COMPUTED BY ANOTHER + // FORMULA FOR ORDERS FNU TO FNU+NLAST-1 BECAUSE FNU+NLAST-1.LT.FNUL. + // Y(I)=CZERO FOR I=NLAST+1,N + // + //***ROUTINES CALLED ZUCHK,ZUNIK,ZUOIK,D1MACH,ZABS + //***END PROLOGUE ZUNI1 + + #endregion + + const double zeror = 0.0; + const double zeroi = 0.0; + const double coner = 1.0; + + double aphi, ascle, crsc; + double cscl, c1r, c2i, c2m, c2r, fn; + double phii = 0, phir = 0, rast, rs1, rzi, rzr, sti, str, sumi = 0; + double sumr = 0, s1i, s1r, s2i, s2r, zeta1i = 0; + double zeta1r = 0, zeta2i = 0, zeta2r = 0; + int i, iflag = 0, init, k, m, nd, nn, nuf = 0, nw = 0; + + double[] bry = new double[3]; + double[] csrr = new double[3]; + double[] cssr = new double[3]; + double[] cwrki = new double[16]; + double[] cwrkr = new double[16]; + double[] cyi = new double[2]; + double[] cyr = new double[2]; + + nz = 0; + nd = n; + nlast = 0; + //----------------------------------------------------------------------- + // COMPUTED VALUES WITH EXPONENTS BETWEEN ALIM AND ELIM IN MAG- + // NITUDE ARE SCALED TO KEEP INTERMEDIATE ARITHMETIC ON SCALE, + // EXP(ALIM)=EXP(ELIM)*TOL + //----------------------------------------------------------------------- + cscl = 1.0 / tol; + crsc = tol; + cssr[0] = cscl; + cssr[1] = coner; + cssr[2] = crsc; + csrr[0] = crsc; + csrr[1] = coner; + csrr[2] = cscl; + bry[0] = d1mach(1) * 1.0E3 / tol; + //----------------------------------------------------------------------- + // CHECK FOR UNDERFLOW AND OVERFLOW ON FIRST MEMBER + //----------------------------------------------------------------------- + fn = Math.Max(fnu, 1.0); + init = 0; + zunik(zr, zi, fn, 1, 1, tol, ref init, ref phir, ref phii, ref zeta1r, ref zeta1i, ref zeta2r, ref zeta2i, ref sumr, ref sumi, ref cwrkr, ref cwrki); + if (kode == 1) + goto L10; + str = zr + zeta2r; + sti = zi + zeta2i; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = -zeta1r + str; + s1i = -zeta1i + sti; + goto L20; + L10: + s1r = -zeta1r + zeta2r; + s1i = -zeta1i + zeta2i; + L20: + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L130; + L30: + nn = Math.Min(2, nd); + for (i = 1; i <= nn; i++) + { + fn = fnu + (nd - i); + init = 0; + zunik(zr, zi, fn, 1, 0, tol, ref init, ref phir, ref phii, ref zeta1r, ref zeta1i, ref zeta2r, ref zeta2i, ref sumr, ref sumi, ref cwrkr, ref cwrki); + if (kode == 1) + goto L40; + str = zr + zeta2r; + sti = zi + zeta2i; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = -zeta1r + str; + s1i = -zeta1i + sti + zi; + goto L50; + L40: + s1r = -zeta1r + zeta2r; + s1i = -zeta1i + zeta2i; + L50: + //----------------------------------------------------------------------- + // TEST FOR UNDERFLOW AND OVERFLOW + //----------------------------------------------------------------------- + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L110; + if (i == 1) + iflag = 2; + if (Math.Abs(rs1) < alim) + goto L60; + //----------------------------------------------------------------------- + // REFINE TEST AND SCALE + //----------------------------------------------------------------------- + aphi = zabs(phir, phii); + rs1 += Math.Log(aphi); + if (Math.Abs(rs1) > elim) + goto L110; + if (i == 1) + iflag = 1; + if (rs1 < 0.0) + goto L60; + if (i == 1) + iflag = 3; + L60: + //----------------------------------------------------------------------- + // SCALE S1 IF ABS(S1).LT.ASCLE + //----------------------------------------------------------------------- + s2r = phir * sumr - phii * sumi; + s2i = phir * sumi + phii * sumr; + str = Math.Exp(s1r) * cssr[iflag - 1]; + s1r = str * Math.Cos(s1i); + s1i = str * Math.Sin(s1i); + str = s2r * s1r - s2i * s1i; + s2i = s2r * s1i + s2i * s1r; + s2r = str; + if (iflag != 1) + goto L70; + zuchk(s2r, s2i, ref nw, bry[0], tol); + if (nw != 0) + goto L110; + L70: + cyr[i - 1] = s2r; + cyi[i - 1] = s2i; + m = nd - i + 1; + yr[m - 1] = s2r * csrr[iflag - 1]; + yi[m - 1] = s2i * csrr[iflag - 1]; + } + + if (nd <= 2) + goto L100; + rast = 1.0 / zabs(zr, zi); + str = zr * rast; + sti = -zi * rast; + rzr = (str + str) * rast; + rzi = (sti + sti) * rast; + bry[1] = 1.0 / bry[0]; + bry[2] = d1mach(2); + s1r = cyr[0]; + s1i = cyi[0]; + s2r = cyr[1]; + s2i = cyi[1]; + c1r = csrr[iflag - 1]; + ascle = bry[iflag - 1]; + k = nd - 2; + fn = (double)k; + for (i = 3; i <= nd; i++) + { + c2r = s2r; + c2i = s2i; + s2r = s1r + (fnu + fn) * (rzr * c2r - rzi * c2i); + s2i = s1i + (fnu + fn) * (rzr * c2i + rzi * c2r); + s1r = c2r; + s1i = c2i; + c2r = s2r * c1r; + c2i = s2i * c1r; + yr[k - 1] = c2r; + yi[k - 1] = c2i; + k--; + fn += -1.0; + if (iflag >= 3) + goto L90; + str = Math.Abs(c2r); + sti = Math.Abs(c2i); + c2m = Math.Max(str, sti); + if (c2m <= ascle) + goto L90; + iflag++; + ascle = bry[iflag - 1]; + s1r *= c1r; + s1i *= c1r; + s2r = c2r; + s2i = c2i; + s1r *= cssr[iflag - 1]; + s1i *= cssr[iflag - 1]; + s2r *= cssr[iflag - 1]; + s2i *= cssr[iflag - 1]; + c1r = csrr[iflag - 1]; + L90: + ; + } + + L100: + return 0; + //----------------------------------------------------------------------- + // SET UNDERFLOW AND UPDATE PARAMETERS + //----------------------------------------------------------------------- + L110: + if (rs1 > 0.0) + { + goto L120; + } + + yr[nd - 1] = zeror; + yi[nd - 1] = zeroi; + nz++; + nd--; + if (nd == 0) + { + goto L100; + } + + zuoik(zr, zi, fnu, kode, 1, nd, yr, yi, ref nuf, tol, elim, alim); + if (nuf < 0) + { + goto L120; + } + + nd -= nuf; + nz += nuf; + if (nd == 0) + { + goto L100; + } + + fn = fnu + (nd - 1); + if (fn >= fnul) + { + goto L30; + } + + nlast = nd; + return 0; + L120: + nz = -1; + return 0; + L130: + if (rs1 > 0.0) + { + goto L120; + } + + nz = n; + for (i = 1; i <= n; i++) + { + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + } + + return 0; + } + + private static int zuni2(double zr, double zi, double fnu, int kode, int n, double[] yr, double[] yi, ref int nz, ref int nlast, double fnul, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZUNI2 + //***REFER TO ZBESI,ZBESK + // + // ZUNI2 COMPUTES I(FNU,Z) IN THE RIGHT HALF PLANE BY MEANS OF + // UNIFORM ASYMPTOTIC EXPANSION FOR J(FNU,ZN) WHERE ZN IS Z*I + // OR -Z*I AND ZN IS IN THE RIGHT HALF PLANE ALSO. + // + // FNUL IS THE SMALLEST ORDER PERMITTED FOR THE ASYMPTOTIC + // EXPANSION. NLAST=0 MEANS ALL OF THE Y VALUES WERE SET. + // NLAST.NE.0 IS THE NUMBER LEFT TO BE COMPUTED BY ANOTHER + // FORMULA FOR ORDERS FNU TO FNU+NLAST-1 BECAUSE FNU+NLAST-1.LT.FNUL. + // Y(I)=CZERO FOR I=NLAST+1,N + // + //***ROUTINES CALLED ZAIRY,ZUCHK,ZUNHJ,ZUOIK,D1MACH,ZABS + //***END PROLOGUE ZUNI2 + + #endregion + + const double aic = 1.265512123484645396; + const double coner = 1.0; + const double hpi = 1.570796326794896619231321696; + const double zeroi = 0.0; + const double zeror = 0.0; + + double aarg, aii = 0, air = 0, ang, aphi, argi = 0; + double argr = 0, ascle, asumi = 0, asumr = 0, bsumi = 0, bsumr = 0, cidi; + double crsc, cscl, c1r, c2i, c2m, c2r, daii = 0; + double dair = 0, fn, phii = 0, phir = 0, rast, raz, rs1, rzi; + double rzr, sti, str, s1i, s1r, s2i, s2r, zbi, zbr; + double zeta1i = 0, zeta1r = 0, zeta2i = 0, zeta2r = 0, zni, znr; + double car, sar; + int i, iflag = 0, ink, inu, j, k, nai = 0, nd, ndai = 0; + int nn, nuf = 0, nw = 0, idum = 0; + + double[] cipi = { 0.0, 1.0, 0.0, -1.0 }; + double[] cipr = { 1.0, 0.0, -1.0, 0.0 }; + double[] bry = new double[3]; + double[] csrr = new double[3]; + double[] cssr = new double[3]; + double[] cyi = new double[2]; + double[] cyr = new double[2]; + + nz = 0; + nd = n; + nlast = 0; + // ----------------------------------------------------------------------- + // COMPUTED VALUES WITH EXPONENTS BETWEEN ALIM AND ELIM IN MAG- + // NITUDE ARE SCALED TO KEEP INTERMEDIATE ARITHMETIC ON SCALE, + // EXP(ALIM)=EXP(ELIM)*TOL + // ----------------------------------------------------------------------- + cscl = 1.0 / tol; + crsc = tol; + cssr[0] = cscl; + cssr[1] = coner; + cssr[2] = crsc; + csrr[0] = crsc; + csrr[1] = coner; + csrr[2] = cscl; + bry[0] = d1mach(1) * 1.0E3 / tol; + // ----------------------------------------------------------------------- + // ZN IS IN THE RIGHT HALF PLANE AFTER ROTATION BY CI OR -CI + // ----------------------------------------------------------------------- + znr = zi; + zni = -zr; + zbr = zr; + zbi = zi; + cidi = -coner; + inu = (int)fnu; + ang = hpi * (fnu - (double)inu); + c2r = Math.Cos(ang); + c2i = Math.Sin(ang); + car = c2r; + sar = c2i; + ink = inu + n - 1; + ink = (ink % 4) + 1; + str = c2r * cipr[ink - 1] - c2i * cipi[ink - 1]; + c2i = c2r * cipi[ink - 1] + c2i * cipr[ink - 1]; + c2r = str; + if (zi > 0.0) + goto L10; + znr = -znr; + zbi = -zbi; + cidi = -cidi; + c2i = -c2i; + L10: + // ----------------------------------------------------------------------- + // CHECK FOR UNDERFLOW AND OVERFLOW ON FIRST MEMBER + // ----------------------------------------------------------------------- + fn = Math.Max(fnu, 1.0); + zunhj(znr, zni, fn, 1, tol, ref phir, ref phii, ref argr, ref argi, ref zeta1r, ref zeta1i, ref zeta2r, ref zeta2i, ref asumr, ref asumi, ref bsumr, ref bsumi); + if (kode == 1) + goto L20; + str = zbr + zeta2r; + sti = zbi + zeta2i; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = -zeta1r + str; + s1i = -zeta1i + sti; + goto L30; + L20: + s1r = -zeta1r + zeta2r; + s1i = -zeta1i + zeta2i; + L30: + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L150; + L40: + nn = Math.Min(2, nd); + for (i = 1; i <= nn; i++) + { + fn = fnu + (nd - i); + zunhj(znr, zni, fn, 0, tol, ref phir, ref phii, ref argr, ref argi, ref zeta1r, ref zeta1i, ref zeta2r, ref zeta2i, ref asumr, ref asumi, ref bsumr, ref bsumi); + if (kode == 1) + goto L50; + str = zbr + zeta2r; + sti = zbi + zeta2i; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = -zeta1r + str; + s1i = -zeta1i + sti + Math.Abs(zi); + goto L60; + L50: + s1r = -zeta1r + zeta2r; + s1i = -zeta1i + zeta2i; + L60: + // ----------------------------------------------------------------------- + // TEST FOR UNDERFLOW AND OVERFLOW + // ----------------------------------------------------------------------- + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L120; + if (i == 1) + iflag = 2; + if (Math.Abs(rs1) < alim) + goto L70; + // ----------------------------------------------------------------------- + // REFINE TEST AND SCALE + // ----------------------------------------------------------------------- + // ----------------------------------------------------------------------- + aphi = zabs(phir, phii); + aarg = zabs(argr, argi); + rs1 = rs1 + Math.Log(aphi) - Math.Log(aarg) * 0.25 - aic; + if (Math.Abs(rs1) > elim) + goto L120; + if (i == 1) + iflag = 1; + if (rs1 < 0.0) + goto L70; + if (i == 1) + iflag = 3; + L70: + // ----------------------------------------------------------------------- + // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR + // EXPONENT EXTREMES + // ----------------------------------------------------------------------- + zairy(argr, argi, 0, 2, ref air, ref aii, ref nai, ref idum); + zairy(argr, argi, 1, 2, ref dair, ref daii, ref ndai, ref idum); + str = dair * bsumr - daii * bsumi; + sti = dair * bsumi + daii * bsumr; + str += air * asumr - aii * asumi; + sti += air * asumi + aii * asumr; + s2r = phir * str - phii * sti; + s2i = phir * sti + phii * str; + str = Math.Exp(s1r) * cssr[iflag - 1]; + s1r = str * Math.Cos(s1i); + s1i = str * Math.Sin(s1i); + str = s2r * s1r - s2i * s1i; + s2i = s2r * s1i + s2i * s1r; + s2r = str; + if (iflag != 1) + goto L80; + zuchk(s2r, s2i, ref nw, bry[0], tol); + if (nw != 0) + goto L120; + L80: + if (zi <= 0.0) + s2i = -s2i; + str = s2r * c2r - s2i * c2i; + s2i = s2r * c2i + s2i * c2r; + s2r = str; + cyr[i - 1] = s2r; + cyi[i - 1] = s2i; + j = nd - i + 1; + yr[j - 1] = s2r * csrr[iflag - 1]; + yi[j - 1] = s2i * csrr[iflag - 1]; + str = -c2i * cidi; + c2i = c2r * cidi; + c2r = str; + } + + if (nd <= 2) + goto L110; + raz = 1.0 / zabs(zr, zi); + str = zr * raz; + sti = -zi * raz; + rzr = (str + str) * raz; + rzi = (sti + sti) * raz; + bry[1] = 1.0 / bry[0]; + bry[2] = d1mach(2); + s1r = cyr[0]; + s1i = cyi[0]; + s2r = cyr[1]; + s2i = cyi[1]; + c1r = csrr[iflag - 1]; + ascle = bry[iflag - 1]; + k = nd - 2; + fn = (double)k; + for (i = 3; i <= nd; i++) + { + c2r = s2r; + c2i = s2i; + s2r = s1r + (fnu + fn) * (rzr * c2r - rzi * c2i); + s2i = s1i + (fnu + fn) * (rzr * c2i + rzi * c2r); + s1r = c2r; + s1i = c2i; + c2r = s2r * c1r; + c2i = s2i * c1r; + yr[k - 1] = c2r; + yi[k - 1] = c2i; + k--; + fn += -1.0; + if (iflag >= 3) + goto L100; + str = Math.Abs(c2r); + sti = Math.Abs(c2i); + c2m = Math.Max(str, sti); + if (c2m <= ascle) + goto L100; + iflag++; + ascle = bry[iflag - 1]; + s1r *= c1r; + s1i *= c1r; + s2r = c2r; + s2i = c2i; + s1r *= cssr[iflag - 1]; + s1i *= cssr[iflag - 1]; + s2r *= cssr[iflag - 1]; + s2i *= cssr[iflag - 1]; + c1r = csrr[iflag - 1]; + L100: + ; + } + + L110: + return 0; + L120: + if (rs1 > 0.0) + goto L140; + // ----------------------------------------------------------------------- + // SET UNDERFLOW AND UPDATE PARAMETERS + // ----------------------------------------------------------------------- + yr[nd - 1] = zeror; + yi[nd - 1] = zeroi; + nz++; + nd--; + if (nd == 0) + goto L110; + zuoik(zr, zi, fnu, kode, 1, nd, yr, yi, ref nuf, tol, elim, alim); + if (nuf < 0) + goto L140; + nd -= nuf; + nz += nuf; + if (nd == 0) + goto L110; + fn = fnu + (nd - 1); + if (fn < fnul) + goto L130; + // FN = CIDI + // J = NUF + 1 + // K = MOD(J,4) + 1 + // S1R = CIPR(K) + // S1I = CIPI(K) + // IF (FN.LT.0.0D0) S1I = -S1I + // STR = C2R*S1R - C2I*S1I + // C2I = C2R*S1I + C2I*S1R + // C2R = STR + ink = inu + nd - 1; + ink = (ink % 4) + 1; + c2r = car * cipr[ink - 1] - sar * cipi[ink - 1]; + c2i = car * cipi[ink - 1] + sar * cipr[ink - 1]; + if (zi <= 0.0) + c2i = -c2i; + goto L40; + L130: + nlast = nd; + return 0; + L140: + nz = -1; + return 0; + L150: + if (rs1 > 0.0) + goto L140; + nz = n; + for (i = 1; i <= n; i++) + { + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + } + + return 0; + } + + private static int zunik(double zrr, double zri, double fnu, int ikflg, int ipmtr, double tol, ref int init, ref double phir, ref double phii, ref double zeta1r, ref double zeta1i, ref double zeta2r, ref double zeta2i, ref double sumr, ref double sumi, ref double[] cwrkr, ref double[] cwrki) + { + #region Description + + //***BEGIN PROLOGUE ZUNIK + //***REFER TO ZBESI,ZBESK + // + // ZUNIK COMPUTES PARAMETERS FOR THE UNIFORM ASYMPTOTIC + // EXPANSIONS OF THE I AND K FUNCTIONS ON IKFLG= 1 OR 2 + // RESPECTIVELY BY + // + // W(FNU,ZR) = PHI*EXP(ZETA)*SUM + // + // WHERE ZETA=-ZETA1 + ZETA2 OR + // ZETA1 - ZETA2 + // + // THE FIRST CALL MUST HAVE INIT=0. SUBSEQUENT CALLS WITH THE + // SAME ZR AND FNU WILL RETURN THE I OR K FUNCTION ON IKFLG= + // 1 OR 2 WITH NO CHANGE IN INIT. CWRK IS A COMPLEX WORK + // ARRAY. IPMTR=0 COMPUTES ALL PARAMETERS. IPMTR=1 COMPUTES PHI, + // ZETA1,ZETA2. + // + //***ROUTINES CALLED ZDIV,ZLOG,ZSQRT,D1MACH + //***END PROLOGUE ZUNIK + + #endregion + + const double conei = 0.0; + const double coner = 1.0; + const double zeroi = 0.0; + const double zeror = 0.0; + + double[] con = { 0.398942280401432678, 1.25331413731550025 }; + double[] c = { 1.0,-.208333333333333333, .125, + .334201388888888889,-.401041666666666667,.0703125, + -1.02581259645061728,1.84646267361111111,-.8912109375,.0732421875, + 4.66958442342624743,-11.2070026162229938,8.78912353515625, + -2.3640869140625,.112152099609375,-28.2120725582002449, + 84.6362176746007346,-91.8182415432400174,42.5349987453884549, + -7.3687943594796317,.227108001708984375,212.570130039217123, + -765.252468141181642,1059.99045252799988,-699.579627376132541, + 218.19051174421159,-26.4914304869515555,.572501420974731445, + -1919.457662318407,8061.72218173730938,-13586.5500064341374, + 11655.3933368645332,-5305.64697861340311,1200.90291321635246, + -108.090919788394656,1.7277275025844574,20204.2913309661486, + -96980.5983886375135,192547.001232531532,-203400.177280415534, + 122200.46498301746,-41192.6549688975513,7109.51430248936372, + -493.915304773088012,6.07404200127348304,-242919.187900551333, + 1311763.6146629772,-2998015.91853810675,3763271.297656404, + -2813563.22658653411,1268365.27332162478,-331645.172484563578, + 45218.7689813627263,-2499.83048181120962,24.3805296995560639, + 3284469.85307203782,-19706819.1184322269,50952602.4926646422, + -74105148.2115326577,66344512.2747290267,-37567176.6607633513, + 13288767.1664218183,-2785618.12808645469,308186.404612662398, + -13886.0897537170405,110.017140269246738,-49329253.664509962, + 325573074.185765749,-939462359.681578403,1553596899.57058006, + -1621080552.10833708,1106842816.82301447,-495889784.275030309, + 142062907.797533095,-24474062.7257387285,2243768.17792244943, + -84005.4336030240853,551.335896122020586,814789096.118312115, + -5866481492.05184723,18688207509.2958249,-34632043388.1587779, + 41280185579.753974,-33026599749.8007231,17954213731.1556001, + -6563293792.61928433,1559279864.87925751,-225105661.889415278, + 17395107.5539781645,-549842.327572288687,3038.09051092238427, + -14679261247.6956167,114498237732.02581,-399096175224.466498, + 819218669548.577329,-1098375156081.22331,1008158106865.38209, + -645364869245.376503,287900649906.150589,-87867072178.0232657, + 17634730606.8349694,-2167164983.22379509,143157876.718888981, + -3871833.44257261262,18257.7554742931747,286464035717.679043, + -2406297900028.50396,9109341185239.89896,-20516899410934.4374, + 30565125519935.3206,-31667088584785.1584,23348364044581.8409, + -12320491305598.2872,4612725780849.13197,-1196552880196.1816, + 205914503232.410016,-21822927757.5292237,1247009293.51271032, + -29188388.1222208134,118838.426256783253 }; + + double ac, crfni, crfnr; + double rfn, si, sr, sri = 0, srr = 0, sti = 0, str = 0; + double test, ti, tr, t2i = 0, t2r = 0; + double zni = 0, znr = 0; + int i, idum = 0, j, k, l; + + zeta1r = 0.0; + zeta1i = 0.0; + + if (init != 0) + goto L40; + //----------------------------------------------------------------------- + // INITIALIZE ALL VARIABLES + //----------------------------------------------------------------------- + rfn = 1.0 / fnu; + //----------------------------------------------------------------------- + // OVERFLOW TEST (ZR/FNU TOO SMALL) + //----------------------------------------------------------------------- + test = d1mach(1) * 1.0E3; + ac = fnu * test; + if (Math.Abs(zrr) > ac || Math.Abs(zri) > ac) + goto L15; + zeta1r = 2.0 * Math.Abs(Math.Log(test)) + fnu; + zeta1i = 0.0; + zeta2r = fnu; + zeta2i = 0.0; + phir = 1.0; + phii = 0.0; + return 0; + L15: + tr = zrr * rfn; + ti = zri * rfn; + sr = coner + (tr * tr - ti * ti); + si = conei + (tr * ti + ti * tr); + zsqrt(sr, si, ref srr, ref sri); + str = coner + srr; + sti = conei + sri; + zdiv(str, sti, tr, ti, ref znr, ref zni); + zlog(znr, zni, ref str, ref sti, ref idum); + zeta1r = fnu * str; + zeta1i = fnu * sti; + zeta2r = fnu * srr; + zeta2i = fnu * sri; + zdiv(coner, conei, srr, sri, ref tr, ref ti); + srr = tr * rfn; + sri = ti * rfn; + zsqrt(srr, sri, ref cwrkr[15], ref cwrki[15]); + phir = cwrkr[15] * con[ikflg - 1]; + phii = cwrki[15] * con[ikflg - 1]; + if (ipmtr != 0) + return 0; + zdiv(coner, conei, sr, si, ref t2r, ref t2i); + cwrkr[0] = coner; + cwrki[0] = conei; + crfnr = coner; + crfni = conei; + ac = 1.0; + l = 1; + for (k = 2; k <= 15; k++) + { + sr = zeror; + si = zeroi; + for (j = 1; j <= k; j++) + { + l++; + str = sr * t2r - si * t2i + c[l - 1]; + si = sr * t2i + si * t2r; + sr = str; + } + + str = crfnr * srr - crfni * sri; + crfni = crfnr * sri + crfni * srr; + crfnr = str; + cwrkr[k - 1] = crfnr * sr - crfni * si; + cwrki[k - 1] = crfnr * si + crfni * sr; + ac *= rfn; + test = Math.Abs(cwrkr[k - 1]) + Math.Abs(cwrki[k - 1]); + if (ac < tol && test < tol) + goto L30; + } + + k = 15; + L30: + init = k; + L40: + if (ikflg == 2) + goto L60; + //----------------------------------------------------------------------- + // COMPUTE SUM FOR THE I FUNCTION + //----------------------------------------------------------------------- + sr = zeror; + si = zeroi; + for (i = 1; i <= init; i++) + { + sr += cwrkr[i - 1]; + si += cwrki[i - 1]; + } + + sumr = sr; + sumi = si; + phir = cwrkr[15] * con[0]; + phii = cwrki[15] * con[0]; + return 0; + L60: + //----------------------------------------------------------------------- + // COMPUTE SUM FOR THE K FUNCTION + //----------------------------------------------------------------------- + sr = zeror; + si = zeroi; + tr = coner; + for (i = 1; i <= init; i++) + { + sr += tr * cwrkr[i - 1]; + si += tr * cwrki[i - 1]; + tr = -tr; + } + + sumr = sr; + sumi = si; + phir = cwrkr[15] * con[1]; + phii = cwrki[15] * con[1]; + return 0; + } + + private static int zunk1(double zr, double zi, double fnu, int kode, int mr, int n, double[] yr, double[] yi, ref int nz, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZUNK1 + //***REFER TO ZBESK + // + // ZUNK1 COMPUTES K(FNU,Z) AND ITS ANALYTIC CONTINUATION FROM THE + // RIGHT HALF PLANE TO THE LEFT HALF PLANE BY MEANS OF THE + // UNIFORM ASYMPTOTIC EXPANSION. + // MR INDICATES THE DIRECTION OF ROTATION FOR ANALYTIC CONTINUATION. + // NZ=-1 MEANS AN OVERFLOW WILL OCCUR + // + //***ROUTINES CALLED ZKSCL,ZS1S2,ZUCHK,ZUNIK,D1MACH,ZABS + //***END PROLOGUE ZUNK1 + + #endregion + + const double coner = 1.0; + const double pi = 3.14159265358979323846264338327950; + const double zeroi = 0.0; + const double zeror = 0.0; + + double ang, aphi, asc, ascle, cki, ckr; + double crsc, cscl, csgni, cspni, cspnr, csr; + double c1i, c1r, c2i, c2m, c2r, fmr, fn = 0; + double fnf, phidi = 0, phidr = 0, rast, razr, rs1, rzi; + double rzr, sgn, sti, str, sumdi = 0, sumdr = 0, s1i, s1r, s2i; + double s2r; + double zet1di = 0, zet1dr = 0, zet2di = 0, zet2dr = 0, zri, zrr; + int i, ib, iflag = 0, ifn, il, initd = 0, inu, iuf, k, kdflg, kflag = 0; + int kk, m, nw = 0, ic, ipard, j; + + double[] bry = new double[3]; + double[] csrr = new double[3]; + double[] cssr = new double[3]; + double[][] cwrki = new double[3][]; //was [16][3] + cwrki[0] = new double[16]; + cwrki[1] = new double[16]; + cwrki[2] = new double[16]; + double[][] cwrkr = new double[3][]; //was [16][3] + cwrkr[0] = new double[16]; + cwrkr[1] = new double[16]; + cwrkr[2] = new double[16]; + double[] cyi = new double[2]; + double[] cyr = new double[2]; + double[] phii = new double[2]; + double[] phir = new double[2]; + double[] sumi = new double[2]; + double[] sumr = new double[2]; + double[] zeta1i = new double[2]; + double[] zeta2i = new double[2]; + double[] zeta1r = new double[2]; + double[] zeta2r = new double[2]; + int[] init = new int[2]; + + kdflg = 1; + nz = 0; + //----------------------------------------------------------------------- + // EXP(-ALIM)=EXP(-ELIM)/TOL=APPROX. ONE PRECISION GREATER THAN + // THE UNDERFLOW LIMIT + //----------------------------------------------------------------------- + cscl = 1.0 / tol; + crsc = tol; + cssr[0] = cscl; + cssr[1] = coner; + cssr[2] = crsc; + csrr[0] = crsc; + csrr[1] = coner; + csrr[2] = cscl; + bry[0] = 1.0E3 * d1mach(1) / tol; + bry[1] = 1.0 / bry[0]; + bry[2] = d1mach(2); + zrr = zr; + zri = zi; + if (zr >= 0.0) + goto L10; + zrr = -zr; + zri = -zi; + L10: + j = 2; + for (i = 1; i <= n; i++) + { + //----------------------------------------------------------------------- + // J FLIP FLOPS BETWEEN 1 AND 2 IN J = 3 - J + //----------------------------------------------------------------------- + j = 3 - j; + fn = fnu + (i - 1); + init[j - 1] = 0; + zunik(zrr, zri, fn, 2, 0, tol, ref init[j - 1], ref phir[j - 1], + ref phii[j - 1], ref zeta1r[j - 1], ref zeta1i[j - 1], ref zeta2r[j - 1], + ref zeta2i[j - 1], ref sumr[j - 1], ref sumi[j - 1], + ref cwrkr[j - 1], ref cwrki[j - 1]); + if (kode == 1) + goto L20; + str = zrr + zeta2r[j - 1]; + sti = zri + zeta2i[j - 1]; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = zeta1r[j - 1] - str; + s1i = zeta1i[j - 1] - sti; + goto L30; + L20: + s1r = zeta1r[j - 1] - zeta2r[j - 1]; + s1i = zeta1i[j - 1] - zeta2i[j - 1]; + L30: + rs1 = s1r; + //----------------------------------------------------------------------- + // TEST FOR UNDERFLOW AND OVERFLOW + //----------------------------------------------------------------------- + if (Math.Abs(rs1) > elim) + goto L60; + if (kdflg == 1) + kflag = 2; + if (Math.Abs(rs1) < alim) + goto L40; + //----------------------------------------------------------------------- + // REFINE TEST AND SCALE + //----------------------------------------------------------------------- + aphi = zabs(phir[j - 1], phii[j - 1]); + rs1 += Math.Log(aphi); + if (Math.Abs(rs1) > elim) + goto L60; + if (kdflg == 1) + kflag = 1; + if (rs1 < 0.0) + goto L40; + if (kdflg == 1) + kflag = 3; + L40: + //----------------------------------------------------------------------- + // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR + // EXPONENT EXTREMES + //----------------------------------------------------------------------- + s2r = phir[j - 1] * sumr[j - 1] - phii[j - 1] * sumi[j - 1]; + s2i = phir[j - 1] * sumi[j - 1] + phii[j - 1] * sumr[j - 1]; + str = Math.Exp(s1r) * cssr[kflag - 1]; + s1r = str * Math.Cos(s1i); + s1i = str * Math.Sin(s1i); + str = s2r * s1r - s2i * s1i; + s2i = s1r * s2i + s2r * s1i; + s2r = str; + if (kflag != 1) + goto L50; + zuchk(s2r, s2i, ref nw, bry[0], tol); + if (nw != 0) + goto L60; + L50: + cyr[kdflg - 1] = s2r; + cyi[kdflg - 1] = s2i; + yr[i - 1] = s2r * csrr[kflag - 1]; + yi[i - 1] = s2i * csrr[kflag - 1]; + if (kdflg == 2) + goto L75; + kdflg = 2; + goto L70; + L60: + if (rs1 > 0.0) + goto L300; + //----------------------------------------------------------------------- + // FOR ZR.LT.0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW + //----------------------------------------------------------------------- + if (zr < 0.0) + goto L300; + kdflg = 1; + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + nz++; + if (i == 1) + goto L70; + if (yr[i - 2] == zeror && yi[i - 2] == zeroi) + goto L70; + yr[i - 2] = zeror; + yi[i - 2] = zeroi; + nz++; + L70: + ; + } + + i = n; + L75: + razr = 1.0 / zabs(zrr, zri); + str = zrr * razr; + sti = -zri * razr; + rzr = (str + str) * razr; + rzi = (sti + sti) * razr; + ckr = fn * rzr; + cki = fn * rzi; + ib = i + 1; + if (n < ib) + goto L160; + //----------------------------------------------------------------------- + // TEST LAST MEMBER FOR UNDERFLOW AND OVERFLOW. SET SEQUENCE TO ZERO + // ON UNDERFLOW. + //----------------------------------------------------------------------- + fn = fnu + (double)(n - 1); + ipard = 1; + if (mr != 0) + ipard = 0; + zunik(zrr, zri, fn, 2, ipard, tol, ref initd, ref phidr, ref phidi, + ref zet1dr, ref zet1di, ref zet2dr, ref zet2di, ref sumdr, ref sumdi, ref cwrkr[2], + ref cwrki[2]); + if (kode == 1) + goto L80; + str = zrr + zet2dr; + sti = zri + zet2di; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = zet1dr - str; + s1i = zet1di - sti; + goto L90; + L80: + s1r = zet1dr - zet2dr; + s1i = zet1di - zet2di; + L90: + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L95; + if (Math.Abs(rs1) < alim) + goto L100; + //----------------------------------------------------------------------- + // REFINE ESTIMATE AND TEST + //----------------------------------------------------------------------- + aphi = zabs(phidr, phidi); + rs1 += Math.Log(aphi); + if (Math.Abs(rs1) < elim) + goto L100; + L95: + if (Math.Abs(rs1) > 0.0) + goto L300; + //----------------------------------------------------------------------- + // FOR ZR.LT.0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW + //----------------------------------------------------------------------- + if (zr < 0.0) + goto L300; + nz = n; + for (i = 1; i <= n; i++) + { + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + } + + return 0; + //----------------------------------------------------------------------- + // FORWARD RECUR FOR REMAINDER OF THE SEQUENCE + //----------------------------------------------------------------------- + L100: + s1r = cyr[0]; + s1i = cyi[0]; + s2r = cyr[1]; + s2i = cyi[1]; + c1r = csrr[kflag - 1]; + ascle = bry[kflag - 1]; + for (i = ib; i <= n; i++) + { + c2r = s2r; + c2i = s2i; + s2r = ckr * c2r - cki * c2i + s1r; + s2i = ckr * c2i + cki * c2r + s1i; + s1r = c2r; + s1i = c2i; + ckr += rzr; + cki += rzi; + c2r = s2r * c1r; + c2i = s2i * c1r; + yr[i - 1] = c2r; + yi[i - 1] = c2i; + if (kflag >= 3) + goto L120; + str = Math.Abs(c2r); + sti = Math.Abs(c2i); + c2m = Math.Max(str, sti); + if (c2m <= ascle) + goto L120; + kflag++; + ascle = bry[kflag - 1]; + s1r *= c1r; + s1i *= c1r; + s2r = c2r; + s2i = c2i; + s1r *= cssr[kflag - 1]; + s1i *= cssr[kflag - 1]; + s2r *= cssr[kflag - 1]; + s2i *= cssr[kflag - 1]; + c1r = csrr[kflag - 1]; + L120: + ; + } + + L160: + if (mr == 0) + return 0; + //----------------------------------------------------------------------- + // ANALYTIC CONTINUATION FOR RE(Z).LT.0.0D0 + //----------------------------------------------------------------------- + nz = 0; + fmr = (double)mr; + sgn = -dsign(pi, fmr); + //----------------------------------------------------------------------- + // CSPN AND CSGN ARE COEFF OF K AND I FUNCTIONS RESP. + //----------------------------------------------------------------------- + csgni = sgn; + inu = (int)fnu; + fnf = fnu - (double)inu; + ifn = inu + n - 1; + ang = fnf * sgn; + cspnr = Math.Cos(ang); + cspni = Math.Sin(ang); + if (ifn % 2 == 0) + goto L170; + cspnr = -cspnr; + cspni = -cspni; + L170: + asc = bry[0]; + iuf = 0; + kk = n; + kdflg = 1; + ib = ib - 1; + ic = ib - 1; + for (k = 1; k <= n; k++) + { + fn = fnu + (double)(kk - 1); + //----------------------------------------------------------------------- + // LOGIC TO SORT ref CASES WHOSE PARAMETERS WERE SET FOR THE K + // FUNCTION ABOVE + //----------------------------------------------------------------------- + m = 3; + if (n > 2) + goto L175; + L172: + initd = init[j - 1]; + phidr = phir[j - 1]; + phidi = phii[j - 1]; + zet1dr = zeta1r[j - 1]; + zet1di = zeta1i[j - 1]; + zet2dr = zeta2r[j - 1]; + zet2di = zeta2i[j - 1]; + sumdr = sumr[j - 1]; + sumdi = sumi[j - 1]; + m = j; + j = 3 - j; + goto L180; + L175: + if (kk == n && ib < n) + goto L180; + if (kk == ib || kk == ic) + goto L172; + initd = 0; + L180: + zunik(zrr, zri, fn, 1, 0, tol, ref initd, ref phidr, ref phidi, + ref zet1dr, ref zet1di, ref zet2dr, ref zet2di, ref sumdr, ref sumdi, + ref cwrkr[m - 1], ref cwrki[m - 1]); + if (kode == 1) + goto L200; + str = zrr + zet2dr; + sti = zri + zet2di; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = -zet1dr + str; + s1i = -zet1di + sti; + goto L210; + L200: + s1r = -zet1dr + zet2dr; + s1i = -zet1di + zet2di; + L210: + //----------------------------------------------------------------------- + // TEST FOR UNDERFLOW AND OVERFLOW + //----------------------------------------------------------------------- + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L260; + if (kdflg == 1) + iflag = 2; + if (Math.Abs(rs1) < alim) + goto L220; + //----------------------------------------------------------------------- + // REFINE TEST AND SCALE + //----------------------------------------------------------------------- + aphi = zabs(phidr, phidi); + rs1 += Math.Log(aphi); + if (Math.Abs(rs1) > elim) + goto L260; + if (kdflg == 1) + iflag = 1; + if (rs1 < 0.0) + goto L220; + if (kdflg == 1) + iflag = 3; + L220: + str = phidr * sumdr - phidi * sumdi; + sti = phidr * sumdi + phidi * sumdr; + s2r = -csgni * sti; + s2i = csgni * str; + str = Math.Exp(s1r) * cssr[iflag - 1]; + s1r = str * Math.Cos(s1i); + s1i = str * Math.Sin(s1i); + str = s2r * s1r - s2i * s1i; + s2i = s2r * s1i + s2i * s1r; + s2r = str; + if (iflag != 1) + goto L230; + zuchk(s2r, s2i, ref nw, bry[0], tol); + if (nw == 0) + goto L230; + s2r = zeror; + s2i = zeroi; + L230: + cyr[kdflg - 1] = s2r; + cyi[kdflg - 1] = s2i; + c2r = s2r; + c2i = s2i; + s2r *= csrr[iflag - 1]; + s2i *= csrr[iflag - 1]; + //----------------------------------------------------------------------- + // ADD I AND K FUNCTIONS, K SEQUENCE IN Y(I), I=1,N + //----------------------------------------------------------------------- + s1r = yr[kk - 1]; + s1i = yi[kk - 1]; + if (kode == 1) + goto L250; + zs1s2(zrr, zri, ref s1r, ref s1i, ref s2r, ref s2i, ref nw, asc, alim, ref iuf); + nz += nw; + L250: + yr[kk - 1] = s1r * cspnr - s1i * cspni + s2r; + yi[kk - 1] = cspnr * s1i + cspni * s1r + s2i; + kk--; + cspnr = -cspnr; + cspni = -cspni; + if (c2r != 0.0 || c2i != 0.0) + goto L255; + kdflg = 1; + goto L270; + L255: + if (kdflg == 2) + goto L275; + kdflg = 2; + goto L270; + L260: + if (rs1 > 0.0) + goto L300; + s2r = zeror; + s2i = zeroi; + goto L230; + L270: + ; + } + + k = n; + L275: + il = n - k; + if (il == 0) + return 0; + //----------------------------------------------------------------------- + // RECUR BACKWARD FOR REMAINDER OF I SEQUENCE AND ADD IN THE + // K FUNCTIONS, SCALING THE I SEQUENCE DURING RECURRENCE TO KEEP + // INTERMEDIATE ARITHMETIC ON SCALE NEAR EXPONENT EXTREMES. + //----------------------------------------------------------------------- + s1r = cyr[0]; + s1i = cyi[0]; + s2r = cyr[1]; + s2i = cyi[1]; + csr = csrr[iflag - 1]; + ascle = bry[iflag - 1]; + fn = (double)(inu + il); + for (i = 1; i <= il; i++) + { + c2r = s2r; + c2i = s2i; + s2r = s1r + (fn + fnf) * (rzr * c2r - rzi * c2i); + s2i = s1i + (fn + fnf) * (rzr * c2i + rzi * c2r); + s1r = c2r; + s1i = c2i; + fn = fn - 1.0; + c2r = s2r * csr; + c2i = s2i * csr; + ckr = c2r; + cki = c2i; + c1r = yr[kk - 1]; + c1i = yi[kk - 1]; + if (kode == 1) + { + goto L280; + } + + zs1s2(zrr, zri, ref c1r, ref c1i, ref c2r, ref c2i, ref nw, asc, alim, ref iuf); + nz += nw; + L280: + yr[kk - 1] = c1r * cspnr - c1i * cspni + c2r; + yi[kk - 1] = c1r * cspni + c1i * cspnr + c2i; + kk--; + cspnr = -cspnr; + cspni = -cspni; + if (iflag >= 3) + { + goto L290; + } + + c2r = Math.Abs(ckr); + c2i = Math.Abs(cki); + c2m = Math.Max(c2r, c2i); + if (c2m <= ascle) + goto L290; + iflag++; + ascle = bry[iflag - 1]; + s1r *= csr; + s1i *= csr; + s2r = ckr; + s2i = cki; + s1r *= cssr[iflag - 1]; + s1i *= cssr[iflag - 1]; + s2r *= cssr[iflag - 1]; + s2i *= cssr[iflag - 1]; + csr = csrr[iflag - 1]; + L290: + ; + } + + return 0; + L300: + nz = -1; + return 0; + } + + private static int zunk2(double zr, double zi, double fnu, int kode, int mr, int n, double[] yr, double[] yi, ref int nz, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZUNK2 + //***REFER TO ZBESK + // + // ZUNK2 COMPUTES K(FNU,Z) AND ITS ANALYTIC CONTINUATION FROM THE + // RIGHT HALF PLANE TO THE LEFT HALF PLANE BY MEANS OF THE + // UNIFORM ASYMPTOTIC EXPANSIONS FOR H(KIND,FNU,ZN) AND J(FNU,ZN) + // WHERE ZN IS IN THE RIGHT HALF PLANE, KIND=(3-MR)/2, MR=+1 OR + // -1. HERE ZN=ZR*I OR -ZR*I WHERE ZR=Z IF Z IS IN THE RIGHT + // HALF PLANE OR ZR=-Z IF Z IS IN THE LEFT HALF PLANE. MR INDIC- + // ATES THE DIRECTION OF ROTATION FOR ANALYTIC CONTINUATION. + // NZ=-1 MEANS AN OVERFLOW WILL OCCUR + // + //***ROUTINES CALLED ZAIRY,ZKSCL,ZS1S2,ZUCHK,ZUNHJ,D1MACH,ZABS + //***END PROLOGUE ZUNK2 + + #endregion + + const double zeror = 0.0; + const double aic = 1.26551212348464539; + const double zeroi = 0.0; + const double coner = 1.0; + const double cr1r = 1.0; + const double cr1i = 1.73205080756887729; + const double cr2r = -0.5; + const double cr2i = -0.866025403784438647; + const double hpi = 1.570796326794896619231321696; + const double pi = 3.14159265358979323846264338327950; + + double aarg, aii = 0, air = 0, ang, aphi, argdi = 0; + double argdr = 0, asc, ascle, asumdi = 0, asumdr = 0; + double bsumdi = 0, bsumdr = 0, car, cki, ckr; + double crsc, cscl, csgni, csi; + double cspni, cspnr, csr, c1i, c1r, c2i, c2m; + double c2r, daii = 0, dair = 0, fmr, fn = 0, fnf, phidi = 0, phidr = 0; + double pti, ptr, rast, razr, rs1, rzi, rzr, sar, sgn; + double sti, str, s1i, s1r, s2i, s2r, yy, zbi, zbr; + double zet1di = 0, zet1dr = 0, zet2di = 0; + double zet2dr = 0, zni, znr, zri, zrr; + int i, ib, iflag = 0, ifn, il, inn, inu, iuf, k, kdflg, kflag = 0, kk; + int nai = 0, ndai = 0, nw = 0, idum = 0, j, ipard, ic; + + double[] argi = new double[2]; + double[] argr = new double[2]; + double[] asumi = new double[2]; + double[] asumr = new double[2]; + double[] bsumi = new double[2]; + double[] bsumr = new double[2]; + double[] bry = new double[3]; + double[] cipi = { 0.0, -1.0, 0.0, 1.0 }; + double[] cipr = { 1.0, 0.0, -1.0, 0.0 }; + double[] csrr = new double[3]; + double[] cssr = new double[3]; + double[] cyi = new double[2]; + double[] cyr = new double[2]; + double[] phii = new double[2]; + double[] phir = new double[2]; + double[] zeta1i = new double[2]; + double[] zeta2i = new double[2]; + double[] zeta1r = new double[2]; + double[] zeta2r = new double[2]; + + kdflg = 1; + nz = 0; + //----------------------------------------------------------------------- + // EXP(-ALIM)=EXP(-ELIM)/TOL=APPROX. ONE PRECISION GREATER THAN + // THE UNDERFLOW LIMIT + //----------------------------------------------------------------------- + cscl = 1.0 / tol; + crsc = tol; + cssr[0] = cscl; + cssr[1] = coner; + cssr[2] = crsc; + csrr[0] = crsc; + csrr[1] = coner; + csrr[2] = cscl; + bry[0] = d1mach(1) * 1.0E3 / tol; + bry[1] = 1.0 / bry[0]; + bry[2] = d1mach(2); + zrr = zr; + zri = zi; + if (zr >= 0.0) + goto L10; + zrr = -zr; + zri = -zi; + L10: + yy = zri; + znr = zri; + zni = -zrr; + zbr = zrr; + zbi = zri; + inu = (int)fnu; + fnf = fnu - (double)inu; + ang = -hpi * fnf; + car = Math.Cos(ang); + sar = Math.Sin(ang); + c2r = hpi * sar; + c2i = -hpi * car; + kk = (inu % 4) + 1; + str = c2r * cipr[kk - 1] - c2i * cipi[kk - 1]; + sti = c2r * cipi[kk - 1] + c2i * cipr[kk - 1]; + csr = cr1r * str - cr1i * sti; + csi = cr1r * sti + cr1i * str; + if (yy > 0.0) + goto L20; + znr = -znr; + zbi = -zbi; + L20: + //----------------------------------------------------------------------- + // K(FNU,Z) IS COMPUTED FROM H(2,FNU,-I*Z) WHERE Z IS IN THE FIRST + // QUADRANT. FOURTH QUADRANT VALUES (YY.LE.0.0E0) ARE COMPUTED BY + // CONJUGATION SINCE THE K FUNCTION IS REAL ON THE POSITIVE REAL AXIS + //----------------------------------------------------------------------- + j = 2; + for (i = 1; i <= n; i++) + { + //----------------------------------------------------------------------- + // J FLIP FLOPS BETWEEN 1 AND 2 IN J = 3 - J + //----------------------------------------------------------------------- + j = 3 - j; + fn = fnu + (double)(i - 1); + zunhj(znr, zni, fn, 0, tol, ref phir[j - 1], ref phii[j - 1], ref argr[j - 1], ref argi[j - 1], ref zeta1r[j - 1], ref zeta1i[j - 1], ref zeta2r[j - 1], ref zeta2i[j - 1], ref asumr[j - 1], ref asumi[j - 1], ref bsumr[j - 1], ref bsumi[j - 1]); + if (kode == 1) + goto L30; + str = zbr + zeta2r[j - 1]; + sti = zbi + zeta2i[j - 1]; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = zeta1r[j - 1] - str; + s1i = zeta1i[j - 1] - sti; + goto L40; + L30: + s1r = zeta1r[j - 1] - zeta2r[j - 1]; + s1i = zeta1i[j - 1] - zeta2i[j - 1]; + L40: + //----------------------------------------------------------------------- + // TEST FOR UNDERFLOW AND OVERFLOW + //----------------------------------------------------------------------- + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L70; + if (kdflg == 1) + kflag = 2; + if (Math.Abs(rs1) < alim) + goto L50; + //----------------------------------------------------------------------- + // REFINE TEST AND SCALE + //----------------------------------------------------------------------- + aphi = zabs(phir[j - 1], phii[j - 1]); + aarg = zabs(argr[j - 1], argi[j - 1]); + rs1 = rs1 + Math.Log(aphi) - Math.Log(aarg) * 0.25 - aic; + if (Math.Abs(rs1) > elim) + goto L70; + if (kdflg == 1) + kflag = 1; + if (rs1 < 0.0) + goto L50; + if (kdflg == 1) + kflag = 3; + L50: + //----------------------------------------------------------------------- + // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR + // EXPONENT EXTREMES + //----------------------------------------------------------------------- + c2r = argr[j - 1] * cr2r - argi[j - 1] * cr2i; + c2i = argr[j - 1] * cr2i + argi[j - 1] * cr2r; + zairy(c2r, c2i, 0, 2, ref air, ref aii, ref nai, ref idum); + zairy(c2r, c2i, 1, 2, ref dair, ref daii, ref ndai, ref idum); + str = dair * bsumr[j - 1] - daii * bsumi[j - 1]; + sti = dair * bsumi[j - 1] + daii * bsumr[j - 1]; + ptr = str * cr2r - sti * cr2i; + pti = str * cr2i + sti * cr2r; + str = ptr + (air * asumr[j - 1] - aii * asumi[j - 1]); + sti = pti + (air * asumi[j - 1] + aii * asumr[j - 1]); + ptr = str * phir[j - 1] - sti * phii[j - 1]; + pti = str * phii[j - 1] + sti * phir[j - 1]; + s2r = ptr * csr - pti * csi; + s2i = ptr * csi + pti * csr; + str = Math.Exp(s1r) * cssr[kflag - 1]; + s1r = str * Math.Cos(s1i); + s1i = str * Math.Sin(s1i); + str = s2r * s1r - s2i * s1i; + s2i = s1r * s2i + s2r * s1i; + s2r = str; + if (kflag != 1) + goto L60; + zuchk(s2r, s2i, ref nw, bry[0], tol); + if (nw != 0) + goto L70; + L60: + if (yy <= 0.0) + s2i = -s2i; + cyr[kdflg - 1] = s2r; + cyi[kdflg - 1] = s2i; + yr[i - 1] = s2r * csrr[kflag - 1]; + yi[i - 1] = s2i * csrr[kflag - 1]; + str = csi; + csi = -csr; + csr = str; + if (kdflg == 2) + goto L85; + kdflg = 2; + goto L80; + L70: + if (rs1 > 0.0) + goto L320; + //----------------------------------------------------------------------- + // FOR ZR.LT.0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW + //----------------------------------------------------------------------- + if (zr < 0.0) + goto L320; + kdflg = 1; + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + nz++; + str = csi; + csi = -csr; + csr = str; + if (i == 1) + goto L80; + if (yr[i - 1] == zeror && yi[i - 1] == zeroi) + goto L80; + yr[i - 2] = zeror; + yi[i - 2] = zeroi; + nz++; + L80: + ; + } + + i = n; + L85: + razr = 1.0 / zabs(zrr, zri); + str = zrr * razr; + sti = -zri * razr; + rzr = (str + str) * razr; + rzi = (sti + sti) * razr; + ckr = fn * rzr; + cki = fn * rzi; + ib = i + 1; + if (n < ib) + goto L180; + //----------------------------------------------------------------------- + // TEST LAST MEMBER FOR UNDERFLOW AND OVERFLOW. SET SEQUENCE TO ZERO + // ON UNDERFLOW. + //----------------------------------------------------------------------- + fn = fnu + (double)(n - 1); + ipard = 1; + if (mr != 0) + ipard = 0; + zunhj(znr, zni, fn, ipard, tol, ref phidr, ref phidi, ref argdr, ref argdi, ref zet1dr, ref zet1di, ref zet2dr, ref zet2di, ref asumdr, ref asumdi, ref bsumdr, ref bsumdi); + if (kode == 1) + goto L90; + str = zbr + zet2dr; + sti = zbi + zet2di; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = zet1dr - str; + s1i = zet1di - sti; + goto L100; + L90: + s1r = zet1dr - zet2dr; + s1i = zet1di - zet2di; + L100: + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L105; + if (Math.Abs(rs1) < alim) + goto L120; + //----------------------------------------------------------------------- + // REFINE ESTIMATE AND TEST + //----------------------------------------------------------------------- + aphi = zabs(phidr, phidi); + rs1 += Math.Log(aphi); + if (Math.Abs(rs1) < elim) + goto L120; + L105: + if (rs1 > 0.0) + goto L320; + //----------------------------------------------------------------------- + // FOR ZR.LT.0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW + //----------------------------------------------------------------------- + if (zr < 0.0) + goto L320; + nz = n; + for (i = 1; i <= n; i++) + { + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + } + + return 0; + L120: + s1r = cyr[0]; + s1i = cyi[0]; + s2r = cyr[1]; + s2i = cyi[1]; + c1r = csrr[kflag - 1]; + ascle = bry[kflag - 1]; + for (i = ib; i <= n; i++) + { + c2r = s2r; + c2i = s2i; + s2r = ckr * c2r - cki * c2i + s1r; + s2i = ckr * c2i + cki * c2r + s1i; + s1r = c2r; + s1i = c2i; + ckr += rzr; + cki += rzi; + c2r = s2r * c1r; + c2i = s2i * c1r; + yr[i - 1] = c2r; + yi[i - 1] = c2i; + if (kflag >= 3) + goto L130; + str = Math.Abs(c2r); + sti = Math.Abs(c2i); + c2m = Math.Max(str, sti); + if (c2m <= ascle) + goto L130; + kflag++; + ascle = bry[kflag - 1]; + s1r *= c1r; + s1i *= c1r; + s2r = c2r; + s2i = c2i; + s1r *= cssr[kflag - 1]; + s1i *= cssr[kflag - 1]; + s2r *= cssr[kflag - 1]; + s2i *= cssr[kflag - 1]; + c1r = csrr[kflag - 1]; + L130: + ; + } + + L180: + if (mr == 0) + return 0; + //----------------------------------------------------------------------- + // ANALYTIC CONTINUATION FOR RE(Z).LT.0.0D0 + //----------------------------------------------------------------------- + nz = 0; + fmr = (double)mr; + sgn = -dsign(pi, fmr); + //----------------------------------------------------------------------- + // CSPN AND CSGN ARE COEFF OF K AND I FUNCTIONS RESP. + //----------------------------------------------------------------------- + csgni = sgn; + if (yy <= 0.0) + csgni = -csgni; + ifn = inu + n - 1; + ang = fnf * sgn; + cspnr = Math.Cos(ang); + cspni = Math.Sin(ang); + if (ifn % 2 == 0) + goto L190; + cspnr = -cspnr; + cspni = -cspni; + L190: + //----------------------------------------------------------------------- + // CS=COEFF OF THE J FUNCTION TO GET THE I FUNCTION. I(FNU,Z) IS + // COMPUTED FROM EXP(I*FNU*HPI)*J(FNU,-I*Z) WHERE Z IS IN THE FIRST + // QUADRANT. FOURTH QUADRANT VALUES (YY.LE.0.0E0) ARE COMPUTED BY + // CONJUGATION SINCE THE I FUNCTION IS REAL ON THE POSITIVE REAL AXIS + //----------------------------------------------------------------------- + csr = sar * csgni; + csi = car * csgni; + inn = (ifn % 4) + 1; + c2r = cipr[inn - 1]; + c2i = cipi[inn - 1]; + str = csr * c2r + csi * c2i; + csi = -csr * c2i + csi * c2r; + csr = str; + asc = bry[0]; + iuf = 0; + kk = n; + kdflg = 1; + ib--; + ic = ib - 1; + for (k = 1; k <= n; k++) + { + fn = fnu + (kk - 1); + //----------------------------------------------------------------------- + // LOGIC TO SORT ref CASES WHOSE PARAMETERS WERE SET FOR THE K + // FUNCTION ABOVE + //----------------------------------------------------------------------- + if (n > 2) + goto L175; + L172: + phidr = phir[j - 1]; + phidi = phii[j - 1]; + argdr = argr[j - 1]; + argdi = argi[j - 1]; + zet1dr = zeta1r[j - 1]; + zet1di = zeta1i[j - 1]; + zet2dr = zeta2r[j - 1]; + zet2di = zeta2i[j - 1]; + asumdr = asumr[j - 1]; + asumdi = asumi[j - 1]; + bsumdr = bsumr[j - 1]; + bsumdi = bsumi[j - 1]; + j = 3 - j; + goto L210; + L175: + if (kk == n && ib < n) + goto L210; + if (kk == ib || kk == ic) + goto L172; + zunhj(znr, zni, fn, 0, tol, ref phidr, ref phidi, ref argdr, ref argdi, ref zet1dr, ref zet1di, ref zet2dr, ref zet2di, ref asumdr, ref asumdi, ref bsumdr, ref bsumdi); + L210: + if (kode == 1) + goto L220; + str = zbr + zet2dr; + sti = zbi + zet2di; + rast = fn / zabs(str, sti); + str = str * rast * rast; + sti = -sti * rast * rast; + s1r = -zet1dr + str; + s1i = -zet1di + sti; + goto L230; + L220: + s1r = -zet1dr + zet2dr; + s1i = -zet1di + zet2di; + L230: + //----------------------------------------------------------------------- + // TEST FOR UNDERFLOW AND OVERFLOW + //----------------------------------------------------------------------- + rs1 = s1r; + if (Math.Abs(rs1) > elim) + goto L280; + if (kdflg == 1) + iflag = 2; + if (Math.Abs(rs1) < alim) + goto L240; + //----------------------------------------------------------------------- + // REFINE TEST AND SCALE + //----------------------------------------------------------------------- + aphi = zabs(phidr, phidi); + aarg = zabs(argdr, argdi); + rs1 = rs1 + Math.Log(aphi) - 0.25 * Math.Log(aarg) - aic; + if (Math.Abs(rs1) > elim) + goto L280; + if (kdflg == 1) + iflag = 1; + if (rs1 < 0.0) + goto L240; + if (kdflg == 1) + iflag = 3; + L240: + zairy(argdr, argdi, 0, 2, ref air, ref aii, ref nai, ref idum); + zairy(argdr, argdi, 1, 2, ref dair, ref daii, ref ndai, ref idum); + str = dair * bsumdr - daii * bsumdi; + sti = dair * bsumdi + daii * bsumdr; + str += air * asumdr - aii * asumdi; + sti += air * asumdi + aii * asumdr; + ptr = str * phidr - sti * phidi; + pti = str * phidi + sti * phidr; + s2r = ptr * csr - pti * csi; + s2i = ptr * csi + pti * csr; + str = Math.Exp(s1r) * cssr[iflag - 1]; + s1r = str * Math.Cos(s1i); + s1i = str * Math.Sin(s1i); + str = s2r * s1r - s2i * s1i; + s2i = s2r * s1i + s2i * s1r; + s2r = str; + if (iflag != 1) + goto L250; + zuchk(s2r, s2i, ref nw, bry[0], tol); + if (nw == 0) + goto L250; + s2r = zeror; + s2i = zeroi; + L250: + if (yy <= 0.0) + s2i = -s2i; + cyr[kdflg - 1] = s2r; + cyi[kdflg - 1] = s2i; + c2r = s2r; + c2i = s2i; + s2r *= csrr[iflag - 1]; + s2i *= csrr[iflag - 1]; + //----------------------------------------------------------------------- + // ADD I AND K FUNCTIONS, K SEQUENCE IN Y(I), I=1,N + //----------------------------------------------------------------------- + s1r = yr[kk - 1]; + s1i = yi[kk - 1]; + if (kode == 1) + goto L270; + zs1s2(zrr, zri, ref s1r, ref s1i, ref s2r, ref s2i, ref nw, asc, alim, ref iuf); + nz += nw; + L270: + yr[kk - 1] = s1r * cspnr - s1i * cspni + s2r; + yi[kk - 1] = s1r * cspni + s1i * cspnr + s2i; + kk--; + cspnr = -cspnr; + cspni = -cspni; + str = csi; + csi = -csr; + csr = str; + if (c2r != 0.0 || c2i != 0.0) + goto L255; + kdflg = 1; + goto L290; + L255: + if (kdflg == 2) + goto L295; + kdflg = 2; + goto L290; + L280: + if (rs1 > 0.0) + goto L320; + s2r = zeror; + s2i = zeroi; + goto L250; + L290: + ; + } + + k = n; + L295: + il = n - k; + if (il == 0) + return 0; + //----------------------------------------------------------------------- + // RECUR BACKWARD FOR REMAINDER OF I SEQUENCE AND ADD IN THE + // K FUNCTIONS, SCALING THE I SEQUENCE DURING RECURRENCE TO KEEP + // INTERMEDIATE ARITHMETIC ON SCALE NEAR EXPONENT EXTREMES. + //----------------------------------------------------------------------- + s1r = cyr[0]; + s1i = cyi[0]; + s2r = cyr[1]; + s2i = cyi[1]; + csr = csrr[iflag - 1]; + ascle = bry[iflag - 1]; + fn = (double)(inu + il); + for (i = 1; i <= il; i++) + { + c2r = s2r; + c2i = s2i; + s2r = s1r + (fn + fnf) * (rzr * c2r - rzi * c2i); + s2i = s1i + (fn + fnf) * (rzr * c2i + rzi * c2r); + s1r = c2r; + s1i = c2i; + fn = fn - 1.0; + c2r = s2r * csr; + c2i = s2i * csr; + ckr = c2r; + cki = c2i; + c1r = yr[kk - 1]; + c1i = yi[kk - 1]; + if (kode == 1) + goto L300; + zs1s2(zrr, zri, ref c1r, ref c1i, ref c2r, ref c2i, ref nw, asc, alim, ref iuf); + nz = nz + nw; + L300: + yr[kk - 1] = c1r * cspnr - c1i * cspni + c2r; + yi[kk - 1] = c1r * cspni + c1i * cspnr + c2i; + kk--; + cspnr = -cspnr; + cspni = -cspni; + if (iflag >= 3) + goto L310; + c2r = Math.Abs(ckr); + c2i = Math.Abs(cki); + c2m = Math.Max(c2r, c2i); + if (c2m <= ascle) + goto L310; + iflag++; + ascle = bry[iflag - 1]; + s1r *= csr; + s1i *= csr; + s2r = ckr; + s2i = cki; + s1r *= cssr[iflag - 1]; + s1i *= cssr[iflag - 1]; + s2r *= cssr[iflag - 1]; + s2i *= cssr[iflag - 1]; + csr = csrr[iflag - 1]; + L310: + ; + } + + return 0; + L320: + nz = -1; + return 0; + } + + private static int zuoik(double zr, double zi, double fnu, int kode, int ikflg, int n, double[] yr, double[] yi, ref int nuf, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZUOIK + //***REFER TO ZBESI,ZBESK,ZBESH + // + // ZUOIK COMPUTES THE LEADING TERMS OF THE UNIFORM ASYMPTOTIC + // EXPANSIONS FOR THE I AND K FUNCTIONS AND COMPARES THEM + // (IN LOGARITHMIC FORM) TO ALIM AND ELIM FOR OVER AND UNDERFLOW + // WHERE ALIM.LT.ELIM. IF THE MAGNITUDE, BASED ON THE LEADING + // EXPONENTIAL, IS LESS THAN ALIM OR GREATER THAN -ALIM, THEN + // THE RESULT IS ON SCALE. IF NOT, THEN A REFINED TEST USING OTHER + // MULTIPLIERS (IN LOGARITHMIC FORM) IS MADE BASED ON ELIM. HERE + // EXP(-ELIM)=SMALLEST MACHINE NUMBER*1.0E+3 AND EXP(-ALIM)= + // EXP(-ELIM)/TOL + // + // IKFLG=1 MEANS THE I SEQUENCE IS TESTED + // =2 MEANS THE K SEQUENCE IS TESTED + // NUF = 0 MEANS THE LAST MEMBER OF THE SEQUENCE IS ON SCALE + // =-1 MEANS AN OVERFLOW WOULD OCCUR + // IKFLG=1 AND NUF.GT.0 MEANS THE LAST NUF Y VALUES WERE SET TO ZERO + // THE FIRST N-NUF VALUES MUST BE SET BY ANOTHER ROUTINE + // IKFLG=2 AND NUF.EQ.N MEANS ALL Y VALUES WERE SET TO ZERO + // IKFLG=2 AND 0.LT.NUF.LT.N NOT CONSIDERED. Y MUST BE SET BY + // ANOTHER ROUTINE + // + //***ROUTINES CALLED ZUCHK,ZUNHJ,ZUNIK,D1MACH,ZABS,ZLOG + //***END PROLOGUE ZUOIK + + #endregion + + const double zeror = 0.0; + const double zeroi = 0.0; + const double aic = 1.265512123484645396; + + double aarg = 0, aphi, argi = 0, argr = 0, asumi = 0, asumr = 0; + double ascle, ax, ay, bsumi = 0, bsumr = 0, czi, czr, fnn; + double gnn, gnu, phii = 0, phir = 0, rcz, str = 0, sti = 0, sumi = 0, sumr = 0; + double zbi, zbr, zeta1i = 0, zeta1r = 0, zeta2i = 0, zeta2r = 0; + double zni = 0, znr = 0, zri, zrr; + int i, idum = 0, iform, init, nn, nw = 0; + + double[] cwrkr = new double[16]; + double[] cwrki = new double[16]; + + nuf = 0; + nn = n; + zrr = zr; + zri = zi; + if (zr >= 0.0) + goto L10; + zrr = -zr; + zri = -zi; + L10: + zbr = zrr; + zbi = zri; + ax = Math.Abs(zr) * 1.7321; + ay = Math.Abs(zi); + iform = 1; + if (ay > ax) + iform = 2; + gnu = Math.Max(fnu, 1.0); + if (ikflg == 1) + goto L20; + fnn = (double)nn; + gnn = fnu + fnn - 1.0; + gnu = Math.Max(gnn, fnn); + L20: + //----------------------------------------------------------------------- + // ONLY THE MAGNITUDE OF ARG AND PHI ARE NEEDED ALONG WITH THE + // REAL PARTS OF ZETA1, ZETA2 AND ZB. NO ATTEMPT IS MADE TO GET + // THE SIGN OF THE IMAGINARY PART CORRECT. + //----------------------------------------------------------------------- + if (iform == 2) + goto L30; + init = 0; + zunik(zrr, zri, gnu, ikflg, 1, tol, ref init, ref phir, ref phii, ref zeta1r, ref zeta1i, ref zeta2r, ref zeta2i, ref sumr, ref sumi, ref cwrkr, ref cwrki); + czr = -zeta1r + zeta2r; + czi = -zeta1i + zeta2i; + goto L50; + L30: + znr = zri; + zni = -zrr; + if (zi > 0.0) + goto L40; + znr = -znr; + L40: + zunhj(znr, zni, gnu, 1, tol, ref phir, ref phii, ref argr, ref argi, ref zeta1r, ref zeta1i, ref zeta2r, ref zeta2i, ref asumr, ref asumi, ref bsumr, ref bsumi); + czr = -zeta1r + zeta2r; + czi = -zeta1i + zeta2i; + aarg = zabs(argr, argi); + L50: + if (kode == 1) + goto L60; + czr -= zbr; + czi -= zbi; + L60: + if (ikflg == 1) + goto L70; + czr = -czr; + czi = -czi; + L70: + aphi = zabs(phir, phii); + rcz = czr; + //----------------------------------------------------------------------- + // OVERFLOW TEST + //----------------------------------------------------------------------- + if (rcz > elim) + goto L210; + if (rcz < alim) + goto L80; + rcz += Math.Log(aphi); + if (iform == 2) + rcz = rcz - Math.Log(aarg) * 0.25 - aic; + if (rcz > elim) + goto L210; + goto L130; + L80: + //----------------------------------------------------------------------- + // UNDERFLOW TEST + //----------------------------------------------------------------------- + if (rcz < -elim) + goto L90; + if (rcz > -alim) + goto L130; + rcz += Math.Log(aphi); + if (iform == 2) + rcz = rcz - Math.Log(aarg) * .25 - aic; + if (rcz > -elim) + goto L110; + L90: + for (i = 1; i <= nn; i++) + { + yr[i - 1] = zeror; + yi[i - 1] = zeroi; + } + + nuf = nn; + return 0; + L110: + ascle = d1mach(1) * 1.0E3 / tol; + zlog(phir, phii, ref str, ref sti, ref idum); + czr += str; + czi += sti; + if (iform == 1) + goto L120; + zlog(argr, argi, ref str, ref sti, ref idum); + czr = czr - str * 0.25 - aic; + czi -= sti * 0.25; + L120: + ax = Math.Exp(rcz) / tol; + ay = czi; + czr = ax * Math.Cos(ay); + czi = ax * Math.Sin(ay); + zuchk(czr, czi, ref nw, ascle, tol); + if (nw != 0) + goto L90; + L130: + if (ikflg == 2) + return 0; + if (n == 1) + return 0; + //----------------------------------------------------------------------- + // SET UNDERFLOWS ON I SEQUENCE + //----------------------------------------------------------------------- + L140: + gnu = fnu + (nn - 1); + if (iform == 2) + goto L150; + init = 0; + zunik(zrr, zri, gnu, ikflg, 1, tol, ref init, ref phir, ref phii, ref zeta1r, ref zeta1i, ref zeta2r, ref zeta2i, ref sumr, ref sumi, ref cwrkr, ref cwrki); + czr = -zeta1r + zeta2r; + czi = -zeta1i + zeta2i; + goto L160; + L150: + zunhj(znr, zni, gnu, 1, tol, ref phir, ref phii, ref argr, ref argi, ref zeta1r, ref zeta1i, ref zeta2r, ref zeta2i, ref asumr, ref asumi, ref bsumr, ref bsumi); + czr = -zeta1r + zeta2r; + czi = -zeta1i + zeta2i; + aarg = zabs(argr, argi); + L160: + if (kode == 1) + goto L170; + czr -= zbr; + czi -= zbi; + L170: + aphi = zabs(phir, phii); + rcz = czr; + if (rcz < -elim) + goto L180; + if (rcz > -alim) + return 0; + rcz += Math.Log(aphi); + if (iform == 2) + rcz = rcz - Math.Log(aarg) * 0.25 - aic; + if (rcz > -elim) + goto L190; + L180: + yr[nn - 1] = zeror; + yi[nn - 1] = zeroi; + nn--; + nuf++; + if (nn == 0) + return 0; + goto L140; + L190: + ascle = d1mach(1) * 1.0E3 / tol; + zlog(phir, phii, ref str, ref sti, ref idum); + czr += str; + czi += sti; + if (iform == 1) + goto L200; + zlog(argr, argi, ref str, ref sti, ref idum); + czr = czr - str * 0.25 - aic; + czi -= sti * 0.25; + L200: + ax = Math.Exp(rcz) / tol; + ay = czi; + czr = ax * Math.Cos(ay); + czi = ax * Math.Sin(ay); + zuchk(czr, czi, ref nw, ascle, tol); + if (nw != 0) + goto L180; + return 0; + L210: + nuf = -1; + return 0; + } + + private static int zwrsk(double zrr, double zri, double fnu, int kode, int n, double[] yr, double[] yi, ref int nz, double[] cwr, double[] cwi, double tol, double elim, double alim) + { + #region Description + + //***BEGIN PROLOGUE ZWRSK + //***REFER TO ZBESI,ZBESK + // + // ZWRSK COMPUTES THE I BESSEL FUNCTION FOR RE(Z).GE.0.0 BY + // NORMALIZING THE I FUNCTION RATIOS FROM ZRATI BY THE WRONSKIAN + // + //***ROUTINES CALLED D1MACH,ZBKNU,ZRATI,ZABS + //***END PROLOGUE ZWRSK + + #endregion + + double act, acw, ascle, cinui, cinur, csclr, cti; + double ctr, c1i, c1r, c2i, c2r, pti, ptr, ract; + double sti, str; + int i, nw = 0; + + //----------------------------------------------------------------------- + // I(FNU+I-1,Z) BY BACKWARD RECURRENCE FOR RATIOS + // Y(I)=I(FNU+I,Z)/I(FNU+I-1,Z) FROM CRATI NORMALIZED BY THE + // WRONSKIAN WITH K(FNU,Z) AND K(FNU+1,Z) FROM CBKNU. + //----------------------------------------------------------------------- + nz = 0; + zbknu(zrr, zri, fnu, kode, 2, cwr, cwi, ref nw, tol, elim, alim); + if (nw != 0) + goto L50; + zrati(zrr, zri, fnu, n, yr, yi, tol); + //----------------------------------------------------------------------- + // RECUR FORWARD ON I(FNU+1,Z) = R(FNU,Z)*I(FNU,Z), + // R(FNU+J-1,Z)=Y(J), J=1,...,N + //----------------------------------------------------------------------- + cinur = 1.0; + cinui = 0.0; + if (kode == 1) + goto L10; + cinur = Math.Cos(zri); + cinui = Math.Sin(zri); + L10: + //----------------------------------------------------------------------- + // ON LOW EXPONENT MACHINES THE K FUNCTIONS CAN BE CLOSE TO BOTH + // THE UNDER AND OVERFLOW LIMITS AND THE NORMALIZATION MUST BE + // SCALED TO PREVENT OVER OR UNDERFLOW. CUOIK HAS DETERMINED THAT + // THE RESULT IS ON SCALE. + //----------------------------------------------------------------------- + acw = zabs(cwr[1], cwi[1]); + ascle = d1mach(1) * 1.0E3 / tol; + csclr = 1.0; + if (acw > ascle) + goto L20; + csclr = 1.0 / tol; + goto L30; + L20: + ascle = 1.0 / ascle; + if (acw < ascle) + goto L30; + csclr = tol; + L30: + c1r = cwr[0] * csclr; + c1i = cwi[0] * csclr; + c2r = cwr[1] * csclr; + c2i = cwi[1] * csclr; + str = yr[0]; + sti = yi[0]; + //----------------------------------------------------------------------- + // CINU=CINU*(CONJG(CT)/ABS(CT))*(1.0D0/ABS(CT) PREVENTS + // UNDER- OR OVERFLOW PREMATURELY BY SQUARING ABS(CT) + //----------------------------------------------------------------------- + ptr = str * c1r - sti * c1i; + pti = str * c1i + sti * c1r; + ptr += c2r; + pti += c2i; + ctr = zrr * ptr - zri * pti; + cti = zrr * pti + zri * ptr; + act = zabs(ctr, cti); + ract = 1.0 / act; + ctr *= ract; + cti = -cti * ract; + ptr = cinur * ract; + pti = cinui * ract; + cinur = ptr * ctr - pti * cti; + cinui = ptr * cti + pti * ctr; + yr[0] = cinur * csclr; + yi[0] = cinui * csclr; + if (n == 1) + return 0; + for (i = 2; i <= n; i++) + { + ptr = str * cinur - sti * cinui; + cinui = str * cinui + sti * cinur; + cinur = ptr; + str = yr[i - 1]; + sti = yi[i - 1]; + yr[i - 1] = cinur * csclr; + yi[i - 1] = cinui * csclr; + } + + return 0; + L50: + nz = -1; + if (nw == -2) + nz = -2; + return 0; + } + + #endregion + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Bessel.cs b/MathNet.Numerics/SpecialFunctions/Bessel.cs new file mode 100644 index 0000000..5529602 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Bessel.cs @@ -0,0 +1,201 @@ +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the Bessel functions. +/// +public static partial class SpecialFunctions +{ + /// + /// Returns the Bessel function of the first kind. + /// BesselJ(n, z) is a solution to the Bessel differential equation. + /// + /// The order of the Bessel function. + /// The value to compute the Bessel function of. + /// The Bessel function of the first kind. + public static Complex BesselJ(double n, Complex z) + { + return Amos.Cbesj(n, z); + } + + /// + /// Returns the exponentially scaled Bessel function of the first kind. + /// ScaledBesselJ(n, z) is given by Exp(-Abs(z.Imaginary)) * BesselJ(n, z). + /// + /// The order of the Bessel function. + /// The value to compute the Bessel function of. + /// The exponentially scaled Bessel function of the first kind. + public static Complex BesselJScaled(double n, Complex z) + { + return Amos.ScaledCbesj(n, z); + } + + /// + /// Returns the Bessel function of the first kind. + /// BesselJ(n, z) is a solution to the Bessel differential equation. + /// + /// The order of the Bessel function. + /// The value to compute the Bessel function of. + /// The Bessel function of the first kind. + public static double BesselJ(double n, double z) + { + return Amos.Cbesj(n, z); + } + + /// + /// Returns the exponentially scaled Bessel function of the first kind. + /// ScaledBesselJ(n, z) is given by Exp(-Abs(z.Imaginary)) * BesselJ(n, z). + /// + /// The order of the Bessel function. + /// The value to compute the Bessel function of. + /// The exponentially scaled Bessel function of the first kind. + public static double BesselJScaled(double n, double z) + { + return Amos.ScaledCbesj(n, z); + } + + /// + /// Returns the Bessel function of the second kind. + /// BesselY(n, z) is a solution to the Bessel differential equation. + /// + /// The order of the Bessel function. + /// The value to compute the Bessel function of. + /// The Bessel function of the second kind. + public static Complex BesselY(double n, Complex z) + { + return Amos.Cbesy(n, z); + } + + /// + /// Returns the exponentially scaled Bessel function of the second kind. + /// ScaledBesselY(n, z) is given by Exp(-Abs(z.Imaginary)) * Y(n, z). + /// + /// The order of the Bessel function. + /// The value to compute the Bessel function of. + /// The exponentially scaled Bessel function of the second kind. + public static Complex BesselYScaled(double n, Complex z) + { + return Amos.ScaledCbesy(n, z); + } + + /// + /// Returns the Bessel function of the second kind. + /// BesselY(n, z) is a solution to the Bessel differential equation. + /// + /// The order of the Bessel function. + /// The value to compute the Bessel function of. + /// The Bessel function of the second kind. + public static double BesselY(double n, double z) + { + return Amos.Cbesy(n, z); + } + + /// + /// Returns the exponentially scaled Bessel function of the second kind. + /// ScaledBesselY(n, z) is given by Exp(-Abs(z.Imaginary)) * BesselY(n, z). + /// + /// The order of the Bessel function. + /// The value to compute the Bessel function of. + /// The exponentially scaled Bessel function of the second kind. + public static double BesselYScaled(double n, double z) + { + return Amos.ScaledCbesy(n, z); + } + + /// + /// Returns the modified Bessel function of the first kind. + /// BesselI(n, z) is a solution to the modified Bessel differential equation. + /// + /// The order of the modified Bessel function. + /// The value to compute the modified Bessel function of. + /// The modified Bessel function of the first kind. + public static Complex BesselI(double n, Complex z) + { + return Amos.Cbesi(n, z); + } + + /// + /// Returns the exponentially scaled modified Bessel function of the first kind. + /// ScaledBesselI(n, z) is given by Exp(-Abs(z.Real)) * BesselI(n, z). + /// + /// The order of the modified Bessel function. + /// The value to compute the modified Bessel function of. + /// The exponentially scaled modified Bessel function of the first kind. + public static Complex BesselIScaled(double n, Complex z) + { + return Amos.ScaledCbesi(n, z); + } + + /// + /// Returns the modified Bessel function of the first kind. + /// BesselI(n, z) is a solution to the modified Bessel differential equation. + /// + /// The order of the modified Bessel function. + /// The value to compute the modified Bessel function of. + /// The modified Bessel function of the first kind. + public static double BesselI(double n, double z) + { + return BesselI(n, new Complex(z, 0)).Real; + } + + /// + /// Returns the exponentially scaled modified Bessel function of the first kind. + /// ScaledBesselI(n, z) is given by Exp(-Abs(z.Real)) * BesselI(n, z). + /// + /// The order of the modified Bessel function. + /// The value to compute the modified Bessel function of. + /// The exponentially scaled modified Bessel function of the first kind. + public static double BesselIScaled(double n, double z) + { + return Amos.ScaledCbesi(n, z); + } + + /// + /// Returns the modified Bessel function of the second kind. + /// BesselK(n, z) is a solution to the modified Bessel differential equation. + /// + /// The order of the modified Bessel function. + /// The value to compute the modified Bessel function of. + /// The modified Bessel function of the second kind. + public static Complex BesselK(double n, Complex z) + { + return Amos.Cbesk(n, z); + } + + /// + /// Returns the exponentially scaled modified Bessel function of the second kind. + /// ScaledBesselK(n, z) is given by Exp(z) * BesselK(n, z). + /// + /// The order of the modified Bessel function. + /// The value to compute the modified Bessel function of. + /// The exponentially scaled modified Bessel function of the second kind. + public static Complex BesselKScaled(double n, Complex z) + { + return Amos.ScaledCbesk(n, z); + } + + /// + /// Returns the modified Bessel function of the second kind. + /// BesselK(n, z) is a solution to the modified Bessel differential equation. + /// + /// The order of the modified Bessel function. + /// The value to compute the modified Bessel function of. + /// The modified Bessel function of the second kind. + public static double BesselK(double n, double z) + { + return Amos.Cbesk(n, z); + } + + /// + /// Returns the exponentially scaled modified Bessel function of the second kind. + /// ScaledBesselK(n, z) is given by Exp(z) * BesselK(n, z). + /// + /// The order of the modified Bessel function. + /// The value to compute the modified Bessel function of. + /// The exponentially scaled modified Bessel function of the second kind. + public static double BesselKScaled(double n, double z) + { + return Amos.ScaledCbesk(n, z); + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Beta.cs b/MathNet.Numerics/SpecialFunctions/Beta.cs new file mode 100644 index 0000000..1ef67a6 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Beta.cs @@ -0,0 +1,194 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// +// Cephes Math Library, Stephen L. Moshier +// ALGLIB 2.0.1, Sergey Bochkanov +// + +using MathNet.Numerics.Properties; + +using System; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +public static partial class SpecialFunctions +{ + /// + /// Computes the logarithm of the Euler Beta function. + /// + /// The first Beta parameter, a positive real number. + /// The second Beta parameter, a positive real number. + /// The logarithm of the Euler Beta function evaluated at z,w. + /// If or are not positive. + public static double BetaLn(double z, double w) + { + if (z <= 0.0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(z)); + } + + if (w <= 0.0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(w)); + } + + return GammaLn(z) + GammaLn(w) - GammaLn(z + w); + } + + /// + /// Computes the Euler Beta function. + /// + /// The first Beta parameter, a positive real number. + /// The second Beta parameter, a positive real number. + /// The Euler Beta function evaluated at z,w. + /// If or are not positive. + public static double Beta(double z, double w) + { + return Math.Exp(BetaLn(z, w)); + } + + /// + /// Returns the lower incomplete (unregularized) beta function + /// B(a,b,x) = int(t^(a-1)*(1-t)^(b-1),t=0..x) for real a > 0, b > 0, 1 >= x >= 0. + /// + /// The first Beta parameter, a positive real number. + /// The second Beta parameter, a positive real number. + /// The upper limit of the integral. + /// The lower incomplete (unregularized) beta function. + public static double BetaIncomplete(double a, double b, double x) + { + return BetaRegularized(a, b, x) * Beta(a, b); + } + + /// + /// Returns the regularized lower incomplete beta function + /// I_x(a,b) = 1/Beta(a,b) * int(t^(a-1)*(1-t)^(b-1),t=0..x) for real a > 0, b > 0, 1 >= x >= 0. + /// + /// The first Beta parameter, a positive real number. + /// The second Beta parameter, a positive real number. + /// The upper limit of the integral. + /// The regularized lower incomplete beta function. + public static double BetaRegularized(double a, double b, double x) + { + if (a < 0.0) + { + throw new ArgumentOutOfRangeException(nameof(a), Resources.ArgumentNotNegative); + } + + if (b < 0.0) + { + throw new ArgumentOutOfRangeException(nameof(b), Resources.ArgumentNotNegative); + } + + if (x < 0.0 || x > 1.0) + { + throw new ArgumentOutOfRangeException(nameof(x), Resources.ArgumentInIntervalXYInclusive); + } + + var bt = (x == 0.0 || x == 1.0) + ? 0.0 + : Math.Exp(GammaLn(a + b) - GammaLn(a) - GammaLn(b) + (a * Math.Log(x)) + (b * Math.Log(1.0 - x))); + + var symmetryTransformation = x >= (a + 1.0) / (a + b + 2.0); + + /* Continued fraction representation */ + var eps = Precision.DoublePrecision; + var fpmin = 0.0.Increment() / eps; + + if (symmetryTransformation) + { + x = 1.0 - x; + var swap = a; + a = b; + b = swap; + } + + var qab = a + b; + var qap = a + 1.0; + var qam = a - 1.0; + var c = 1.0; + var d = 1.0 - (qab * x / qap); + + if (Math.Abs(d) < fpmin) + { + d = fpmin; + } + + d = 1.0 / d; + var h = d; + + for (int m = 1, m2 = 2; m <= 50000; m++, m2 += 2) + { + var aa = m * (b - m) * x / ((qam + m2) * (a + m2)); + d = 1.0 + (aa * d); + + if (Math.Abs(d) < fpmin) + { + d = fpmin; + } + + c = 1.0 + (aa / c); + if (Math.Abs(c) < fpmin) + { + c = fpmin; + } + + d = 1.0 / d; + h *= d * c; + aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2)); + d = 1.0 + (aa * d); + + if (Math.Abs(d) < fpmin) + { + d = fpmin; + } + + c = 1.0 + (aa / c); + + if (Math.Abs(c) < fpmin) + { + c = fpmin; + } + + d = 1.0 / d; + var del = d * c; + h *= del; + + if (Math.Abs(del - 1.0) <= eps) + { + return symmetryTransformation ? 1.0 - (bt * h / a) : bt * h / a; + } + } + + return symmetryTransformation ? 1.0 - (bt * h / a) : bt * h / a; + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Erf.cs b/MathNet.Numerics/SpecialFunctions/Erf.cs new file mode 100644 index 0000000..21271f6 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Erf.cs @@ -0,0 +1,677 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +// This file uses code from the Boost Project. +// (C) Copyright John Maddock 2006. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt) +// +// In particular the following functions are based on Boost: +// * double ErfImp(double z, bool invert) +// * double ErfInvImpl(double p, double q, double s) +// + +using System; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the error function. +/// +public partial class SpecialFunctions +{ + /// + /// ************************************** + /// COEFFICIENTS FOR METHOD ErfImp * + /// ************************************** + /// + /// Polynomial coefficients for a numerator of ErfImp + /// calculation for Erf(x) in the interval [1e-10, 0.5]. + /// + private static readonly double[] ErfImpAn = { 0.00337916709551257388990745, -0.00073695653048167948530905, -0.374732337392919607868241, 0.0817442448733587196071743, -0.0421089319936548595203468, 0.0070165709512095756344528, -0.00495091255982435110337458, 0.000871646599037922480317225 }; + + /// Polynomial coefficients for a denominator of ErfImp + /// calculation for Erf(x) in the interval [1e-10, 0.5]. + /// + private static readonly double[] ErfImpAd = { 1, -0.218088218087924645390535, 0.412542972725442099083918, -0.0841891147873106755410271, 0.0655338856400241519690695, -0.0120019604454941768171266, 0.00408165558926174048329689, -0.000615900721557769691924509 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [0.5, 0.75]. + /// + private static readonly double[] ErfImpBn = { -0.0361790390718262471360258, 0.292251883444882683221149, 0.281447041797604512774415, 0.125610208862766947294894, 0.0274135028268930549240776, 0.00250839672168065762786937 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [0.5, 0.75]. + /// + private static readonly double[] ErfImpBd = { 1, 1.8545005897903486499845, 1.43575803037831418074962, 0.582827658753036572454135, 0.124810476932949746447682, 0.0113724176546353285778481 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [0.75, 1.25]. + /// + private static readonly double[] ErfImpCn = { -0.0397876892611136856954425, 0.153165212467878293257683, 0.191260295600936245503129, 0.10276327061989304213645, 0.029637090615738836726027, 0.0046093486780275489468812, 0.000307607820348680180548455 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [0.75, 1.25]. + /// + private static readonly double[] ErfImpCd = { 1, 1.95520072987627704987886, 1.64762317199384860109595, 0.768238607022126250082483, 0.209793185936509782784315, 0.0319569316899913392596356, 0.00213363160895785378615014 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [1.25, 2.25]. + /// + private static readonly double[] ErfImpDn = { -0.0300838560557949717328341, 0.0538578829844454508530552, 0.0726211541651914182692959, 0.0367628469888049348429018, 0.00964629015572527529605267, 0.00133453480075291076745275, 0.778087599782504251917881e-4 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [1.25, 2.25]. + /// + private static readonly double[] ErfImpDd = { 1, 1.75967098147167528287343, 1.32883571437961120556307, 0.552528596508757581287907, 0.133793056941332861912279, 0.0179509645176280768640766, 0.00104712440019937356634038, -0.106640381820357337177643e-7 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [2.25, 3.5]. + /// + private static readonly double[] ErfImpEn = { -0.0117907570137227847827732, 0.014262132090538809896674, 0.0202234435902960820020765, 0.00930668299990432009042239, 0.00213357802422065994322516, 0.00025022987386460102395382, 0.120534912219588189822126e-4 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [2.25, 3.5]. + /// + private static readonly double[] ErfImpEd = { 1, 1.50376225203620482047419, 0.965397786204462896346934, 0.339265230476796681555511, 0.0689740649541569716897427, 0.00771060262491768307365526, 0.000371421101531069302990367 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [3.5, 5.25]. + /// + private static readonly double[] ErfImpFn = { -0.00546954795538729307482955, 0.00404190278731707110245394, 0.0054963369553161170521356, 0.00212616472603945399437862, 0.000394984014495083900689956, 0.365565477064442377259271e-4, 0.135485897109932323253786e-5 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [3.5, 5.25]. + /// + private static readonly double[] ErfImpFd = { 1, 1.21019697773630784832251, 0.620914668221143886601045, 0.173038430661142762569515, 0.0276550813773432047594539, 0.00240625974424309709745382, 0.891811817251336577241006e-4, -0.465528836283382684461025e-11 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [5.25, 8]. + /// + private static readonly double[] ErfImpGn = { -0.00270722535905778347999196, 0.0013187563425029400461378, 0.00119925933261002333923989, 0.00027849619811344664248235, 0.267822988218331849989363e-4, 0.923043672315028197865066e-6 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [5.25, 8]. + /// + private static readonly double[] ErfImpGd = { 1, 0.814632808543141591118279, 0.268901665856299542168425, 0.0449877216103041118694989, 0.00381759663320248459168994, 0.000131571897888596914350697, 0.404815359675764138445257e-11 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [8, 11.5]. + /// + private static readonly double[] ErfImpHn = { -0.00109946720691742196814323, 0.000406425442750422675169153, 0.000274499489416900707787024, 0.465293770646659383436343e-4, 0.320955425395767463401993e-5, 0.778286018145020892261936e-7 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [8, 11.5]. + /// + private static readonly double[] ErfImpHd = { 1, 0.588173710611846046373373, 0.139363331289409746077541, 0.0166329340417083678763028, 0.00100023921310234908642639, 0.24254837521587225125068e-4 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [11.5, 17]. + /// + private static readonly double[] ErfImpIn = { -0.00056907993601094962855594, 0.000169498540373762264416984, 0.518472354581100890120501e-4, 0.382819312231928859704678e-5, 0.824989931281894431781794e-7 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [11.5, 17]. + /// + private static readonly double[] ErfImpId = { 1, 0.339637250051139347430323, 0.043472647870310663055044, 0.00248549335224637114641629, 0.535633305337152900549536e-4, -0.117490944405459578783846e-12 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [17, 24]. + /// + private static readonly double[] ErfImpJn = { -0.000241313599483991337479091, 0.574224975202501512365975e-4, 0.115998962927383778460557e-4, 0.581762134402593739370875e-6, 0.853971555085673614607418e-8 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [17, 24]. + /// + private static readonly double[] ErfImpJd = { 1, 0.233044138299687841018015, 0.0204186940546440312625597, 0.000797185647564398289151125, 0.117019281670172327758019e-4 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [24, 38]. + /// + private static readonly double[] ErfImpKn = { -0.000146674699277760365803642, 0.162666552112280519955647e-4, 0.269116248509165239294897e-5, 0.979584479468091935086972e-7, 0.101994647625723465722285e-8 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [24, 38]. + /// + private static readonly double[] ErfImpKd = { 1, 0.165907812944847226546036, 0.0103361716191505884359634, 0.000286593026373868366935721, 0.298401570840900340874568e-5 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [38, 60]. + /// + private static readonly double[] ErfImpLn = { -0.583905797629771786720406e-4, 0.412510325105496173512992e-5, 0.431790922420250949096906e-6, 0.993365155590013193345569e-8, 0.653480510020104699270084e-10 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [38, 60]. + /// + private static readonly double[] ErfImpLd = { 1, 0.105077086072039915406159, 0.00414278428675475620830226, 0.726338754644523769144108e-4, 0.477818471047398785369849e-6 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [60, 85]. + /// + private static readonly double[] ErfImpMn = { -0.196457797609229579459841e-4, 0.157243887666800692441195e-5, 0.543902511192700878690335e-7, 0.317472492369117710852685e-9 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [60, 85]. + /// + private static readonly double[] ErfImpMd = { 1, 0.052803989240957632204885, 0.000926876069151753290378112, 0.541011723226630257077328e-5, 0.535093845803642394908747e-15 }; + + /// Polynomial coefficients for a numerator in ErfImp + /// calculation for Erfc(x) in the interval [85, 110]. + /// + private static readonly double[] ErfImpNn = { -0.789224703978722689089794e-5, 0.622088451660986955124162e-6, 0.145728445676882396797184e-7, 0.603715505542715364529243e-10 }; + + /// Polynomial coefficients for a denominator in ErfImp + /// calculation for Erfc(x) in the interval [85, 110]. + /// + private static readonly double[] ErfImpNd = { 1, 0.0375328846356293715248719, 0.000467919535974625308126054, 0.193847039275845656900547e-5 }; + + /// + /// ************************************** + /// COEFFICIENTS FOR METHOD ErfInvImp * + /// ************************************** + /// + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0, 0.5]. + /// + private static readonly double[] ErvInvImpAn = { -0.000508781949658280665617, -0.00836874819741736770379, 0.0334806625409744615033, -0.0126926147662974029034, -0.0365637971411762664006, 0.0219878681111168899165, 0.00822687874676915743155, -0.00538772965071242932965 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0, 0.5]. + /// + private static readonly double[] ErvInvImpAd = { 1, -0.970005043303290640362, -1.56574558234175846809, 1.56221558398423026363, 0.662328840472002992063, -0.71228902341542847553, -0.0527396382340099713954, 0.0795283687341571680018, -0.00233393759374190016776, 0.000886216390456424707504 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.5, 0.75]. + /// + private static readonly double[] ErvInvImpBn = { -0.202433508355938759655, 0.105264680699391713268, 8.37050328343119927838, 17.6447298408374015486, -18.8510648058714251895, -44.6382324441786960818, 17.445385985570866523, 21.1294655448340526258, -3.67192254707729348546 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.5, 0.75]. + /// + private static readonly double[] ErvInvImpBd = { 1, 6.24264124854247537712, 3.9713437953343869095, -28.6608180499800029974, -20.1432634680485188801, 48.5609213108739935468, 10.8268667355460159008, -22.6436933413139721736, 1.72114765761200282724 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x less than 3. + /// + private static readonly double[] ErvInvImpCn = { -0.131102781679951906451, -0.163794047193317060787, 0.117030156341995252019, 0.387079738972604337464, 0.337785538912035898924, 0.142869534408157156766, 0.0290157910005329060432, 0.00214558995388805277169, -0.679465575181126350155e-6, 0.285225331782217055858e-7, -0.681149956853776992068e-9 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x less than 3. + /// + private static readonly double[] ErvInvImpCd = { 1, 3.46625407242567245975, 5.38168345707006855425, 4.77846592945843778382, 2.59301921623620271374, 0.848854343457902036425, 0.152264338295331783612, 0.01105924229346489121 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 3 and 6. + /// + private static readonly double[] ErvInvImpDn = { -0.0350353787183177984712, -0.00222426529213447927281, 0.0185573306514231072324, 0.00950804701325919603619, 0.00187123492819559223345, 0.000157544617424960554631, 0.460469890584317994083e-5, -0.230404776911882601748e-9, 0.266339227425782031962e-11 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 3 and 6. + /// + private static readonly double[] ErvInvImpDd = { 1, 1.3653349817554063097, 0.762059164553623404043, 0.220091105764131249824, 0.0341589143670947727934, 0.00263861676657015992959, 0.764675292302794483503e-4 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 6 and 18. + /// + private static readonly double[] ErvInvImpEn = { -0.0167431005076633737133, -0.00112951438745580278863, 0.00105628862152492910091, 0.000209386317487588078668, 0.149624783758342370182e-4, 0.449696789927706453732e-6, 0.462596163522878599135e-8, -0.281128735628831791805e-13, 0.99055709973310326855e-16 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 6 and 18. + /// + private static readonly double[] ErvInvImpEd = { 1, 0.591429344886417493481, 0.138151865749083321638, 0.0160746087093676504695, 0.000964011807005165528527, 0.275335474764726041141e-4, 0.282243172016108031869e-6 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 18 and 44. + /// + private static readonly double[] ErvInvImpFn = { -0.0024978212791898131227, -0.779190719229053954292e-5, 0.254723037413027451751e-4, 0.162397777342510920873e-5, 0.396341011304801168516e-7, 0.411632831190944208473e-9, 0.145596286718675035587e-11, -0.116765012397184275695e-17 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x between 18 and 44. + /// + private static readonly double[] ErvInvImpFd = { 1, 0.207123112214422517181, 0.0169410838120975906478, 0.000690538265622684595676, 0.145007359818232637924e-4, 0.144437756628144157666e-6, 0.509761276599778486139e-9 }; + + /// Polynomial coefficients for a numerator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x greater than 44. + /// + private static readonly double[] ErvInvImpGn = { -0.000539042911019078575891, -0.28398759004727721098e-6, 0.899465114892291446442e-6, 0.229345859265920864296e-7, 0.225561444863500149219e-9, 0.947846627503022684216e-12, 0.135880130108924861008e-14, -0.348890393399948882918e-21 }; + + /// Polynomial coefficients for a denominator of ErfInvImp + /// calculation for Erf^-1(z) in the interval [0.75, 1] with x greater than 44. + /// + private static readonly double[] ErvInvImpGd = { 1, 0.0845746234001899436914, 0.00282092984726264681981, 0.468292921940894236786e-4, 0.399968812193862100054e-6, 0.161809290887904476097e-8, 0.231558608310259605225e-11 }; + + /// Calculates the error function. + /// The value to evaluate. + /// the error function evaluated at given value. + /// + /// + /// returns 1 if x == double.PositiveInfinity. + /// returns -1 if x == double.NegativeInfinity. + /// + /// + public static double Erf(double x) + { + if (x == 0) + { + return 0; + } + + if (double.IsPositiveInfinity(x)) + { + return 1; + } + + if (double.IsNegativeInfinity(x)) + { + return -1; + } + + if (double.IsNaN(x)) + { + return double.NaN; + } + + return ErfImp(x, false); + } + + /// Calculates the complementary error function. + /// The value to evaluate. + /// the complementary error function evaluated at given value. + /// + /// + /// returns 0 if x == double.PositiveInfinity. + /// returns 2 if x == double.NegativeInfinity. + /// + /// + public static double Erfc(double x) + { + if (x == 0) + { + return 1; + } + + if (double.IsPositiveInfinity(x)) + { + return 0; + } + + if (double.IsNegativeInfinity(x)) + { + return 2; + } + + if (double.IsNaN(x)) + { + return double.NaN; + } + + return ErfImp(x, true); + } + + /// Calculates the inverse error function evaluated at z. + /// The inverse error function evaluated at given value. + /// + /// + /// returns double.PositiveInfinity if z >= 1.0. + /// returns double.NegativeInfinity if z <= -1.0. + /// + /// + /// Calculates the inverse error function evaluated at z. + /// value to evaluate. + /// the inverse error function evaluated at Z. + public static double ErfInv(double z) + { + if (z == 0.0) + { + return 0.0; + } + + if (z >= 1.0) + { + return double.PositiveInfinity; + } + + if (z <= -1.0) + { + return double.NegativeInfinity; + } + + double p, q, s; + if (z < 0) + { + p = -z; + q = 1 - p; + s = -1; + } + else + { + p = z; + q = 1 - z; + s = 1; + } + + return ErfInvImpl(p, q, s); + } + + /// + /// Implementation of the error function. + /// + /// Where to evaluate the error function. + /// Whether to compute 1 - the error function. + /// the error function. + static double ErfImp(double z, bool invert) + { + if (z < 0) + { + if (!invert) + { + return -ErfImp(-z, false); + } + + if (z < -0.5) + { + return 2 - ErfImp(-z, true); + } + + return 1 + ErfImp(-z, false); + } + + double result; + + // Big bunch of selection statements now to pick which + // implementation to use, try to put most likely options + // first: + if (z < 0.5) + { + // We're going to calculate erf: + if (z < 1e-10) + { + result = (z * 1.125) + (z * 0.003379167095512573896158903121545171688); + } + else + { + // Worst case absolute error found: 6.688618532e-21 + result = (z * 1.125) + (z * Polynomial.Evaluate(z, ErfImpAn) / Polynomial.Evaluate(z, ErfImpAd)); + } + } + else if (z < 110) + { + // We'll be calculating erfc: + invert = !invert; + double r, b; + if (z < 0.75) + { + // Worst case absolute error found: 5.582813374e-21 + r = Polynomial.Evaluate(z - 0.5, ErfImpBn) / Polynomial.Evaluate(z - 0.5, ErfImpBd); + b = 0.3440242112F; + } + else if (z < 1.25) + { + // Worst case absolute error found: 4.01854729e-21 + r = Polynomial.Evaluate(z - 0.75, ErfImpCn) / Polynomial.Evaluate(z - 0.75, ErfImpCd); + b = 0.419990927F; + } + else if (z < 2.25) + { + // Worst case absolute error found: 2.866005373e-21 + r = Polynomial.Evaluate(z - 1.25, ErfImpDn) / Polynomial.Evaluate(z - 1.25, ErfImpDd); + b = 0.4898625016F; + } + else if (z < 3.5) + { + // Worst case absolute error found: 1.045355789e-21 + r = Polynomial.Evaluate(z - 2.25, ErfImpEn) / Polynomial.Evaluate(z - 2.25, ErfImpEd); + b = 0.5317370892F; + } + else if (z < 5.25) + { + // Worst case absolute error found: 8.300028706e-22 + r = Polynomial.Evaluate(z - 3.5, ErfImpFn) / Polynomial.Evaluate(z - 3.5, ErfImpFd); + b = 0.5489973426F; + } + else if (z < 8) + { + // Worst case absolute error found: 1.700157534e-21 + r = Polynomial.Evaluate(z - 5.25, ErfImpGn) / Polynomial.Evaluate(z - 5.25, ErfImpGd); + b = 0.5571740866F; + } + else if (z < 11.5) + { + // Worst case absolute error found: 3.002278011e-22 + r = Polynomial.Evaluate(z - 8, ErfImpHn) / Polynomial.Evaluate(z - 8, ErfImpHd); + b = 0.5609807968F; + } + else if (z < 17) + { + // Worst case absolute error found: 6.741114695e-21 + r = Polynomial.Evaluate(z - 11.5, ErfImpIn) / Polynomial.Evaluate(z - 11.5, ErfImpId); + b = 0.5626493692F; + } + else if (z < 24) + { + // Worst case absolute error found: 7.802346984e-22 + r = Polynomial.Evaluate(z - 17, ErfImpJn) / Polynomial.Evaluate(z - 17, ErfImpJd); + b = 0.5634598136F; + } + else if (z < 38) + { + // Worst case absolute error found: 2.414228989e-22 + r = Polynomial.Evaluate(z - 24, ErfImpKn) / Polynomial.Evaluate(z - 24, ErfImpKd); + b = 0.5638477802F; + } + else if (z < 60) + { + // Worst case absolute error found: 5.896543869e-24 + r = Polynomial.Evaluate(z - 38, ErfImpLn) / Polynomial.Evaluate(z - 38, ErfImpLd); + b = 0.5640528202F; + } + else if (z < 85) + { + // Worst case absolute error found: 3.080612264e-21 + r = Polynomial.Evaluate(z - 60, ErfImpMn) / Polynomial.Evaluate(z - 60, ErfImpMd); + b = 0.5641309023F; + } + else + { + // Worst case absolute error found: 8.094633491e-22 + r = Polynomial.Evaluate(z - 85, ErfImpNn) / Polynomial.Evaluate(z - 85, ErfImpNd); + b = 0.5641584396F; + } + + double g = Math.Exp(-z * z) / z; + result = (g * b) + (g * r); + } + else + { + // Any value of z larger than 28 will underflow to zero: + result = 0; + invert = !invert; + } + + if (invert) + { + result = 1 - result; + } + + return result; + } + + /// Calculates the complementary inverse error function evaluated at z. + /// The complementary inverse error function evaluated at given value. + /// We have tested this implementation against the arbitrary precision mpmath library + /// and found cases where we can only guarantee 9 significant figures correct. + /// + /// returns double.PositiveInfinity if z <= 0.0. + /// returns double.NegativeInfinity if z >= 2.0. + /// + /// + /// calculates the complementary inverse error function evaluated at z. + /// value to evaluate. + /// the complementary inverse error function evaluated at Z. + public static double ErfcInv(double z) + { + if (z <= 0.0) + { + return double.PositiveInfinity; + } + + if (z >= 2.0) + { + return double.NegativeInfinity; + } + + double p, q, s; + if (z > 1) + { + q = 2 - z; + p = 1 - q; + s = -1; + } + else + { + p = 1 - z; + q = z; + s = 1; + } + + return ErfInvImpl(p, q, s); + } + + /// + /// The implementation of the inverse error function. + /// + /// First intermediate parameter. + /// Second intermediate parameter. + /// Third intermediate parameter. + /// the inverse error function. + static double ErfInvImpl(double p, double q, double s) + { + double result; + + if (p <= 0.5) + { + // Evaluate inverse erf using the rational approximation: + // + // x = p(p+10)(Y+R(p)) + // + // Where Y is a constant, and R(p) is optimized for a low + // absolute error compared to |Y|. + // + // double: Max error found: 2.001849e-18 + // long double: Max error found: 1.017064e-20 + // Maximum Deviation Found (actual error term at infinite precision) 8.030e-21 + const float y = 0.0891314744949340820313f; + double g = p * (p + 10); + double r = Polynomial.Evaluate(p, ErvInvImpAn) / Polynomial.Evaluate(p, ErvInvImpAd); + result = (g * y) + (g * r); + } + else if (q >= 0.25) + { + // Rational approximation for 0.5 > q >= 0.25 + // + // x = sqrt(-2*log(q)) / (Y + R(q)) + // + // Where Y is a constant, and R(q) is optimized for a low + // absolute error compared to Y. + // + // double : Max error found: 7.403372e-17 + // long double : Max error found: 6.084616e-20 + // Maximum Deviation Found (error term) 4.811e-20 + const float y = 2.249481201171875f; + double g = Math.Sqrt(-2 * Math.Log(q)); + double xs = q - 0.25; + double r = Polynomial.Evaluate(xs, ErvInvImpBn) / Polynomial.Evaluate(xs, ErvInvImpBd); + result = g / (y + r); + } + else + { + // For q < 0.25 we have a series of rational approximations all + // of the general form: + // + // let: x = sqrt(-log(q)) + // + // Then the result is given by: + // + // x(Y+R(x-B)) + // + // where Y is a constant, B is the lowest value of x for which + // the approximation is valid, and R(x-B) is optimized for a low + // absolute error compared to Y. + // + // Note that almost all code will really go through the first + // or maybe second approximation. After than we're dealing with very + // small input values indeed: 80 and 128 bit long double's go all the + // way down to ~ 1e-5000 so the "tail" is rather long... + double x = Math.Sqrt(-Math.Log(q)); + if (x < 3) + { + // Max error found: 1.089051e-20 + const float y = 0.807220458984375f; + double xs = x - 1.125; + double r = Polynomial.Evaluate(xs, ErvInvImpCn) / Polynomial.Evaluate(xs, ErvInvImpCd); + result = (y * x) + (r * x); + } + else if (x < 6) + { + // Max error found: 8.389174e-21 + const float y = 0.93995571136474609375f; + double xs = x - 3; + double r = Polynomial.Evaluate(xs, ErvInvImpDn) / Polynomial.Evaluate(xs, ErvInvImpDd); + result = (y * x) + (r * x); + } + else if (x < 18) + { + // Max error found: 1.481312e-19 + const float y = 0.98362827301025390625f; + double xs = x - 6; + double r = Polynomial.Evaluate(xs, ErvInvImpEn) / Polynomial.Evaluate(xs, ErvInvImpEd); + result = (y * x) + (r * x); + } + else if (x < 44) + { + // Max error found: 5.697761e-20 + const float y = 0.99714565277099609375f; + double xs = x - 18; + double r = Polynomial.Evaluate(xs, ErvInvImpFn) / Polynomial.Evaluate(xs, ErvInvImpFd); + result = (y * x) + (r * x); + } + else + { + // Max error found: 1.279746e-20 + const float y = 0.99941349029541015625f; + double xs = x - 44; + double r = Polynomial.Evaluate(xs, ErvInvImpGn) / Polynomial.Evaluate(xs, ErvInvImpGd); + result = (y * x) + (r * x); + } + } + + return s * result; + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Evaluate.cs b/MathNet.Numerics/SpecialFunctions/Evaluate.cs new file mode 100644 index 0000000..7b46264 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Evaluate.cs @@ -0,0 +1,262 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// +// CERN - European Laboratory for Particle Physics +// http://www.docjar.com/html/api/cern/jet/math/Bessel.java.html +// Copyright 1999 CERN - European Laboratory for Particle Physics. +// Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose +// is hereby granted without fee, provided that the above copyright notice appear in all copies and +// that both that copyright notice and this permission notice appear in supporting documentation. +// CERN makes no representations about the suitability of this software for any purpose. +// It is provided "as is" without expressed or implied warranty. +// TOMS757 - Uncommon Special Functions (Fortran77) by Allan McLeod +// http://people.sc.fsu.edu/~jburkardt/f77_src/toms757/toms757.html +// Wei Wu +// Cephes Math Library, Stephen L. Moshier +// ALGLIB 2.0.1, Sergey Bochkanov +// + +using System; + +using Complex = System.Numerics.Complex; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +/// +/// Evaluation functions, useful for function approximation. +/// +public static class Evaluate +{ + /// + /// Evaluate a polynomial at point x. + /// Coefficients are ordered by power with power k at index k. + /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. + /// + /// The location where to evaluate the polynomial at. + /// The coefficients of the polynomial, coefficient for power k at index k. + [Obsolete("Use Polynomial.Evaluate instead. Will be removed in the next major version.")] + public static double Polynomial(double z, params double[] coefficients) + { + return Numerics.Polynomial.Evaluate(z, coefficients); + } + + /// + /// Evaluate a polynomial at point x. + /// Coefficients are ordered by power with power k at index k. + /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. + /// + /// The location where to evaluate the polynomial at. + /// The coefficients of the polynomial, coefficient for power k at index k. + [Obsolete("Use Polynomial.Evaluate instead. Will be removed in the next major version.")] + public static Complex Polynomial(Complex z, params double[] coefficients) + { + return Numerics.Polynomial.Evaluate(z, coefficients); + } + + /// + /// Evaluate a polynomial at point x. + /// Coefficients are ordered by power with power k at index k. + /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. + /// + /// The location where to evaluate the polynomial at. + /// The coefficients of the polynomial, coefficient for power k at index k. + [Obsolete("Use Polynomial.Evaluate instead. Will be removed in the next major version.")] + public static Complex Polynomial(Complex z, params Complex[] coefficients) + { + return Numerics.Polynomial.Evaluate(z, coefficients); + } + + /// + /// Numerically stable series summation + /// + /// provides the summands sequentially + /// Sum + internal static double Series(Func nextSummand) + { + double compensation = 0.0; + double current; + const double factor = 1 << 16; + + double sum = nextSummand(); + + do + { + // Kahan Summation + // NOTE (ruegg): do NOT optimize. Now, how to tell that the compiler? + current = nextSummand(); + double y = current - compensation; + double t = sum + y; + compensation = t - sum; + compensation -= y; + sum = t; + } + while (Math.Abs(sum) < Math.Abs(factor * current)); + + return sum; + } + + /// Evaluates the series of Chebyshev polynomials Ti at argument x/2. + /// The series is given by + ///
+    ///       N-1
+    ///        - '
+    /// y  =   >   coef[i] T (x/2)
+    ///        -            i
+    ///       i=0
+    /// 
+ /// Coefficients are stored in reverse order, i.e. the zero + /// order term is last in the array. Note N is the number of + /// coefficients, not the order. + ///

+ /// If coefficients are for the interval a to b, x must + /// have been transformed to x -> 2(2x - b - a)/(b-a) before + /// entering the routine. This maps x from (a, b) to (-1, 1), + /// over which the Chebyshev polynomials are defined. + ///

+ /// If the coefficients are for the inverted interval, in + /// which (a, b) is mapped to (1/b, 1/a), the transformation + /// required is x -> 2(2ab/x - b - a)/(b-a). If b is infinity, + /// this becomes x -> 4a/x - 1. + ///

+ /// SPEED: + ///

+ /// Taking advantage of the recurrence properties of the + /// Chebyshev polynomials, the routine requires one more + /// addition per loop than evaluating a nested polynomial of + /// the same degree. + ///

+ /// The coefficients of the polynomial. + /// Argument to the polynomial. + /// + /// Reference: https://bpm2.svn.codeplex.com/svn/Common.Numeric/Arithmetic.cs + ///

+ /// Marked as Deprecated in + /// http://people.apache.org/~isabel/mahout_site/mahout-matrix/apidocs/org/apache/mahout/jet/math/Arithmetic.html + /// + internal static double ChebyshevA(double[] coefficients, double x) + { + // TODO: Unify, normalize, then make public + double b2; + + int p = 0; + + double b0 = coefficients[p++]; + double b1 = 0.0; + int i = coefficients.Length - 1; + + do + { + b2 = b1; + b1 = b0; + b0 = x * b1 - b2 + coefficients[p++]; + } + while (--i > 0); + + return 0.5 * (b0 - b2); + } + + ///

+ /// Summation of Chebyshev polynomials, using the Clenshaw method with Reinsch modification. + /// + /// The no. of terms in the sequence. + /// The coefficients of the Chebyshev series, length n+1. + /// The value at which the series is to be evaluated. + /// + /// ORIGINAL AUTHOR: + /// Dr. Allan J. MacLeod; Dept. of Mathematics and Statistics, University of Paisley; High St., PAISLEY, SCOTLAND + /// REFERENCES: + /// "An error analysis of the modified Clenshaw method for evaluating Chebyshev and Fourier series" + /// J. Oliver, J.I.M.A., vol. 20, 1977, pp379-391 + /// + internal static double ChebyshevSum(int n, double[] coefficients, double x) + { + // TODO: Unify, normalize, then make public + + // If |x| < 0.6 use the standard Clenshaw method + if (Math.Abs(x) < 0.6) + { + double u0 = 0.0; + double u1 = 0.0; + double u2 = 0.0; + double xx = x + x; + + for (int i = n; i >= 0; i--) + { + u2 = u1; + u1 = u0; + u0 = xx * u1 + coefficients[i] - u2; + } + + return (u0 - u2) / 2.0; + } + + // If ABS ( T ) > = 0.6 use the Reinsch modification + // T > = 0.6 code + if (x > 0.0) + { + double u1 = 0.0; + double d1 = 0.0; + double d2 = 0.0; + double xx = (x - 0.5) - 0.5; + xx = xx + xx; + + for (int i = n; i >= 0; i--) + { + d2 = d1; + double u2 = u1; + d1 = xx * u2 + coefficients[i] + d2; + u1 = d1 + u2; + } + + return (d1 + d2) / 2.0; + } + else + { + // T < = -0.6 code + double u1 = 0.0; + double d1 = 0.0; + double d2 = 0.0; + double xx = (x + 0.5) + 0.5; + xx = xx + xx; + + for (int i = n; i >= 0; i--) + { + d2 = d1; + double u2 = u1; + d1 = xx * u2 + coefficients[i] - d2; + u1 = d1 - u2; + } + + return (d1 - d2) / 2.0; + } + } +} diff --git a/MathNet.Numerics/SpecialFunctions/ExponentialIntegral.cs b/MathNet.Numerics/SpecialFunctions/ExponentialIntegral.cs new file mode 100644 index 0000000..d1a826e --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/ExponentialIntegral.cs @@ -0,0 +1,145 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// +// Ashley Messer +// + +using System; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +public static partial class SpecialFunctions +{ + /// + /// Computes the generalized Exponential Integral function (En). + /// + /// The argument of the Exponential Integral function. + /// Integer power of the denominator term. Generalization index. + /// The value of the Exponential Integral function. + /// + /// This implementation of the computation of the Exponential Integral function follows the derivation in + /// "Handbook of Mathematical Functions, Applied Mathematics Series, Volume 55", Abramowitz, M., and Stegun, I.A. 1964, reprinted 1968 by + /// Dover Publications, New York), Chapters 6, 7, and 26. + /// AND + /// "Advanced mathematical methods for scientists and engineers", Bender, Carl M.; Steven A. Orszag (1978). page 253 + /// + /// + /// for x > 1 uses continued fraction approach that is often used to compute incomplete gamma. + /// for 0 < x <= 1 uses Taylor series expansion + /// + /// Our unit tests suggest that the accuracy of the Exponential Integral function is correct up to 13 floating point digits. + /// + public static double ExponentialIntegral(double x, int n) + { + //parameter validation + if (n < 0 || x < 0.0) + { + throw new ArgumentOutOfRangeException(string.Format("x and n must be positive: x={0}, n={1}", x, n)); + } + + const double epsilon = 0.00000000000000001; + int maxIterations = 100; + int i, ii; + double ndbl = (double)n; + double result; + double nearDoubleMin = 1e-100; //needs a very small value that is not quite as small as the lowest value double can take + double factorial = 1.0d; + double del; + double psi; + double a, b, c, d, h; //variables for continued fraction + + //special cases + if (n == 0) + { + return Math.Exp(-1.0d * x) / x; + } + else if (x == 0.0d) + { + return 1.0d / (ndbl - 1.0d); + } + //general cases + //continued fraction for large x + if (x > 1.0d) + { + b = x + ((double)n); + c = 1.0d / nearDoubleMin; + d = 1.0d / b; + h = d; + for (i = 1; i <= maxIterations; i++) + { + a = -1.0d * ((double)i) * ((ndbl - 1.0d) + (double)i); + b += 2.0d; + d = 1.0d / (a * d + b); + c = b + a / c; + del = c * d; + h = h * del; + if (Math.Abs(del - 1.0d) < epsilon) + { + return h * Math.Exp(-x); + } + } + + throw new ArithmeticException(string.Format("continued fraction failed to converge for x={0}, n={1})", x, n)); + } + //series computation for small x + else + { + result = ((ndbl - 1.0d) != 0 ? 1.0 / (ndbl - 1.0d) : (-1.0d * Math.Log(x) - Constants.EulerMascheroni)); //Set first term. + for (i = 1; i <= maxIterations; i++) + { + factorial *= (-1.0d * x / ((double)i)); + if (i != (ndbl - 1.0d)) + { + del = -factorial / (i - (ndbl - 1.0d)); + } + else + { + psi = -1.0d * Constants.EulerMascheroni; + for (ii = 1; ii <= (ndbl - 1.0d); ii++) + { + psi += (1.0d / ((double)ii)); + } + + del = factorial * (-1.0d * Math.Log(x) + psi); + } + + result += del; + if (Math.Abs(del) < Math.Abs(result) * epsilon) + { + return result; + } + } + + throw new ArithmeticException(string.Format("series failed to converge for x={0}, n={1})", x, n)); + } + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Factorial.cs b/MathNet.Numerics/SpecialFunctions/Factorial.cs new file mode 100644 index 0000000..ddd1085 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Factorial.cs @@ -0,0 +1,197 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +using BigInteger = System.Numerics.BigInteger; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +public partial class SpecialFunctions +{ + const int FactorialMaxArgument = 170; + static readonly double[] _factorialCache = new double[171] + { + 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000, 355687428096000, 6402373705728000, 1.21645100408832E+17, 2.43290200817664E+18, 5.109094217170944E+19, 1.1240007277776077E+21, 2.5852016738884978E+22, 6.2044840173323941E+23, 1.5511210043330986E+25, 4.0329146112660565E+26, 1.0888869450418352E+28, 3.0488834461171384E+29, 8.8417619937397008E+30, 2.6525285981219103E+32, 8.2228386541779224E+33, 2.6313083693369352E+35, 8.6833176188118859E+36, 2.9523279903960412E+38, 1.0333147966386144E+40, 3.7199332678990118E+41, 1.3763753091226343E+43, 5.2302261746660104E+44, 2.0397882081197442E+46, 8.1591528324789768E+47, 3.3452526613163803E+49, 1.4050061177528798E+51, 6.0415263063373834E+52, 2.6582715747884485E+54, 1.1962222086548019E+56, 5.5026221598120885E+57, 2.5862324151116818E+59, 1.2413915592536073E+61, 6.0828186403426752E+62, 3.0414093201713376E+64, 1.5511187532873822E+66, 8.0658175170943877E+67, 4.2748832840600255E+69, 2.3084369733924138E+71, 1.2696403353658276E+73, 7.1099858780486348E+74, 4.0526919504877221E+76, 2.3505613312828789E+78, 1.3868311854568986E+80, 8.3209871127413916E+81, 5.0758021387722484E+83, 3.1469973260387939E+85, 1.9826083154044401E+87, 1.2688693218588417E+89, 8.2476505920824715E+90, 5.4434493907744307E+92, 3.6471110918188683E+94, 2.4800355424368305E+96, 1.711224524281413E+98, 1.197857166996989E+100, 8.5047858856786218E+101, 6.1234458376886077E+103, 4.4701154615126834E+105, 3.3078854415193856E+107, 2.4809140811395391E+109, 1.8854947016660498E+111, 1.4518309202828584E+113, 1.1324281178206295E+115, 8.9461821307829729E+116, 7.1569457046263779E+118, 5.7971260207473655E+120, 4.7536433370128398E+122, 3.9455239697206569E+124, 3.314240134565352E+126, 2.8171041143805494E+128, 2.4227095383672724E+130, 2.1077572983795269E+132, 1.8548264225739836E+134, 1.6507955160908452E+136, 1.4857159644817607E+138, 1.3520015276784023E+140, 1.24384140546413E+142, 1.1567725070816409E+144, 1.0873661566567424E+146, 1.0329978488239052E+148, 9.916779348709491E+149, 9.6192759682482062E+151, 9.426890448883242E+153, 9.3326215443944096E+155, 9.3326215443944102E+157, 9.4259477598383536E+159, 9.6144667150351211E+161, 9.9029007164861754E+163, 1.0299016745145622E+166, 1.0813967582402903E+168, 1.1462805637347078E+170, 1.2265202031961373E+172, 1.3246418194518284E+174, 1.4438595832024928E+176, 1.5882455415227421E+178, 1.7629525510902437E+180, 1.9745068572210728E+182, 2.2311927486598123E+184, 2.5435597334721862E+186, 2.9250936934930141E+188, 3.3931086844518965E+190, 3.969937160808719E+192, 4.6845258497542883E+194, 5.5745857612076033E+196, 6.6895029134491239E+198, 8.09429852527344E+200, 9.8750442008335976E+202, 1.2146304367025325E+205, 1.5061417415111404E+207, 1.8826771768889254E+209, 2.3721732428800459E+211, 3.0126600184576582E+213, 3.8562048236258025E+215, 4.9745042224772855E+217, 6.4668554892204716E+219, 8.4715806908788174E+221, 1.1182486511960039E+224, 1.4872707060906852E+226, 1.9929427461615181E+228, 2.6904727073180495E+230, 3.6590428819525472E+232, 5.0128887482749898E+234, 6.9177864726194859E+236, 9.6157231969410859E+238, 1.346201247571752E+241, 1.8981437590761701E+243, 2.6953641378881614E+245, 3.8543707171800706E+247, 5.5502938327393013E+249, 8.0479260574719866E+251, 1.1749972043909099E+254, 1.7272458904546376E+256, 2.5563239178728637E+258, 3.8089226376305671E+260, 5.7133839564458505E+262, 8.6272097742332346E+264, 1.3113358856834518E+267, 2.0063439050956811E+269, 3.0897696138473489E+271, 4.7891429014633912E+273, 7.4710629262828905E+275, 1.1729568794264138E+278, 1.8532718694937338E+280, 2.9467022724950369E+282, 4.714723635992059E+284, 7.5907050539472148E+286, 1.2296942187394488E+289, 2.0044015765453015E+291, 3.2872185855342945E+293, 5.423910666131586E+295, 9.0036917057784329E+297, 1.5036165148649983E+300, 2.5260757449731969E+302, 4.2690680090047027E+304, 7.257415615307994E+306 + }; + + /// + /// Computes the factorial function x -> x! of an integer number > 0. The function can represent all number up + /// to 22! exactly, all numbers up to 170! using a double representation. All larger values will overflow. + /// + /// A value value! for value > 0 + /// + /// If you need to multiply or divide various such factorials, consider using the logarithmic version + /// instead so you can add instead of multiply and subtract instead of divide, and + /// then exponentiate the result using . This will also circumvent the problem that + /// factorials become very large even for small parameters. + /// + /// + public static double Factorial(int x) + { + if (x < 0) + { + throw new ArgumentOutOfRangeException(nameof(x), Resources.ArgumentPositive); + } + + if (x < _factorialCache.Length) + { + return _factorialCache[x]; + } + + return double.PositiveInfinity; + } + + /// + /// Computes the factorial of an integer. + /// + public static BigInteger Factorial(BigInteger x) + { + if (x < 0) + { + throw new ArgumentOutOfRangeException(nameof(x), Resources.ArgumentPositive); + } + + if (x == 0) + { + return BigInteger.One; + } + + BigInteger r = x; + while (--x > 1) + { + r *= x; + } + + return r; + } + + /// + /// Computes the logarithmic factorial function x -> ln(x!) of an integer number > 0. + /// + /// A value value! for value > 0 + public static double FactorialLn(int x) + { + if (x < 0) + { + throw new ArgumentOutOfRangeException(nameof(x), Resources.ArgumentPositive); + } + + if (x <= 1) + { + return 0d; + } + + if (x < _factorialCache.Length) + { + return Math.Log(_factorialCache[x]); + } + + return GammaLn(x + 1.0); + } + + /// + /// Computes the binomial coefficient: n choose k. + /// + /// A nonnegative value n. + /// A nonnegative value h. + /// The binomial coefficient: n choose k. + public static double Binomial(int n, int k) + { + if (k < 0 || n < 0 || k > n) + { + return 0.0; + } + + return Math.Floor(0.5 + Math.Exp(FactorialLn(n) - FactorialLn(k) - FactorialLn(n - k))); + } + + /// + /// Computes the natural logarithm of the binomial coefficient: ln(n choose k). + /// + /// A nonnegative value n. + /// A nonnegative value h. + /// The logarithmic binomial coefficient: ln(n choose k). + public static double BinomialLn(int n, int k) + { + if (k < 0 || n < 0 || k > n) + { + return double.NegativeInfinity; + } + + return FactorialLn(n) - FactorialLn(k) - FactorialLn(n - k); + } + + /// + /// Computes the multinomial coefficient: n choose n1, n2, n3, ... + /// + /// A nonnegative value n. + /// An array of nonnegative values that sum to . + /// The multinomial coefficient. + /// if is . + /// If or any of the are negative. + /// If the sum of all is not equal to . + public static double Multinomial(int n, int[] ni) + { + if (n < 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, nameof(n)); + } + + if (ni == null) + { + throw new ArgumentNullException(nameof(ni)); + } + + int sum = 0; + double ret = FactorialLn(n); + for (int i = 0; i < ni.Length; i++) + { + if (ni[i] < 0) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "ni[" + i + "]"); + } + + ret -= FactorialLn(ni[i]); + sum += ni[i]; + } + + // Before returning, check that the sum of all elements was equal to n. + if (sum != n) + { + throw new ArgumentException(Resources.ArgumentParameterSetInvalid, nameof(ni)); + } + + return Math.Floor(0.5 + Math.Exp(ret)); + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Gamma.cs b/MathNet.Numerics/SpecialFunctions/Gamma.cs new file mode 100644 index 0000000..8c86816 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Gamma.cs @@ -0,0 +1,656 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// +// Cephes Math Library, Stephen L. Moshier +// ALGLIB 2.0.1, Sergey Bochkanov +// + +using System; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +public static partial class SpecialFunctions +{ + /// + /// The order of the approximation. + /// + const int GammaN = 10; + + /// + /// Auxiliary variable when evaluating the function. + /// + const double GammaR = 10.900511; + + /// + /// Polynomial coefficients for the approximation. + /// + static readonly double[] GammaDk = + { + 2.48574089138753565546e-5, + 1.05142378581721974210, + -3.45687097222016235469, + 4.51227709466894823700, + -2.98285225323576655721, + 1.05639711577126713077, + -1.95428773191645869583e-1, + 1.70970543404441224307e-2, + -5.71926117404305781283e-4, + 4.63399473359905636708e-6, + -2.71994908488607703910e-9 + }; + + /// + /// Computes the logarithm of the Gamma function. + /// + /// The argument of the gamma function. + /// The logarithm of the gamma function. + /// + /// This implementation of the computation of the gamma and logarithm of the gamma function follows the derivation in + /// "An Analysis Of The Lanczos Gamma Approximation", Glendon Ralph Pugh, 2004. + /// We use the implementation listed on p. 116 which achieves an accuracy of 16 floating point digits. Although 16 digit accuracy + /// should be sufficient for double values, improving accuracy is possible (see p. 126 in Pugh). + /// Our unit tests suggest that the accuracy of the Gamma function is correct up to 14 floating point digits. + /// + public static double GammaLn(double z) + { + if (z < 0.5) + { + double s = GammaDk[0]; + for (int i = 1; i <= GammaN; i++) + { + s += GammaDk[i] / (i - z); + } + + return Constants.LnPi + - Math.Log(Math.Sin(Math.PI * z)) + - Math.Log(s) + - Constants.LogTwoSqrtEOverPi + - ((0.5 - z) * Math.Log((0.5 - z + GammaR) / Math.E)); + } + else + { + double s = GammaDk[0]; + for (int i = 1; i <= GammaN; i++) + { + s += GammaDk[i] / (z + i - 1.0); + } + + return Math.Log(s) + + Constants.LogTwoSqrtEOverPi + + ((z - 0.5) * Math.Log((z - 0.5 + GammaR) / Math.E)); + } + } + + /// + /// Computes the Gamma function. + /// + /// The argument of the gamma function. + /// The logarithm of the gamma function. + /// + /// + /// This implementation of the computation of the gamma and logarithm of the gamma function follows the derivation in + /// "An Analysis Of The Lanczos Gamma Approximation", Glendon Ralph Pugh, 2004. + /// We use the implementation listed on p. 116 which should achieve an accuracy of 16 floating point digits. Although 16 digit accuracy + /// should be sufficient for double values, improving accuracy is possible (see p. 126 in Pugh). + /// + /// Our unit tests suggest that the accuracy of the Gamma function is correct up to 13 floating point digits. + /// + public static double Gamma(double z) + { + if (z < 0.5) + { + double s = GammaDk[0]; + for (int i = 1; i <= GammaN; i++) + { + s += GammaDk[i] / (i - z); + } + + return Math.PI / (Math.Sin(Math.PI * z) + * s + * Constants.TwoSqrtEOverPi + * Math.Pow((0.5 - z + GammaR) / Math.E, 0.5 - z)); + } + else + { + double s = GammaDk[0]; + for (int i = 1; i <= GammaN; i++) + { + s += GammaDk[i] / (z + i - 1.0); + } + + return s * Constants.TwoSqrtEOverPi * Math.Pow((z - 0.5 + GammaR) / Math.E, z - 0.5); + } + } + + /// + /// Returns the upper incomplete regularized gamma function + /// Q(a,x) = 1/Gamma(a) * int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0. + /// + /// The argument for the gamma function. + /// The lower integral limit. + /// The upper incomplete regularized gamma function. + public static double GammaUpperRegularized(double a, double x) + { + const double epsilon = 0.000000000000001; + const double big = 4503599627370496.0; + const double bigInv = 2.22044604925031308085e-16; + + if (x < 1d || x <= a) + { + return 1d - GammaLowerRegularized(a, x); + } + + double ax = a * Math.Log(x) - x - GammaLn(a); + if (ax < -709.78271289338399) + { + return a < x ? 0d : 1d; + } + + ax = Math.Exp(ax); + double t; + double y = 1 - a; + double z = x + y + 1; + double c = 0; + double pkm2 = 1; + double qkm2 = x; + double pkm1 = x + 1; + double qkm1 = z * x; + double ans = pkm1 / qkm1; + do + { + c = c + 1; + y = y + 1; + z = z + 2; + double yc = y * c; + double pk = pkm1 * z - pkm2 * yc; + double qk = qkm1 * z - qkm2 * yc; + if (qk != 0) + { + double r = pk / qk; + t = Math.Abs((ans - r) / r); + ans = r; + } + else + { + t = 1; + } + + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + + if (Math.Abs(pk) > big) + { + pkm2 = pkm2 * bigInv; + pkm1 = pkm1 * bigInv; + qkm2 = qkm2 * bigInv; + qkm1 = qkm1 * bigInv; + } + } + while (t > epsilon); + + return ans * ax; + } + + /// + /// Returns the upper incomplete gamma function + /// Gamma(a,x) = int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0. + /// + /// The argument for the gamma function. + /// The lower integral limit. + /// The upper incomplete gamma function. + public static double GammaUpperIncomplete(double a, double x) + { + return GammaUpperRegularized(a, x) * Gamma(a); + } + + /// + /// Returns the lower incomplete gamma function + /// gamma(a,x) = int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0. + /// + /// The argument for the gamma function. + /// The upper integral limit. + /// The lower incomplete gamma function. + public static double GammaLowerIncomplete(double a, double x) + { + return GammaLowerRegularized(a, x) * Gamma(a); + } + + /// + /// Returns the lower incomplete regularized gamma function + /// P(a,x) = 1/Gamma(a) * int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0. + /// + /// The argument for the gamma function. + /// The upper integral limit. + /// The lower incomplete gamma function. + public static double GammaLowerRegularized(double a, double x) + { + const double epsilon = 0.000000000000001; + const double big = 4503599627370496.0; + const double bigInv = 2.22044604925031308085e-16; + + if (a < 0d) + { + throw new ArgumentOutOfRangeException(nameof(a), Properties.Resources.ArgumentNotNegative); + } + + if (x < 0d) + { + throw new ArgumentOutOfRangeException(nameof(x), Properties.Resources.ArgumentNotNegative); + } + + if (a.AlmostEqual(0.0)) + { + if (x.AlmostEqual(0.0)) + { + //use right hand limit value because so that regularized upper/lower gamma definition holds. + return 1d; + } + + return 1d; + } + + if (x.AlmostEqual(0.0)) + { + return 0d; + } + + double ax = (a * Math.Log(x)) - x - GammaLn(a); + if (ax < -709.78271289338399) + { + return a < x ? 1d : 0d; + } + + if (x <= 1 || x <= a) + { + double r2 = a; + double c2 = 1; + double ans2 = 1; + + do + { + r2 = r2 + 1; + c2 = c2 * x / r2; + ans2 += c2; + } + while ((c2 / ans2) > epsilon); + + return Math.Exp(ax) * ans2 / a; + } + + int c = 0; + double y = 1 - a; + double z = x + y + 1; + + double p3 = 1; + double q3 = x; + double p2 = x + 1; + double q2 = z * x; + double ans = p2 / q2; + + double error; + + do + { + c++; + y += 1; + z += 2; + double yc = y * c; + + double p = (p2 * z) - (p3 * yc); + double q = (q2 * z) - (q3 * yc); + + if (q != 0) + { + double nextans = p / q; + error = Math.Abs((ans - nextans) / nextans); + ans = nextans; + } + else + { + // zero div, skip + error = 1; + } + + // shift + p3 = p2; + p2 = p; + q3 = q2; + q2 = q; + + // normalize fraction when the numerator becomes large + if (Math.Abs(p) > big) + { + p3 *= bigInv; + p2 *= bigInv; + q3 *= bigInv; + q2 *= bigInv; + } + } + while (error > epsilon); + + return 1d - (Math.Exp(ax) * ans); + } + + /// + /// Returns the inverse P^(-1) of the regularized lower incomplete gamma function + /// P(a,x) = 1/Gamma(a) * int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0, + /// such that P^(-1)(a,P(a,x)) == x. + /// + public static double GammaLowerRegularizedInv(double a, double y0) + { + const double epsilon = 0.000000000000001; + const double big = 4503599627370496.0; + const double threshold = 5 * epsilon; + + if (double.IsNaN(a) || double.IsNaN(y0)) + { + return double.NaN; + } + + if (a < 0 || a.AlmostEqual(0.0)) + { + throw new ArgumentOutOfRangeException(nameof(a)); + } + + if (y0 < 0 || y0 > 1) + { + throw new ArgumentOutOfRangeException(nameof(y0)); + } + + if (y0.AlmostEqual(0.0)) + { + return 0d; + } + + if (y0.AlmostEqual(1.0)) + { + return double.PositiveInfinity; + } + + y0 = 1 - y0; + + double xUpper = big; + double xLower = 0; + double yUpper = 1; + double yLower = 0; + + // Initial Guess + double d = 1 / (9 * a); + double y = 1 - d - (0.98 * Constants.Sqrt2 * ErfInv((2.0 * y0) - 1.0) * Math.Sqrt(d)); + double x = a * y * y * y; + double lgm = GammaLn(a); + + for (int i = 0; i < 20; i++) + { + if (x < xLower || x > xUpper) + { + d = 0.0625; + break; + } + + y = 1 - GammaLowerRegularized(a, x); + if (y < yLower || y > yUpper) + { + d = 0.0625; + break; + } + + if (y < y0) + { + xUpper = x; + yLower = y; + } + else + { + xLower = x; + yUpper = y; + } + + d = ((a - 1) * Math.Log(x)) - x - lgm; + if (d < -709.78271289338399) + { + d = 0.0625; + break; + } + + d = -Math.Exp(d); + d = (y - y0) / d; + if (Math.Abs(d / x) < epsilon) + { + return x; + } + + if ((d > (x / 4)) && (y0 < 0.05)) + { + // Naive heuristics for cases near the singularity + d = x / 10; + } + + x -= d; + } + + if (xUpper == big) + { + if (x <= 0) + { + x = 1; + } + + while (xUpper == big) + { + x = (1 + d) * x; + y = 1 - GammaLowerRegularized(a, x); + if (y < y0) + { + xUpper = x; + yLower = y; + break; + } + + d = d + d; + } + } + + int dir = 0; + d = 0.5; + for (int i = 0; i < 400; i++) + { + x = xLower + (d * (xUpper - xLower)); + y = 1 - GammaLowerRegularized(a, x); + lgm = (xUpper - xLower) / (xLower + xUpper); + if (Math.Abs(lgm) < threshold) + { + return x; + } + + lgm = (y - y0) / y0; + if (Math.Abs(lgm) < threshold) + { + return x; + } + + if (x <= 0d) + { + return 0d; + } + + if (y >= y0) + { + xLower = x; + yUpper = y; + if (dir < 0) + { + dir = 0; + d = 0.5; + } + else + { + if (dir > 1) + { + d = (0.5 * d) + 0.5; + } + else + { + d = (y0 - yLower) / (yUpper - yLower); + } + } + + dir = dir + 1; + } + else + { + xUpper = x; + yLower = y; + if (dir > 0) + { + dir = 0; + d = 0.5; + } + else + { + if (dir < -1) + { + d = 0.5 * d; + } + else + { + d = (y0 - yLower) / (yUpper - yLower); + } + } + + dir = dir - 1; + } + } + + return x; + } + + /// + /// Computes the Digamma function which is mathematically defined as the derivative of the logarithm of the gamma function. + /// This implementation is based on + /// Jose Bernardo + /// Algorithm AS 103: + /// Psi ( Digamma ) Function, + /// Applied Statistics, + /// Volume 25, Number 3, 1976, pages 315-317. + /// Using the modifications as in Tom Minka's lightspeed toolbox. + /// + /// The argument of the digamma function. + /// The value of the DiGamma function at . + public static double DiGamma(double x) + { + const double c = 12.0; + const double d1 = -0.57721566490153286; + const double d2 = 1.6449340668482264365; + const double s = 1e-6; + const double s3 = 1.0 / 12.0; + const double s4 = 1.0 / 120.0; + const double s5 = 1.0 / 252.0; + const double s6 = 1.0 / 240.0; + const double s7 = 1.0 / 132.0; + + if (double.IsNegativeInfinity(x) || double.IsNaN(x)) + { + return double.NaN; + } + + // Handle special cases. + if (x <= 0 && Math.Floor(x) == x) + { + return double.NegativeInfinity; + } + + // Use inversion formula for negative numbers. + if (x < 0) + { + return DiGamma(1.0 - x) + (Math.PI / Math.Tan(-Math.PI * x)); + } + + if (x <= s) + { + return d1 - (1 / x) + (d2 * x); + } + + double result = 0; + while (x < c) + { + result -= 1 / x; + x++; + } + + if (x >= c) + { + var r = 1 / x; + result += Math.Log(x) - (0.5 * r); + r *= r; + + result -= r * (s3 - (r * (s4 - (r * (s5 - (r * (s6 - (r * s7)))))))); + } + + return result; + } + + /// + /// Computes the inverse Digamma function: this is the inverse of the logarithm of the gamma function. This function will + /// only return solutions that are positive. + /// This implementation is based on the bisection method. + /// + /// The argument of the inverse digamma function. + /// The positive solution to the inverse DiGamma function at . + public static double DiGammaInv(double p) + { + if (double.IsNaN(p)) + { + return double.NaN; + } + + if (double.IsNegativeInfinity(p)) + { + return 0.0; + } + + if (double.IsPositiveInfinity(p)) + { + return double.PositiveInfinity; + } + + var x = Math.Exp(p); + for (var d = 1.0; d > 1.0e-15; d /= 2.0) + { + x += d * Math.Sign(p - DiGamma(x)); + } + + return x; + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Hankel.cs b/MathNet.Numerics/SpecialFunctions/Hankel.cs new file mode 100644 index 0000000..2709351 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Hankel.cs @@ -0,0 +1,57 @@ +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the Hankel function. +/// +public static partial class SpecialFunctions +{ + /// + /// Returns the Hankel function of the first kind. + /// HankelH1(n, z) is defined as BesselJ(n, z) + j * BesselY(n, z). + /// + /// The order of the Hankel function. + /// The value to compute the Hankel function of. + /// The Hankel function of the first kind. + public static Complex HankelH1(double n, Complex z) + { + return Amos.Cbesh1(n, z); + } + + /// + /// Returns the exponentially scaled Hankel function of the first kind. + /// ScaledHankelH1(n, z) is given by Exp(-z * j) * HankelH1(n, z) where j = Sqrt(-1). + /// + /// The order of the Hankel function. + /// The value to compute the Hankel function of. + /// The exponentially scaled Hankel function of the first kind. + public static Complex HankelH1Scaled(double n, Complex z) + { + return Amos.ScaledCbesh1(n, z); + } + + /// + /// Returns the Hankel function of the second kind. + /// HankelH2(n, z) is defined as BesselJ(n, z) - j * BesselY(n, z). + /// + /// The order of the Hankel function. + /// The value to compute the Hankel function of. + /// The Hankel function of the second kind. + public static Complex HankelH2(double n, Complex z) + { + return Amos.Cbesh2(n, z); + } + + /// + /// Returns the exponentially scaled Hankel function of the second kind. + /// ScaledHankelH2(n, z) is given by Exp(z * j) * HankelH2(n, z) where j = Sqrt(-1). + /// + /// The order of the Hankel function. + /// The value to compute the Hankel function of. + /// The exponentially scaled Hankel function of the second kind. + public static Complex HankelH2Scaled(double n, Complex z) + { + return Amos.ScaledCbesh2(n, z); + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Harmonic.cs b/MathNet.Numerics/SpecialFunctions/Harmonic.cs new file mode 100644 index 0000000..19888cb --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Harmonic.cs @@ -0,0 +1,72 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2011 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// +// Cephes Math Library, Stephen L. Moshier +// ALGLIB 2.0.1, Sergey Bochkanov +// + +using System; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the harmonic function. +/// +public static partial class SpecialFunctions +{ + /// + /// Computes the 'th Harmonic number. + /// + /// The Harmonic number which needs to be computed. + /// The t'th Harmonic number. + public static double Harmonic(int t) + { + return Constants.EulerMascheroni + DiGamma(t + 1.0); + } + + /// + /// Compute the generalized harmonic number of order n of m. (1 + 1/2^m + 1/3^m + ... + 1/n^m) + /// + /// The order parameter. + /// The power parameter. + /// General Harmonic number. + public static double GeneralHarmonic(int n, double m) + { + double sum = 0; + for (int i = 0; i < n; i++) + { + sum += Math.Pow(i + 1, -m); + } + + return sum; + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Kelvin.cs b/MathNet.Numerics/SpecialFunctions/Kelvin.cs new file mode 100644 index 0000000..9cae68d --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Kelvin.cs @@ -0,0 +1,263 @@ +using System; +using System.Numerics; + +namespace MathNet.Numerics; + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the modified Bessel function. +/// +public static partial class SpecialFunctions +{ + /// + /// Returns the Kelvin function of the first kind. + /// KelvinBe(nu, x) is given by BesselJ(0, j * sqrt(j) * x) where j = sqrt(-1). + /// KelvinBer(nu, x) and KelvinBei(nu, x) are the real and imaginary parts of the KelvinBe(nu, x) + /// + /// the order of the the Kelvin function. + /// The value to compute the Kelvin function of. + /// The Kelvin function of the first kind. + public static Complex KelvinBe(double nu, double x) + { + Complex ISqrtI = new Complex(-Constants.Sqrt1Over2, Constants.Sqrt1Over2); // j * sqrt(j) = (-1)^(3/4) = (-1 + j)/sqrt(2) + return BesselJ(nu, ISqrtI * x); + } + + /// + /// Returns the Kelvin function ber. + /// KelvinBer(nu, x) is given by the real part of BesselJ(nu, j * sqrt(j) * x) where j = sqrt(-1). + /// + /// the order of the the Kelvin function. + /// The value to compute the Kelvin function of. + /// The Kelvin function ber. + public static double KelvinBer(double nu, double x) + { + return KelvinBe(nu, x).Real; + } + + /// + /// Returns the Kelvin function ber. + /// KelvinBer(x) is given by the real part of BesselJ(0, j * sqrt(j) * x) where j = sqrt(-1). + /// KelvinBer(x) is equivalent to KelvinBer(0, x). + /// + /// The value to compute the Kelvin function of. + /// The Kelvin function ber. + public static double KelvinBer(double x) + { + return KelvinBe(0, x).Real; + } + + /// + /// Returns the Kelvin function bei. + /// KelvinBei(nu, x) is given by the imaginary part of BesselJ(nu, j * sqrt(j) * x) where j = sqrt(-1). + /// + /// the order of the the Kelvin function. + /// The value to compute the Kelvin function of. + /// The Kelvin function bei. + public static double KelvinBei(double nu, double x) + { + return KelvinBe(nu, x).Imaginary; + } + + /// + /// Returns the Kelvin function bei. + /// KelvinBei(x) is given by the imaginary part of BesselJ(0, j * sqrt(j) * x) where j = sqrt(-1). + /// KelvinBei(x) is equivalent to KelvinBei(0, x). + /// + /// The value to compute the Kelvin function of. + /// The Kelvin function bei. + public static double KelvinBei(double x) + { + return KelvinBe(0, x).Imaginary; + } + + /// + /// Returns the derivative of the Kelvin function ber. + /// + /// The order of the Kelvin function. + /// The value to compute the derivative of the Kelvin function of. + /// the derivative of the Kelvin function ber + public static double KelvinBerPrime(double nu, double x) + { + const double inv2Sqrt2 = 0.35355339059327376220042218105242451964241796884424; // 1/(2 * sqrt(2)) + return inv2Sqrt2 * (-KelvinBer(nu - 1, x) + KelvinBer(nu + 1, x) - KelvinBei(nu - 1, x) + KelvinBei(nu + 1, x)); + } + + /// + /// Returns the derivative of the Kelvin function ber. + /// + /// The value to compute the derivative of the Kelvin function of. + /// The derivative of the Kelvin function ber. + public static double KelvinBerPrime(double x) + { + return KelvinBerPrime(0, x); + } + + /// + /// Returns the derivative of the Kelvin function bei. + /// + /// The order of the Kelvin function. + /// The value to compute the derivative of the Kelvin function of. + /// the derivative of the Kelvin function bei. + public static double KelvinBeiPrime(double nu, double x) + { + const double inv2Sqrt2 = 0.35355339059327376220042218105242451964241796884424; // 1/(2 * sqrt(2)) + return inv2Sqrt2 * (KelvinBer(nu - 1, x) - KelvinBer(nu + 1, x) - KelvinBei(nu - 1, x) + KelvinBei(nu + 1, x)); + } + + /// + /// Returns the derivative of the Kelvin function bei. + /// + /// The value to compute the derivative of the Kelvin function of. + /// The derivative of the Kelvin function bei. + public static double KelvinBeiPrime(double x) + { + return KelvinBeiPrime(0, x); + } + + /// + /// Returns the Kelvin function of the second kind + /// KelvinKe(nu, x) is given by Exp(-nu * pi * j / 2) * BesselK(nu, x * sqrt(j)) where j = sqrt(-1). + /// KelvinKer(nu, x) and KelvinKei(nu, x) are the real and imaginary parts of the KelvinBe(nu, x) + /// + /// The order of the Kelvin function. + /// The value to calculate the kelvin function of, + /// + public static Complex KelvinKe(double nu, double x) + { + Complex PiIOver2 = new Complex(0.0, Constants.PiOver2); // pi * I / 2 + Complex SqrtI = new Complex(Constants.Sqrt1Over2, Constants.Sqrt1Over2); // sqrt(j) = (-1)^(1/4) = (1 + j)/sqrt(2) + return Complex.Exp(-nu * PiIOver2) * BesselK(nu, SqrtI * x); + } + + /// + /// Returns the Kelvin function ker. + /// KelvinKer(nu, x) is given by the real part of Exp(-nu * pi * j / 2) * BesselK(nu, sqrt(j) * x) where j = sqrt(-1). + /// + /// the order of the the Kelvin function. + /// The non-negative real value to compute the Kelvin function of. + /// The Kelvin function ker. + public static double KelvinKer(double nu, double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + return KelvinKe(nu, x).Real; + } + + /// + /// Returns the Kelvin function ker. + /// KelvinKer(x) is given by the real part of Exp(-nu * pi * j / 2) * BesselK(0, sqrt(j) * x) where j = sqrt(-1). + /// KelvinKer(x) is equivalent to KelvinKer(0, x). + /// + /// The non-negative real value to compute the Kelvin function of. + /// The Kelvin function ker. + public static double KelvinKer(double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + return KelvinKe(0, x).Real; + } + + /// + /// Returns the Kelvin function kei. + /// KelvinKei(nu, x) is given by the imaginary part of Exp(-nu * pi * j / 2) * BesselK(nu, sqrt(j) * x) where j = sqrt(-1). + /// + /// the order of the the Kelvin function. + /// The non-negative real value to compute the Kelvin function of. + /// The Kelvin function kei. + public static double KelvinKei(double nu, double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + return KelvinKe(nu, x).Imaginary; + } + + /// + /// Returns the Kelvin function kei. + /// KelvinKei(x) is given by the imaginary part of Exp(-nu * pi * j / 2) * BesselK(0, sqrt(j) * x) where j = sqrt(-1). + /// KelvinKei(x) is equivalent to KelvinKei(0, x). + /// + /// The non-negative real value to compute the Kelvin function of. + /// The Kelvin function kei. + public static double KelvinKei(double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + return KelvinKe(0, x).Imaginary; + } + + /// + /// Returns the derivative of the Kelvin function ker. + /// + /// The order of the Kelvin function. + /// The non-negative real value to compute the derivative of the Kelvin function of. + /// The derivative of the Kelvin function ker. + public static double KelvinKerPrime(double nu, double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + const double inv2Sqrt2 = 0.35355339059327376220042218105242451964241796884424; // 1/(2 * sqrt(2)) + return inv2Sqrt2 * (-KelvinKer(nu - 1, x) + KelvinKer(nu + 1, x) - KelvinKei(nu - 1, x) + KelvinKei(nu + 1, x)); + } + + /// + /// Returns the derivative of the Kelvin function ker. + /// + /// The value to compute the derivative of the Kelvin function of. + /// The derivative of the Kelvin function ker. + public static double KelvinKerPrime(double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + return KelvinKerPrime(0, x); + } + + /// + /// Returns the derivative of the Kelvin function kei. + /// + /// The order of the Kelvin function. + /// The value to compute the derivative of the Kelvin function of. + /// The derivative of the Kelvin function kei. + public static double KelvinKeiPrime(double nu, double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + const double inv2Sqrt2 = 0.35355339059327376220042218105242451964241796884424; // 1/(2 * sqrt(2)) + return inv2Sqrt2 * (KelvinKer(nu - 1, x) - KelvinKer(nu + 1, x) - KelvinKei(nu - 1, x) + KelvinKei(nu + 1, x)); + } + + /// + /// Returns the derivative of the Kelvin function kei. + /// + /// The value to compute the derivative of the Kelvin function of. + /// The derivative of the Kelvin function kei. + public static double KelvinKeiPrime(double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + return KelvinKeiPrime(0, x); + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Logistic.cs b/MathNet.Numerics/SpecialFunctions/Logistic.cs new file mode 100644 index 0000000..25851ee --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Logistic.cs @@ -0,0 +1,73 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2011 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// +// Cephes Math Library, Stephen L. Moshier +// ALGLIB 2.0.1, Sergey Bochkanov +// + +using MathNet.Numerics.Properties; + +using System; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the logistic function. +/// +public static partial class SpecialFunctions +{ + /// + /// Computes the logistic function. see: http://en.wikipedia.org/wiki/Logistic + /// + /// The parameter for which to compute the logistic function. + /// The logistic function of . + public static double Logistic(double p) + { + return 1.0 / (Math.Exp(-p) + 1.0); + } + + /// + /// Computes the logit function, the inverse of the sigmoid logistic function. see: http://en.wikipedia.org/wiki/Logit + /// + /// The parameter for which to compute the logit function. This number should be + /// between 0 and 1. + /// The logarithm of divided by 1.0 - . + public static double Logit(double p) + { + if (p < 0.0 || p > 1.0) + { + throw new ArgumentOutOfRangeException(nameof(p), Resources.ArgumentBetween0And1); + } + + return Math.Log(p / (1.0 - p)); + } +} diff --git a/MathNet.Numerics/SpecialFunctions/ModifiedBessel.cs b/MathNet.Numerics/SpecialFunctions/ModifiedBessel.cs new file mode 100644 index 0000000..125c156 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/ModifiedBessel.cs @@ -0,0 +1,292 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2012 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// +// CERN - European Laboratory for Particle Physics +// http://www.docjar.com/html/api/cern/jet/math/Bessel.java.html +// Copyright 1999 CERN - European Laboratory for Particle Physics. +// Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose +// is hereby granted without fee, provided that the above copyright notice appear in all copies and +// that both that copyright notice and this permission notice appear in supporting documentation. +// CERN makes no representations about the suitability of this software for any purpose. +// It is provided "as is" without expressed or implied warranty. +// TOMS757 - Uncommon Special Functions (Fortran77) by Allan McLeod +// http://people.sc.fsu.edu/~jburkardt/f77_src/toms757/toms757.html +// Wei Wu +// Cephes Math Library, Stephen L. Moshier +// ALGLIB 2.0.1, Sergey Bochkanov +// + +using System; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the modified Bessel function. +/// +public static partial class SpecialFunctions +{ + /// + /// ************************************** + /// COEFFICIENTS FOR METHODS bessi0 * + /// ************************************** + /// + /// Chebyshev coefficients for exp(-x) I0(x) + /// in the interval [0, 8]. + /// + /// lim(x->0){ exp(-x) I0(x) } = 1. + /// + private static readonly double[] BesselI0A = { -4.41534164647933937950e-18, 3.33079451882223809783e-17, -2.43127984654795469359e-16, 1.71539128555513303061e-15, -1.16853328779934516808e-14, 7.67618549860493561688e-14, -4.85644678311192946090e-13, 2.95505266312963983461e-12, -1.72682629144155570723e-11, 9.67580903537323691224e-11, -5.18979560163526290666e-10, 2.65982372468238665035e-9, -1.30002500998624804212e-8, 6.04699502254191894932e-8, -2.67079385394061173391e-7, 1.11738753912010371815e-6, -4.41673835845875056359e-6, 1.64484480707288970893e-5, -5.75419501008210370398e-5, 1.88502885095841655729e-4, -5.76375574538582365885e-4, 1.63947561694133579842e-3, -4.32430999505057594430e-3, 1.05464603945949983183e-2, -2.37374148058994688156e-2, 4.93052842396707084878e-2, -9.49010970480476444210e-2, 1.71620901522208775349e-1, -3.04682672343198398683e-1, 6.76795274409476084995e-1 }; + + /// Chebyshev coefficients for exp(-x) sqrt(x) I0(x) + /// in the inverted interval [8, infinity]. + /// + /// lim(x->inf){ exp(-x) sqrt(x) I0(x) } = 1/sqrt(2pi). + /// + private static readonly double[] BesselI0B = { -7.23318048787475395456e-18, -4.83050448594418207126e-18, 4.46562142029675999901e-17, 3.46122286769746109310e-17, -2.82762398051658348494e-16, -3.42548561967721913462e-16, 1.77256013305652638360e-15, 3.81168066935262242075e-15, -9.55484669882830764870e-15, -4.15056934728722208663e-14, 1.54008621752140982691e-14, 3.85277838274214270114e-13, 7.18012445138366623367e-13, -1.79417853150680611778e-12, -1.32158118404477131188e-11, -3.14991652796324136454e-11, 1.18891471078464383424e-11, 4.94060238822496958910e-10, 3.39623202570838634515e-9, 2.26666899049817806459e-8, 2.04891858946906374183e-7, 2.89137052083475648297e-6, 6.88975834691682398426e-5, 3.36911647825569408990e-3, 8.04490411014108831608e-1 }; + + /// + /// ************************************** + /// COEFFICIENTS FOR METHODS bessi1 * + /// ************************************** + /// + /// Chebyshev coefficients for exp(-x) I1(x) / x + /// in the interval [0, 8]. + /// + /// lim(x->0){ exp(-x) I1(x) / x } = 1/2. + /// + private static readonly double[] BesselI1A = { 2.77791411276104639959e-18, -2.11142121435816608115e-17, 1.55363195773620046921e-16, -1.10559694773538630805e-15, 7.60068429473540693410e-15, -5.04218550472791168711e-14, 3.22379336594557470981e-13, -1.98397439776494371520e-12, 1.17361862988909016308e-11, -6.66348972350202774223e-11, 3.62559028155211703701e-10, -1.88724975172282928790e-9, 9.38153738649577178388e-9, -4.44505912879632808065e-8, 2.00329475355213526229e-7, -8.56872026469545474066e-7, 3.47025130813767847674e-6, -1.32731636560394358279e-5, 4.78156510755005422638e-5, -1.61760815825896745588e-4, 5.12285956168575772895e-4, -1.51357245063125314899e-3, 4.15642294431288815669e-3, -1.05640848946261981558e-2, 2.47264490306265168283e-2, -5.29459812080949914269e-2, 1.02643658689847095384e-1, -1.76416518357834055153e-1, 2.52587186443633654823e-1 }; + + /// Chebyshev coefficients for exp(-x) sqrt(x) I1(x) + /// in the inverted interval [8, infinity]. + /// + /// lim(x->inf){ exp(-x) sqrt(x) I1(x) } = 1/sqrt(2pi). + /// + private static readonly double[] BesselI1B = { 7.51729631084210481353e-18, 4.41434832307170791151e-18, -4.65030536848935832153e-17, -3.20952592199342395980e-17, 2.96262899764595013876e-16, 3.30820231092092828324e-16, -1.88035477551078244854e-15, -3.81440307243700780478e-15, 1.04202769841288027642e-14, 4.27244001671195135429e-14, -2.10154184277266431302e-14, -4.08355111109219731823e-13, -7.19855177624590851209e-13, 2.03562854414708950722e-12, 1.41258074366137813316e-11, 3.25260358301548823856e-11, -1.89749581235054123450e-11, -5.58974346219658380687e-10, -3.83538038596423702205e-9, -2.63146884688951950684e-8, -2.51223623787020892529e-7, -3.88256480887769039346e-6, -1.10588938762623716291e-4, -9.76109749136146840777e-3, 7.78576235018280120474e-1 }; + + /// + /// ************************************** + /// COEFFICIENTS FOR METHODS bessk0, bessk0e * + /// ************************************** + /// + /// Chebyshev coefficients for K0(x) + log(x/2) I0(x) + /// in the interval [0, 2]. The odd order coefficients are all + /// zero; only the even order coefficients are listed. + /// + /// lim(x->0){ K0(x) + log(x/2) I0(x) } = -EUL. + /// + private static readonly double[] BesselK0A = { 1.37446543561352307156e-16, 4.25981614279661018399e-14, 1.03496952576338420167e-11, 1.90451637722020886025e-9, 2.53479107902614945675e-7, 2.28621210311945178607e-5, 1.26461541144692592338e-3, 3.59799365153615016266e-2, 3.44289899924628486886e-1, -5.35327393233902768720e-1 }; + + /// Chebyshev coefficients for exp(x) sqrt(x) K0(x) + /// in the inverted interval [2, infinity]. + /// + /// lim(x->inf){ exp(x) sqrt(x) K0(x) } = sqrt(pi/2). + /// + private static readonly double[] BesselK0B = { 5.30043377268626276149e-18, -1.64758043015242134646e-17, 5.21039150503902756861e-17, -1.67823109680541210385e-16, 5.51205597852431940784e-16, -1.84859337734377901440e-15, 6.34007647740507060557e-15, -2.22751332699166985548e-14, 8.03289077536357521100e-14, -2.98009692317273043925e-13, 1.14034058820847496303e-12, -4.51459788337394416547e-12, 1.85594911495471785253e-11, -7.95748924447710747776e-11, 3.57739728140030116597e-10, -1.69753450938905987466e-9, 8.57403401741422608519e-9, -4.66048989768794782956e-8, 2.76681363944501510342e-7, -1.83175552271911948767e-6, 1.39498137188764993662e-5, -1.28495495816278026384e-4, 1.56988388573005337491e-3, -3.14481013119645005427e-2, 2.44030308206595545468e0 }; + + /// + /// ************************************** + /// COEFFICIENTS FOR METHODS bessk1, bessk1e * + /// ************************************** + /// + /// Chebyshev coefficients for x(K1(x) - log(x/2) I1(x)) + /// in the interval [0, 2]. + /// + /// lim(x->0){ x(K1(x) - log(x/2) I1(x)) } = 1. + /// + private static readonly double[] BesselK1A = { -7.02386347938628759343e-18, -2.42744985051936593393e-15, -6.66690169419932900609e-13, -1.41148839263352776110e-10, -2.21338763073472585583e-8, -2.43340614156596823496e-6, -1.73028895751305206302e-4, -6.97572385963986435018e-3, -1.22611180822657148235e-1, -3.53155960776544875667e-1, 1.52530022733894777053e0 }; + + /// Chebyshev coefficients for exp(x) sqrt(x) K1(x) + /// in the interval [2, infinity]. + /// + /// lim(x->inf){ exp(x) sqrt(x) K1(x) } = sqrt(pi/2). + /// + private static readonly double[] BesselK1B = { -5.75674448366501715755e-18, 1.79405087314755922667e-17, -5.68946255844285935196e-17, 1.83809354436663880070e-16, -6.05704724837331885336e-16, 2.03870316562433424052e-15, -7.01983709041831346144e-15, 2.47715442448130437068e-14, -8.97670518232499435011e-14, 3.34841966607842919884e-13, -1.28917396095102890680e-12, 5.13963967348173025100e-12, -2.12996783842756842877e-11, 9.21831518760500529508e-11, -4.19035475934189648750e-10, 2.01504975519703286596e-9, -1.03457624656780970260e-8, 5.74108412545004946722e-8, -3.50196060308781257119e-7, 2.40648494783721712015e-6, -1.93619797416608296024e-5, 1.95215518471351631108e-4, -2.85781685962277938680e-3, 1.03923736576817238437e-1, 2.72062619048444266945e0 }; + + /// Returns the modified Bessel function of first kind, order 0 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. + ///

+ /// The value to compute the Bessel function of. + /// + public static double BesselI0(double x) + { + if (x < 0) + { + x = -x; + } + + if (x <= 8.0) + { + double y = (x / 2.0) - 2.0; + return Math.Exp(x) * Evaluate.ChebyshevA(BesselI0A, y); + } + + double x1 = 32.0 / x - 2.0; + return Math.Exp(x) * Evaluate.ChebyshevA(BesselI0B, x1) / Math.Sqrt(x); + } + + /// Returns the modified Bessel function of first kind, + /// order 1 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. + ///

+ /// The value to compute the Bessel function of. + /// + public static double BesselI1(double x) + { + double z = Math.Abs(x); + if (z <= 8.0) + { + double y = (z / 2.0) - 2.0; + z = Evaluate.ChebyshevA(BesselI1A, y) * z * Math.Exp(z); + } + else + { + double x1 = 32.0 / z - 2.0; + z = Math.Exp(z) * Evaluate.ChebyshevA(BesselI1B, x1) / Math.Sqrt(z); + } + + if (x < 0.0) + { + z = -z; + } + + return z; + } + + /// Returns the modified Bessel function of the second kind + /// of order 0 of the argument. + ///

+ /// The range is partitioned into the two intervals [0, 8] and + /// (8, infinity). Chebyshev polynomial expansions are employed + /// in each interval. + ///

+ /// The value to compute the Bessel function of. + /// + public static double BesselK0(double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + if (x <= 2.0) + { + double y = x * x - 2.0; + return Evaluate.ChebyshevA(BesselK0A, y) - Math.Log(0.5 * x) * BesselI0(x); + } + + double z = 8.0 / x - 2.0; + return Math.Exp(-x) * Evaluate.ChebyshevA(BesselK0B, z) / Math.Sqrt(x); + } + + /// Returns the exponentially scaled modified Bessel function + /// of the second kind of order 0 of the argument. + /// + /// The value to compute the Bessel function of. + /// + public static double BesselK0e(double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + if (x <= 2.0) + { + double y = x * x - 2.0; + return Evaluate.ChebyshevA(BesselK0A, y) - Math.Log(0.5 * x) * BesselI0(x) * Math.Exp(x); + } + + double x1 = 8.0 / x - 2.0; + return Evaluate.ChebyshevA(BesselK0B, x1) / Math.Sqrt(x); + } + + /// Returns the modified Bessel function of the second kind + /// of order 1 of the argument. + ///

+ /// The range is partitioned into the two intervals [0, 2] and + /// (2, infinity). Chebyshev polynomial expansions are employed + /// in each interval. + ///

+ /// The value to compute the Bessel function of. + /// + public static double BesselK1(double x) + { + double z = 0.5 * x; + if (z <= 0.0) + { + throw new ArithmeticException(); + } + + if (x <= 2.0) + { + double y = x * x - 2.0; + return Math.Log(z) * BesselI1(x) + Evaluate.ChebyshevA(BesselK1A, y) / x; + } + + double x1 = 8.0 / x - 2.0; + return Math.Exp(-x) * Evaluate.ChebyshevA(BesselK1B, x1) / Math.Sqrt(x); + } + + /// Returns the exponentially scaled modified Bessel function + /// of the second kind of order 1 of the argument. + ///

+ /// k1e(x) = exp(x) * k1(x). + ///

+ /// The value to compute the Bessel function of. + /// + public static double BesselK1e(double x) + { + if (x <= 0.0) + { + throw new ArithmeticException(); + } + + if (x <= 2.0) + { + double y = x * x - 2.0; + return Math.Log(0.5 * x) * BesselI1(x) + Evaluate.ChebyshevA(BesselK1A, y) / x * Math.Exp(x); + } + + double x1 = 8.0 / x - 2.0; + return Evaluate.ChebyshevA(BesselK1B, x1) / Math.Sqrt(x); + } +} diff --git a/MathNet.Numerics/SpecialFunctions/ModifiedStruve.cs b/MathNet.Numerics/SpecialFunctions/ModifiedStruve.cs new file mode 100644 index 0000000..e2077ff --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/ModifiedStruve.cs @@ -0,0 +1,552 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2012 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +// +// CERN - European Laboratory for Particle Physics +// http://www.docjar.com/html/api/cern/jet/math/Bessel.java.html +// Copyright 1999 CERN - European Laboratory for Particle Physics. +// Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose +// is hereby granted without fee, provided that the above copyright notice appear in all copies and +// that both that copyright notice and this permission notice appear in supporting documentation. +// CERN makes no representations about the suitability of this software for any purpose. +// It is provided "as is" without expressed or implied warranty. +// TOMS757 - Uncommon Special Functions (Fortran77) by Allan McLeod +// http://people.sc.fsu.edu/~jburkardt/f77_src/toms757/toms757.html +// Wei Wu +// Cephes Math Library, Stephen L. Moshier +// ALGLIB 2.0.1, Sergey Bochkanov +// + +using System; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the modified Bessel function. +/// +public static partial class SpecialFunctions +{ + /// + /// Returns the modified Struve function of order 0. + /// + /// The value to compute the function of. + public static double StruveL0(double x) + { + //*********************************************************************72 + // + //c STRVL0 calculates the modified Struve function of order 0. + // + // DESCRIPTION: + // + // This function calculates the modified Struve function of + // order 0, denoted L0(x), defined as the solution of the + // second-order equation + // + // x*D(Df) + Df - x*f = 2x/pi + // + // This subroutine is set up to work on IEEE machines. + // For other machines, you should retrieve the code + // from the general MISCFUN archive. + // + // + // ERROR RETURNS: + // + // If the value of |XVALUE| is too large, the result + // would cause an floating-pt overflow. An error message + // is printed and the function returns the value of + // sign(XVALUE)*XMAX where XMAX is the largest possible + // floating-pt argument. + // + // + // MACHINE-DEPENDENT PARAMETERS: + // + // NTERM1 - INTEGER - The no. of terms for the array ARL0. + // The recommended value is such that + // ABS(ARL0(NTERM1)) < EPS/100 + // + // NTERM2 - INTEGER - The no. of terms for the array ARL0AS. + // The recommended value is such that + // ABS(ARL0AS(NTERM2)) < EPS/100 + // + // NTERM3 - INTEGER - The no. of terms for the array AI0ML0. + // The recommended value is such that + // ABS(AI0ML0(NTERM3)) < EPS/100 + // + // XLOW - DOUBLE PRECISION - The value of x below which L0(x) = 2*x/pi + // to machine precision. The recommended value is + // 3*SQRT(EPS) + // + // XHIGH1 - DOUBLE PRECISION - The value beyond which the Chebyshev series + // in the asymptotic expansion of I0 - L0 gives + // 1.0 to machine precision. The recommended value + // is SQRT( 30/EPSNEG ) + // + // XHIGH2 - DOUBLE PRECISION - The value beyond which the Chebyshev series + // in the asymptotic expansion of I0 gives 1.0 + // to machine precision. The recommended value + // is 28 / EPSNEG + // + // XMAX - DOUBLE PRECISION - The value of XMAX, where XMAX is the + // largest possible floating-pt argument. + // This is used to prevent overflow. + // + // For values of EPS, EPSNEG and XMAX the user should refer + // to the file MACHCON.TXT + // + // The machine-arithmetic constants are given in DATA + // statements. + // + // + // INTRINSIC FUNCTIONS USED: + // + // EXP , LOG , SQRT + // + // + // OTHER MISCFUN SUBROUTINES USED: + // + // CHEVAL , ERRPRN + // + // + // AUTHOR: + // DR. ALLAN J. MACLEOD + // DEPT. OF MATHEMATICS AND STATISTICS + // UNIVERSITY OF PAISLEY + // HIGH ST. + // PAISLEY + // SCOTLAND + // PA1 2BE + // + // (e-mail: macl_ms0@paisley.ac.uk ) + // + // + // LATEST REVISION: + // 12 JANUARY, 1996 + // + // + + if (x < 0.0) + { + return -StruveL0(-x); + } + + double[] ARL0 = new double[28]; + ARL0[0] = 0.42127458349979924863; + ARL0[1] = -0.33859536391220612188; + ARL0[2] = 0.21898994812710716064; + ARL0[3] = -0.12349482820713185712; + ARL0[4] = 0.6214209793866958440e-1; + ARL0[5] = -0.2817806028109547545e-1; + ARL0[6] = 0.1157419676638091209e-1; + ARL0[7] = -0.431658574306921179e-2; + ARL0[8] = 0.146142349907298329e-2; + ARL0[9] = -0.44794211805461478e-3; + ARL0[10] = 0.12364746105943761e-3; + ARL0[11] = -0.3049028334797044e-4; + ARL0[12] = 0.663941401521146e-5; + ARL0[13] = -0.125538357703889e-5; + ARL0[14] = 0.20073446451228e-6; + ARL0[15] = -0.2588260170637e-7; + ARL0[16] = 0.241143742758e-8; + ARL0[17] = -0.10159674352e-9; + ARL0[18] = -0.1202430736e-10; + ARL0[19] = 0.262906137e-11; + ARL0[20] = -0.15313190e-12; + ARL0[21] = -0.1574760e-13; + ARL0[22] = 0.315635e-14; + ARL0[23] = -0.4096e-16; + ARL0[24] = -0.3620e-16; + ARL0[25] = 0.239e-17; + ARL0[26] = 0.36e-18; + ARL0[27] = -0.4e-19; + + double[] ARL0AS = new double[16]; + ARL0AS[0] = 2.00861308235605888600; + ARL0AS[1] = 0.403737966500438470e-2; + ARL0AS[2] = -0.25199480286580267e-3; + ARL0AS[3] = 0.1605736682811176e-4; + ARL0AS[4] = -0.103692182473444e-5; + ARL0AS[5] = 0.6765578876305e-7; + ARL0AS[6] = -0.444999906756e-8; + ARL0AS[7] = 0.29468889228e-9; + ARL0AS[8] = -0.1962180522e-10; + ARL0AS[9] = 0.131330306e-11; + ARL0AS[10] = -0.8819190e-13; + ARL0AS[11] = 0.595376e-14; + ARL0AS[12] = -0.40389e-15; + ARL0AS[13] = 0.2651e-16; + ARL0AS[14] = -0.208e-17; + ARL0AS[15] = 0.11e-18; + + double[] AI0ML0 = new double[24]; + AI0ML0[0] = 2.00326510241160643125; + AI0ML0[1] = 0.195206851576492081e-2; + AI0ML0[2] = 0.38239523569908328e-3; + AI0ML0[3] = 0.7534280817054436e-4; + AI0ML0[4] = 0.1495957655897078e-4; + AI0ML0[5] = 0.299940531210557e-5; + AI0ML0[6] = 0.60769604822459e-6; + AI0ML0[7] = 0.12399495544506e-6; + AI0ML0[8] = 0.2523262552649e-7; + AI0ML0[9] = 0.504634857332e-8; + AI0ML0[10] = 0.97913236230e-9; + AI0ML0[11] = 0.18389115241e-9; + AI0ML0[12] = 0.3376309278e-10; + AI0ML0[13] = 0.611179703e-11; + AI0ML0[14] = 0.108472972e-11; + AI0ML0[15] = 0.18861271e-12; + AI0ML0[16] = 0.3280345e-13; + AI0ML0[17] = 0.565647e-14; + AI0ML0[18] = 0.93300e-15; + AI0ML0[19] = 0.15881e-15; + AI0ML0[20] = 0.2791e-16; + AI0ML0[21] = 0.389e-17; + AI0ML0[22] = 0.70e-18; + AI0ML0[23] = 0.16e-18; + + // MACHINE-DEPENDENT VALUES (Suitable for IEEE-arithmetic machines) + const int nterm1 = 25; + const int nterm2 = 14; + const int nterm3 = 21; + const double xlow = 4.4703484e-8; + const double xmax = 1.797693e308; + const double xhigh1 = 5.1982303e8; + const double xhigh2 = 2.5220158e17; + + // Code for |xvalue| <= 16 + if (x <= 16.0) + { + if (x < xlow) + { + return Constants.TwoInvPi * x; + } + + double T = (4.0 * x - 24.0) / (x + 24.0); + return Constants.TwoInvPi * x * Evaluate.ChebyshevSum(nterm1, ARL0, T) * Math.Exp(x); + } + + // Code for |xvalue| > 16 + double ch1; + if (x > xhigh2) + { + ch1 = 1.0; + } + else + { + double T = (x - 28.0) / (4.0 - x); + ch1 = Evaluate.ChebyshevSum(nterm2, ARL0AS, T); + } + + double ch2; + if (x > xhigh1) + { + ch2 = 1.0; + } + else + { + double xsq = x * x; + double T = (800.0 - xsq) / (288.0 + xsq); + ch2 = Evaluate.ChebyshevSum(nterm3, AI0ML0, T); + } + + double test = Math.Log(ch1) - Constants.LogSqrt2Pi - Math.Log(x) / 2.0 + x; + if (test > Math.Log(xmax)) + { + throw new ArithmeticException("ERROR IN MISCFUN FUNCTION STRVL0: ARGUMENT CAUSES OVERFLOW"); + } + + return Math.Exp(test) - Constants.TwoInvPi * ch2 / x; + } + + /// + /// Returns the modified Struve function of order 1. + /// + /// The value to compute the function of. + public static double StruveL1(double x) + { + //*********************************************************************72 + // + //c STRVL1 calculates the modified Struve function of order 1. + // + // DESCRIPTION: + // + // This function calculates the modified Struve function of + // order 1, denoted L1(x), defined as the solution of + // + // x*x*D(Df) + x*Df - (x*x+1)f = 2*x*x/pi + // + // This subroutine is set up to work on IEEE machines. + // For other machines, you should retrieve the code + // from the general MISCFUN archive. + // + // + // ERROR RETURNS: + // + // If the value of |XVALUE| is too large, the result + // would cause an floating-pt overflow. An error message + // is printed and the function returns the value of + // sign(XVALUE)*XMAX where XMAX is the largest possible + // floating-pt argument. + // + // + // MACHINE-DEPENDENT PARAMETERS: + // + // NTERM1 - INTEGER - The no. of terms for the array ARL1. + // The recommended value is such that + // ABS(ARL1(NTERM1)) < EPS/100 + // + // NTERM2 - INTEGER - The no. of terms for the array ARL1AS. + // The recommended value is such that + // ABS(ARL1AS(NTERM2)) < EPS/100 + // + // NTERM3 - INTEGER - The no. of terms for the array AI1ML1. + // The recommended value is such that + // ABS(AI1ML1(NTERM3)) < EPS/100 + // + // XLOW1 - DOUBLE PRECISION - The value of x below which + // L1(x) = 2*x*x/(3*pi) + // to machine precision. The recommended + // value is SQRT(15*EPS) + // + // XLOW2 - DOUBLE PRECISION - The value of x below which L1(x) set to 0.0. + // This is used to prevent underflow. The + // recommended value is + // SQRT(5*XMIN) + // + // XHIGH1 - DOUBLE PRECISION - The value of |x| above which the Chebyshev + // series in the asymptotic expansion of I1 + // equals 1.0 to machine precision. The + // recommended value is SQRT( 30 / EPSNEG ). + // + // XHIGH2 - DOUBLE PRECISION - The value of |x| above which the Chebyshev + // series in the asymptotic expansion of I1 - L1 + // equals 1.0 to machine precision. The recommended + // value is 30 / EPSNEG. + // + // XMAX - DOUBLE PRECISION - The value of XMAX, where XMAX is the + // largest possible floating-pt argument. + // This is used to prevent overflow. + // + // For values of EPS, EPSNEG, XMIN, and XMAX the user should refer + // to the file MACHCON.TXT + // + // The machine-arithmetic constants are given in DATA + // statements. + // + // + // INTRINSIC FUNCTIONS USED: + // + // EXP , LOG , SQRT + // + // + // OTHER MISCFUN SUBROUTINES USED: + // + // CHEVAL , ERRPRN + // + // + // AUTHOR: + // DR. ALLAN J. MACLEOD + // DEPT. OF MATHEMATICS AND STATISTICS + // UNIVERSITY OF PAISLEY + // HIGH ST. + // PAISLEY + // SCOTLAND + // PA1 2BE + // + // (e-mail: macl_ms0@paisley.ac.uk ) + // + // + // LATEST UPDATE: + // 12 JANUARY, 1996 + // + // + + if (x < 0.0) + { + return StruveL1(-x); + } + + double[] ARL1 = new double[27]; + ARL1[0] = 0.38996027351229538208; + ARL1[1] = -0.33658096101975749366; + ARL1[2] = 0.23012467912501645616; + ARL1[3] = -0.13121594007960832327; + ARL1[4] = 0.6425922289912846518e-1; + ARL1[5] = -0.2750032950616635833e-1; + ARL1[6] = 0.1040234148637208871e-1; + ARL1[7] = -0.350532294936388080e-2; + ARL1[8] = 0.105748498421439717e-2; + ARL1[9] = -0.28609426403666558e-3; + ARL1[10] = 0.6925708785942208e-4; + ARL1[11] = -0.1489693951122717e-4; + ARL1[12] = 0.281035582597128e-5; + ARL1[13] = -0.45503879297776e-6; + ARL1[14] = 0.6090171561770e-7; + ARL1[15] = -0.623543724808e-8; + ARL1[16] = 0.38430012067e-9; + ARL1[17] = 0.790543916e-11; + ARL1[18] = -0.489824083e-11; + ARL1[19] = 0.46356884e-12; + ARL1[20] = 0.684205e-14; + ARL1[21] = -0.569748e-14; + ARL1[22] = 0.35324e-15; + ARL1[23] = 0.4244e-16; + ARL1[24] = -0.644e-17; + ARL1[25] = -0.21e-18; + ARL1[26] = 0.9e-19; + + double[] ARL1AS = new double[17]; + ARL1AS[0] = 1.97540378441652356868; + ARL1AS[1] = -0.1195130555088294181e-1; + ARL1AS[2] = 0.33639485269196046e-3; + ARL1AS[3] = -0.1009115655481549e-4; + ARL1AS[4] = 0.30638951321998e-6; + ARL1AS[5] = -0.953704370396e-8; + ARL1AS[6] = 0.29524735558e-9; + ARL1AS[7] = -0.951078318e-11; + ARL1AS[8] = 0.28203667e-12; + ARL1AS[9] = -0.1134175e-13; + ARL1AS[10] = 0.147e-17; + ARL1AS[11] = -0.6232e-16; + ARL1AS[12] = -0.751e-17; + ARL1AS[13] = -0.17e-18; + ARL1AS[14] = 0.51e-18; + ARL1AS[15] = 0.23e-18; + ARL1AS[16] = 0.5e-19; + + double[] AI1ML1 = new double[26]; + AI1ML1[0] = 1.99679361896789136501; + AI1ML1[1] = -0.190663261409686132e-2; + AI1ML1[2] = -0.36094622410174481e-3; + AI1ML1[3] = -0.6841847304599820e-4; + AI1ML1[4] = -0.1299008228509426e-4; + AI1ML1[5] = -0.247152188705765e-5; + AI1ML1[6] = -0.47147839691972e-6; + AI1ML1[7] = -0.9020819982592e-7; + AI1ML1[8] = -0.1730458637504e-7; + AI1ML1[9] = -0.332323670159e-8; + AI1ML1[10] = -0.63736421735e-9; + AI1ML1[11] = -0.12180239756e-9; + AI1ML1[12] = -0.2317346832e-10; + AI1ML1[13] = -0.439068833e-11; + AI1ML1[14] = -0.82847110e-12; + AI1ML1[15] = -0.15562249e-12; + AI1ML1[16] = -0.2913112e-13; + AI1ML1[17] = -0.543965e-14; + AI1ML1[18] = -0.101177e-14; + AI1ML1[19] = -0.18767e-15; + AI1ML1[20] = -0.3484e-16; + AI1ML1[21] = -0.643e-17; + AI1ML1[22] = -0.118e-17; + AI1ML1[23] = -0.22e-18; + AI1ML1[24] = -0.4e-19; + AI1ML1[25] = -0.1e-19; + + // MACHINE-DEPENDENT VALUES (Suitable for IEEE-arithmetic machines) + const int nterm1 = 24; + const int nterm2 = 13; + const int nterm3 = 22; + const double xlow1 = 5.7711949e-8; + const double xlow2 = 3.3354714e-154; + const double xmax = 1.797693e308; + const double xhigh1 = 5.19823025e8; + const double xhigh2 = 2.7021597e17; + + // CODE FOR |x| <= 16 + if (x <= 16.0) + { + if (x <= xlow2) + { + return 0.0; + } + + double xsq = x * x; + if (x < xlow1) + { + return xsq / Constants.Pi3Over2; + } + + double t = (4.0 * x - 24.0) / (x + 24.0); + return xsq * Evaluate.ChebyshevSum(nterm1, ARL1, t) * Math.Exp(x) / Constants.Pi3Over2; + } + + // CODE FOR |x| > 16 + double ch1; + if (x > xhigh2) + { + ch1 = 1.0; + } + else + { + double t = (x - 30.0) / (2.0 - x); + ch1 = Evaluate.ChebyshevSum(nterm2, ARL1AS, t); + } + + double ch2; + if (x > xhigh1) + { + ch2 = 1.0; + } + else + { + double xsq = x * x; + double t = (800.0 - xsq) / (288.0 + xsq); + ch2 = Evaluate.ChebyshevSum(nterm3, AI1ML1, t); + } + + double test = Math.Log(ch1) - Constants.LogSqrt2Pi - Math.Log(x) / 2.0 + x; + if (test > Math.Log(xmax)) + { + throw new ArithmeticException("ERROR IN MISCFUN FUNCTION STRVL1: ARGUMENT CAUSES OVERFLOW"); + } + + return Math.Exp(test) - Constants.TwoInvPi * ch2; + } + + /// + /// Returns the difference between the Bessel I0 and Struve L0 functions. + /// + /// The value to compute the function of. + public static double BesselI0MStruveL0(double x) + { + // TODO: way off for large x (e.g. 100) - needs direct approximation + return BesselI0(x) - StruveL0(x); + } + + /// + /// Returns the difference between the Bessel I1 and Struve L1 functions. + /// + /// The value to compute the function of. + public static double BesselI1MStruveL1(double x) + { + // TODO: way off for large x (e.g. 100) - needs direct approximation + return BesselI1(x) - StruveL1(x); + } +} diff --git a/MathNet.Numerics/SpecialFunctions/SphericalBessel.cs b/MathNet.Numerics/SpecialFunctions/SphericalBessel.cs new file mode 100644 index 0000000..a8c2440 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/SphericalBessel.cs @@ -0,0 +1,129 @@ +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +/// +/// This partial implementation of the SpecialFunctions class contains all methods related to the spherical Bessel functions. +/// +public static partial class SpecialFunctions +{ + /// + /// Returns the spherical Bessel function of the first kind. + /// SphericalBesselJ(n, z) is given by Sqrt(pi/2) / Sqrt(z) * BesselJ(n + 1/2, z). + /// + /// The order of the spherical Bessel function. + /// The value to compute the spherical Bessel function of. + /// The spherical Bessel function of the first kind. + public static Complex SphericalBesselJ(double n, Complex z) + { + if (double.IsNaN(n) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + if (double.IsInfinity(z.Real)) + { + return (z.Imaginary == 0) ? Complex.Zero : new Complex(double.PositiveInfinity, double.PositiveInfinity); + } + + if (z.Real == 0 && z.Imaginary == 0) + { + return (n == 0) ? 1 : 0; + } + + return Constants.SqrtPiOver2 * BesselJ(n + 0.5, z) / Complex.Sqrt(z); + } + + /// + /// Returns the spherical Bessel function of the first kind. + /// SphericalBesselJ(n, z) is given by Sqrt(pi/2) / Sqrt(z) * BesselJ(n + 1/2, z). + /// + /// The order of the spherical Bessel function. + /// The value to compute the spherical Bessel function of. + /// The spherical Bessel function of the first kind. + public static double SphericalBesselJ(double n, double z) + { + if (double.IsNaN(n) || double.IsNaN(z)) + { + return double.NaN; + } + + if (n < 0) + { + return double.NaN; + } + + if (double.IsInfinity(z)) + { + return 0; + } + + if (z == 0) + { + return (n == 0) ? 1 : 0; + } + + return Constants.SqrtPiOver2 * BesselJ(n + 0.5, z) / Math.Sqrt(z); + } + + /// + /// Returns the spherical Bessel function of the second kind. + /// SphericalBesselY(n, z) is given by Sqrt(pi/2) / Sqrt(z) * BesselY(n + 1/2, z). + /// + /// The order of the spherical Bessel function. + /// The value to compute the spherical Bessel function of. + /// The spherical Bessel function of the second kind. + public static Complex SphericalBesselY(double n, Complex z) + { + if (double.IsNaN(n) || double.IsNaN(z.Real) || double.IsNaN(z.Imaginary)) + { + return new Complex(double.NaN, double.NaN); + } + + if (double.IsInfinity(z.Real)) + { + return (z.Imaginary == 0) ? Complex.Zero : new Complex(double.PositiveInfinity, double.PositiveInfinity); + } + + if (z.Real == 0 && z.Imaginary == 0) + { + return new Complex(double.NaN, double.NaN); + } + + return Constants.SqrtPiOver2 * BesselY(n + 0.5, z) / Complex.Sqrt(z); + } + + /// + /// Returns the spherical Bessel function of the second kind. + /// SphericalBesselY(n, z) is given by Sqrt(pi/2) / Sqrt(z) * BesselY(n + 1/2, z). + /// + /// The order of the spherical Bessel function. + /// The value to compute the spherical Bessel function of. + /// The spherical Bessel function of the second kind. + public static double SphericalBesselY(double n, double z) + { + if (double.IsNaN(n) || double.IsNaN(z)) + { + return double.NaN; + } + + if (n < 0) + { + return double.NaN; + } + + if (double.IsInfinity(z)) + { + return 0; + } + + if (z == 0) + { + return double.NegativeInfinity; + } + + return Constants.SqrtPiOver2 * BesselY(n + 0.5, z) / Math.Sqrt(z); + } +} diff --git a/MathNet.Numerics/SpecialFunctions/Stability.cs b/MathNet.Numerics/SpecialFunctions/Stability.cs new file mode 100644 index 0000000..a05a674 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/Stability.cs @@ -0,0 +1,166 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +using Complex = System.Numerics.Complex; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +public partial class SpecialFunctions +{ + /// + /// Numerically stable exponential minus one, i.e. x -> exp(x)-1 + /// + /// A number specifying a power. + /// Returns exp(power)-1. + public static double ExponentialMinusOne(double power) + { + double x = Math.Abs(power); + if (x > 0.1) + { + return Math.Exp(power) - 1.0; + } + + if (x < x.PositiveEpsilonOf()) + { + return x; + } + + // Series Expansion to x^k / k! + int k = 0; + double term = 1.0; + return Evaluate.Series( + () => + { + k++; + term *= power; + term /= k; + return term; + }); + } + + /// + /// Numerically stable hypotenuse of a right angle triangle, i.e. (a,b) -> sqrt(a^2 + b^2) + /// + /// The length of side a of the triangle. + /// The length of side b of the triangle. + /// Returns sqrt(a2 + b2) without underflow/overflow. + public static Complex Hypotenuse(Complex a, Complex b) + { + if (a.Magnitude > b.Magnitude) + { + var r = b.Magnitude / a.Magnitude; + return a.Magnitude * Math.Sqrt(1 + (r * r)); + } + + if (b != 0.0) + { + // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm) + var r = a.Magnitude / b.Magnitude; + return b.Magnitude * Math.Sqrt(1 + (r * r)); + } + + return 0d; + } + + /// + /// Numerically stable hypotenuse of a right angle triangle, i.e. (a,b) -> sqrt(a^2 + b^2) + /// + /// The length of side a of the triangle. + /// The length of side b of the triangle. + /// Returns sqrt(a2 + b2) without underflow/overflow. + public static Complex32 Hypotenuse(Complex32 a, Complex32 b) + { + if (a.Magnitude > b.Magnitude) + { + var r = b.Magnitude / a.Magnitude; + return a.Magnitude * (float)Math.Sqrt(1 + (r * r)); + } + + if (b != 0.0f) + { + // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm) + var r = a.Magnitude / b.Magnitude; + return b.Magnitude * (float)Math.Sqrt(1 + (r * r)); + } + + return 0f; + } + + /// + /// Numerically stable hypotenuse of a right angle triangle, i.e. (a,b) -> sqrt(a^2 + b^2) + /// + /// The length of side a of the triangle. + /// The length of side b of the triangle. + /// Returns sqrt(a2 + b2) without underflow/overflow. + public static double Hypotenuse(double a, double b) + { + if (Math.Abs(a) > Math.Abs(b)) + { + double r = b / a; + return Math.Abs(a) * Math.Sqrt(1 + (r * r)); + } + + if (b != 0.0) + { + // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm) + double r = a / b; + return Math.Abs(b) * Math.Sqrt(1 + (r * r)); + } + + return 0d; + } + + /// + /// Numerically stable hypotenuse of a right angle triangle, i.e. (a,b) -> sqrt(a^2 + b^2) + /// + /// The length of side a of the triangle. + /// The length of side b of the triangle. + /// Returns sqrt(a2 + b2) without underflow/overflow. + public static float Hypotenuse(float a, float b) + { + if (Math.Abs(a) > Math.Abs(b)) + { + float r = b / a; + return Math.Abs(a) * (float)Math.Sqrt(1 + (r * r)); + } + + if (b != 0.0) + { + // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm) + float r = a / b; + return Math.Abs(b) * (float)Math.Sqrt(1 + (r * r)); + } + + return 0f; + } +} diff --git a/MathNet.Numerics/SpecialFunctions/TestFunctions.cs b/MathNet.Numerics/SpecialFunctions/TestFunctions.cs new file mode 100644 index 0000000..dce65e4 --- /dev/null +++ b/MathNet.Numerics/SpecialFunctions/TestFunctions.cs @@ -0,0 +1,173 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2014 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Linq; + +// ReSharper disable CheckNamespace +namespace MathNet.Numerics; +// ReSharper restore CheckNamespace + +public static class TestFunctions +{ + /// + /// Valley-shaped Rosenbrock function for 2 dimensions: (x,y) -> (1-x)^2 + 100*(y-x^2)^2. + /// This function has a global minimum at (1,1) with f(1,1) = 0. + /// Common range: [-5,10] or [-2.048,2.048]. + /// + /// + /// https://en.wikipedia.org/wiki/Rosenbrock_function + /// http://www.sfu.ca/~ssurjano/rosen.html + /// + public static double Rosenbrock(double x, double y) + { + double a = 1.0 - x; + double b = y - x * x; + return a * a + 100 * b * b; + } + + /// + /// Valley-shaped Rosenbrock function for 2 or more dimensions. + /// This function have a global minimum of all ones and, for 8 > N > 3, a local minimum at (-1,1,...,1). + /// + /// + /// https://en.wikipedia.org/wiki/Rosenbrock_function + /// http://www.sfu.ca/~ssurjano/rosen.html + /// + public static double Rosenbrock(params double[] x) + { + double sum = 0; + for (int i = 1; i < x.Length; i++) + { + sum += Rosenbrock(x[i - 1], x[i]); + } + + return sum; + } + + /// + /// Himmelblau, a multi-modal function: (x,y) -> (x^2+y-11)^2 + (x+y^2-7)^2 + /// This function has 4 global minima with f(x,y) = 0. + /// Common range: [-6,6]. + /// Named after David Mautner Himmelblau + /// + /// + /// https://en.wikipedia.org/wiki/Himmelblau%27s_function + /// + public static double Himmelblau(double x, double y) + { + double a = x * x + y - 11.0; + double b = x + y * y - 7.0; + return a * a + b * b; + } + + /// + /// Rastrigin, a highly multi-modal function with many local minima. + /// Global minimum of all zeros with f(0) = 0. + /// Common range: [-5.12,5.12]. + /// + /// + /// https://en.wikipedia.org/wiki/Rastrigin_function + /// http://www.sfu.ca/~ssurjano/rastr.html + /// + public static double Rastrigin(params double[] x) + { + return x.Sum(xi => xi * xi - 10.0 * Math.Cos(Constants.Pi2 * xi)) + 10.0 * x.Length; + } + + /// + /// Drop-Wave, a multi-modal and highly complex function with many local minima. + /// Global minimum of all zeros with f(0) = -1. + /// Common range: [-5.12,5.12]. + /// + /// + /// http://www.sfu.ca/~ssurjano/drop.html + /// + public static double DropWave(double x, double y) + { + double t = x * x + y * y; + return -(1.0 + Math.Cos(12.0 * Math.Sqrt(t))) / (0.5 * t + 2.0); + } + + /// + /// Ackley, a function with many local minima. It is nearly flat in outer regions but has a large hole at the center. + /// Global minimum of all zeros with f(0) = 0. + /// Common range: [-32.768, 32.768]. + /// + /// + /// http://www.sfu.ca/~ssurjano/ackley.html + /// + public static double Ackley(params double[] x) + { + double u = x.Sum(xi => xi * xi) / x.Length; + double v = x.Sum(xi => Math.Cos(Constants.Pi2 * xi)) / x.Length; + return -20 * Math.Exp(-0.2 * Math.Sqrt(u)) - Math.Exp(v) + 20 + Math.E; + } + + /// + /// Bowl-shaped first Bohachevsky function. + /// Global minimum of all zeros with f(0,0) = 0. + /// Common range: [-100, 100] + /// + /// + /// http://www.sfu.ca/~ssurjano/boha.html + /// + public static double Bohachevsky1(double x, double y) + { + return x * x + 2 * y * y - 0.3 * Math.Cos(3 * Math.PI * x) - 0.4 * Math.Cos(4 * Math.PI * y); + } + + /// + /// Plate-shaped Matyas function. + /// Global minimum of all zeros with f(0,0) = 0. + /// Common range: [-10, 10]. + /// + /// + /// http://www.sfu.ca/~ssurjano/matya.html + /// + public static double Matyas(double x, double y) + { + return 0.26 * (x * x + y * y) - 0.48 * x * y; + } + + /// + /// Valley-shaped six-hump camel back function. + /// Two global minima and four local minima. Global minima with f(x) ) -1.0316 at (0.0898,-0.7126) and (-0.0898,0.7126). + /// Common range: x in [-3,3], y in [-2,2]. + /// + /// + /// http://www.sfu.ca/~ssurjano/camel6.html + /// + public static double SixHumpCamel(double x, double y) + { + double x2 = x * x; + double y2 = y * y; + return (4 - 2.1 * x2 + x2 * x2 / 3) * x2 + x * y + (-4 + 4 * y2) * y2; + } +} diff --git a/MathNet.Numerics/Statistics/ArrayStatistics.Complex.cs b/MathNet.Numerics/Statistics/ArrayStatistics.Complex.cs new file mode 100644 index 0000000..818bf28 --- /dev/null +++ b/MathNet.Numerics/Statistics/ArrayStatistics.Complex.cs @@ -0,0 +1,163 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Statistics; + +public static partial class ArrayStatistics +{ + /// + /// Returns the smallest absolute value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Complex MinimumMagnitudePhase(Complex[] data) + { + if (data.Length == 0) + { + return new Complex(double.NaN, double.NaN); + } + + double minMagnitude = double.PositiveInfinity; + Complex min = new Complex(double.PositiveInfinity, double.PositiveInfinity); + for (int i = 0; i < data.Length; i++) + { + double magnitude = data[i].Magnitude; + if (double.IsNaN(magnitude)) + { + return new Complex(double.NaN, double.NaN); + } + + if (magnitude < minMagnitude || magnitude == minMagnitude && data[i].Phase < min.Phase) + { + minMagnitude = magnitude; + min = data[i]; + } + } + + return min; + } + + /// + /// Returns the smallest absolute value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Complex32 MinimumMagnitudePhase(Complex32[] data) + { + if (data.Length == 0) + { + return new Complex32(float.NaN, float.NaN); + } + + float minMagnitude = float.PositiveInfinity; + Complex32 min = new Complex32(float.PositiveInfinity, float.PositiveInfinity); + for (int i = 0; i < data.Length; i++) + { + float magnitude = data[i].Magnitude; + if (float.IsNaN(magnitude)) + { + return new Complex32(float.NaN, float.NaN); + } + + if (magnitude < minMagnitude || magnitude == minMagnitude && data[i].Phase < min.Phase) + { + minMagnitude = magnitude; + min = data[i]; + } + } + + return min; + } + + /// + /// Returns the largest absolute value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Complex MaximumMagnitudePhase(Complex[] data) + { + if (data.Length == 0) + { + return new Complex(double.NaN, double.NaN); + } + + double maxMagnitude = 0.0d; + Complex max = Complex.Zero; + for (int i = 0; i < data.Length; i++) + { + double magnitude = data[i].Magnitude; + if (double.IsNaN(magnitude)) + { + return new Complex(double.NaN, double.NaN); + } + + if (magnitude > maxMagnitude || magnitude == maxMagnitude && data[i].Phase > max.Phase) + { + maxMagnitude = magnitude; + max = data[i]; + } + } + + return max; + } + + /// + /// Returns the largest absolute value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Complex32 MaximumMagnitudePhase(Complex32[] data) + { + if (data.Length == 0) + { + return new Complex32(float.NaN, float.NaN); + } + + float maxMagnitude = 0.0f; + Complex32 max = Complex32.Zero; + for (int i = 0; i < data.Length; i++) + { + float magnitude = data[i].Magnitude; + if (float.IsNaN(magnitude)) + { + return new Complex32(float.NaN, float.NaN); + } + + if (magnitude > maxMagnitude || magnitude == maxMagnitude && data[i].Phase > max.Phase) + { + maxMagnitude = magnitude; + max = data[i]; + } + } + + return max; + } +} diff --git a/MathNet.Numerics/Statistics/ArrayStatistics.Int32.cs b/MathNet.Numerics/Statistics/ArrayStatistics.Int32.cs new file mode 100644 index 0000000..cdc79a1 --- /dev/null +++ b/MathNet.Numerics/Statistics/ArrayStatistics.Int32.cs @@ -0,0 +1,277 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.Statistics; + +public static partial class ArrayStatistics +{ + /// + /// Estimates the arithmetic sample mean from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double Mean(int[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double mean = 0; + ulong m = 0; + for (int i = 0; i < data.Length; i++) + { + mean += (data[i] - mean) / ++m; + } + + return mean; + } + + /// + /// Evaluates the geometric mean of the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double GeometricMean(int[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double sum = 0; + for (int i = 0; i < data.Length; i++) + { + sum += Math.Log(data[i]); + } + + return Math.Exp(sum / data.Length); + } + + /// + /// Evaluates the harmonic mean of the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double HarmonicMean(int[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double sum = 0; + for (int i = 0; i < data.Length; i++) + { + sum += 1.0 / data[i]; + } + + return data.Length / sum; + } + + /// + /// Estimates the unbiased population variance from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double Variance(int[] samples) + { + if (samples.Length <= 1) + { + return double.NaN; + } + + double variance = 0; + double t = samples[0]; + for (int i = 1; i < samples.Length; i++) + { + t += samples[i]; + double diff = ((i + 1) * samples[i]) - t; + variance += (diff * diff) / ((i + 1.0) * i); + } + + return variance / (samples.Length - 1); + } + + /// + /// Evaluates the population variance from the full population provided as unsorted array. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double PopulationVariance(int[] population) + { + if (population.Length == 0) + { + return double.NaN; + } + + double variance = 0; + double t = population[0]; + for (int i = 1; i < population.Length; i++) + { + t += population[i]; + double diff = ((i + 1) * population[i]) - t; + variance += (diff * diff) / ((i + 1.0) * i); + } + + return variance / population.Length; + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double StandardDeviation(int[] samples) + { + return Math.Sqrt(Variance(samples)); + } + + /// + /// Evaluates the population standard deviation from the full population provided as unsorted array. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double PopulationStandardDeviation(int[] population) + { + return Math.Sqrt(PopulationVariance(population)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population variance from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN and NaN for variance if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Tuple MeanVariance(int[] samples) + { + return new Tuple(Mean(samples), Variance(samples)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population standard deviation from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN and NaN for standard deviation if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Tuple MeanStandardDeviation(int[] samples) + { + return new Tuple(Mean(samples), StandardDeviation(samples)); + } + + /// + /// Estimates the unbiased population covariance from the provided two sample arrays. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// First sample array. + /// Second sample array. + public static double Covariance(int[] samples1, int[] samples2) + { + if (samples1.Length != samples2.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (samples1.Length <= 1) + { + return double.NaN; + } + + double mean1 = Mean(samples1); + double mean2 = Mean(samples2); + double covariance = 0.0; + for (int i = 0; i < samples1.Length; i++) + { + covariance += (samples1[i] - mean1) * (samples2[i] - mean2); + } + + return covariance / (samples1.Length - 1); + } + + /// + /// Evaluates the population covariance from the full population provided as two arrays. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// First population array. + /// Second population array. + public static double PopulationCovariance(int[] population1, int[] population2) + { + if (population1.Length != population2.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (population1.Length == 0) + { + return double.NaN; + } + + double mean1 = Mean(population1); + double mean2 = Mean(population2); + double covariance = 0.0; + for (int i = 0; i < population1.Length; i++) + { + covariance += (population1[i] - mean1) * (population2[i] - mean2); + } + + return covariance / population1.Length; + } + + /// + /// Estimates the root mean square (RMS) also known as quadratic mean from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double RootMeanSquare(int[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double mean = 0; + ulong m = 0; + for (int i = 0; i < data.Length; i++) + { + mean += (data[i] * data[i] - mean) / ++m; + } + + return Math.Sqrt(mean); + } +} diff --git a/MathNet.Numerics/Statistics/ArrayStatistics.Single.cs b/MathNet.Numerics/Statistics/ArrayStatistics.Single.cs new file mode 100644 index 0000000..55643ad --- /dev/null +++ b/MathNet.Numerics/Statistics/ArrayStatistics.Single.cs @@ -0,0 +1,841 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.Statistics; + +public static partial class ArrayStatistics +{ + /// + /// Returns the smallest value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static float Minimum(float[] data) + { + if (data.Length == 0) + { + return float.NaN; + } + + var min = float.PositiveInfinity; + for (int i = 0; i < data.Length; i++) + { + if (data[i] < min || float.IsNaN(data[i])) + { + min = data[i]; + } + } + + return min; + } + + /// + /// Returns the smallest value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static float Maximum(float[] data) + { + if (data.Length == 0) + { + return float.NaN; + } + + var max = float.NegativeInfinity; + for (int i = 0; i < data.Length; i++) + { + if (data[i] > max || float.IsNaN(data[i])) + { + max = data[i]; + } + } + + return max; + } + + /// + /// Returns the smallest absolute value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static float MinimumAbsolute(float[] data) + { + if (data.Length == 0) + { + return float.NaN; + } + + float min = float.PositiveInfinity; + for (int i = 0; i < data.Length; i++) + { + if (Math.Abs(data[i]) < min || float.IsNaN(data[i])) + { + min = Math.Abs(data[i]); + } + } + + return min; + } + + /// + /// Returns the largest absolute value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static float MaximumAbsolute(float[] data) + { + if (data.Length == 0) + { + return float.NaN; + } + + float max = 0.0f; + for (int i = 0; i < data.Length; i++) + { + if (Math.Abs(data[i]) > max || float.IsNaN(data[i])) + { + max = Math.Abs(data[i]); + } + } + + return max; + } + + /// + /// Estimates the arithmetic sample mean from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double Mean(float[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double mean = 0; + ulong m = 0; + for (int i = 0; i < data.Length; i++) + { + mean += (data[i] - mean) / ++m; + } + + return mean; + } + + /// + /// Evaluates the geometric mean of the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double GeometricMean(float[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double sum = 0; + for (int i = 0; i < data.Length; i++) + { + sum += Math.Log(data[i]); + } + + return Math.Exp(sum / data.Length); + } + + /// + /// Evaluates the harmonic mean of the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double HarmonicMean(float[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double sum = 0; + for (int i = 0; i < data.Length; i++) + { + sum += 1.0 / data[i]; + } + + return data.Length / sum; + } + + /// + /// Estimates the unbiased population variance from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double Variance(float[] samples) + { + if (samples.Length <= 1) + { + return double.NaN; + } + + double variance = 0; + double t = samples[0]; + for (int i = 1; i < samples.Length; i++) + { + t += samples[i]; + double diff = ((i + 1) * samples[i]) - t; + variance += (diff * diff) / ((i + 1.0) * i); + } + + return variance / (samples.Length - 1); + } + + /// + /// Evaluates the population variance from the full population provided as unsorted array. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double PopulationVariance(float[] population) + { + if (population.Length == 0) + { + return double.NaN; + } + + double variance = 0; + double t = population[0]; + for (int i = 1; i < population.Length; i++) + { + t += population[i]; + double diff = ((i + 1) * population[i]) - t; + variance += (diff * diff) / ((i + 1.0) * i); + } + + return variance / population.Length; + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double StandardDeviation(float[] samples) + { + return Math.Sqrt(Variance(samples)); + } + + /// + /// Evaluates the population standard deviation from the full population provided as unsorted array. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double PopulationStandardDeviation(float[] population) + { + return Math.Sqrt(PopulationVariance(population)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population variance from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN and NaN for variance if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Tuple MeanVariance(float[] samples) + { + return new Tuple(Mean(samples), Variance(samples)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population standard deviation from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN and NaN for standard deviation if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Tuple MeanStandardDeviation(float[] samples) + { + return new Tuple(Mean(samples), StandardDeviation(samples)); + } + + /// + /// Estimates the unbiased population covariance from the provided two sample arrays. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// First sample array. + /// Second sample array. + public static double Covariance(float[] samples1, float[] samples2) + { + if (samples1.Length != samples2.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (samples1.Length <= 1) + { + return double.NaN; + } + + double mean1 = Mean(samples1); + double mean2 = Mean(samples2); + double covariance = 0.0; + for (int i = 0; i < samples1.Length; i++) + { + covariance += (samples1[i] - mean1) * (samples2[i] - mean2); + } + + return covariance / (samples1.Length - 1); + } + + /// + /// Evaluates the population covariance from the full population provided as two arrays. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// First population array. + /// Second population array. + public static double PopulationCovariance(float[] population1, float[] population2) + { + if (population1.Length != population2.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (population1.Length == 0) + { + return double.NaN; + } + + double mean1 = Mean(population1); + double mean2 = Mean(population2); + double covariance = 0.0; + for (int i = 0; i < population1.Length; i++) + { + covariance += (population1[i] - mean1) * (population2[i] - mean2); + } + + return covariance / population1.Length; + } + + /// + /// Estimates the root mean square (RMS) also known as quadratic mean from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double RootMeanSquare(float[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double mean = 0; + ulong m = 0; + for (int i = 0; i < data.Length; i++) + { + mean += (data[i] * data[i] - mean) / ++m; + } + + return Math.Sqrt(mean); + } + + /// + /// Returns the order statistic (order 1..N) from the unsorted data array. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// One-based order of the statistic, must be between 1 and N (inclusive). + public static float OrderStatisticInplace(float[] data, int order) + { + if (order < 1 || order > data.Length) + { + return float.NaN; + } + + if (order == 1) + { + return Minimum(data); + } + + if (order == data.Length) + { + return Maximum(data); + } + + return SelectInplace(data, order - 1); + } + + /// + /// Estimates the median value from the unsorted data array. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static float MedianInplace(float[] data) + { + var k = data.Length / 2; + return data.Length.IsOdd() + ? SelectInplace(data, k) + : (SelectInplace(data, k - 1) + SelectInplace(data, k)) / 2.0f; + } + + /// + /// Estimates the p-Percentile value from the unsorted data array. + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// Percentile selector, between 0 and 100 (inclusive). + public static float PercentileInplace(float[] data, int p) + { + return QuantileInplace(data, p / 100.0d); + } + + /// + /// Estimates the first quartile value from the unsorted data array. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static float LowerQuartileInplace(float[] data) + { + return QuantileInplace(data, 0.25d); + } + + /// + /// Estimates the third quartile value from the unsorted data array. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static float UpperQuartileInplace(float[] data) + { + return QuantileInplace(data, 0.75d); + } + + /// + /// Estimates the inter-quartile range from the unsorted data array. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static float InterquartileRangeInplace(float[] data) + { + return QuantileInplace(data, 0.75d) - QuantileInplace(data, 0.25d); + } + + /// + /// Estimates {min, lower-quantile, median, upper-quantile, max} from the unsorted data array. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static float[] FiveNumberSummaryInplace(float[] data) + { + if (data.Length == 0) + { + return new[] { float.NaN, float.NaN, float.NaN, float.NaN, float.NaN }; + } + + // TODO: Benchmark: is this still faster than sorting the array then using SortedArrayStatistics instead? + return new[] { Minimum(data), QuantileInplace(data, 0.25d), MedianInplace(data), QuantileInplace(data, 0.75d), Maximum(data) }; + } + + /// + /// Estimates the tau-th quantile from the unsorted data array. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// + /// R-8, SciPy-(1/3,1/3): + /// Linear interpolation of the approximate medians for order statistics. + /// When tau < (2/3) / (N + 1/3), use x1. When tau >= (N - 1/3) / (N + 1/3), use xN. + /// + public static float QuantileInplace(float[] data, double tau) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return float.NaN; + } + + double h = (data.Length + 1d / 3d) * tau + 1d / 3d; + var hf = (int)h; + + if (hf <= 0 || tau == 0d) + { + return Minimum(data); + } + + if (hf >= data.Length || tau == 1d) + { + return Maximum(data); + } + + var a = SelectInplace(data, hf - 1); + var b = SelectInplace(data, hf); + return (float)(a + (h - hf) * (b - a)); + } + + /// + /// Estimates the tau-th quantile from the unsorted data array. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified + /// by 4 parameters a, b, c and d, consistent with Mathematica. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// Quantile selector, between 0.0 and 1.0 (inclusive) + /// a-parameter + /// b-parameter + /// c-parameter + /// d-parameter + public static float QuantileCustomInplace(float[] data, double tau, double a, double b, double c, double d) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return float.NaN; + } + + var x = a + (data.Length + b) * tau - 1; + var ip = Math.Truncate(x); + var fp = x - ip; + + if (Math.Abs(fp) < 1e-9) + { + return SelectInplace(data, (int)ip); + } + + var lower = SelectInplace(data, (int)Math.Floor(x)); + var upper = SelectInplace(data, (int)Math.Ceiling(x)); + return (float)(lower + (upper - lower) * (c + d * fp)); + } + + /// + /// Estimates the tau-th quantile from the unsorted data array. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// Quantile selector, between 0.0 and 1.0 (inclusive) + /// Quantile definition, to choose what product/definition it should be consistent with + public static float QuantileCustomInplace(float[] data, double tau, QuantileDefinition definition) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return float.NaN; + } + + if (tau == 0d || data.Length == 1) + { + return Minimum(data); + } + + if (tau == 1d) + { + return Maximum(data); + } + + switch (definition) + { + case QuantileDefinition.R1: + { + double h = data.Length * tau + 0.5d; + return SelectInplace(data, (int)Math.Ceiling(h - 0.5d) - 1); + } + + case QuantileDefinition.R2: + { + double h = data.Length * tau + 0.5d; + return (SelectInplace(data, (int)Math.Ceiling(h - 0.5d) - 1) + SelectInplace(data, (int)(h + 0.5d) - 1)) * 0.5f; + } + + case QuantileDefinition.R3: + { + double h = data.Length * tau; + return SelectInplace(data, (int)Math.Round(h) - 1); + } + + case QuantileDefinition.R4: + { + double h = data.Length * tau; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R5: + { + double h = data.Length * tau + 0.5d; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R6: + { + double h = (data.Length + 1) * tau; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R7: + { + double h = (data.Length - 1) * tau + 1d; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R8: + { + double h = (data.Length + 1 / 3d) * tau + 1 / 3d; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R9: + { + double h = (data.Length + 0.25d) * tau + 0.375d; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return (float)(lower + (h - hf) * (upper - lower)); + } + + default: + throw new NotSupportedException(); + } + } + + static float SelectInplace(float[] workingData, int rank) + { + // Numerical Recipes: select + // http://en.wikipedia.org/wiki/Selection_algorithm + if (rank <= 0) + { + return Minimum(workingData); + } + + if (rank >= workingData.Length - 1) + { + return Maximum(workingData); + } + + float[] a = workingData; + int low = 0; + int high = a.Length - 1; + + while (true) + { + if (high <= low + 1) + { + if (high == low + 1 && a[high] < a[low]) + { + var tmp = a[low]; + a[low] = a[high]; + a[high] = tmp; + } + + return a[rank]; + } + + int middle = (low + high) >> 1; + + var tmp1 = a[middle]; + a[middle] = a[low + 1]; + a[low + 1] = tmp1; + + if (a[low] > a[high]) + { + var tmp = a[low]; + a[low] = a[high]; + a[high] = tmp; + } + + if (a[low + 1] > a[high]) + { + var tmp = a[low + 1]; + a[low + 1] = a[high]; + a[high] = tmp; + } + + if (a[low] > a[low + 1]) + { + var tmp = a[low]; + a[low] = a[low + 1]; + a[low + 1] = tmp; + } + + int begin = low + 1; + int end = high; + float pivot = a[begin]; + + while (true) + { + do + { + begin++; + } + while (a[begin] < pivot); + + do + { + end--; + } + while (a[end] > pivot); + + if (end < begin) + { + break; + } + + var tmp = a[begin]; + a[begin] = a[end]; + a[end] = tmp; + } + + a[low + 1] = a[end]; + a[end] = pivot; + + if (end >= rank) + { + high = end - 1; + } + + if (end <= rank) + { + low = begin; + } + } + } + + /// + /// Evaluates the rank of each entry of the unsorted data array. + /// The rank definition can be specified to be compatible + /// with an existing system. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + public static float[] RanksInplace(float[] data, RankDefinition definition = RankDefinition.Default) + { + var ranks = new float[data.Length]; + var index = new int[data.Length]; + for (int i = 0; i < index.Length; i++) + { + index[i] = i; + } + + if (definition == RankDefinition.First) + { + Sorting.SortAll(data, index); + for (int i = 0; i < ranks.Length; i++) + { + ranks[index[i]] = i + 1; + } + + return ranks; + } + + Sorting.Sort(data, index); + int previousIndex = 0; + for (int i = 1; i < data.Length; i++) + { + if (Math.Abs(data[i] - data[previousIndex]) <= 0d) + { + continue; + } + + if (i == previousIndex + 1) + { + ranks[index[previousIndex]] = i; + } + else + { + RanksTies(ranks, index, previousIndex, i, definition); + } + + previousIndex = i; + } + + RanksTies(ranks, index, previousIndex, data.Length, definition); + return ranks; + } + + static void RanksTies(float[] ranks, int[] index, int a, int b, RankDefinition definition) + { + // TODO: potential for PERF optimization + float rank; + switch (definition) + { + case RankDefinition.Average: + { + rank = (b + a - 1) / 2f + 1; + break; + } + + case RankDefinition.Min: + { + rank = a + 1; + break; + } + + case RankDefinition.Max: + { + rank = b; + break; + } + + default: + throw new NotSupportedException(); + } + + for (int k = a; k < b; k++) + { + ranks[index[k]] = rank; + } + } +} diff --git a/MathNet.Numerics/Statistics/ArrayStatistics.cs b/MathNet.Numerics/Statistics/ArrayStatistics.cs new file mode 100644 index 0000000..0de112d --- /dev/null +++ b/MathNet.Numerics/Statistics/ArrayStatistics.cs @@ -0,0 +1,851 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; + +namespace MathNet.Numerics.Statistics; + +/// +/// Statistics operating on arrays assumed to be unsorted. +/// WARNING: Methods with the Inplace-suffix may modify the data array by reordering its entries. +/// +/// +/// +/// +public static partial class ArrayStatistics +{ + // TODO: Benchmark various options to find out the best approach (-> branch prediction) + // TODO: consider leveraging MKL + + /// + /// Returns the smallest value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double Minimum(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double min = double.PositiveInfinity; + for (int i = 0; i < data.Length; i++) + { + if (data[i] < min || double.IsNaN(data[i])) + { + min = data[i]; + } + } + + return min; + } + + /// + /// Returns the largest value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double Maximum(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double max = double.NegativeInfinity; + for (int i = 0; i < data.Length; i++) + { + if (data[i] > max || double.IsNaN(data[i])) + { + max = data[i]; + } + } + + return max; + } + + /// + /// Returns the smallest absolute value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double MinimumAbsolute(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double min = double.PositiveInfinity; + for (int i = 0; i < data.Length; i++) + { + if (Math.Abs(data[i]) < min || double.IsNaN(data[i])) + { + min = Math.Abs(data[i]); + } + } + + return min; + } + + /// + /// Returns the largest absolute value from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double MaximumAbsolute(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double max = 0.0d; + for (int i = 0; i < data.Length; i++) + { + if (Math.Abs(data[i]) > max || double.IsNaN(data[i])) + { + max = Math.Abs(data[i]); + } + } + + return max; + } + + /// + /// Estimates the arithmetic sample mean from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double Mean(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double mean = 0; + ulong m = 0; + for (int i = 0; i < data.Length; i++) + { + mean += (data[i] - mean) / ++m; + } + + return mean; + } + + /// + /// Evaluates the geometric mean of the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double GeometricMean(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double sum = 0; + for (int i = 0; i < data.Length; i++) + { + sum += Math.Log(data[i]); + } + + return Math.Exp(sum / data.Length); + } + + /// + /// Evaluates the harmonic mean of the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double HarmonicMean(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double sum = 0; + for (int i = 0; i < data.Length; i++) + { + sum += 1.0 / data[i]; + } + + return data.Length / sum; + } + + /// + /// Estimates the unbiased population variance from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double Variance(double[] samples) + { + if (samples.Length <= 1) + { + return double.NaN; + } + + double variance = 0; + double t = samples[0]; + for (int i = 1; i < samples.Length; i++) + { + t += samples[i]; + double diff = ((i + 1) * samples[i]) - t; + variance += (diff * diff) / ((i + 1.0) * i); + } + + return variance / (samples.Length - 1); + } + + /// + /// Evaluates the population variance from the full population provided as unsorted array. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double PopulationVariance(double[] population) + { + if (population.Length == 0) + { + return double.NaN; + } + + double variance = 0; + double t = population[0]; + for (int i = 1; i < population.Length; i++) + { + t += population[i]; + double diff = ((i + 1) * population[i]) - t; + variance += (diff * diff) / ((i + 1.0) * i); + } + + return variance / population.Length; + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double StandardDeviation(double[] samples) + { + return Math.Sqrt(Variance(samples)); + } + + /// + /// Evaluates the population standard deviation from the full population provided as unsorted array. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double PopulationStandardDeviation(double[] population) + { + return Math.Sqrt(PopulationVariance(population)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population variance from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN and NaN for variance if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Tuple MeanVariance(double[] samples) + { + return new Tuple(Mean(samples), Variance(samples)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population standard deviation from the provided samples as unsorted array. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN and NaN for standard deviation if data has less than two entries or if any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static Tuple MeanStandardDeviation(double[] samples) + { + return new Tuple(Mean(samples), StandardDeviation(samples)); + } + + /// + /// Estimates the unbiased population covariance from the provided two sample arrays. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// First sample array. + /// Second sample array. + public static double Covariance(double[] samples1, double[] samples2) + { + if (samples1.Length != samples2.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (samples1.Length <= 1) + { + return double.NaN; + } + + double mean1 = Mean(samples1); + double mean2 = Mean(samples2); + double covariance = 0.0; + for (int i = 0; i < samples1.Length; i++) + { + covariance += (samples1[i] - mean1) * (samples2[i] - mean2); + } + + return covariance / (samples1.Length - 1); + } + + /// + /// Evaluates the population covariance from the full population provided as two arrays. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// First population array. + /// Second population array. + public static double PopulationCovariance(double[] population1, double[] population2) + { + if (population1.Length != population2.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (population1.Length == 0) + { + return double.NaN; + } + + double mean1 = Mean(population1); + double mean2 = Mean(population2); + double covariance = 0.0; + for (int i = 0; i < population1.Length; i++) + { + covariance += (population1[i] - mean1) * (population2[i] - mean2); + } + + return covariance / population1.Length; + } + + /// + /// Estimates the root mean square (RMS) also known as quadratic mean from the unsorted data array. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample array, no sorting is assumed. + public static double RootMeanSquare(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + double mean = 0; + ulong m = 0; + for (int i = 0; i < data.Length; i++) + { + mean += (data[i] * data[i] - mean) / ++m; + } + + return Math.Sqrt(mean); + } + + /// + /// Returns the order statistic (order 1..N) from the unsorted data array. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// One-based order of the statistic, must be between 1 and N (inclusive). + public static double OrderStatisticInplace(double[] data, int order) + { + if (order < 1 || order > data.Length) + { + return double.NaN; + } + + if (order == 1) + { + return Minimum(data); + } + + if (order == data.Length) + { + return Maximum(data); + } + + return SelectInplace(data, order - 1); + } + + /// + /// Estimates the median value from the unsorted data array. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static double MedianInplace(double[] data) + { + var k = data.Length / 2; + return data.Length.IsOdd() + ? SelectInplace(data, k) + : (SelectInplace(data, k - 1) + SelectInplace(data, k)) / 2.0; + } + + /// + /// Estimates the p-Percentile value from the unsorted data array. + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// Percentile selector, between 0 and 100 (inclusive). + public static double PercentileInplace(double[] data, int p) + { + return QuantileInplace(data, p / 100d); + } + + /// + /// Estimates the first quartile value from the unsorted data array. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static double LowerQuartileInplace(double[] data) + { + return QuantileInplace(data, 0.25d); + } + + /// + /// Estimates the third quartile value from the unsorted data array. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static double UpperQuartileInplace(double[] data) + { + return QuantileInplace(data, 0.75d); + } + + /// + /// Estimates the inter-quartile range from the unsorted data array. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static double InterquartileRangeInplace(double[] data) + { + return QuantileInplace(data, 0.75d) - QuantileInplace(data, 0.25d); + } + + /// + /// Estimates {min, lower-quantile, median, upper-quantile, max} from the unsorted data array. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + public static double[] FiveNumberSummaryInplace(double[] data) + { + if (data.Length == 0) + { + return new[] { double.NaN, double.NaN, double.NaN, double.NaN, double.NaN }; + } + + // TODO: Benchmark: is this still faster than sorting the array then using SortedArrayStatistics instead? + return new[] { Minimum(data), QuantileInplace(data, 0.25d), MedianInplace(data), QuantileInplace(data, 0.75d), Maximum(data) }; + } + + /// + /// Estimates the tau-th quantile from the unsorted data array. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// + /// R-8, SciPy-(1/3,1/3): + /// Linear interpolation of the approximate medians for order statistics. + /// When tau < (2/3) / (N + 1/3), use x1. When tau >= (N - 1/3) / (N + 1/3), use xN. + /// + public static double QuantileInplace(double[] data, double tau) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return double.NaN; + } + + double h = (data.Length + 1d / 3d) * tau + 1d / 3d; + var hf = (int)h; + + if (hf <= 0 || tau == 0d) + { + return Minimum(data); + } + + if (hf >= data.Length || tau == 1d) + { + return Maximum(data); + } + + var a = SelectInplace(data, hf - 1); + var b = SelectInplace(data, hf); + return a + (h - hf) * (b - a); + } + + /// + /// Estimates the tau-th quantile from the unsorted data array. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified + /// by 4 parameters a, b, c and d, consistent with Mathematica. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// Quantile selector, between 0.0 and 1.0 (inclusive) + /// a-parameter + /// b-parameter + /// c-parameter + /// d-parameter + public static double QuantileCustomInplace(double[] data, double tau, double a, double b, double c, double d) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return double.NaN; + } + + var x = a + (data.Length + b) * tau - 1; + var ip = Math.Truncate(x); + var fp = x - ip; + + if (Math.Abs(fp) < 1e-9) + { + return SelectInplace(data, (int)ip); + } + + var lower = SelectInplace(data, (int)Math.Floor(x)); + var upper = SelectInplace(data, (int)Math.Ceiling(x)); + return lower + (upper - lower) * (c + d * fp); + } + + /// + /// Estimates the tau-th quantile from the unsorted data array. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + /// Sample array, no sorting is assumed. Will be reordered. + /// Quantile selector, between 0.0 and 1.0 (inclusive) + /// Quantile definition, to choose what product/definition it should be consistent with + public static double QuantileCustomInplace(double[] data, double tau, QuantileDefinition definition) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return double.NaN; + } + + if (tau == 0d || data.Length == 1) + { + return Minimum(data); + } + + if (tau == 1d) + { + return Maximum(data); + } + + switch (definition) + { + case QuantileDefinition.R1: + { + double h = data.Length * tau + 0.5d; + return SelectInplace(data, (int)Math.Ceiling(h - 0.5d) - 1); + } + + case QuantileDefinition.R2: + { + double h = data.Length * tau + 0.5d; + return (SelectInplace(data, (int)Math.Ceiling(h - 0.5d) - 1) + SelectInplace(data, (int)(h + 0.5d) - 1)) * 0.5d; + } + + case QuantileDefinition.R3: + { + double h = data.Length * tau; + return SelectInplace(data, (int)Math.Round(h) - 1); + } + + case QuantileDefinition.R4: + { + double h = data.Length * tau; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R5: + { + double h = data.Length * tau + 0.5d; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R6: + { + double h = (data.Length + 1) * tau; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R7: + { + double h = (data.Length - 1) * tau + 1d; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R8: + { + double h = (data.Length + 1 / 3d) * tau + 1 / 3d; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R9: + { + double h = (data.Length + 0.25d) * tau + 0.375d; + var hf = (int)h; + var lower = SelectInplace(data, hf - 1); + var upper = SelectInplace(data, hf); + return lower + (h - hf) * (upper - lower); + } + + default: + throw new NotSupportedException(); + } + } + + static double SelectInplace(double[] workingData, int rank) + { + // Numerical Recipes: select + // http://en.wikipedia.org/wiki/Selection_algorithm + if (rank <= 0) + { + return Minimum(workingData); + } + + if (rank >= workingData.Length - 1) + { + return Maximum(workingData); + } + + double[] a = workingData; + int low = 0; + int high = a.Length - 1; + + while (true) + { + if (high <= low + 1) + { + if (high == low + 1 && a[high] < a[low]) + { + var tmp = a[low]; + a[low] = a[high]; + a[high] = tmp; + } + + return a[rank]; + } + + int middle = (low + high) >> 1; + + var tmp1 = a[middle]; + a[middle] = a[low + 1]; + a[low + 1] = tmp1; + + if (a[low] > a[high]) + { + var tmp = a[low]; + a[low] = a[high]; + a[high] = tmp; + } + + if (a[low + 1] > a[high]) + { + var tmp = a[low + 1]; + a[low + 1] = a[high]; + a[high] = tmp; + } + + if (a[low] > a[low + 1]) + { + var tmp = a[low]; + a[low] = a[low + 1]; + a[low + 1] = tmp; + } + + int begin = low + 1; + int end = high; + double pivot = a[begin]; + + while (true) + { + do + { + begin++; + } + while (a[begin] < pivot); + + do + { + end--; + } + while (a[end] > pivot); + + if (end < begin) + { + break; + } + + var tmp = a[begin]; + a[begin] = a[end]; + a[end] = tmp; + } + + a[low + 1] = a[end]; + a[end] = pivot; + + if (end >= rank) + { + high = end - 1; + } + + if (end <= rank) + { + low = begin; + } + } + } + + /// + /// Evaluates the rank of each entry of the unsorted data array. + /// The rank definition can be specified to be compatible + /// with an existing system. + /// WARNING: Works inplace and can thus causes the data array to be reordered. + /// + public static double[] RanksInplace(double[] data, RankDefinition definition = RankDefinition.Default) + { + var ranks = new double[data.Length]; + var index = new int[data.Length]; + for (int i = 0; i < index.Length; i++) + { + index[i] = i; + } + + if (definition == RankDefinition.First) + { + Sorting.SortAll(data, index); + for (int i = 0; i < ranks.Length; i++) + { + ranks[index[i]] = i + 1; + } + + return ranks; + } + + Sorting.Sort(data, index); + int previousIndex = 0; + for (int i = 1; i < data.Length; i++) + { + if (Math.Abs(data[i] - data[previousIndex]) <= 0d) + { + continue; + } + + if (i == previousIndex + 1) + { + ranks[index[previousIndex]] = i; + } + else + { + RanksTies(ranks, index, previousIndex, i, definition); + } + + previousIndex = i; + } + + RanksTies(ranks, index, previousIndex, data.Length, definition); + return ranks; + } + + static void RanksTies(double[] ranks, int[] index, int a, int b, RankDefinition definition) + { + // TODO: potential for PERF optimization + double rank; + switch (definition) + { + case RankDefinition.Average: + { + rank = (b + a - 1) / 2d + 1; + break; + } + + case RankDefinition.Min: + { + rank = a + 1; + break; + } + + case RankDefinition.Max: + { + rank = b; + break; + } + + default: + throw new NotSupportedException(); + } + + for (int k = a; k < b; k++) + { + ranks[index[k]] = rank; + } + } +} diff --git a/MathNet.Numerics/Statistics/Correlation.cs b/MathNet.Numerics/Statistics/Correlation.cs new file mode 100644 index 0000000..5afc779 --- /dev/null +++ b/MathNet.Numerics/Statistics/Correlation.cs @@ -0,0 +1,365 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.IntegralTransforms; +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Statistics; + +/// +/// A class with correlation measures between two datasets. +/// +public static class Correlation +{ + /// + /// Auto-correlation function (ACF) based on FFT for all possible lags k. + /// + /// Data array to calculate auto correlation for. + /// An array with the ACF as a function of the lags k. + public static double[] Auto(double[] x) + { + return AutoCorrelationFft(x, 0, x.Length - 1); + } + + /// + /// Auto-correlation function (ACF) based on FFT for lags between kMin and kMax. + /// + /// The data array to calculate auto correlation for. + /// Max lag to calculate ACF for must be positive and smaller than x.Length. + /// Min lag to calculate ACF for (0 = no shift with acf=1) must be zero or positive and smaller than x.Length. + /// An array with the ACF as a function of the lags k. + public static double[] Auto(double[] x, int kMax, int kMin = 0) + { + // assert max and min in proper order + var kMax2 = Math.Max(kMax, kMin); + var kMin2 = Math.Min(kMax, kMin); + + return AutoCorrelationFft(x, kMin2, kMax2); + } + + /// + /// Auto-correlation function based on FFT for lags k. + /// + /// The data array to calculate auto correlation for. + /// Array with lags to calculate ACF for. + /// An array with the ACF as a function of the lags k. + public static double[] Auto(double[] x, int[] k) + { + if (k == null) + { + throw new ArgumentNullException(nameof(k)); + } + + if (k.Length < 1) + { + throw new ArgumentException("k"); + } + + var kMin = k.Min(); + var kMax = k.Max(); + + // get acf between full range + var acf = AutoCorrelationFft(x, kMin, kMax); + + // map output by indexing + var result = new double[k.Length]; + for (int i = 0; i < result.Length; i++) + { + result[i] = acf[k[i] - kMin]; + } + + return result; + } + + /// + /// The internal method for calculating the auto-correlation. + /// + /// The data array to calculate auto-correlation for + /// Min lag to calculate ACF for (0 = no shift with acf=1) must be zero or positive and smaller than x.Length + /// Max lag (EXCLUSIVE) to calculate ACF for must be positive and smaller than x.Length + /// An array with the ACF as a function of the lags k. + private static double[] AutoCorrelationFft(double[] x, int kLow, int kHigh) + { + if (x == null) + throw new ArgumentNullException(nameof(x)); + + int N = x.Length; // Sample size + + if (kLow < 0 || kLow >= N) + throw new ArgumentOutOfRangeException(nameof(kLow), "kMin must be zero or positive and smaller than x.Length"); + if (kHigh < 0 || kHigh >= N) + throw new ArgumentOutOfRangeException(nameof(kHigh), "kMax must be positive and smaller than x.Length"); + if (N < 1) + return new double[0]; + + int nFFT = Euclid.CeilingToPowerOfTwo(N) * 2; + + Complex[] xFFT = new Complex[nFFT]; + Complex[] xFFT2 = new Complex[nFFT]; + + double xDash = ArrayStatistics.Mean(x); + + // copy values in range and substract mean - all the remaining parts are padded with zero. + for (int i = 0; i < x.Length; i++) + { + xFFT[i] = new Complex(x[i] - xDash, 0.0); // copy values in range and substract mean + } + + Fourier.Forward(xFFT, FourierOptions.Matlab); + + // maybe a Vector implementation here would be faster + for (int i = 0; i < xFFT.Length; i++) + { + xFFT2[i] = Complex.Multiply(xFFT[i], Complex.Conjugate(xFFT[i])); + } + + Fourier.Inverse(xFFT2, FourierOptions.Matlab); + + double dc = xFFT2[0].Real; + + double[] result = new double[kHigh - kLow + 1]; + + // normalize such that acf[0] would be 1.0 + for (int i = 0; i < (kHigh - kLow + 1); i++) + { + result[i] = xFFT2[kLow + i].Real / dc; + } + + return result; + } + + /// + /// Computes the Pearson Product-Moment Correlation coefficient. + /// + /// Sample data A. + /// Sample data B. + /// The Pearson product-moment correlation coefficient. + public static double Pearson(IEnumerable dataA, IEnumerable dataB) + { + int n = 0; + double r = 0.0; + + double meanA = 0; + double meanB = 0; + double varA = 0; + double varB = 0; + + // WARNING: do not try to "optimize" by summing up products instead of using differences. + // It would indeed be faster, but numerically much less robust if large mean + low variance. + + using (IEnumerator ieA = dataA.GetEnumerator()) + using (IEnumerator ieB = dataB.GetEnumerator()) + { + while (ieA.MoveNext()) + { + if (!ieB.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(dataB), Resources.ArgumentArraysSameLength); + } + + double currentA = ieA.Current; + double currentB = ieB.Current; + + double deltaA = currentA - meanA; + double scaleDeltaA = deltaA / ++n; + + double deltaB = currentB - meanB; + double scaleDeltaB = deltaB / n; + + meanA += scaleDeltaA; + meanB += scaleDeltaB; + + varA += scaleDeltaA * deltaA * (n - 1); + varB += scaleDeltaB * deltaB * (n - 1); + r += (deltaA * deltaB * (n - 1)) / n; + } + + if (ieB.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(dataA), Resources.ArgumentArraysSameLength); + } + } + + return r / Math.Sqrt(varA * varB); + } + + /// + /// Computes the Weighted Pearson Product-Moment Correlation coefficient. + /// + /// Sample data A. + /// Sample data B. + /// Corresponding weights of data. + /// The Weighted Pearson product-moment correlation coefficient. + public static double WeightedPearson(IEnumerable dataA, IEnumerable dataB, IEnumerable weights) + { + int n = 0; + + double meanA = 0; + double meanB = 0; + double varA = 0; + double varB = 0; + double sumWeight = 0; + + double covariance = 0; + + using (IEnumerator ieA = dataA.GetEnumerator()) + using (IEnumerator ieB = dataB.GetEnumerator()) + using (IEnumerator ieW = weights.GetEnumerator()) + { + while (ieA.MoveNext()) + { + if (!ieB.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(dataB), Resources.ArgumentArraysSameLength); + } + + if (!ieW.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(weights), Resources.ArgumentArraysSameLength); + } + + ++n; + + double xi = ieA.Current; + double yi = ieB.Current; + double wi = ieW.Current; + + double temp = sumWeight + wi; + + double deltaX = xi - meanA; + double rX = deltaX * wi / temp; + meanA += rX; + varA += sumWeight * deltaX * rX; + + double deltaY = yi - meanB; + double rY = deltaY * wi / temp; + meanB += rY; + varB += sumWeight * deltaY * rY; + + covariance += deltaX * deltaY * wi * (sumWeight / temp); + sumWeight = temp; + } + + if (ieB.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(dataB), Resources.ArgumentArraysSameLength); + } + + if (ieW.MoveNext()) + { + throw new ArgumentOutOfRangeException(nameof(weights), Resources.ArgumentArraysSameLength); + } + } + + return covariance / Math.Sqrt(varA * varB); + } + + /// + /// Computes the Pearson Product-Moment Correlation matrix. + /// + /// Array of sample data vectors. + /// The Pearson product-moment correlation matrix. + public static Matrix PearsonMatrix(params double[][] vectors) + { + var m = Matrix.Build.DenseIdentity(vectors.Length); + for (int i = 0; i < vectors.Length; i++) + { + for (int j = i + 1; j < vectors.Length; j++) + { + var c = Pearson(vectors[i], vectors[j]); + m.At(i, j, c); + m.At(j, i, c); + } + } + + return m; + } + + /// + /// Computes the Pearson Product-Moment Correlation matrix. + /// + /// Enumerable of sample data vectors. + /// The Pearson product-moment correlation matrix. + public static Matrix PearsonMatrix(IEnumerable vectors) + { + return PearsonMatrix(vectors as double[][] ?? vectors.ToArray()); + } + + /// + /// Computes the Spearman Ranked Correlation coefficient. + /// + /// Sample data series A. + /// Sample data series B. + /// The Spearman ranked correlation coefficient. + public static double Spearman(IEnumerable dataA, IEnumerable dataB) + { + return Pearson(Rank(dataA), Rank(dataB)); + } + + /// + /// Computes the Spearman Ranked Correlation matrix. + /// + /// Array of sample data vectors. + /// The Spearman ranked correlation matrix. + public static Matrix SpearmanMatrix(params double[][] vectors) + { + return PearsonMatrix(vectors.Select(Rank).ToArray()); + } + + /// + /// Computes the Spearman Ranked Correlation matrix. + /// + /// Enumerable of sample data vectors. + /// The Spearman ranked correlation matrix. + public static Matrix SpearmanMatrix(IEnumerable vectors) + { + return PearsonMatrix(vectors.Select(Rank).ToArray()); + } + + static double[] Rank(IEnumerable series) + { + if (series == null) + { + return new double[0]; + } + + // WARNING: do not try to cast series to an array and use it directly, + // as we need to sort it (inplace operation) + + var data = series.ToArray(); + return ArrayStatistics.RanksInplace(data, RankDefinition.Average); + } +} diff --git a/MathNet.Numerics/Statistics/DescriptiveStatistics.cs b/MathNet.Numerics/Statistics/DescriptiveStatistics.cs new file mode 100644 index 0000000..2db04c2 --- /dev/null +++ b/MathNet.Numerics/Statistics/DescriptiveStatistics.cs @@ -0,0 +1,396 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.Statistics; + +/// +/// Computes the basic statistics of data set. The class meets the +/// NIST standard of accuracy for mean, variance, and standard deviation +/// (the only statistics they provide exact values for) and exceeds them +/// in increased accuracy mode. +/// Recommendation: consider to use RunningStatistics instead. +/// +/// +/// This type declares a DataContract for out of the box ephemeral serialization +/// with engines like DataContractSerializer, Protocol Buffers and FsPickler, +/// but does not guarantee any compatibility between versions. +/// It is not recommended to rely on this mechanism for durable persistence. +/// +[DataContract(Namespace = "urn:MathNet/Numerics")] +public class DescriptiveStatistics +{ + /// + /// Initializes a new instance of the class. + /// + /// The sample data. + /// + /// If set to true, increased accuracy mode used. + /// Increased accuracy mode uses types for internal calculations. + /// + /// + /// Don't use increased accuracy for data sets containing large values (in absolute value). + /// This may cause the calculations to overflow. + /// + public DescriptiveStatistics(IEnumerable data, bool increasedAccuracy = false) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (increasedAccuracy) + { + ComputeDecimal(data); + } + else + { + Compute(data); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The sample data. + /// + /// If set to true, increased accuracy mode used. + /// Increased accuracy mode uses types for internal calculations. + /// + /// + /// Don't use increased accuracy for data sets containing large values (in absolute value). + /// This may cause the calculations to overflow. + /// + public DescriptiveStatistics(IEnumerable data, bool increasedAccuracy = false) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + if (increasedAccuracy) + { + ComputeDecimal(data); + } + else + { + Compute(data); + } + } + + /// + /// Gets the size of the sample. + /// + /// The size of the sample. + [DataMember(Order = 1)] + public long Count { get; private set; } + + /// + /// Gets the sample mean. + /// + /// The sample mean. + [DataMember(Order = 2)] + public double Mean { get; private set; } + + /// + /// Gets the unbiased population variance estimator (on a dataset of size N will use an N-1 normalizer). + /// + /// The sample variance. + [DataMember(Order = 3)] + public double Variance { get; private set; } + + /// + /// Gets the unbiased population standard deviation (on a dataset of size N will use an N-1 normalizer). + /// + /// The sample standard deviation. + [DataMember(Order = 4)] + public double StandardDeviation { get; private set; } + + /// + /// Gets the sample skewness. + /// + /// The sample skewness. + /// Returns zero if is less than three. + [DataMember(Order = 5)] + public double Skewness { get; private set; } + + /// + /// Gets the sample kurtosis. + /// + /// The sample kurtosis. + /// Returns zero if is less than four. + [DataMember(Order = 6)] + public double Kurtosis { get; private set; } + + /// + /// Gets the maximum sample value. + /// + /// The maximum sample value. + [DataMember(Order = 7)] + public double Maximum { get; private set; } + + /// + /// Gets the minimum sample value. + /// + /// The minimum sample value. + [DataMember(Order = 8)] + public double Minimum { get; private set; } + + /// + /// Computes descriptive statistics from a stream of data values. + /// + /// A sequence of datapoints. + void Compute(IEnumerable data) + { + double mean = 0; + double variance = 0; + double skewness = 0; + double kurtosis = 0; + double minimum = double.PositiveInfinity; + double maximum = double.NegativeInfinity; + long n = 0; + + foreach (var xi in data) + { + double delta = xi - mean; + double scaleDelta = delta / ++n; + double scaleDeltaSqr = scaleDelta * scaleDelta; + double tmpDelta = delta * (n - 1); + + mean += scaleDelta; + + kurtosis += tmpDelta * scaleDelta * scaleDeltaSqr * (n * n - 3 * n + 3) + + 6 * scaleDeltaSqr * variance - 4 * scaleDelta * skewness; + + skewness += tmpDelta * scaleDeltaSqr * (n - 2) - 3 * scaleDelta * variance; + variance += tmpDelta * scaleDelta; + + if (minimum > xi) + { + minimum = xi; + } + + if (maximum < xi) + { + maximum = xi; + } + } + + SetStatistics(mean, variance, skewness, kurtosis, minimum, maximum, n); + } + + /// + /// Computes descriptive statistics from a stream of nullable data values. + /// + /// A sequence of datapoints. + void Compute(IEnumerable data) + { + double mean = 0; + double variance = 0; + double skewness = 0; + double kurtosis = 0; + double minimum = double.PositiveInfinity; + double maximum = double.NegativeInfinity; + long n = 0; + + foreach (var xi in data) + { + if (xi.HasValue) + { + double delta = xi.Value - mean; + double scaleDelta = delta / ++n; + double scaleDeltaSqr = scaleDelta * scaleDelta; + double tmpDelta = delta * (n - 1); + + mean += scaleDelta; + + kurtosis += tmpDelta * scaleDelta * scaleDeltaSqr * (n * n - 3 * n + 3) + + 6 * scaleDeltaSqr * variance - 4 * scaleDelta * skewness; + + skewness += tmpDelta * scaleDeltaSqr * (n - 2) - 3 * scaleDelta * variance; + variance += tmpDelta * scaleDelta; + + if (minimum > xi) + { + minimum = xi.Value; + } + + if (maximum < xi) + { + maximum = xi.Value; + } + } + } + + SetStatistics(mean, variance, skewness, kurtosis, minimum, maximum, n); + } + + /// + /// Computes descriptive statistics from a stream of data values. + /// + /// A sequence of datapoints. + void ComputeDecimal(IEnumerable data) + { + decimal mean = 0; + decimal variance = 0; + decimal skewness = 0; + decimal kurtosis = 0; + decimal minimum = decimal.MaxValue; + decimal maximum = decimal.MinValue; + long n = 0; + + foreach (double x in data) + { + decimal xi = (decimal)x; + decimal delta = xi - mean; + decimal scaleDelta = delta / ++n; + decimal scaleDelta2 = scaleDelta * scaleDelta; + decimal tmpDelta = delta * (n - 1); + + mean += scaleDelta; + + kurtosis += tmpDelta * scaleDelta * scaleDelta2 * (n * n - 3 * n + 3) + + 6 * scaleDelta2 * variance - 4 * scaleDelta * skewness; + + skewness += tmpDelta * scaleDelta2 * (n - 2) - 3 * scaleDelta * variance; + variance += tmpDelta * scaleDelta; + + if (minimum > xi) + { + minimum = xi; + } + + if (maximum < xi) + { + maximum = xi; + } + } + + SetStatistics((double)mean, (double)variance, (double)skewness, (double)kurtosis, (double)minimum, (double)maximum, n); + } + + /// + /// Computes descriptive statistics from a stream of nullable data values. + /// + /// A sequence of datapoints. + void ComputeDecimal(IEnumerable data) + { + decimal mean = 0; + decimal variance = 0; + decimal skewness = 0; + decimal kurtosis = 0; + decimal minimum = decimal.MaxValue; + decimal maximum = decimal.MinValue; + long n = 0; + + foreach (double? x in data) + { + if (x.HasValue) + { + decimal xi = (decimal)x.Value; + decimal delta = xi - mean; + decimal scaleDelta = delta / ++n; + decimal scaleDeltaSQR = scaleDelta * scaleDelta; + decimal tmpDelta = delta * (n - 1); + + mean += scaleDelta; + + kurtosis += tmpDelta * scaleDelta * scaleDeltaSQR * (n * n - 3 * n + 3) + + 6 * scaleDeltaSQR * variance - 4 * scaleDelta * skewness; + + skewness += tmpDelta * scaleDeltaSQR * (n - 2) - 3 * scaleDelta * variance; + variance += tmpDelta * scaleDelta; + + if (minimum > xi) + { + minimum = xi; + } + + if (maximum < xi) + { + maximum = xi; + } + } + } + + SetStatistics((double)mean, (double)variance, (double)skewness, (double)kurtosis, (double)minimum, (double)maximum, n); + } + + /// + /// Internal use. Method use for setting the statistics. + /// + /// For setting Mean. + /// For setting Variance. + /// For setting Skewness. + /// For setting Kurtosis. + /// For setting Minimum. + /// For setting Maximum. + /// For setting Count. + void SetStatistics(double mean, double variance, double skewness, double kurtosis, double minimum, double maximum, long n) + { + Mean = mean; + Count = n; + + Minimum = double.NaN; + Maximum = double.NaN; + Variance = double.NaN; + StandardDeviation = double.NaN; + Skewness = double.NaN; + Kurtosis = double.NaN; + + if (n > 0) + { + Minimum = minimum; + Maximum = maximum; + + if (n > 1) + { + Variance = variance / (n - 1); + StandardDeviation = Math.Sqrt(Variance); + } + + if (Variance != 0) + { + if (n > 2) + { + Skewness = (double)n / ((n - 1) * (n - 2)) * (skewness / (Variance * StandardDeviation)); + } + + if (n > 3) + { + Kurtosis = ((double)n * n - 1) / ((n - 2) * (n - 3)) + * (n * kurtosis / (variance * variance) - 3 + 6.0 / (n + 1)); + } + } + } + } +} diff --git a/MathNet.Numerics/Statistics/Histogram.cs b/MathNet.Numerics/Statistics/Histogram.cs new file mode 100644 index 0000000..507af10 --- /dev/null +++ b/MathNet.Numerics/Statistics/Histogram.cs @@ -0,0 +1,519 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Text; + +namespace MathNet.Numerics.Statistics; + +/// +/// A consists of a series of s, +/// each representing a region limited by a lower bound (exclusive) and an upper bound (inclusive). +/// +/// +/// This type declares a DataContract for out of the box ephemeral serialization +/// with engines like DataContractSerializer, Protocol Buffers and FsPickler, +/// but does not guarantee any compatibility between versions. +/// It is not recommended to rely on this mechanism for durable persistence. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics")] +public class Bucket : IComparable +#if !NETSTANDARD1_3 + , ICloneable +#endif +{ + /// + /// This IComparer performs comparisons between a point and a bucket. + /// + private sealed class PointComparer : IComparer + { + /// + /// Compares a point and a bucket. The point will be encapsulated in a bucket with width 0. + /// + /// The first bucket to compare. + /// The second bucket to compare. + /// -1 when the point is less than this bucket, 0 when it is in this bucket and 1 otherwise. + public int Compare(Bucket bkt1, Bucket bkt2) + { + return bkt2.IsSinglePoint + ? -bkt1.Contains(bkt2.UpperBound) + : -bkt2.Contains(bkt1.UpperBound); + } + } + + private static readonly PointComparer Comparer = new PointComparer(); + + /// + /// Lower Bound of the Bucket. + /// + [DataMember(Order = 1)] + public double LowerBound { get; set; } + + /// + /// Upper Bound of the Bucket. + /// + [DataMember(Order = 2)] + public double UpperBound { get; set; } + + /// + /// The number of datapoints in the bucket. + /// + /// + /// Value may be NaN if this was constructed as a argument. + /// + [DataMember(Order = 3)] + public double Count { get; set; } + + /// + /// Initializes a new instance of the Bucket class. + /// + public Bucket(double lowerBound, double upperBound, double count = 0.0) + { + if (lowerBound > upperBound) + { + throw new ArgumentException(Resources.ArgumentUpperBoundMustBeLargerThanOrEqualToLowerBound); + } + + if (count < 0.0) + { + throw new ArgumentOutOfRangeException(nameof(count), Resources.ArgumentMustBePositive); + } + + LowerBound = lowerBound; + UpperBound = upperBound; + Count = count; + } + + /// + /// Constructs a Bucket that can be used as an argument for a + /// like when performing a Binary search. + /// + /// Value to look for + public Bucket(double targetValue) + { + LowerBound = targetValue; + UpperBound = targetValue; + Count = double.NaN; + } + + /// + /// Creates a copy of the Bucket with the lowerbound, upperbound and counts exactly equal. + /// + /// A cloned Bucket object. + public object Clone() + { + return new Bucket(LowerBound, UpperBound, Count); + } + + /// + /// Width of the Bucket. + /// + public double Width + { + get { return UpperBound - LowerBound; } + } + + /// + /// True if this is a single point argument for + /// when performing a Binary search. + /// + private bool IsSinglePoint + { + get { return double.IsNaN(Count); } + } + + /// + /// Default comparer. + /// + public static IComparer DefaultPointComparer + { + get { return Comparer; } + } + + /// + /// This method check whether a point is contained within this bucket. + /// + /// The point to check. + /// + /// 0 if the point falls within the bucket boundaries; + /// -1 if the point is smaller than the bucket, + /// +1 if the point is larger than the bucket. + public int Contains(double x) + { + if (LowerBound < x) + { + if (UpperBound >= x) + { + return 0; + } + + return 1; + } + + return -1; + } + + /// + /// Comparison of two disjoint buckets. The buckets cannot be overlapping. + /// + /// + /// 0 if UpperBound and LowerBound are bit-for-bit equal + /// 1 if This bucket is lower that the compared bucket + /// -1 otherwise + /// + public int CompareTo(Bucket bucket) + { + if (UpperBound > bucket.LowerBound && LowerBound < bucket.LowerBound) + { + throw new ArgumentException(Resources.PartialOrderException); + } + + if (UpperBound.Equals(bucket.UpperBound) + && LowerBound.Equals(bucket.LowerBound)) + { + return 0; + } + + if (bucket.UpperBound <= LowerBound) + { + return 1; + } + + return -1; + } + + /// + /// Checks whether two Buckets are equal. + /// + /// + /// UpperBound and LowerBound are compared bit-for-bit, but This method tolerates a + /// difference in Count given by . + /// + public override bool Equals(object obj) + { + if (!(obj is Bucket)) + { + return false; + } + + var bucket = (Bucket)obj; + return LowerBound.Equals(bucket.LowerBound) + && UpperBound.Equals(bucket.UpperBound) + && Count.AlmostEqual(bucket.Count); + } + + /// + /// Provides a hash code for this bucket. + /// + public override int GetHashCode() + { + return LowerBound.GetHashCode() ^ UpperBound.GetHashCode() ^ Count.GetHashCode(); + } + + /// + /// Formats a human-readable string for this bucket. + /// + public override string ToString() + { + return "(" + LowerBound + ";" + UpperBound + "] = " + Count; + } +} + +/// +/// A class which computes histograms of data. +/// +[Serializable] +[DataContract(Namespace = "urn:MathNet/Numerics")] +public class Histogram +{ + /// + /// Contains all the Buckets of the Histogram. + /// + [DataMember(Order = 1)] + private readonly List _buckets; + + /// + /// Indicates whether the elements of buckets are currently sorted. + /// + [DataMember(Order = 2)] + private bool _areBucketsSorted; + + /// + /// Initializes a new instance of the Histogram class. + /// + public Histogram() + { + _buckets = new List(); + _areBucketsSorted = true; + } + + /// + /// Constructs a Histogram with a specific number of equally sized buckets. The upper and lower bound of the histogram + /// will be set to the smallest and largest datapoint. + /// + /// The data sequence to build a histogram on. + /// The number of buckets to use. + public Histogram(IEnumerable data, int nbuckets) + : this() + { + if (nbuckets < 1) + { + throw new ArgumentOutOfRangeException(nameof(data), "The number of bins in a histogram should be at least 1."); + } + + double lower = data.Minimum(); + double upper = data.Maximum(); + double width = (upper - lower) / nbuckets; + + if (double.IsNaN(width)) + { + throw new ArgumentException("Data must contain at least one entry.", nameof(data)); + } + + // Add buckets for each bin; the smallest bucket's lowerbound must be slightly smaller + // than the minimal element. + double fNextLowerBound = lower + width; + AddBucket(new Bucket(lower.Decrement(), fNextLowerBound)); + for (int n = 1; n < nbuckets; n++) + { + AddBucket(new Bucket( + fNextLowerBound, + fNextLowerBound = (lower + (n + 1) * width))); + } + + AddData(data); + } + + /// + /// Constructs a Histogram with a specific number of equally sized buckets. + /// + /// The data sequence to build a histogram on. + /// The number of buckets to use. + /// The histogram lower bound. + /// The histogram upper bound. + public Histogram(IEnumerable data, int nbuckets, double lower, double upper) + : this() + { + if (lower > upper) + { + throw new ArgumentOutOfRangeException(nameof(upper), "The histogram lower bound must be smaller than the upper bound."); + } + + if (nbuckets < 1) + { + throw new ArgumentOutOfRangeException(nameof(nbuckets), "The number of bins in a histogram should be at least 1."); + } + + double width = (upper - lower) / nbuckets; + + // Add buckets for each bin. + for (int n = 0; n < nbuckets; n++) + { + AddBucket(new Bucket(lower + n * width, lower + (n + 1) * width)); + } + + AddData(data); + } + + /// + /// Add one data point to the histogram. If the datapoint falls outside the range of the histogram, + /// the lowerbound or upperbound will automatically adapt. + /// + /// The datapoint which we want to add. + public void AddData(double d) + { + // Sort if needed. + LazySort(); + + if (d <= LowerBound) + { + // Make the lower bound just slightly smaller than the datapoint so it is contained in this bucket. + _buckets[0].LowerBound = d.Decrement(); + _buckets[0].Count++; + } + else if (d > UpperBound) + { + _buckets[BucketCount - 1].UpperBound = d; + _buckets[BucketCount - 1].Count++; + } + else + { + _buckets[GetBucketIndexOf(d)].Count++; + } + } + + /// + /// Add a sequence of data point to the histogram. If the datapoint falls outside the range of the histogram, + /// the lowerbound or upperbound will automatically adapt. + /// + /// The sequence of datapoints which we want to add. + public void AddData(IEnumerable data) + { + foreach (double d in data) + { + AddData(d); + } + } + + /// + /// Adds a Bucket to the Histogram. + /// + public void AddBucket(Bucket bucket) + { + _buckets.Add(bucket); + _areBucketsSorted = false; + } + + /// + /// Sort the buckets if needed. + /// + private void LazySort() + { + if (!_areBucketsSorted) + { + _buckets.Sort(); + _areBucketsSorted = true; + } + } + + /// + /// Returns the Bucket that contains the value v. + /// + /// The point to search the bucket for. + /// A copy of the bucket containing point . + public Bucket GetBucketOf(double v) + { + return (Bucket)_buckets[GetBucketIndexOf(v)].Clone(); + } + + /// + /// Returns the index in the Histogram of the Bucket + /// that contains the value v. + /// + /// The point to search the bucket index for. + /// The index of the bucket containing the point. + public int GetBucketIndexOf(double v) + { + // Sort if needed. + LazySort(); + + // Binary search for the bucket index. + int index = _buckets.BinarySearch(new Bucket(v), Bucket.DefaultPointComparer); + + if (index < 0) + { + throw new ArgumentException(Resources.ArgumentHistogramContainsNot); + } + + return index; + } + + /// + /// Returns the lower bound of the histogram. + /// + public double LowerBound + { + get + { + LazySort(); + return _buckets[0].LowerBound; + } + } + + /// + /// Returns the upper bound of the histogram. + /// + public double UpperBound + { + get + { + LazySort(); + return _buckets[_buckets.Count - 1].UpperBound; + } + } + + /// + /// Gets the n'th bucket. + /// + /// The index of the bucket to be returned. + /// A copy of the n'th bucket. + public Bucket this[int n] + { + get + { + LazySort(); + return (Bucket)_buckets[n].Clone(); + } + } + + /// + /// Gets the number of buckets. + /// + public int BucketCount + { + get { return _buckets.Count; } + } + + /// + /// Gets the total number of datapoints in the histogram. + /// + public double DataCount + { + get + { + double totalCount = 0; + + for (int i = 0; i < BucketCount; i++) + { + totalCount += this[i].Count; + } + + return totalCount; + } + } + + /// + /// Prints the buckets contained in the . + /// + public override string ToString() + { + var sb = new StringBuilder(); + foreach (Bucket b in _buckets) + { + sb.Append(b); + } + + return sb.ToString(); + } +} diff --git a/MathNet.Numerics/Statistics/KernelDensity.cs b/MathNet.Numerics/Statistics/KernelDensity.cs new file mode 100644 index 0000000..f1f8e3f --- /dev/null +++ b/MathNet.Numerics/Statistics/KernelDensity.cs @@ -0,0 +1,133 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2018 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.Threading; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Statistics; + +/// +/// Kernel density estimation (KDE). +/// +public static class KernelDensity +{ + /// + /// Estimate the probability density function of a random variable. + /// + /// + /// The routine assumes that the provided kernel is well defined, i.e. a real non-negative function that integrates to 1. + /// + public static double Estimate(double x, double bandwidth, IList samples, Func kernel) + { + if (bandwidth <= 0) + { + throw new ArgumentException("The bandwidth must be a positive number!"); + } + + var n = samples.Count; + var estimate = CommonParallel.Aggregate(0, n, + i => kernel((x - samples[i]) / bandwidth), + (a, b) => a + b, + 0d) / (n * bandwidth); + + return estimate; + } + + /// + /// Estimate the probability density function of a random variable with a Gaussian kernel. + /// + public static double EstimateGaussian(double x, double bandwidth, IList samples) + { + return Estimate(x, bandwidth, samples, GaussianKernel); + } + + /// + /// Estimate the probability density function of a random variable with an Epanechnikov kernel. + /// The Epanechnikov kernel is optimal in a mean square error sense. + /// + public static double EstimateEpanechnikov(double x, double bandwidth, IList samples) + { + return Estimate(x, bandwidth, samples, EpanechnikovKernel); + } + + /// + /// Estimate the probability density function of a random variable with a uniform kernel. + /// + public static double EstimateUniform(double x, double bandwidth, IList samples) + { + return Estimate(x, bandwidth, samples, UniformKernel); + } + + /// + /// Estimate the probability density function of a random variable with a triangular kernel. + /// + public static double EstimateTriangular(double x, double bandwidth, IList samples) + { + return Estimate(x, bandwidth, samples, TriangularKernel); + } + + /// + /// A Gaussian kernel (PDF of Normal distribution with mean 0 and variance 1). + /// This kernel is the default. + /// + public static double GaussianKernel(double x) + { + return Normal.PDF(0.0, 1.0, x); + } + + /// + /// Epanechnikov Kernel: + /// x => Math.Abs(x) <= 1.0 ? 3.0/4.0(1.0-x^2) : 0.0 + /// + public static double EpanechnikovKernel(double x) + { + return Math.Abs(x) <= 1.0 ? 0.75 * (1 - x * x) : 0.0; + } + + /// + /// Uniform Kernel: + /// x => Math.Abs(x) <= 1.0 ? 1.0/2.0 : 0.0 + /// + public static double UniformKernel(double x) + { + return ContinuousUniform.PDF(-1.0, 1.0, x); + } + + /// + /// Triangular Kernel: + /// x => Math.Abs(x) <= 1.0 ? (1.0-Math.Abs(x)) : 0.0 + /// + public static double TriangularKernel(double x) + { + return Triangular.PDF(-1.0, 1.0, 0.0, x); + } +} diff --git a/MathNet.Numerics/Statistics/MCMC/HybridMC.cs b/MathNet.Numerics/Statistics/MCMC/HybridMC.cs new file mode 100644 index 0000000..91e9191 --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/HybridMC.cs @@ -0,0 +1,292 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.Random; + +using System; +using System.Linq; + +namespace MathNet.Numerics.Statistics.Mcmc; + +/// +/// A hybrid Monte Carlo sampler for multivariate distributions. +/// +public class HybridMC : HybridMCGeneric +{ + /// + /// Number of parameters in the density function. + /// + private readonly int _length; + + /// + /// Distribution to sample momentum from. + /// + private Normal _pDistribution; + + /// + /// Standard deviations used in the sampling of different components of the + /// momentum. + /// + private double[] _mpSdv; + + /// + /// Gets or sets the standard deviations used in the sampling of different components of the + /// momentum. + /// + /// When the length of pSdv is not the same as Length. + public double[] MomentumStdDev + { + get { return (double[])_mpSdv.Clone(); } + set + { + CheckVariance(value); + _mpSdv = (double[])value.Clone(); + } + } + + /// + /// Constructs a new Hybrid Monte Carlo sampler for a multivariate probability distribution. + /// The components of the momentum will be sampled from a normal distribution with standard deviation + /// 1 using the default random + /// number generator. A three point estimation will be used for differentiation. + /// This constructor will set the burn interval. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// Number frog leap simulation steps. + /// Size of the frog leap simulation steps. + /// The number of iterations in between returning samples. + /// When the number of burnInterval iteration is negative. + public HybridMC(double[] x0, DensityLn pdfLnP, int frogLeapSteps, double stepSize, int burnInterval = 0) + : this(x0, pdfLnP, frogLeapSteps, stepSize, burnInterval, new double[x0.Length], SystemRandomSource.Default, Grad) + { + for (int i = 0; i < _length; i++) + { + _mpSdv[i] = 1; + } + } + + /// + /// Constructs a new Hybrid Monte Carlo sampler for a multivariate probability distribution. + /// The components of the momentum will be sampled from a normal distribution with standard deviation + /// specified by pSdv using the default random + /// number generator. A three point estimation will be used for differentiation. + /// This constructor will set the burn interval. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// Number frog leap simulation steps. + /// Size of the frog leap simulation steps. + /// The number of iterations in between returning samples. + /// The standard deviations of the normal distributions that are used to sample + /// the components of the momentum. + /// When the number of burnInterval iteration is negative. + public HybridMC(double[] x0, DensityLn pdfLnP, int frogLeapSteps, double stepSize, int burnInterval, double[] pSdv) + : this(x0, pdfLnP, frogLeapSteps, stepSize, burnInterval, pSdv, SystemRandomSource.Default) + { + } + + /// + /// Constructs a new Hybrid Monte Carlo sampler for a multivariate probability distribution. + /// The components of the momentum will be sampled from a normal distribution with standard deviation + /// specified by pSdv using the a random number generator provided by the user. + /// A three point estimation will be used for differentiation. + /// This constructor will set the burn interval. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// Number frog leap simulation steps. + /// Size of the frog leap simulation steps. + /// The number of iterations in between returning samples. + /// The standard deviations of the normal distributions that are used to sample + /// the components of the momentum. + /// Random number generator used for sampling the momentum. + /// When the number of burnInterval iteration is negative. + public HybridMC(double[] x0, DensityLn pdfLnP, int frogLeapSteps, double stepSize, int burnInterval, double[] pSdv, System.Random randomSource) + : this(x0, pdfLnP, frogLeapSteps, stepSize, burnInterval, pSdv, randomSource, Grad) + { + } + + /// + /// Constructs a new Hybrid Monte Carlo sampler for a multivariate probability distribution. + /// The components of the momentum will be sampled from a normal distribution with standard deviations + /// given by pSdv. This constructor will set the burn interval, the method used for + /// numerical differentiation and the random number generator. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// Number frog leap simulation steps. + /// Size of the frog leap simulation steps. + /// The number of iterations in between returning samples. + /// The standard deviations of the normal distributions that are used to sample + /// the components of the momentum. + /// Random number generator used for sampling the momentum. + /// The method used for numerical differentiation. + /// When the number of burnInterval iteration is negative. + /// When the length of pSdv is not the same as x0. + public HybridMC(double[] x0, DensityLn pdfLnP, int frogLeapSteps, double stepSize, int burnInterval, double[] pSdv, System.Random randomSource, DiffMethod diff) + : base(x0, pdfLnP, frogLeapSteps, stepSize, burnInterval, randomSource, diff) + { + _length = x0.Length; + MomentumStdDev = pSdv; + + Initialize(x0); + + Burn(BurnInterval); + } + + /// + /// Initialize parameters. + /// + /// The current location of the sampler. + private void Initialize(double[] x0) + { + Current = (double[])x0.Clone(); + _pDistribution = new Normal(0.0, 1.0, RandomSource); + } + + /// + /// Checking that the location and the momentum are of the same dimension and that each component is positive. + /// + /// The standard deviations used for sampling the momentum. + /// When the length of pSdv is not the same as Length or if any + /// component is negative. + /// When pSdv is null. + private void CheckVariance(double[] pSdv) + { + if (pSdv == null) + { + throw new ArgumentNullException(nameof(pSdv), "Standard deviation cannot be null."); + } + + if (pSdv.Length != _length) + { + throw new ArgumentOutOfRangeException(nameof(pSdv), "Standard deviation of momentum must have same length as sample."); + } + + if (pSdv.Any(sdv => sdv < 0)) + { + throw new ArgumentOutOfRangeException(nameof(pSdv), "Standard deviation must be positive."); + } + } + + /// + /// Use for copying objects in the Burn method. + /// + /// The source of copying. + /// A copy of the source object. + protected override double[] Copy(double[] source) + { + var destination = new double[_length]; + Array.Copy(source, 0, destination, 0, _length); + return destination; + } + + /// + /// Use for creating temporary objects in the Burn method. + /// + /// An object of type T. + protected override double[] Create() + { + return new double[_length]; + } + + /// + protected override void DoAdd(ref double[] first, double factor, double[] second) + { + for (int i = 0; i < _length; i++) + { + first[i] += factor * second[i]; + } + } + + /// + protected override void DoSubtract(ref double[] first, double factor, double[] second) + { + for (int i = 0; i < _length; i++) + { + first[i] -= factor * second[i]; + } + } + + /// + protected override double DoProduct(double[] first, double[] second) + { + double prod = 0; + for (int i = 0; i < _length; i++) + { + prod += first[i] * second[i]; + } + + return prod; + } + + /// + /// Samples the momentum from a normal distribution. + /// + /// The momentum to be randomized. + protected override void RandomizeMomentum(ref double[] p) + { + for (int j = 0; j < _length; j++) + { + p[j] = _mpSdv[j] * _pDistribution.Sample(); + } + } + + /// + /// The default method used for computing the gradient. Uses a simple three point estimation. + /// + /// Function which the gradient is to be evaluated. + /// The location where the gradient is to be evaluated. + /// The gradient of the function at the point x. + static double[] Grad(DensityLn function, double[] x) + { + int length = x.Length; + var returnValue = new double[length]; + var increment = new double[length]; + var decrement = new double[length]; + + Array.Copy(x, 0, increment, 0, length); + Array.Copy(x, 0, decrement, 0, length); + + for (int i = 0; i < length; i++) + { + double y = x[i]; + double h = Math.Max(10e-4, (10e-7) * y); + increment[i] += h; + decrement[i] -= h; + returnValue[i] = (function(increment) - function(decrement)) / (2 * h); + increment[i] = y; + decrement[i] = y; + } + + return returnValue; + } +} diff --git a/MathNet.Numerics/Statistics/MCMC/HybridMCGeneric.cs b/MathNet.Numerics/Statistics/MCMC/HybridMCGeneric.cs new file mode 100644 index 0000000..cc92671 --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/HybridMCGeneric.cs @@ -0,0 +1,329 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Statistics.Mcmc; + +using Distributions; + +using Properties; + +using System; + +/// +/// The Hybrid (also called Hamiltonian) Monte Carlo produces samples from distribution P using a set +/// of Hamiltonian equations to guide the sampling process. It uses the negative of the log density as +/// a potential energy, and a randomly generated momentum to set up a Hamiltonian system, which is then used +/// to sample the distribution. This can result in a faster convergence than the random walk Metropolis sampler +/// (). +/// +/// The type of samples this sampler produces. +public abstract class HybridMCGeneric : McmcSampler +{ + /// + /// The delegate type that defines a derivative evaluated at a certain point. + /// + /// Function to be differentiated. + /// Value where the derivative is computed. + public delegate T DiffMethod(DensityLn f, T x); + + /// + /// Evaluates the energy function of the target distribution. + /// + readonly DensityLn _energy; + + /// + /// The current location of the sampler. + /// + protected T Current; + + /// + /// The number of burn iterations between two samples. + /// + int _burnInterval; + + /// + /// The size of each step in the Hamiltonian equation. + /// + double _stepSize; + + /// + /// The number of iterations in the Hamiltonian equation. + /// + int _frogLeapSteps; + + /// + /// The algorithm used for differentiation. + /// + readonly DiffMethod _diff; + + /// + /// Gets or sets the number of iterations in between returning samples. + /// + /// When burn interval is negative. + public int BurnInterval + { + get { return _burnInterval; } + set + { + _burnInterval = SetNonNegative(value); + } + } + + /// + /// Gets or sets the number of iterations in the Hamiltonian equation. + /// + /// When frog leap steps is negative or zero. + public int FrogLeapSteps + { + get { return _frogLeapSteps; } + set + { + _frogLeapSteps = SetPositive(value); + } + } + + /// + /// Gets or sets the size of each step in the Hamiltonian equation. + /// + /// When step size is negative or zero. + public double StepSize + { + get { return _stepSize; } + set + { + _stepSize = SetPositive(value); + } + } + + /// + /// Constructs a new Hybrid Monte Carlo sampler. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// Number frog leap simulation steps. + /// Size of the frog leap simulation steps. + /// The number of iterations in between returning samples. + /// Random number generator used for sampling the momentum. + /// The method used for differentiation. + /// When the number of burnInterval iteration is negative. + /// When either x0, pdfLnP or diff is null. + protected HybridMCGeneric(T x0, DensityLn pdfLnP, int frogLeapSteps, double stepSize, int burnInterval, Random randomSource, DiffMethod diff) + { + _energy = x => -pdfLnP(x); + FrogLeapSteps = frogLeapSteps; + StepSize = stepSize; + BurnInterval = burnInterval; + Current = x0; + _diff = diff; + RandomSource = randomSource; + } + + /// + /// Returns a sample from the distribution P. + /// + public override T Sample() + { + Burn(_burnInterval + 1); + + return Current; + } + + /// + /// This method runs the sampler for a number of iterations without returning a sample + /// + protected void Burn(int n) + { + T p = Create(); + double e = _energy(Current); + T gradient = _diff(_energy, Current); + for (int i = 0; i < n; i++) + { + RandomizeMomentum(ref p); + double h = Hamiltonian(p, e); + + T mNew = Copy(Current); + T gNew = Copy(gradient); + + for (int j = 0; j < _frogLeapSteps; j++) + { + HamiltonianEquations(ref gNew, ref mNew, ref p); + } + + double enew = _energy(mNew); + double hnew = Hamiltonian(p, enew); + + double dh = hnew - h; + + Update(ref e, ref gradient, mNew, gNew, enew, dh); + Samples++; + } + } + + /// + /// Method used to update the sample location. Used in the end of the loop. + /// + /// The old energy. + /// The old gradient/derivative of the energy. + /// The new sample. + /// The new gradient/derivative of the energy. + /// The new energy. + /// The difference between the old Hamiltonian and new Hamiltonian. Use to determine + /// if an update should take place. + protected void Update(ref double e, ref T gradient, T mNew, T gNew, double enew, double dh) + { + if (dh <= 0) + { + Current = mNew; + gradient = gNew; + e = enew; + Accepts++; + } + else if (Bernoulli.Sample(RandomSource, Math.Exp(-dh)) == 1) + { + Current = mNew; + gradient = gNew; + e = enew; + Accepts++; + } + } + + /// + /// Use for creating temporary objects in the Burn method. + /// + /// An object of type T. + protected abstract T Create(); + + /// + /// Use for copying objects in the Burn method. + /// + /// The source of copying. + /// A copy of the source object. + protected abstract T Copy(T source); + + /// + /// Method for doing dot product. + /// + /// First vector/scalar in the product. + /// Second vector/scalar in the product. + protected abstract double DoProduct(T first, T second); + + /// + /// Method for adding, multiply the second vector/scalar by factor and then + /// add it to the first vector/scalar. + /// + /// First vector/scalar. + /// Scalar factor multiplying by the second vector/scalar. + /// Second vector/scalar. + protected abstract void DoAdd(ref T first, double factor, T second); + + /// + /// Multiplying the second vector/scalar by factor and then subtract it from + /// the first vector/scalar. + /// + /// First vector/scalar. + /// Scalar factor to be multiplied to the second vector/scalar. + /// Second vector/scalar. + protected abstract void DoSubtract(ref T first, double factor, T second); + + /// + /// Method for sampling a random momentum. + /// + /// Momentum to be randomized. + protected abstract void RandomizeMomentum(ref T p); + + /// + /// The Hamiltonian equations that is used to produce the new sample. + /// + protected void HamiltonianEquations(ref T gNew, ref T mNew, ref T p) + { + DoSubtract(ref p, _stepSize / 2, gNew); + DoAdd(ref mNew, _stepSize, p); + gNew = _diff(_energy, mNew); + DoSubtract(ref p, _stepSize / 2, gNew); + } + + /// + /// Method to compute the Hamiltonian used in the method. + /// + /// The momentum. + /// The energy. + /// Hamiltonian=E+p.p/2 + protected double Hamiltonian(T momentum, double e) + { + return e + DoProduct(momentum, momentum) / 2; + } + + /// + /// Method to check and set a quantity to a non-negative value. + /// + /// Proposed value to be checked. + /// Returns value if it is greater than or equal to zero. + /// Throws when value is negative. + protected int SetNonNegative(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), Resources.ArgumentNotNegative); + } + + return value; + } + + /// + /// Method to check and set a quantity to a non-negative value. + /// + /// Proposed value to be checked. + /// Returns value if it is greater than to zero. + /// Throws when value is negative or zero. + protected int SetPositive(int value) + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value), Resources.ArgumentNotNegative); + } + + return value; + } + + /// + /// Method to check and set a quantity to a non-negative value. + /// + /// Proposed value to be checked. + /// Returns value if it is greater than zero. + /// Throws when value is negative or zero. + protected double SetPositive(double value) + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value), Resources.ArgumentNotNegative); + } + + return value; + } +} diff --git a/MathNet.Numerics/Statistics/MCMC/MCMCDiagnostics.cs b/MathNet.Numerics/Statistics/MCMC/MCMCDiagnostics.cs new file mode 100644 index 0000000..ae83363 --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/MCMCDiagnostics.cs @@ -0,0 +1,87 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MathNet.Numerics.Statistics.Mcmc; + +/// +/// Provides utilities to analysis the convergence of a set of samples from +/// a . +/// +public static class MCMCDiagnostics +{ + /// + /// Computes the auto correlations of a series evaluated by a function f. + /// + /// The series for computing the auto correlation. + /// The lag in the series + /// The function used to evaluate the series. + /// The auto correlation. + /// Throws if lag is zero or if lag is + /// greater than or equal to the length of Series. + public static double ACF(IEnumerable series, int lag, Func f) + { + if (lag < 0) + { + throw new ArgumentOutOfRangeException(nameof(lag), Resources.LagMustBePositive); + } + + int length = series.Count(); + if (lag >= length) + { + throw new ArgumentOutOfRangeException(nameof(lag), Resources.LagMustBeSmallerThanTheSampleSize); + } + + var transformedSeries = series.Select(f); + + var enumerable = transformedSeries as double[] ?? transformedSeries.ToArray(); + var firstSeries = enumerable.Take(length - lag); + var secondSeries = enumerable.Skip(lag); + + return Correlation.Pearson(firstSeries, secondSeries); + } + + /// + /// Computes the effective size of the sample when evaluated by a function f. + /// + /// The samples. + /// The function use for evaluating the series. + /// The effective size when auto correlation is taken into account. + public static double EffectiveSize(IEnumerable series, Func f) + { + int length = series.Count(); + double rho = ACF(series, 1, f); + return ((1 - rho) / (1 + rho)) * length; + } +} diff --git a/MathNet.Numerics/Statistics/MCMC/MCMCSampler.cs b/MathNet.Numerics/Statistics/MCMC/MCMCSampler.cs new file mode 100644 index 0000000..4db0ee1 --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/MCMCSampler.cs @@ -0,0 +1,159 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Random; + +using System; + +namespace MathNet.Numerics.Statistics.Mcmc; + +/// +/// A method which samples datapoints from a proposal distribution. The implementation of this sampler +/// is stateless: no variables are saved between two calls to Sample. This proposal is different from +/// in that it doesn't take any parameters; it samples random +/// variables from the whole domain. +/// +/// The type of the datapoints. +/// A sample from the proposal distribution. +public delegate T GlobalProposalSampler(); + +/// +/// A method which samples datapoints from a proposal distribution given an initial sample. The implementation +/// of this sampler is stateless: no variables are saved between two calls to Sample. This proposal is different from +/// in that it samples locally around an initial point. In other words, it +/// makes a small local move rather than producing a global sample from the proposal. +/// +/// The type of the datapoints. +/// The initial sample. +/// A sample from the proposal distribution. +public delegate T LocalProposalSampler(T init); + +/// +/// A function which evaluates a density. +/// +/// The type of data the distribution is over. +/// The sample we want to evaluate the density for. +public delegate double Density(T sample); + +/// +/// A function which evaluates a log density. +/// +/// The type of data the distribution is over. +/// The sample we want to evaluate the log density for. +public delegate double DensityLn(T sample); + +/// +/// A function which evaluates the log of a transition kernel probability. +/// +/// The type for the space over which this transition kernel is defined. +/// The new state in the transition. +/// The previous state in the transition. +/// The log probability of the transition. +public delegate double TransitionKernelLn(T to, T from); + +/// +/// The interface which every sampler must implement. +/// +/// The type of samples this sampler produces. +public abstract class McmcSampler +{ + /// + /// The random number generator for this class. + /// + private System.Random _randomNumberGenerator; + + /// + /// Keeps track of the number of accepted samples. + /// + protected int Accepts; + + /// + /// Keeps track of the number of calls to the proposal sampler. + /// + protected int Samples; + + /// + /// Initializes a new instance of the class. + /// + /// Thread safe instances are two and half times slower than non-thread + /// safe classes. + protected McmcSampler() + { + Accepts = 0; + Samples = 0; + RandomSource = SystemRandomSource.Default; + } + + /// + /// Gets or sets the random number generator. + /// + /// When the random number generator is null. + public System.Random RandomSource + { + get { return _randomNumberGenerator; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _randomNumberGenerator = value; + } + } + + /// + /// Returns one sample. + /// + public abstract T Sample(); + + /// + /// Returns a number of samples. + /// + /// The number of samples we want. + /// An array of samples. + public virtual T[] Sample(int n) + { + var ret = new T[n]; + for (int i = 0; i < n; i++) + { + ret[i] = Sample(); + } + + return ret; + } + + /// + /// Gets the acceptance rate of the sampler. + /// + public double AcceptanceRate + { + get { return Accepts / (double)Samples; } + } +} \ No newline at end of file diff --git a/MathNet.Numerics/Statistics/MCMC/MetropolisHastingsSampler.cs b/MathNet.Numerics/Statistics/MCMC/MetropolisHastingsSampler.cs new file mode 100644 index 0000000..1da8c9d --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/MetropolisHastingsSampler.cs @@ -0,0 +1,163 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Statistics.Mcmc; + +using Distributions; + +using Properties; + +using System; + +/// +/// Metropolis-Hastings sampling produces samples from distribution P by sampling from a proposal distribution Q +/// and accepting/rejecting based on the density of P. Metropolis-Hastings sampling doesn't require that the +/// proposal distribution Q is symmetric in comparison to . It does need to +/// be able to evaluate the proposal sampler's log density though. All densities are required to be in log space. +/// +/// The Metropolis-Hastings sampler is a stateful sampler. It keeps track of where it currently is in the domain +/// of the distribution P. +/// +/// The type of samples this sampler produces. +public class MetropolisHastingsSampler : McmcSampler +{ + /// + /// Evaluates the log density function of the target distribution. + /// + private readonly DensityLn _pdfLnP; + + /// + /// Evaluates the log transition probability for the proposal distribution. + /// + private readonly TransitionKernelLn _krnlQ; + + /// + /// A function which samples from a proposal distribution. + /// + private readonly LocalProposalSampler _proposal; + + /// + /// The current location of the sampler. + /// + private T _current; + + /// + /// The log density at the current location. + /// + private double _currentDensityLn; + + /// + /// The number of burn iterations between two samples. + /// + private int _burnInterval; + + /// + /// Constructs a new Metropolis-Hastings sampler using the default random number generator. This + /// constructor will set the burn interval. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// The log transition probability for the proposal distribution. + /// A method that samples from the proposal distribution. + /// The number of iterations in between returning samples. + /// When the number of burnInterval iteration is negative. + public MetropolisHastingsSampler(T x0, DensityLn pdfLnP, TransitionKernelLn krnlQ, LocalProposalSampler proposal, int burnInterval = 0) + { + _current = x0; + _currentDensityLn = pdfLnP(x0); + _pdfLnP = pdfLnP; + _krnlQ = krnlQ; + _proposal = proposal; + BurnInterval = burnInterval; + + Burn(BurnInterval); + } + + /// + /// Gets or sets the number of iterations in between returning samples. + /// + /// When burn interval is negative. + public int BurnInterval + { + get { return _burnInterval; } + set + { + if (value < 0) + { + throw new ArgumentException(Resources.ArgumentNotNegative); + } + + _burnInterval = value; + } + } + + /// + /// This method runs the sampler for a number of iterations without returning a sample + /// + private void Burn(int n) + { + for (int i = 0; i < n; i++) + { + // Get a sample from the proposal. + T next = _proposal(_current); + // Evaluate the density at the next sample. + double p = _pdfLnP(next); + // Evaluate the forward transition probability. + double fwd = _krnlQ(next, _current); + // Evaluate the backward transition probability + double bwd = _krnlQ(_current, next); + + Samples++; + + double acc = Math.Min(0.0, p + bwd - _currentDensityLn - fwd); + if (acc == 0.0) + { + _current = next; + _currentDensityLn = p; + Accepts++; + } + else if (Bernoulli.Sample(RandomSource, Math.Exp(acc)) == 1) + { + _current = next; + _currentDensityLn = p; + Accepts++; + } + } + } + + /// + /// Returns a sample from the distribution P. + /// + public override T Sample() + { + Burn(BurnInterval + 1); + + return _current; + } +} diff --git a/MathNet.Numerics/Statistics/MCMC/MetropolisSampler.cs b/MathNet.Numerics/Statistics/MCMC/MetropolisSampler.cs new file mode 100644 index 0000000..003131e --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/MetropolisSampler.cs @@ -0,0 +1,150 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Statistics.Mcmc; + +using Distributions; + +using Properties; + +using System; + +/// +/// Metropolis sampling produces samples from distribution P by sampling from a proposal distribution Q +/// and accepting/rejecting based on the density of P. Metropolis sampling requires that the proposal +/// distribution Q is symmetric. All densities are required to be in log space. +/// +/// The Metropolis sampler is a stateful sampler. It keeps track of where it currently is in the domain +/// of the distribution P. +/// +/// The type of samples this sampler produces. +public class MetropolisSampler : McmcSampler +{ + /// + /// Evaluates the log density function of the sampling distribution. + /// + private readonly DensityLn _pdfLnP; + + /// + /// A function which samples from a proposal distribution. + /// + private readonly LocalProposalSampler _proposal; + + /// + /// The current location of the sampler. + /// + private T _current; + + /// + /// The log density at the current location. + /// + private double _currentDensityLn; + + /// + /// The number of burn iterations between two samples. + /// + private int _burnInterval; + + /// + /// Constructs a new Metropolis sampler using the default random number generator. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// A method that samples from the symmetric proposal distribution. + /// The number of iterations in between returning samples. + /// When the number of burnInterval iteration is negative. + public MetropolisSampler(T x0, DensityLn pdfLnP, LocalProposalSampler proposal, int burnInterval = 0) + { + _current = x0; + _currentDensityLn = pdfLnP(x0); + _pdfLnP = pdfLnP; + _proposal = proposal; + BurnInterval = burnInterval; + + Burn(BurnInterval); + } + + /// + /// Gets or sets the number of iterations in between returning samples. + /// + /// When burn interval is negative. + public int BurnInterval + { + get { return _burnInterval; } + set + { + if (value < 0) + { + throw new ArgumentException(Resources.ArgumentNotNegative); + } + + _burnInterval = value; + } + } + + /// + /// This method runs the sampler for a number of iterations without returning a sample + /// + private void Burn(int n) + { + for (int i = 0; i < n; i++) + { + // Get a sample from the proposal. + T next = _proposal(_current); + // Evaluate the density at the next sample. + double p = _pdfLnP(next); + + Samples++; + + double acc = Math.Min(0.0, p - _currentDensityLn); + if (acc == 0.0) + { + _current = next; + _currentDensityLn = p; + Accepts++; + } + else if (Bernoulli.Sample(RandomSource, Math.Exp(acc)) == 1) + { + _current = next; + _currentDensityLn = p; + Accepts++; + } + } + } + + /// + /// Returns a sample from the distribution P. + /// + public override T Sample() + { + Burn(BurnInterval + 1); + + return _current; + } +} diff --git a/MathNet.Numerics/Statistics/MCMC/RejectionSampler.cs b/MathNet.Numerics/Statistics/MCMC/RejectionSampler.cs new file mode 100644 index 0000000..9a2aebf --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/RejectionSampler.cs @@ -0,0 +1,104 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Statistics.Mcmc; + +using Properties; + +using System; + +/// +/// Rejection sampling produces samples from distribution P by sampling from a proposal distribution Q +/// and accepting/rejecting based on the density of P and Q. The density of P and Q don't need to +/// to be normalized, but we do need that for each x, P(x) < Q(x). +/// +/// The type of samples this sampler produces. +public class RejectionSampler : McmcSampler +{ + /// + /// Evaluates the density function of the sampling distribution. + /// + private readonly Density _pdfP; + + /// + /// Evaluates the density function of the proposal distribution. + /// + private readonly Density _pdfQ; + + /// + /// A function which samples from a proposal distribution. + /// + private readonly GlobalProposalSampler _proposal; + + /// + /// Constructs a new rejection sampler using the default random number generator. + /// + /// The density of the distribution we want to sample from. + /// The density of the proposal distribution. + /// A method that samples from the proposal distribution. + public RejectionSampler(Density pdfP, Density pdfQ, GlobalProposalSampler proposal) + { + _pdfP = pdfP; + _pdfQ = pdfQ; + _proposal = proposal; + } + + /// + /// Returns a sample from the distribution P. + /// + /// When the algorithms detects that the proposal + /// distribution doesn't upper bound the target distribution. + public override T Sample() + { + while (true) + { + // Get a sample from the proposal. + T x = _proposal(); + // Evaluate the density for proposal. + double q = _pdfQ(x); + // Evaluate the density for the target density. + double p = _pdfP(x); + // Sample a variable between 0.0 and proposal density. + double u = RandomSource.NextDouble() * q; + + Samples++; + + if (q < p) + { + throw new ArgumentException(Resources.ProposalDistributionNoUpperBound); + } + + if (u < p) + { + Accepts++; + return x; + } + } + } +} diff --git a/MathNet.Numerics/Statistics/MCMC/UnivariateHybridMC.cs b/MathNet.Numerics/Statistics/MCMC/UnivariateHybridMC.cs new file mode 100644 index 0000000..495861d --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/UnivariateHybridMC.cs @@ -0,0 +1,195 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Distributions; +using MathNet.Numerics.Random; + +using System; + +namespace MathNet.Numerics.Statistics.Mcmc; + +/// +/// A hybrid Monte Carlo sampler for univariate distributions. +/// +public class UnivariateHybridMC : HybridMCGeneric +{ + /// + /// Distribution to sample momentum from. + /// + private readonly Normal _distribution; + + /// + /// Standard deviations used in the sampling of the + /// momentum. + /// + private double _sdv; + + /// + /// Gets or sets the standard deviation used in the sampling of the + /// momentum. + /// + /// When standard deviation is negative. + public double MomentumStdDev + { + get { return _sdv; } + set + { + if (_sdv != value) + { + _sdv = SetPositive(value); + } + } + } + + /// + /// Constructs a new Hybrid Monte Carlo sampler for a univariate probability distribution. + /// The momentum will be sampled from a normal distribution with standard deviation + /// specified by pSdv using the default random + /// number generator. A three point estimation will be used for differentiation. + /// This constructor will set the burn interval. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// Number frog leap simulation steps. + /// Size of the frog leap simulation steps. + /// The number of iterations in between returning samples. + /// The standard deviation of the normal distribution that is used to sample + /// the momentum. + /// When the number of burnInterval iteration is negative. + public UnivariateHybridMC(double x0, DensityLn pdfLnP, int frogLeapSteps, double stepSize, int burnInterval = 0, double pSdv = 1) + : this(x0, pdfLnP, frogLeapSteps, stepSize, burnInterval, pSdv, SystemRandomSource.Default) + { + } + + /// + /// Constructs a new Hybrid Monte Carlo sampler for a univariate probability distribution. + /// The momentum will be sampled from a normal distribution with standard deviation + /// specified by pSdv using a random + /// number generator provided by the user. A three point estimation will be used for differentiation. + /// This constructor will set the burn interval. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// Number frog leap simulation steps. + /// Size of the frog leap simulation steps. + /// The number of iterations in between returning samples. + /// The standard deviation of the normal distribution that is used to sample + /// the momentum. + /// Random number generator used to sample the momentum. + /// When the number of burnInterval iteration is negative. + public UnivariateHybridMC(double x0, DensityLn pdfLnP, int frogLeapSteps, double stepSize, int burnInterval, double pSdv, System.Random randomSource) + : this(x0, pdfLnP, frogLeapSteps, stepSize, burnInterval, pSdv, randomSource, Grad) + { + } + + /// + /// Constructs a new Hybrid Monte Carlo sampler for a multivariate probability distribution. + /// The momentum will be sampled from a normal distribution with standard deviation + /// given by pSdv using a random + /// number generator provided by the user. This constructor will set both the burn interval and the method used for + /// numerical differentiation. + /// + /// The initial sample. + /// The log density of the distribution we want to sample from. + /// Number frog leap simulation steps. + /// Size of the frog leap simulation steps. + /// The number of iterations in between returning samples. + /// The standard deviation of the normal distribution that is used to sample + /// the momentum. + /// The method used for numerical differentiation. + /// Random number generator used for sampling the momentum. + /// When the number of burnInterval iteration is negative. + public UnivariateHybridMC(double x0, DensityLn pdfLnP, int frogLeapSteps, double stepSize, int burnInterval, double pSdv, System.Random randomSource, DiffMethod diff) + : base(x0, pdfLnP, frogLeapSteps, stepSize, burnInterval, randomSource, diff) + { + MomentumStdDev = pSdv; + _distribution = new Normal(0.0, MomentumStdDev, RandomSource); + Burn(BurnInterval); + } + + /// + /// Use for copying objects in the Burn method. + /// + /// The source of copying. + /// A copy of the source object. + protected override double Copy(double source) + { + return source; + } + + /// + /// Use for creating temporary objects in the Burn method. + /// + /// An object of type T. + protected override double Create() + { + return 0; + } + + /// + protected override void DoAdd(ref double first, double factor, double second) + { + first += factor * second; + } + + /// + protected override double DoProduct(double first, double second) + { + return first * second; + } + + /// + protected override void DoSubtract(ref double first, double factor, double second) + { + first -= factor * second; + } + + /// + /// Samples the momentum from a normal distribution. + /// + /// The momentum to be randomized. + protected override void RandomizeMomentum(ref double p) + { + p = _distribution.Sample(); + } + + /// + /// The default method used for computing the derivative. Uses a simple three point estimation. + /// + /// Function for which the derivative is to be evaluated. + /// The location where the derivative is to be evaluated. + /// The derivative of the function at the point x. + static double Grad(DensityLn function, double x) + { + double h = Math.Max(10e-4, (10e-7) * x); + double increment = x + h; + double decrement = x - h; + return (function(increment) - function(decrement)) / (2 * h); + } +} diff --git a/MathNet.Numerics/Statistics/MCMC/UnivariateSliceSampler.cs b/MathNet.Numerics/Statistics/MCMC/UnivariateSliceSampler.cs new file mode 100644 index 0000000..6ff95f8 --- /dev/null +++ b/MathNet.Numerics/Statistics/MCMC/UnivariateSliceSampler.cs @@ -0,0 +1,199 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Statistics.Mcmc; + +using Properties; + +using System; + +/// +/// Slice sampling produces samples from distribution P by uniformly sampling from under the pdf of P using +/// a technique described in "Slice Sampling", R. Neal, 2003. All densities are required to be in log space. +/// +/// The slice sampler is a stateful sampler. It keeps track of where it currently is in the domain +/// of the distribution P. +/// +public class UnivariateSliceSampler : McmcSampler +{ + /// + /// Evaluates the log density function of the target distribution. + /// + private readonly DensityLn _pdfLnP; + + /// + /// The current location of the sampler. + /// + private double _current; + + /// + /// The log density at the current location. + /// + private double _currentDensityLn; + + /// + /// The number of burn iterations between two samples. + /// + private int _burnInterval; + + /// + /// The scale of the slice sampler. + /// + private double _scale; + + /// + /// Constructs a new Slice sampler using the default random + /// number generator. The burn interval will be set to 0. + /// + /// The initial sample. + /// The density of the distribution we want to sample from. + /// The scale factor of the slice sampler. + /// When the scale of the slice sampler is not positive. + public UnivariateSliceSampler(double x0, DensityLn pdfLnP, double scale) + : this(x0, pdfLnP, 0, scale) + { + } + + /// + /// Constructs a new slice sampler using the default random number generator. It + /// will set the number of burnInterval iterations and run a burnInterval phase. + /// + /// The initial sample. + /// The density of the distribution we want to sample from. + /// The number of iterations in between returning samples. + /// The scale factor of the slice sampler. + /// When the number of burnInterval iteration is negative. + /// When the scale of the slice sampler is not positive. + public UnivariateSliceSampler(double x0, DensityLn pdfLnP, int burnInterval, double scale) + { + _current = x0; + _currentDensityLn = pdfLnP(x0); + _pdfLnP = pdfLnP; + Scale = scale; + BurnInterval = burnInterval; + + Burn(BurnInterval); + } + + /// + /// Gets or sets the number of iterations in between returning samples. + /// + /// When burn interval is negative. + public int BurnInterval + { + get { return _burnInterval; } + set + { + if (value < 0) + { + throw new ArgumentException(Resources.ArgumentNotNegative); + } + + _burnInterval = value; + } + } + + /// + /// Gets or sets the scale of the slice sampler. + /// + public double Scale + { + get { return _scale; } + set + { + if (value <= 0.0) + { + throw new ArgumentException(Resources.ArgumentPositive); + } + + _scale = value; + } + } + + /// + /// This method runs the sampler for a number of iterations without returning a sample + /// + private void Burn(int n) + { + for (int i = 0; i < n; i++) + { + // The logarithm of the slice height. + double lu = Math.Log(RandomSource.NextDouble()) + _currentDensityLn; + + // Create a horizontal interval (x_l, x_r) enclosing x. + double r = RandomSource.NextDouble(); + double xL = _current - r * Scale; + double xR = _current + (1.0 - r) * Scale; + + // Stepping out procedure. + while (_pdfLnP(xL) > lu) + { + xL -= Scale; + } + + while (_pdfLnP(xR) > lu) + { + xR += Scale; + } + + // Shrinking: propose new x and shrink interval until good one found. + while (true) + { + double xnew = RandomSource.NextDouble() * (xR - xL) + xL; + _currentDensityLn = _pdfLnP(xnew); + if (_currentDensityLn > lu) + { + _current = xnew; + Accepts++; + Samples++; + break; + } + + if (xnew > _current) + { + xR = xnew; + } + else + { + xL = xnew; + } + } + } + } + + /// + /// Returns a sample from the distribution P. + /// + public override double Sample() + { + Burn(BurnInterval + 1); + + return _current; + } +} diff --git a/MathNet.Numerics/Statistics/MovingStatistics.cs b/MathNet.Numerics/Statistics/MovingStatistics.cs new file mode 100644 index 0000000..1578eca --- /dev/null +++ b/MathNet.Numerics/Statistics/MovingStatistics.cs @@ -0,0 +1,360 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; + +namespace MathNet.Numerics.Statistics; + +/// +/// Running statistics over a window of data, allows updating by adding values. +/// +public class MovingStatistics +{ + readonly double[] _oldValues; + readonly int _windowSize; + + long _count; + long _totalCountOffset; + int _lastIndex; + int _lastNaNTimeToLive; + int _lastPosInfTimeToLive; + int _lastNegInfTimeToLive; + + double _m1; + double _m2; + double _max = double.NegativeInfinity; + double _min = double.PositiveInfinity; + + public MovingStatistics(int windowSize) + { + if (windowSize < 1) + { + throw new ArgumentException(string.Format(Resources.ArgumentMustBePositive), nameof(windowSize)); + } + + _windowSize = windowSize; + _oldValues = new double[_windowSize]; + } + + public MovingStatistics(int windowSize, IEnumerable values) + : this(windowSize) + { + PushRange(values); + } + + public int WindowSize + { + get { return _windowSize; } + } + + /// + /// Gets the total number of samples. + /// + public long Count + { + get { return _totalCountOffset + _count; } + } + + /// + /// Returns the minimum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double Minimum + { + get + { + if (_lastNaNTimeToLive > 0) + { + return double.NaN; + } + + if (_lastNegInfTimeToLive > 0) + { + return double.NegativeInfinity; + } + + return (_count > 0 || _lastPosInfTimeToLive > 0) ? _min : double.NaN; + } + } + + /// + /// Returns the maximum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double Maximum + { + get + { + if (_lastNaNTimeToLive > 0) + { + return double.NaN; + } + + if (_lastPosInfTimeToLive > 0) + { + return double.PositiveInfinity; + } + + return (_count > 0 || _lastNegInfTimeToLive > 0) ? _max : double.NaN; + } + } + + /// + /// Evaluates the sample mean, an estimate of the population mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double Mean + { + get + { + if (_lastNaNTimeToLive > 0 || (_lastPosInfTimeToLive > 0 && _lastNegInfTimeToLive > 0)) + { + return double.NaN; + } + + if (_lastPosInfTimeToLive > 0) + { + return double.PositiveInfinity; + } + + if (_lastNegInfTimeToLive > 0) + { + return double.NegativeInfinity; + } + + return _count == 0 ? double.NaN : _m1; + } + } + + /// + /// Estimates the unbiased population variance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + public double Variance + { + get + { + if (_lastNaNTimeToLive > 0 || _lastNegInfTimeToLive > 0) + { + return double.NaN; + } + + if (_lastPosInfTimeToLive > 0) + { + return double.PositiveInfinity; + } + + return _count < 2 ? double.NaN : _m2 / (_count - 1); + } + } + + /// + /// Evaluates the variance from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double PopulationVariance + { + get + { + if (_lastNaNTimeToLive > 0 || _lastNegInfTimeToLive > 0) + { + return double.NaN; + } + + if (_lastPosInfTimeToLive > 0) + { + return double.PositiveInfinity; + } + + return _count < 2 ? double.NaN : _m2 / _count; + } + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + public double StandardDeviation + { + get + { + if (_lastNaNTimeToLive > 0 || _lastNegInfTimeToLive > 0) + { + return double.NaN; + } + + if (_lastPosInfTimeToLive > 0) + { + return double.PositiveInfinity; + } + + return _count < 2 ? double.NaN : Math.Sqrt(_m2 / (_count - 1)); + } + } + + /// + /// Evaluates the standard deviation from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double PopulationStandardDeviation + { + get + { + if (_lastNaNTimeToLive > 0 || _lastNegInfTimeToLive > 0) + { + return double.NaN; + } + + if (_lastPosInfTimeToLive > 0) + { + return double.PositiveInfinity; + } + + return _count < 2 ? double.NaN : Math.Sqrt(_m2 / _count); + } + } + + /// + /// Update the running statistics by adding another observed sample (in-place). + /// + public void Push(double value) + { + DecrementTimeToLive(); + + if (double.IsNaN(value)) + { + _lastNaNTimeToLive = _windowSize; + Reset(double.PositiveInfinity, double.NegativeInfinity); + return; + } + + if (double.IsPositiveInfinity(value)) + { + _lastPosInfTimeToLive = _windowSize; + Reset(_min, double.NegativeInfinity); + return; + } + + if (double.IsNegativeInfinity(value)) + { + _lastNegInfTimeToLive = _windowSize; + Reset(double.PositiveInfinity, _max); + return; + } + + if (_count < _windowSize) + { + _oldValues[_count] = value; + _count++; + var d = value - _m1; + var s = d / _count; + var t = d * s * (_count - 1); + + _m1 += s; + _m2 += t; + + if (value < _min) + { + _min = value; + } + + if (value > _max) + { + _max = value; + } + } + else + { + var oldValue = _oldValues[_lastIndex]; + var d = value - oldValue; + var s = d / _count; + var oldM1 = _m1; + _m1 += s; + + var x = (value - _m1 + oldValue - oldM1); + var t = d * x; + _m2 += t; + + _oldValues[_lastIndex] = value; + _lastIndex++; + if (_lastIndex == WindowSize) + { + _lastIndex = 0; + } + + _max = value > _max ? value : _oldValues.Maximum(); + _min = value < _min ? value : _oldValues.Minimum(); + } + } + + /// + /// Update the running statistics by adding a sequence of observed sample (in-place). + /// + public void PushRange(IEnumerable values) + { + foreach (var value in values) + { + Push(value); + } + } + + private void DecrementTimeToLive() + { + if (_lastNaNTimeToLive > 0) + { + _lastNaNTimeToLive--; + } + + if (_lastPosInfTimeToLive > 0) + { + _lastPosInfTimeToLive--; + } + + if (_lastNegInfTimeToLive > 0) + { + _lastNegInfTimeToLive--; + } + } + + private void Reset(double min, double max) + { + _totalCountOffset += _count + 1; + _count = 0; + _m1 = 0; + _max = max; + _min = min; + } +} diff --git a/MathNet.Numerics/Statistics/QuantileDefinition.cs b/MathNet.Numerics/Statistics/QuantileDefinition.cs new file mode 100644 index 0000000..5a942cd --- /dev/null +++ b/MathNet.Numerics/Statistics/QuantileDefinition.cs @@ -0,0 +1,43 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Statistics; + +public enum QuantileDefinition +{ + R1 = 1, SAS3 = 1, EmpiricalInvCDF = 1, + R2 = 2, SAS5 = 2, EmpiricalInvCDFAverage = 2, + R3 = 3, SAS2 = 3, Nearest = 3, + R4 = 4, SAS1 = 4, California = 4, + R5 = 5, Hydrology = 5, Hazen = 5, + R6 = 6, SAS4 = 6, Nist = 6, Weibull = 6, SPSS = 6, + R7 = 7, Excel = 7, Mode = 7, S = 7, + R8 = 8, Median = 8, Default = 8, + R9 = 9, Normal = 9, +} diff --git a/MathNet.Numerics/Statistics/RankDefinition.cs b/MathNet.Numerics/Statistics/RankDefinition.cs new file mode 100644 index 0000000..3a3e501 --- /dev/null +++ b/MathNet.Numerics/Statistics/RankDefinition.cs @@ -0,0 +1,49 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.Statistics; + +public enum RankDefinition +{ + /// Replace ties with their mean (non-integer ranks). Default. + Average = 1, + Default = 1, + + /// Replace ties with their minimum (typical sports ranking). + Min = 2, + Sports = 2, + + /// Replace ties with their maximum. + Max = 3, + + /// Permutation with increasing values at each index of ties. + First = 4, + + EmpiricalCDF = 5 +} \ No newline at end of file diff --git a/MathNet.Numerics/Statistics/RunningStatistics.cs b/MathNet.Numerics/Statistics/RunningStatistics.cs new file mode 100644 index 0000000..b485cf4 --- /dev/null +++ b/MathNet.Numerics/Statistics/RunningStatistics.cs @@ -0,0 +1,272 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// +// Adapted from the old DescriptiveStatistics and inspired in design +// among others by http://www.johndcook.com/skewness_kurtosis.html + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace MathNet.Numerics.Statistics; + +/// +/// Running statistics accumulator, allows updating by adding values +/// or by combining two accumulators. +/// +/// +/// This type declares a DataContract for out of the box ephemeral serialization +/// with engines like DataContractSerializer, Protocol Buffers and FsPickler, +/// but does not guarantee any compatibility between versions. +/// It is not recommended to rely on this mechanism for durable persistence. +/// +[DataContract(Namespace = "urn:MathNet/Numerics")] +public class RunningStatistics +{ + [DataMember(Order = 1)] + long _n; + + [DataMember(Order = 2)] + double _min = double.PositiveInfinity; + + [DataMember(Order = 3)] + double _max = double.NegativeInfinity; + + [DataMember(Order = 4)] + double _m1; + + [DataMember(Order = 5)] + double _m2; + + [DataMember(Order = 6)] + double _m3; + + [DataMember(Order = 7)] + double _m4; + + public RunningStatistics() + { + } + + public RunningStatistics(IEnumerable values) + { + PushRange(values); + } + + /// + /// Gets the total number of samples. + /// + public long Count + { + get { return _n; } + } + + /// + /// Returns the minimum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double Minimum + { + get { return _n > 0 ? _min : double.NaN; } + } + + /// + /// Returns the maximum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double Maximum + { + get { return _n > 0 ? _max : double.NaN; } + } + + /// + /// Evaluates the sample mean, an estimate of the population mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double Mean + { + get { return _n > 0 ? _m1 : double.NaN; } + } + + /// + /// Estimates the unbiased population variance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + public double Variance + { + get { return _n < 2 ? double.NaN : _m2 / (_n - 1); } + } + + /// + /// Evaluates the variance from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double PopulationVariance + { + get { return _n < 2 ? double.NaN : _m2 / _n; } + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + public double StandardDeviation + { + get { return _n < 2 ? double.NaN : Math.Sqrt(_m2 / (_n - 1)); } + } + + /// + /// Evaluates the standard deviation from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + public double PopulationStandardDeviation + { + get { return _n < 2 ? double.NaN : Math.Sqrt(_m2 / _n); } + } + + /// + /// Estimates the unbiased population skewness from the provided samples. + /// Uses a normalizer (Bessel's correction; type 2). + /// Returns NaN if data has less than three entries or if any entry is NaN. + /// + public double Skewness + { + get { return _n < 3 ? double.NaN : (_n * _m3 * Math.Sqrt(_m2 / (_n - 1)) / (_m2 * _m2 * (_n - 2))) * (_n - 1); } + } + + /// + /// Evaluates the population skewness from the full population. + /// Does not use a normalizer and would thus be biased if applied to a subset (type 1). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + public double PopulationSkewness + { + get { return _n < 2 ? double.NaN : Math.Sqrt(_n) * _m3 / Math.Pow(_m2, 1.5); } + } + + /// + /// Estimates the unbiased population kurtosis from the provided samples. + /// Uses a normalizer (Bessel's correction; type 2). + /// Returns NaN if data has less than four entries or if any entry is NaN. + /// + public double Kurtosis + { + get { return _n < 4 ? double.NaN : ((double)_n * _n - 1) / ((_n - 2) * (_n - 3)) * (_n * _m4 / (_m2 * _m2) - 3 + 6.0 / (_n + 1)); } + } + + /// + /// Evaluates the population kurtosis from the full population. + /// Does not use a normalizer and would thus be biased if applied to a subset (type 1). + /// Returns NaN if data has less than three entries or if any entry is NaN. + /// + public double PopulationKurtosis + { + get { return _n < 3 ? double.NaN : _n * _m4 / (_m2 * _m2) - 3.0; } + } + + /// + /// Update the running statistics by adding another observed sample (in-place). + /// + public void Push(double value) + { + _n++; + double d = value - _m1; + double s = d / _n; + double s2 = s * s; + double t = d * s * (_n - 1); + + _m1 += s; + _m4 += t * s2 * (_n * _n - 3 * _n + 3) + 6 * s2 * _m2 - 4 * s * _m3; + _m3 += t * s * (_n - 2) - 3 * s * _m2; + _m2 += t; + + if (value < _min || double.IsNaN(value)) + { + _min = value; + } + + if (value > _max || double.IsNaN(value)) + { + _max = value; + } + } + + /// + /// Update the running statistics by adding a sequence of observed sample (in-place). + /// + public void PushRange(IEnumerable values) + { + foreach (double value in values) + { + Push(value); + } + } + + /// + /// Create a new running statistics over the combined samples of two existing running statistics. + /// + public static RunningStatistics Combine(RunningStatistics a, RunningStatistics b) + { + if (a._n == 0) + { + return b; + } + else if (b._n == 0) + { + return a; + } + + long n = a._n + b._n; + double d = b._m1 - a._m1; + double d2 = d * d; + double d3 = d2 * d; + double d4 = d2 * d2; + + double m1 = (a._n * a._m1 + b._n * b._m1) / n; + double m2 = a._m2 + b._m2 + d2 * a._n * b._n / n; + double m3 = a._m3 + b._m3 + d3 * a._n * b._n * (a._n - b._n) / (n * n) + + 3 * d * (a._n * b._m2 - b._n * a._m2) / n; + double m4 = a._m4 + b._m4 + d4 * a._n * b._n * (a._n * a._n - a._n * b._n + b._n * b._n) / (n * n * n) + + 6 * d2 * (a._n * a._n * b._m2 + b._n * b._n * a._m2) / (n * n) + 4 * d * (a._n * b._m3 - b._n * a._m3) / n; + + double min = Math.Min(a._min, b._min); + double max = Math.Max(a._max, b._max); + + return new RunningStatistics { _n = n, _m1 = m1, _m2 = m2, _m3 = m3, _m4 = m4, _min = min, _max = max }; + } + + public static RunningStatistics operator +(RunningStatistics a, RunningStatistics b) + { + return Combine(a, b); + } +} diff --git a/MathNet.Numerics/Statistics/SortedArrayStatistics.Single.cs b/MathNet.Numerics/Statistics/SortedArrayStatistics.Single.cs new file mode 100644 index 0000000..58d604f --- /dev/null +++ b/MathNet.Numerics/Statistics/SortedArrayStatistics.Single.cs @@ -0,0 +1,478 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Statistics; + +public static partial class SortedArrayStatistics +{ + /// + /// Returns the smallest value from the sorted data array (ascending). + /// + /// Sample array, must be sorted ascendingly. + public static float Minimum(float[] data) + { + if (data.Length == 0) + { + return float.NaN; + } + + return data[0]; + } + + /// + /// Returns the largest value from the sorted data array (ascending). + /// + /// Sample array, must be sorted ascendingly. + public static float Maximum(float[] data) + { + if (data.Length == 0) + { + return float.NaN; + } + + return data[data.Length - 1]; + } + + /// + /// Returns the order statistic (order 1..N) from the sorted data array (ascending). + /// + /// Sample array, must be sorted ascendingly. + /// One-based order of the statistic, must be between 1 and N (inclusive). + public static float OrderStatistic(float[] data, int order) + { + if (order < 1 || order > data.Length) + { + return float.NaN; + } + + return data[order - 1]; + } + + /// + /// Estimates the median value from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static float Median(float[] data) + { + if (data.Length == 0) + { + return float.NaN; + } + + var k = data.Length / 2; + return data.Length.IsOdd() + ? data[k] + : (data[k - 1] + data[k]) / 2.0f; + } + + /// + /// Estimates the p-Percentile value from the sorted data array (ascending). + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + /// Percentile selector, between 0 and 100 (inclusive). + public static float Percentile(float[] data, int p) + { + return Quantile(data, p / 100d); + } + + /// + /// Estimates the first quartile value from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static float LowerQuartile(float[] data) + { + return Quantile(data, 0.25d); + } + + /// + /// Estimates the third quartile value from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static float UpperQuartile(float[] data) + { + return Quantile(data, 0.75d); + } + + /// + /// Estimates the inter-quartile range from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static float InterquartileRange(float[] data) + { + return Quantile(data, 0.75d) - Quantile(data, 0.25d); + } + + /// + /// Estimates {min, lower-quantile, median, upper-quantile, max} from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static float[] FiveNumberSummary(float[] data) + { + if (data.Length == 0) + { + return new[] { float.NaN, float.NaN, float.NaN, float.NaN, float.NaN }; + } + + return new[] { data[0], Quantile(data, 0.25), Median(data), Quantile(data, 0.75), data[data.Length - 1] }; + } + + /// + /// Estimates the tau-th quantile from the sorted data array (ascending). + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// + /// R-8, SciPy-(1/3,1/3): + /// Linear interpolation of the approximate medians for order statistics. + /// When tau < (2/3) / (N + 1/3), use x1. When tau >= (N - 1/3) / (N + 1/3), use xN. + /// + public static float Quantile(float[] data, double tau) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return float.NaN; + } + + if (tau == 0d || data.Length == 1) + { + return data[0]; + } + + if (tau == 1d) + { + return data[data.Length - 1]; + } + + double h = (data.Length + 1 / 3d) * tau + 1 / 3d; + var hf = (int)h; + return hf < 1 + ? data[0] + : hf >= data.Length + ? data[data.Length - 1] + : (float)(data[hf - 1] + (h - hf) * (data[hf] - data[hf - 1])); + } + + /// + /// Estimates the tau-th quantile from the sorted data array (ascending). + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified + /// by 4 parameters a, b, c and d, consistent with Mathematica. + /// + /// Sample array, must be sorted ascendingly. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// a-parameter + /// b-parameter + /// c-parameter + /// d-parameter + public static float QuantileCustom(float[] data, double tau, double a, double b, double c, double d) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return float.NaN; + } + + var x = a + (data.Length + b) * tau - 1; + var ip = Math.Truncate(x); + var fp = x - ip; + + if (Math.Abs(fp) < 1e-9) + { + return data[Math.Min(Math.Max((int)ip, 0), data.Length - 1)]; + } + + var lower = data[Math.Max((int)Math.Floor(x), 0)]; + var upper = data[Math.Min((int)Math.Ceiling(x), data.Length - 1)]; + return (float)(lower + (upper - lower) * (c + d * fp)); + } + + /// + /// Estimates the tau-th quantile from the sorted data array (ascending). + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// Sample array, must be sorted ascendingly. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// Quantile definition, to choose what product/definition it should be consistent with + public static float QuantileCustom(float[] data, double tau, QuantileDefinition definition) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return float.NaN; + } + + if (tau == 0d || data.Length == 1) + { + return data[0]; + } + + if (tau == 1d) + { + return data[data.Length - 1]; + } + + switch (definition) + { + case QuantileDefinition.R1: + { + double h = data.Length * tau + 0.5d; + return data[(int)Math.Ceiling(h - 0.5d) - 1]; + } + + case QuantileDefinition.R2: + { + double h = data.Length * tau + 0.5d; + return (data[(int)Math.Ceiling(h - 0.5d) - 1] + data[(int)(h + 0.5d) - 1]) * 0.5f; + } + + case QuantileDefinition.R3: + { + double h = data.Length * tau; + return data[Math.Max((int)Math.Round(h) - 1, 0)]; + } + + case QuantileDefinition.R4: + { + double h = data.Length * tau; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R5: + { + double h = data.Length * tau + 0.5d; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R6: + { + double h = (data.Length + 1) * tau; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R7: + { + double h = (data.Length - 1) * tau + 1d; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R8: + { + double h = (data.Length + 1 / 3d) * tau + 1 / 3d; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return (float)(lower + (h - hf) * (upper - lower)); + } + + case QuantileDefinition.R9: + { + double h = (data.Length + 0.25d) * tau + 0.375d; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return (float)(lower + (h - hf) * (upper - lower)); + } + + default: + throw new NotSupportedException(); + } + } + + /// + /// Estimates the empirical cumulative distribution function (CDF) at x from the sorted data array (ascending). + /// + /// The data sample sequence. + /// The value where to estimate the CDF at. + public static double EmpiricalCDF(float[] data, float x) + { + if (x < data[0]) + { + return 0.0; + } + + if (x >= data[data.Length - 1]) + { + return 1.0; + } + + int right = Array.BinarySearch(data, x); + if (right >= 0) + { + while (right < data.Length - 1 && data[right + 1] == data[right]) + { + right++; + } + + return (right + 1) / (double)data.Length; + } + + return (~right) / (double)data.Length; + } + + /// + /// Estimates the quantile tau from the sorted data array (ascending). + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile value. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static double QuantileRank(float[] data, float x, RankDefinition definition = RankDefinition.Default) + { + if (x < data[0]) + { + return 0.0; + } + + if (x >= data[data.Length - 1]) + { + return 1.0; + } + + int right = Array.BinarySearch(data, x); + if (right >= 0) + { + int left = right; + + while (left > 0 && data[left - 1] == data[left]) + { + left--; + } + + while (right < data.Length - 1 && data[right + 1] == data[right]) + { + right++; + } + + switch (definition) + { + case RankDefinition.EmpiricalCDF: + return (right + 1) / (double)data.Length; + + case RankDefinition.Max: + return right / (double)(data.Length - 1); + + case RankDefinition.Min: + return left / (double)(data.Length - 1); + + case RankDefinition.Average: + return (left / (double)(data.Length - 1) + right / (double)(data.Length - 1)) / 2; + + default: + throw new NotSupportedException(); + } + } + else + { + right = ~right; + int left = right - 1; + + switch (definition) + { + case RankDefinition.EmpiricalCDF: + return (left + 1) / (double)data.Length; + + default: + { + var a = left / (double)(data.Length - 1); + var b = right / (double)(data.Length - 1); + return ((data[right] - x) * a + (x - data[left]) * b) / (data[right] - data[left]); + } + } + } + } + + /// + /// Evaluates the rank of each entry of the sorted data array (ascending). + /// The rank definition can be specified to be compatible + /// with an existing system. + /// + public static double[] Ranks(float[] data, RankDefinition definition = RankDefinition.Default) + { + var ranks = new double[data.Length]; + + if (definition == RankDefinition.First) + { + for (int i = 0; i < ranks.Length; i++) + { + ranks[i] = i + 1; + } + + return ranks; + } + + int previousIndex = 0; + for (int i = 1; i < data.Length; i++) + { + if (Math.Abs(data[i] - data[previousIndex]) <= 0d) + { + continue; + } + + if (i == previousIndex + 1) + { + ranks[previousIndex] = i; + } + else + { + RanksTies(ranks, previousIndex, i, definition); + } + + previousIndex = i; + } + + RanksTies(ranks, previousIndex, data.Length, definition); + return ranks; + } +} diff --git a/MathNet.Numerics/Statistics/SortedArrayStatistics.cs b/MathNet.Numerics/Statistics/SortedArrayStatistics.cs new file mode 100644 index 0000000..fe23676 --- /dev/null +++ b/MathNet.Numerics/Statistics/SortedArrayStatistics.cs @@ -0,0 +1,517 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics.Statistics; + +/// +/// Statistics operating on an array already sorted ascendingly. +/// +/// +/// +/// +public static partial class SortedArrayStatistics +{ + /// + /// Returns the smallest value from the sorted data array (ascending). + /// + /// Sample array, must be sorted ascendingly. + public static double Minimum(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + return data[0]; + } + + /// + /// Returns the largest value from the sorted data array (ascending). + /// + /// Sample array, must be sorted ascendingly. + public static double Maximum(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + return data[data.Length - 1]; + } + + /// + /// Returns the order statistic (order 1..N) from the sorted data array (ascending). + /// + /// Sample array, must be sorted ascendingly. + /// One-based order of the statistic, must be between 1 and N (inclusive). + public static double OrderStatistic(double[] data, int order) + { + if (order < 1 || order > data.Length) + { + return double.NaN; + } + + return data[order - 1]; + } + + /// + /// Estimates the median value from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static double Median(double[] data) + { + if (data.Length == 0) + { + return double.NaN; + } + + var k = data.Length / 2; + return data.Length.IsOdd() + ? data[k] + : (data[k - 1] + data[k]) / 2.0; + } + + /// + /// Estimates the p-Percentile value from the sorted data array (ascending). + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + /// Percentile selector, between 0 and 100 (inclusive). + public static double Percentile(double[] data, int p) + { + return Quantile(data, p / 100d); + } + + /// + /// Estimates the first quartile value from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static double LowerQuartile(double[] data) + { + return Quantile(data, 0.25d); + } + + /// + /// Estimates the third quartile value from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static double UpperQuartile(double[] data) + { + return Quantile(data, 0.75d); + } + + /// + /// Estimates the inter-quartile range from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static double InterquartileRange(double[] data) + { + return Quantile(data, 0.75d) - Quantile(data, 0.25d); + } + + /// + /// Estimates {min, lower-quantile, median, upper-quantile, max} from the sorted data array (ascending). + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + public static double[] FiveNumberSummary(double[] data) + { + if (data.Length == 0) + { + return new[] { double.NaN, double.NaN, double.NaN, double.NaN, double.NaN }; + } + + return new[] { data[0], Quantile(data, 0.25), Quantile(data, 0.50), Quantile(data, 0.75), data[data.Length - 1] }; + } + + /// + /// Estimates the tau-th quantile from the sorted data array (ascending). + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// Sample array, must be sorted ascendingly. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// + /// R-8, SciPy-(1/3,1/3): + /// Linear interpolation of the approximate medians for order statistics. + /// When tau < (2/3) / (N + 1/3), use x1. When tau >= (N - 1/3) / (N + 1/3), use xN. + /// + public static double Quantile(double[] data, double tau) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return double.NaN; + } + + if (tau == 0d || data.Length == 1) + { + return data[0]; + } + + if (tau == 1d) + { + return data[data.Length - 1]; + } + + double h = (data.Length + 1 / 3d) * tau + 1 / 3d; + var hf = (int)h; + return hf < 1 ? data[0] + : hf >= data.Length ? data[data.Length - 1] + : data[hf - 1] + (h - hf) * (data[hf] - data[hf - 1]); + } + + /// + /// Estimates the tau-th quantile from the sorted data array (ascending). + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified + /// by 4 parameters a, b, c and d, consistent with Mathematica. + /// + /// Sample array, must be sorted ascendingly. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// a-parameter + /// b-parameter + /// c-parameter + /// d-parameter + public static double QuantileCustom(double[] data, double tau, double a, double b, double c, double d) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return double.NaN; + } + + var x = a + (data.Length + b) * tau - 1; + var ip = Math.Truncate(x); + var fp = x - ip; + + if (Math.Abs(fp) < 1e-9) + { + return data[Math.Min(Math.Max((int)ip, 0), data.Length - 1)]; + } + + var lower = data[Math.Max((int)Math.Floor(x), 0)]; + var upper = data[Math.Min((int)Math.Ceiling(x), data.Length - 1)]; + return lower + (upper - lower) * (c + d * fp); + } + + /// + /// Estimates the tau-th quantile from the sorted data array (ascending). + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// Sample array, must be sorted ascendingly. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// Quantile definition, to choose what product/definition it should be consistent with + public static double QuantileCustom(double[] data, double tau, QuantileDefinition definition) + { + if (tau < 0d || tau > 1d || data.Length == 0) + { + return double.NaN; + } + + if (tau == 0d || data.Length == 1) + { + return data[0]; + } + + if (tau == 1d) + { + return data[data.Length - 1]; + } + + switch (definition) + { + case QuantileDefinition.R1: + { + double h = data.Length * tau + 0.5d; + return data[(int)Math.Ceiling(h - 0.5d) - 1]; + } + + case QuantileDefinition.R2: + { + double h = data.Length * tau + 0.5d; + return (data[(int)Math.Ceiling(h - 0.5d) - 1] + data[(int)(h + 0.5d) - 1]) * 0.5d; + } + + case QuantileDefinition.R3: + { + double h = data.Length * tau; + return data[Math.Max((int)Math.Round(h) - 1, 0)]; + } + + case QuantileDefinition.R4: + { + double h = data.Length * tau; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R5: + { + double h = data.Length * tau + 0.5d; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R6: + { + double h = (data.Length + 1) * tau; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R7: + { + double h = (data.Length - 1) * tau + 1d; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R8: + { + double h = (data.Length + 1 / 3d) * tau + 1 / 3d; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return lower + (h - hf) * (upper - lower); + } + + case QuantileDefinition.R9: + { + double h = (data.Length + 0.25d) * tau + 0.375d; + var hf = (int)h; + var lower = data[Math.Max(hf - 1, 0)]; + var upper = data[Math.Min(hf, data.Length - 1)]; + return lower + (h - hf) * (upper - lower); + } + + default: + throw new NotSupportedException(); + } + } + + /// + /// Estimates the empirical cumulative distribution function (CDF) at x from the sorted data array (ascending). + /// + /// The data sample sequence. + /// The value where to estimate the CDF at. + public static double EmpiricalCDF(double[] data, double x) + { + if (x < data[0]) + { + return 0.0; + } + + if (x >= data[data.Length - 1]) + { + return 1.0; + } + + int right = Array.BinarySearch(data, x); + if (right >= 0) + { + while (right < data.Length - 1 && data[right + 1] == data[right]) + { + right++; + } + + return (right + 1) / (double)data.Length; + } + + return (~right) / (double)data.Length; + } + + /// + /// Estimates the quantile tau from the sorted data array (ascending). + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile value. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static double QuantileRank(double[] data, double x, RankDefinition definition = RankDefinition.Default) + { + if (x < data[0]) + { + return 0.0; + } + + if (x >= data[data.Length - 1]) + { + return 1.0; + } + + int right = Array.BinarySearch(data, x); + if (right >= 0) + { + int left = right; + + while (left > 0 && data[left - 1] == data[left]) + { + left--; + } + + while (right < data.Length - 1 && data[right + 1] == data[right]) + { + right++; + } + + switch (definition) + { + case RankDefinition.EmpiricalCDF: + return (right + 1) / (double)data.Length; + + case RankDefinition.Max: + return right / (double)(data.Length - 1); + + case RankDefinition.Min: + return left / (double)(data.Length - 1); + + case RankDefinition.Average: + return (left / (double)(data.Length - 1) + right / (double)(data.Length - 1)) / 2; + + default: + throw new NotSupportedException(); + } + } + else + { + right = ~right; + int left = right - 1; + + switch (definition) + { + case RankDefinition.EmpiricalCDF: + return (left + 1) / (double)data.Length; + + default: + { + var a = left / (double)(data.Length - 1); + var b = right / (double)(data.Length - 1); + return ((data[right] - x) * a + (x - data[left]) * b) / (data[right] - data[left]); + } + } + } + } + + /// + /// Evaluates the rank of each entry of the sorted data array (ascending). + /// The rank definition can be specified to be compatible + /// with an existing system. + /// + public static double[] Ranks(double[] data, RankDefinition definition = RankDefinition.Default) + { + var ranks = new double[data.Length]; + + if (definition == RankDefinition.First) + { + for (int i = 0; i < ranks.Length; i++) + { + ranks[i] = i + 1; + } + + return ranks; + } + + int previousIndex = 0; + for (int i = 1; i < data.Length; i++) + { + if (Math.Abs(data[i] - data[previousIndex]) <= 0d) + { + continue; + } + + if (i == previousIndex + 1) + { + ranks[previousIndex] = i; + } + else + { + RanksTies(ranks, previousIndex, i, definition); + } + + previousIndex = i; + } + + RanksTies(ranks, previousIndex, data.Length, definition); + return ranks; + } + + static void RanksTies(double[] ranks, int a, int b, RankDefinition definition) + { + // TODO: potential for PERF optimization + + double rank; + switch (definition) + { + case RankDefinition.Average: + { + rank = (b + a - 1) / 2d + 1; + break; + } + + case RankDefinition.Min: + { + rank = a + 1; + break; + } + + case RankDefinition.Max: + { + rank = b; + break; + } + + default: + throw new NotSupportedException(); + } + + for (int k = a; k < b; k++) + { + ranks[k] = rank; + } + } +} diff --git a/MathNet.Numerics/Statistics/Statistics.cs b/MathNet.Numerics/Statistics/Statistics.cs new file mode 100644 index 0000000..c47020e --- /dev/null +++ b/MathNet.Numerics/Statistics/Statistics.cs @@ -0,0 +1,1541 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Statistics; + +/// +/// Extension methods to return basic statistics on set of data. +/// +public static class Statistics +{ + /// + /// Returns the minimum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The minimum value in the sample data. + public static double Minimum(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.Minimum(array) + : StreamingStatistics.Minimum(data); + } + + /// + /// Returns the minimum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The minimum value in the sample data. + public static float Minimum(this IEnumerable data) + { + var array = data as float[]; + return array != null + ? ArrayStatistics.Minimum(array) + : StreamingStatistics.Minimum(data); + } + + /// + /// Returns the minimum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The sample data. + /// The minimum value in the sample data. + public static double Minimum(this IEnumerable data) + { + return StreamingStatistics.Minimum(data.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Returns the maximum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The maximum value in the sample data. + public static double Maximum(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.Maximum(array) + : StreamingStatistics.Maximum(data); + } + + /// + /// Returns the maximum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The maximum value in the sample data. + public static float Maximum(this IEnumerable data) + { + var array = data as float[]; + return array != null + ? ArrayStatistics.Maximum(array) + : StreamingStatistics.Maximum(data); + } + + /// + /// Returns the maximum value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The sample data. + /// The maximum value in the sample data. + public static double Maximum(this IEnumerable data) + { + return StreamingStatistics.Maximum(data.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Returns the minimum absolute value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The minimum value in the sample data. + public static double MinimumAbsolute(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.MinimumAbsolute(array) + : StreamingStatistics.MinimumAbsolute(data); + } + + /// + /// Returns the minimum absolute value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The minimum value in the sample data. + public static float MinimumAbsolute(this IEnumerable data) + { + var array = data as float[]; + return array != null + ? ArrayStatistics.MinimumAbsolute(array) + : StreamingStatistics.MinimumAbsolute(data); + } + + /// + /// Returns the maximum absolute value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The maximum value in the sample data. + public static double MaximumAbsolute(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.MaximumAbsolute(array) + : StreamingStatistics.MaximumAbsolute(data); + } + + /// + /// Returns the maximum absolute value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The maximum value in the sample data. + public static float MaximumAbsolute(this IEnumerable data) + { + var array = data as float[]; + return array != null + ? ArrayStatistics.MaximumAbsolute(array) + : StreamingStatistics.MaximumAbsolute(data); + } + + /// + /// Returns the minimum magnitude and phase value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The minimum value in the sample data. + public static Complex MinimumMagnitudePhase(this IEnumerable data) + { + var array = data as Complex[]; + return array != null + ? ArrayStatistics.MinimumMagnitudePhase(array) + : StreamingStatistics.MinimumMagnitudePhase(data); + } + + /// + /// Returns the minimum magnitude and phase value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The minimum value in the sample data. + public static Complex32 MinimumMagnitudePhase(this IEnumerable data) + { + var array = data as Complex32[]; + return array != null + ? ArrayStatistics.MinimumMagnitudePhase(array) + : StreamingStatistics.MinimumMagnitudePhase(data); + } + + /// + /// Returns the maximum magnitude and phase value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The minimum value in the sample data. + public static Complex MaximumMagnitudePhase(this IEnumerable data) + { + var array = data as Complex[]; + return array != null + ? ArrayStatistics.MaximumMagnitudePhase(array) + : StreamingStatistics.MaximumMagnitudePhase(data); + } + + /// + /// Returns the maximum magnitude and phase value in the sample data. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The sample data. + /// The minimum value in the sample data. + public static Complex32 MaximumMagnitudePhase(this IEnumerable data) + { + var array = data as Complex32[]; + return array != null + ? ArrayStatistics.MaximumMagnitudePhase(array) + : StreamingStatistics.MaximumMagnitudePhase(data); + } + + /// + /// Evaluates the sample mean, an estimate of the population mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the mean of. + /// The mean of the sample. + public static double Mean(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.Mean(array) + : StreamingStatistics.Mean(data); + } + + /// + /// Evaluates the sample mean, an estimate of the population mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the mean of. + /// The mean of the sample. + public static double Mean(this IEnumerable data) + { + var array = data as float[]; + return array != null + ? ArrayStatistics.Mean(array) + : StreamingStatistics.Mean(data); + } + + /// + /// Evaluates the sample mean, an estimate of the population mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The data to calculate the mean of. + /// The mean of the sample. + public static double Mean(this IEnumerable data) + { + return StreamingStatistics.Mean(data.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Evaluates the geometric mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the geometric mean of. + /// The geometric mean of the sample. + public static double GeometricMean(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.GeometricMean(array) + : StreamingStatistics.GeometricMean(data); + } + + /// + /// Evaluates the geometric mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the geometric mean of. + /// The geometric mean of the sample. + public static double GeometricMean(this IEnumerable data) + { + var array = data as float[]; + return array != null + ? ArrayStatistics.GeometricMean(array) + : StreamingStatistics.GeometricMean(data); + } + + /// + /// Evaluates the harmonic mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the harmonic mean of. + /// The harmonic mean of the sample. + public static double HarmonicMean(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.HarmonicMean(array) + : StreamingStatistics.HarmonicMean(data); + } + + /// + /// Evaluates the harmonic mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the harmonic mean of. + /// The harmonic mean of the sample. + public static double HarmonicMean(this IEnumerable data) + { + var array = data as float[]; + return array != null + ? ArrayStatistics.HarmonicMean(array) + : StreamingStatistics.HarmonicMean(data); + } + + /// + /// Estimates the unbiased population variance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// A subset of samples, sampled from the full population. + public static double Variance(this IEnumerable samples) + { + var array = samples as double[]; + return array != null + ? ArrayStatistics.Variance(array) + : StreamingStatistics.Variance(samples); + } + + /// + /// Estimates the unbiased population variance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// A subset of samples, sampled from the full population. + public static double Variance(this IEnumerable samples) + { + var array = samples as float[]; + return array != null + ? ArrayStatistics.Variance(array) + : StreamingStatistics.Variance(samples); + } + + /// + /// Estimates the unbiased population variance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// Null-entries are ignored. + /// + /// A subset of samples, sampled from the full population. + public static double Variance(this IEnumerable samples) + { + return StreamingStatistics.Variance(samples.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Evaluates the variance from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The full population data. + public static double PopulationVariance(this IEnumerable population) + { + var array = population as double[]; + return array != null + ? ArrayStatistics.PopulationVariance(array) + : StreamingStatistics.PopulationVariance(population); + } + + /// + /// Evaluates the variance from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The full population data. + public static double PopulationVariance(this IEnumerable population) + { + var array = population as float[]; + return array != null + ? ArrayStatistics.PopulationVariance(array) + : StreamingStatistics.PopulationVariance(population); + } + + /// + /// Evaluates the variance from the provided full population. + /// On a dataset of size N will use an N normalize and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The full population data. + public static double PopulationVariance(this IEnumerable population) + { + return StreamingStatistics.PopulationVariance(population.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// A subset of samples, sampled from the full population. + public static double StandardDeviation(this IEnumerable samples) + { + var array = samples as double[]; + return array != null + ? ArrayStatistics.StandardDeviation(array) + : StreamingStatistics.StandardDeviation(samples); + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// A subset of samples, sampled from the full population. + public static double StandardDeviation(this IEnumerable samples) + { + var array = samples as float[]; + return array != null + ? ArrayStatistics.StandardDeviation(array) + : StreamingStatistics.StandardDeviation(samples); + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// Null-entries are ignored. + /// + /// A subset of samples, sampled from the full population. + public static double StandardDeviation(this IEnumerable samples) + { + return StreamingStatistics.StandardDeviation(samples.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Evaluates the standard deviation from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The full population data. + public static double PopulationStandardDeviation(this IEnumerable population) + { + var array = population as double[]; + return array != null + ? ArrayStatistics.PopulationStandardDeviation(array) + : StreamingStatistics.PopulationStandardDeviation(population); + } + + /// + /// Evaluates the standard deviation from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The full population data. + public static double PopulationStandardDeviation(this IEnumerable population) + { + var array = population as float[]; + return array != null + ? ArrayStatistics.PopulationStandardDeviation(array) + : StreamingStatistics.PopulationStandardDeviation(population); + } + + /// + /// Evaluates the standard deviation from the provided full population. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The full population data. + public static double PopulationStandardDeviation(this IEnumerable population) + { + return StreamingStatistics.PopulationStandardDeviation(population.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Estimates the unbiased population skewness from the provided samples. + /// Uses a normalizer (Bessel's correction; type 2). + /// Returns NaN if data has less than three entries or if any entry is NaN. + /// + /// A subset of samples, sampled from the full population. + public static double Skewness(this IEnumerable samples) + { + return new RunningStatistics(samples).Skewness; + } + + /// + /// Estimates the unbiased population skewness from the provided samples. + /// Uses a normalizer (Bessel's correction; type 2). + /// Returns NaN if data has less than three entries or if any entry is NaN. + /// Null-entries are ignored. + /// + /// A subset of samples, sampled from the full population. + public static double Skewness(this IEnumerable samples) + { + return new RunningStatistics(samples.Where(d => d.HasValue).Select(d => d.Value)).Skewness; + } + + /// + /// Evaluates the skewness from the full population. + /// Does not use a normalizer and would thus be biased if applied to a subset (type 1). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// The full population data. + public static double PopulationSkewness(this IEnumerable population) + { + return new RunningStatistics(population).PopulationSkewness; + } + + /// + /// Evaluates the skewness from the full population. + /// Does not use a normalizer and would thus be biased if applied to a subset (type 1). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The full population data. + public static double PopulationSkewness(this IEnumerable population) + { + return new RunningStatistics(population.Where(d => d.HasValue).Select(d => d.Value)).PopulationSkewness; + } + + /// + /// Estimates the unbiased population kurtosis from the provided samples. + /// Uses a normalizer (Bessel's correction; type 2). + /// Returns NaN if data has less than four entries or if any entry is NaN. + /// + /// A subset of samples, sampled from the full population. + public static double Kurtosis(this IEnumerable samples) + { + return new RunningStatistics(samples).Kurtosis; + } + + /// + /// Estimates the unbiased population kurtosis from the provided samples. + /// Uses a normalizer (Bessel's correction; type 2). + /// Returns NaN if data has less than four entries or if any entry is NaN. + /// Null-entries are ignored. + /// + /// A subset of samples, sampled from the full population. + public static double Kurtosis(this IEnumerable samples) + { + return new RunningStatistics(samples.Where(d => d.HasValue).Select(d => d.Value)).Kurtosis; + } + + /// + /// Evaluates the kurtosis from the full population. + /// Does not use a normalizer and would thus be biased if applied to a subset (type 1). + /// Returns NaN if data has less than three entries or if any entry is NaN. + /// + /// The full population data. + public static double PopulationKurtosis(this IEnumerable population) + { + return new RunningStatistics(population).PopulationKurtosis; + } + + /// + /// Evaluates the kurtosis from the full population. + /// Does not use a normalizer and would thus be biased if applied to a subset (type 1). + /// Returns NaN if data has less than three entries or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The full population data. + public static double PopulationKurtosis(this IEnumerable population) + { + return new RunningStatistics(population.Where(d => d.HasValue).Select(d => d.Value)).PopulationKurtosis; + } + + /// + /// Estimates the sample mean and the unbiased population variance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or if any entry is NaN and NaN for variance if data has less than two entries or if any entry is NaN. + /// + /// The data to calculate the mean of. + /// The mean of the sample. + public static Tuple MeanVariance(this IEnumerable samples) + { + var array = samples as double[]; + return array != null + ? ArrayStatistics.MeanVariance(array) + : StreamingStatistics.MeanVariance(samples); + } + + /// + /// Estimates the sample mean and the unbiased population variance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or if any entry is NaN and NaN for variance if data has less than two entries or if any entry is NaN. + /// + /// The data to calculate the mean of. + /// The mean of the sample. + public static Tuple MeanVariance(this IEnumerable samples) + { + var array = samples as float[]; + return array != null + ? ArrayStatistics.MeanVariance(array) + : StreamingStatistics.MeanVariance(samples); + } + + /// + /// Estimates the sample mean and the unbiased population standard deviation from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or if any entry is NaN and NaN for standard deviation if data has less than two entries or if any entry is NaN. + /// + /// The data to calculate the mean of. + /// The mean of the sample. + public static Tuple MeanStandardDeviation(this IEnumerable samples) + { + var array = samples as double[]; + return array != null + ? ArrayStatistics.MeanStandardDeviation(array) + : StreamingStatistics.MeanStandardDeviation(samples); + } + + /// + /// Estimates the sample mean and the unbiased population standard deviation from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or if any entry is NaN and NaN for standard deviation if data has less than two entries or if any entry is NaN. + /// + /// The data to calculate the mean of. + /// The mean of the sample. + public static Tuple MeanStandardDeviation(this IEnumerable samples) + { + var array = samples as float[]; + return array != null + ? ArrayStatistics.MeanStandardDeviation(array) + : StreamingStatistics.MeanStandardDeviation(samples); + } + + /// + /// Estimates the unbiased population skewness and kurtosis from the provided samples in a single pass. + /// Uses a normalizer (Bessel's correction; type 2). + /// + /// A subset of samples, sampled from the full population. + public static Tuple SkewnessKurtosis(this IEnumerable samples) + { + var stats = new RunningStatistics(samples); + return new Tuple(stats.Skewness, stats.Kurtosis); + } + + /// + /// Evaluates the skewness and kurtosis from the full population. + /// Does not use a normalizer and would thus be biased if applied to a subset (type 1). + /// + /// The full population data. + public static Tuple PopulationSkewnessKurtosis(this IEnumerable population) + { + var stats = new RunningStatistics(population); + return new Tuple(stats.PopulationSkewness, stats.PopulationKurtosis); + } + + /// + /// Estimates the unbiased population covariance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// A subset of samples, sampled from the full population. + /// A subset of samples, sampled from the full population. + public static double Covariance(this IEnumerable samples1, IEnumerable samples2) + { + var array1 = samples1 as double[]; + var array2 = samples2 as double[]; + return array1 != null && array2 != null + ? ArrayStatistics.Covariance(array1, array2) + : StreamingStatistics.Covariance(samples1, samples2); + } + + /// + /// Estimates the unbiased population covariance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// A subset of samples, sampled from the full population. + /// A subset of samples, sampled from the full population. + public static double Covariance(this IEnumerable samples1, IEnumerable samples2) + { + var array1 = samples1 as float[]; + var array2 = samples2 as float[]; + return array1 != null && array2 != null + ? ArrayStatistics.Covariance(array1, array2) + : StreamingStatistics.Covariance(samples1, samples2); + } + + /// + /// Estimates the unbiased population covariance from the provided samples. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// Null-entries are ignored. + /// + /// A subset of samples, sampled from the full population. + /// A subset of samples, sampled from the full population. + public static double Covariance(this IEnumerable samples1, IEnumerable samples2) + { + return StreamingStatistics.Covariance(samples1.Where(d => d.HasValue).Select(d => d.Value), samples2.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Evaluates the population covariance from the provided full populations. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The full population data. + /// The full population data. + public static double PopulationCovariance(this IEnumerable population1, IEnumerable population2) + { + var array1 = population1 as double[]; + var array2 = population2 as double[]; + return array1 != null && array2 != null + ? ArrayStatistics.PopulationCovariance(array1, array2) + : StreamingStatistics.PopulationCovariance(population1, population2); + } + + /// + /// Evaluates the population covariance from the provided full populations. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The full population data. + /// The full population data. + public static double PopulationCovariance(this IEnumerable population1, IEnumerable population2) + { + var array1 = population1 as float[]; + var array2 = population2 as float[]; + return array1 != null && array2 != null + ? ArrayStatistics.PopulationCovariance(array1, array2) + : StreamingStatistics.PopulationCovariance(population1, population2); + } + + /// + /// Evaluates the population covariance from the provided full populations. + /// On a dataset of size N will use an N normalize and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The full population data. + /// The full population data. + public static double PopulationCovariance(this IEnumerable population1, IEnumerable population2) + { + return StreamingStatistics.PopulationCovariance(population1.Where(d => d.HasValue).Select(d => d.Value), population2.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Evaluates the root mean square (RMS) also known as quadratic mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the RMS of. + public static double RootMeanSquare(this IEnumerable data) + { + var array = data as double[]; + return array != null + ? ArrayStatistics.RootMeanSquare(array) + : StreamingStatistics.RootMeanSquare(data); + } + + /// + /// Evaluates the root mean square (RMS) also known as quadratic mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// The data to calculate the RMS of. + public static double RootMeanSquare(this IEnumerable data) + { + var array = data as float[]; + return array != null + ? ArrayStatistics.RootMeanSquare(array) + : StreamingStatistics.RootMeanSquare(data); + } + + /// + /// Evaluates the root mean square (RMS) also known as quadratic mean. + /// Returns NaN if data is empty or if any entry is NaN. + /// Null-entries are ignored. + /// + /// The data to calculate the mean of. + public static double RootMeanSquare(this IEnumerable data) + { + return StreamingStatistics.RootMeanSquare(data.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Estimates the sample median from the provided samples (R8). + /// + /// The data sample sequence. + public static double Median(this IEnumerable data) + { + double[] array = data.ToArray(); + return ArrayStatistics.MedianInplace(array); + } + + /// + /// Estimates the sample median from the provided samples (R8). + /// + /// The data sample sequence. + public static float Median(this IEnumerable data) + { + float[] array = data.ToArray(); + return ArrayStatistics.MedianInplace(array); + } + + /// + /// Estimates the sample median from the provided samples (R8). + /// + /// The data sample sequence. + public static double Median(this IEnumerable data) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + return ArrayStatistics.MedianInplace(array); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + public static double Quantile(this IEnumerable data, double tau) + { + double[] array = data.ToArray(); + return ArrayStatistics.QuantileInplace(array, tau); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + public static float Quantile(this IEnumerable data, double tau) + { + float[] array = data.ToArray(); + return ArrayStatistics.QuantileInplace(array, tau); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + public static double Quantile(this IEnumerable data, double tau) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + return ArrayStatistics.QuantileInplace(array, tau); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static Func QuantileFunc(this IEnumerable data) + { + double[] array = data.ToArray(); + Array.Sort(array); + return tau => SortedArrayStatistics.Quantile(array, tau); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static Func QuantileFunc(this IEnumerable data) + { + float[] array = data.ToArray(); + Array.Sort(array); + return tau => SortedArrayStatistics.Quantile(array, tau); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static Func QuantileFunc(this IEnumerable data) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + Array.Sort(array); + return tau => SortedArrayStatistics.Quantile(array, tau); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// Quantile definition, to choose what product/definition it should be consistent with + public static double QuantileCustom(this IEnumerable data, double tau, QuantileDefinition definition) + { + double[] array = data.ToArray(); + return ArrayStatistics.QuantileCustomInplace(array, tau, definition); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// Quantile definition, to choose what product/definition it should be consistent with + public static float QuantileCustom(this IEnumerable data, double tau, QuantileDefinition definition) + { + float[] array = data.ToArray(); + return ArrayStatistics.QuantileCustomInplace(array, tau, definition); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + /// Quantile definition, to choose what product/definition it should be consistent with + public static double QuantileCustom(this IEnumerable data, double tau, QuantileDefinition definition) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + return ArrayStatistics.QuantileCustomInplace(array, tau, definition); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile definition, to choose what product/definition it should be consistent with + public static Func QuantileCustomFunc(this IEnumerable data, QuantileDefinition definition) + { + double[] array = data.ToArray(); + Array.Sort(array); + return tau => SortedArrayStatistics.QuantileCustom(array, tau, definition); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile definition, to choose what product/definition it should be consistent with + public static Func QuantileCustomFunc(this IEnumerable data, QuantileDefinition definition) + { + float[] array = data.ToArray(); + Array.Sort(array); + return tau => SortedArrayStatistics.QuantileCustom(array, tau, definition); + } + + /// + /// Estimates the tau-th quantile from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile definition, to choose what product/definition it should be consistent with + public static Func QuantileCustomFunc(this IEnumerable data, QuantileDefinition definition) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + Array.Sort(array); + return tau => SortedArrayStatistics.QuantileCustom(array, tau, definition); + } + + /// + /// Estimates the p-Percentile value from the provided samples. + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + /// Percentile selector, between 0 and 100 (inclusive). + public static double Percentile(this IEnumerable data, int p) + { + double[] array = data.ToArray(); + return ArrayStatistics.PercentileInplace(array, p); + } + + /// + /// Estimates the p-Percentile value from the provided samples. + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + /// Percentile selector, between 0 and 100 (inclusive). + public static float Percentile(this IEnumerable data, int p) + { + float[] array = data.ToArray(); + return ArrayStatistics.PercentileInplace(array, p); + } + + /// + /// Estimates the p-Percentile value from the provided samples. + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + /// Percentile selector, between 0 and 100 (inclusive). + public static double Percentile(this IEnumerable data, int p) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + return ArrayStatistics.PercentileInplace(array, p); + } + + /// + /// Estimates the p-Percentile value from the provided samples. + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static Func PercentileFunc(this IEnumerable data) + { + double[] array = data.ToArray(); + Array.Sort(array); + return p => SortedArrayStatistics.Percentile(array, p); + } + + /// + /// Estimates the p-Percentile value from the provided samples. + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static Func PercentileFunc(this IEnumerable data) + { + float[] array = data.ToArray(); + Array.Sort(array); + return p => SortedArrayStatistics.Percentile(array, p); + } + + /// + /// Estimates the p-Percentile value from the provided samples. + /// If a non-integer Percentile is needed, use Quantile instead. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static Func PercentileFunc(this IEnumerable data) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + Array.Sort(array); + return p => SortedArrayStatistics.Percentile(array, p); + } + + /// + /// Estimates the first quartile value from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static double LowerQuartile(this IEnumerable data) + { + double[] array = data.ToArray(); + return ArrayStatistics.LowerQuartileInplace(array); + } + + /// + /// Estimates the first quartile value from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static float LowerQuartile(this IEnumerable data) + { + float[] array = data.ToArray(); + return ArrayStatistics.LowerQuartileInplace(array); + } + + /// + /// Estimates the first quartile value from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static double LowerQuartile(this IEnumerable data) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + return ArrayStatistics.LowerQuartileInplace(array); + } + + /// + /// Estimates the third quartile value from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static double UpperQuartile(this IEnumerable data) + { + double[] array = data.ToArray(); + return ArrayStatistics.UpperQuartileInplace(array); + } + + /// + /// Estimates the third quartile value from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static float UpperQuartile(this IEnumerable data) + { + float[] array = data.ToArray(); + return ArrayStatistics.UpperQuartileInplace(array); + } + + /// + /// Estimates the third quartile value from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static double UpperQuartile(this IEnumerable data) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + return ArrayStatistics.UpperQuartileInplace(array); + } + + /// + /// Estimates the inter-quartile range from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static double InterquartileRange(this IEnumerable data) + { + double[] array = data.ToArray(); + return ArrayStatistics.InterquartileRangeInplace(array); + } + + /// + /// Estimates the inter-quartile range from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static float InterquartileRange(this IEnumerable data) + { + float[] array = data.ToArray(); + return ArrayStatistics.InterquartileRangeInplace(array); + } + + /// + /// Estimates the inter-quartile range from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static double InterquartileRange(this IEnumerable data) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + return ArrayStatistics.InterquartileRangeInplace(array); + } + + /// + /// Estimates {min, lower-quantile, median, upper-quantile, max} from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static double[] FiveNumberSummary(this IEnumerable data) + { + double[] array = data.ToArray(); + return ArrayStatistics.FiveNumberSummaryInplace(array); + } + + /// + /// Estimates {min, lower-quantile, median, upper-quantile, max} from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static float[] FiveNumberSummary(this IEnumerable data) + { + float[] array = data.ToArray(); + return ArrayStatistics.FiveNumberSummaryInplace(array); + } + + /// + /// Estimates {min, lower-quantile, median, upper-quantile, max} from the provided samples. + /// Approximately median-unbiased regardless of the sample distribution (R8). + /// + /// The data sample sequence. + public static double[] FiveNumberSummary(this IEnumerable data) + { + double[] array = data.Where(d => d.HasValue).Select(d => d.Value).ToArray(); + return ArrayStatistics.FiveNumberSummaryInplace(array); + } + + /// + /// Returns the order statistic (order 1..N) from the provided samples. + /// + /// The data sample sequence. + /// One-based order of the statistic, must be between 1 and N (inclusive). + public static double OrderStatistic(IEnumerable data, int order) + { + double[] array = data.ToArray(); + return ArrayStatistics.OrderStatisticInplace(array, order); + } + + /// + /// Returns the order statistic (order 1..N) from the provided samples. + /// + /// The data sample sequence. + /// One-based order of the statistic, must be between 1 and N (inclusive). + public static float OrderStatistic(IEnumerable data, int order) + { + float[] array = data.ToArray(); + return ArrayStatistics.OrderStatisticInplace(array, order); + } + + /// + /// Returns the order statistic (order 1..N) from the provided samples. + /// + /// The data sample sequence. + public static Func OrderStatisticFunc(IEnumerable data) + { + double[] array = data.ToArray(); + Array.Sort(array); + return order => SortedArrayStatistics.OrderStatistic(array, order); + } + + /// + /// Returns the order statistic (order 1..N) from the provided samples. + /// + /// The data sample sequence. + public static Func OrderStatisticFunc(IEnumerable data) + { + float[] array = data.ToArray(); + Array.Sort(array); + return order => SortedArrayStatistics.OrderStatistic(array, order); + } + + /// + /// Evaluates the rank of each entry of the provided samples. + /// The rank definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static double[] Ranks(this IEnumerable data, RankDefinition definition = RankDefinition.Default) + { + double[] array = data.ToArray(); + return ArrayStatistics.RanksInplace(array, definition); + } + + /// + /// Evaluates the rank of each entry of the provided samples. + /// The rank definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static float[] Ranks(this IEnumerable data, RankDefinition definition = RankDefinition.Default) + { + float[] array = data.ToArray(); + return ArrayStatistics.RanksInplace(array, definition); + } + + /// + /// Evaluates the rank of each entry of the provided samples. + /// The rank definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static double[] Ranks(this IEnumerable data, RankDefinition definition = RankDefinition.Default) + { + return Ranks(data.Where(d => d.HasValue).Select(d => d.Value), definition); + } + + /// + /// Estimates the quantile tau from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile value. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static double QuantileRank(this IEnumerable data, double x, RankDefinition definition = RankDefinition.Default) + { + double[] array = data.ToArray(); + Array.Sort(array); + return SortedArrayStatistics.QuantileRank(array, x, definition); + } + + /// + /// Estimates the quantile tau from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile value. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static double QuantileRank(this IEnumerable data, float x, RankDefinition definition = RankDefinition.Default) + { + float[] array = data.ToArray(); + Array.Sort(array); + return SortedArrayStatistics.QuantileRank(array, x, definition); + } + + /// + /// Estimates the quantile tau from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Quantile value. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static double QuantileRank(this IEnumerable data, double x, RankDefinition definition = RankDefinition.Default) + { + return QuantileRank(data.Where(d => d.HasValue).Select(d => d.Value), x, definition); + } + + /// + /// Estimates the quantile tau from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static Func QuantileRankFunc(this IEnumerable data, RankDefinition definition = RankDefinition.Default) + { + double[] array = data.ToArray(); + Array.Sort(array); + return x => SortedArrayStatistics.QuantileRank(array, x, definition); + } + + /// + /// Estimates the quantile tau from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static Func QuantileRankFunc(this IEnumerable data, RankDefinition definition = RankDefinition.Default) + { + float[] array = data.ToArray(); + Array.Sort(array); + return x => SortedArrayStatistics.QuantileRank(array, x, definition); + } + + /// + /// Estimates the quantile tau from the provided samples. + /// The tau-th quantile is the data value where the cumulative distribution + /// function crosses tau. The quantile definition can be specified to be compatible + /// with an existing system. + /// + /// The data sample sequence. + /// Rank definition, to choose how ties should be handled and what product/definition it should be consistent with + public static Func QuantileRankFunc(this IEnumerable data, RankDefinition definition = RankDefinition.Default) + { + return QuantileRankFunc(data.Where(d => d.HasValue).Select(d => d.Value), definition); + } + + /// + /// Estimates the empirical cumulative distribution function (CDF) at x from the provided samples. + /// + /// The data sample sequence. + /// The value where to estimate the CDF at. + public static double EmpiricalCDF(this IEnumerable data, double x) + { + double[] array = data.ToArray(); + Array.Sort(array); + return SortedArrayStatistics.EmpiricalCDF(array, x); + } + + /// + /// Estimates the empirical cumulative distribution function (CDF) at x from the provided samples. + /// + /// The data sample sequence. + /// The value where to estimate the CDF at. + public static double EmpiricalCDF(this IEnumerable data, float x) + { + float[] array = data.ToArray(); + Array.Sort(array); + return SortedArrayStatistics.EmpiricalCDF(array, x); + } + + /// + /// Estimates the empirical cumulative distribution function (CDF) at x from the provided samples. + /// + /// The data sample sequence. + /// The value where to estimate the CDF at. + public static double EmpiricalCDF(this IEnumerable data, double x) + { + return EmpiricalCDF(data.Where(d => d.HasValue).Select(d => d.Value), x); + } + + /// + /// Estimates the empirical cumulative distribution function (CDF) at x from the provided samples. + /// + /// The data sample sequence. + public static Func EmpiricalCDFFunc(this IEnumerable data) + { + double[] array = data.ToArray(); + Array.Sort(array); + return x => SortedArrayStatistics.EmpiricalCDF(array, x); + } + + /// + /// Estimates the empirical cumulative distribution function (CDF) at x from the provided samples. + /// + /// The data sample sequence. + public static Func EmpiricalCDFFunc(this IEnumerable data) + { + float[] array = data.ToArray(); + Array.Sort(array); + return x => SortedArrayStatistics.EmpiricalCDF(array, x); + } + + /// + /// Estimates the empirical cumulative distribution function (CDF) at x from the provided samples. + /// + /// The data sample sequence. + public static Func EmpiricalCDFFunc(this IEnumerable data) + { + return EmpiricalCDFFunc(data.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Estimates the empirical inverse CDF at tau from the provided samples. + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + public static double EmpiricalInvCDF(this IEnumerable data, double tau) + { + double[] array = data.ToArray(); + return ArrayStatistics.QuantileCustomInplace(array, tau, QuantileDefinition.EmpiricalInvCDF); + } + + /// + /// Estimates the empirical inverse CDF at tau from the provided samples. + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + public static float EmpiricalInvCDF(this IEnumerable data, double tau) + { + float[] array = data.ToArray(); + return ArrayStatistics.QuantileCustomInplace(array, tau, QuantileDefinition.EmpiricalInvCDF); + } + + /// + /// Estimates the empirical inverse CDF at tau from the provided samples. + /// + /// The data sample sequence. + /// Quantile selector, between 0.0 and 1.0 (inclusive). + public static double EmpiricalInvCDF(this IEnumerable data, double tau) + { + return EmpiricalInvCDF(data.Where(d => d.HasValue).Select(d => d.Value), tau); + } + + /// + /// Estimates the empirical inverse CDF at tau from the provided samples. + /// + /// The data sample sequence. + public static Func EmpiricalInvCDFFunc(this IEnumerable data) + { + double[] array = data.ToArray(); + Array.Sort(array); + return tau => SortedArrayStatistics.QuantileCustom(array, tau, QuantileDefinition.EmpiricalInvCDF); + } + + /// + /// Estimates the empirical inverse CDF at tau from the provided samples. + /// + /// The data sample sequence. + public static Func EmpiricalInvCDFFunc(this IEnumerable data) + { + float[] array = data.ToArray(); + Array.Sort(array); + return tau => SortedArrayStatistics.QuantileCustom(array, tau, QuantileDefinition.EmpiricalInvCDF); + } + + /// + /// Estimates the empirical inverse CDF at tau from the provided samples. + /// + /// The data sample sequence. + public static Func EmpiricalInvCDFFunc(this IEnumerable data) + { + return EmpiricalInvCDFFunc(data.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Calculates the entropy of a stream of double values in bits. + /// Returns NaN if any of the values in the stream are NaN. + /// + /// The data sample sequence. + public static double Entropy(IEnumerable data) + { + return StreamingStatistics.Entropy(data); + } + + /// + /// Calculates the entropy of a stream of double values in bits. + /// Returns NaN if any of the values in the stream are NaN. + /// Null-entries are ignored. + /// + /// The data sample sequence. + public static double Entropy(IEnumerable data) + { + return StreamingStatistics.Entropy(data.Where(d => d.HasValue).Select(d => d.Value)); + } + + /// + /// Evaluates the sample mean over a moving window, for each samples. + /// Returns NaN if no data is empty or if any entry is NaN. + /// + /// The sample stream to calculate the mean of. + /// The number of last samples to consider. + public static IEnumerable MovingAverage(this IEnumerable samples, int windowSize) + { + var movingStatistics = new MovingStatistics(windowSize); + return samples.Select(sample => + { + movingStatistics.Push(sample); + return movingStatistics.Mean; + }); + } +} diff --git a/MathNet.Numerics/Statistics/StreamingStatistics.cs b/MathNet.Numerics/Statistics/StreamingStatistics.cs new file mode 100644 index 0000000..c6de5f2 --- /dev/null +++ b/MathNet.Numerics/Statistics/StreamingStatistics.cs @@ -0,0 +1,828 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2015 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using MathNet.Numerics.Properties; + +using System; +using System.Collections.Generic; +using System.Linq; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics.Statistics; + +/// +/// Statistics operating on an IEnumerable in a single pass, without keeping the full data in memory. +/// Can be used in a streaming way, e.g. on large datasets not fitting into memory. +/// +/// +/// +/// +public static class StreamingStatistics +{ + /// + /// Returns the smallest value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double Minimum(IEnumerable stream) + { + double min = double.PositiveInfinity; + bool any = false; + + foreach (var d in stream) + { + if (d < min || double.IsNaN(d)) + { + min = d; + } + + any = true; + } + + return any ? min : double.NaN; + } + + /// + /// Returns the smallest value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static float Minimum(IEnumerable stream) + { + float min = float.PositiveInfinity; + bool any = false; + + foreach (var d in stream) + { + if (d < min || float.IsNaN(d)) + { + min = d; + } + + any = true; + } + + return any ? min : float.NaN; + } + + /// + /// Returns the largest value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double Maximum(IEnumerable stream) + { + double max = double.NegativeInfinity; + bool any = false; + + foreach (var d in stream) + { + if (d > max || double.IsNaN(d)) + { + max = d; + } + + any = true; + } + + return any ? max : double.NaN; + } + + /// + /// Returns the largest value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static float Maximum(IEnumerable stream) + { + float max = float.NegativeInfinity; + bool any = false; + + foreach (var d in stream) + { + if (d > max || float.IsNaN(d)) + { + max = d; + } + + any = true; + } + + return any ? max : float.NaN; + } + + /// + /// Returns the smallest absolute value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double MinimumAbsolute(IEnumerable stream) + { + double min = double.PositiveInfinity; + bool any = false; + + foreach (var d in stream) + { + if (Math.Abs(d) < min || double.IsNaN(d)) + { + min = Math.Abs(d); + } + + any = true; + } + + return any ? min : double.NaN; + } + + /// + /// Returns the smallest absolute value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static float MinimumAbsolute(IEnumerable stream) + { + float min = float.PositiveInfinity; + bool any = false; + + foreach (var d in stream) + { + if (Math.Abs(d) < min || float.IsNaN(d)) + { + min = Math.Abs(d); + } + + any = true; + } + + return any ? min : float.NaN; + } + + /// + /// Returns the largest absolute value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double MaximumAbsolute(IEnumerable stream) + { + double max = 0.0d; + bool any = false; + + foreach (var d in stream) + { + if (Math.Abs(d) > max || double.IsNaN(d)) + { + max = Math.Abs(d); + } + + any = true; + } + + return any ? max : double.NaN; + } + + /// + /// Returns the largest absolute value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static float MaximumAbsolute(IEnumerable stream) + { + float max = 0.0f; + bool any = false; + + foreach (var d in stream) + { + if (Math.Abs(d) > max || float.IsNaN(d)) + { + max = Math.Abs(d); + } + + any = true; + } + + return any ? max : float.NaN; + } + + /// + /// Returns the smallest absolute value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static Complex MinimumMagnitudePhase(IEnumerable stream) + { + double minMagnitude = double.PositiveInfinity; + Complex min = new Complex(double.PositiveInfinity, double.PositiveInfinity); + bool any = false; + + foreach (var d in stream) + { + double magnitude = d.Magnitude; + if (double.IsNaN(magnitude)) + { + return new Complex(double.NaN, double.NaN); + } + + if (magnitude < minMagnitude || magnitude == minMagnitude && d.Phase < min.Phase) + { + minMagnitude = magnitude; + min = d; + } + + any = true; + } + + return any ? min : new Complex(double.NaN, double.NaN); + } + + /// + /// Returns the smallest absolute value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static Complex32 MinimumMagnitudePhase(IEnumerable stream) + { + float minMagnitude = float.PositiveInfinity; + Complex32 min = new Complex32(float.PositiveInfinity, float.PositiveInfinity); + bool any = false; + + foreach (var d in stream) + { + float magnitude = d.Magnitude; + if (float.IsNaN(magnitude)) + { + return new Complex32(float.NaN, float.NaN); + } + + if (magnitude < minMagnitude || magnitude == minMagnitude && d.Phase < min.Phase) + { + minMagnitude = magnitude; + min = d; + } + + any = true; + } + + return any ? min : new Complex32(float.NaN, float.NaN); + } + + /// + /// Returns the largest absolute value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static Complex MaximumMagnitudePhase(IEnumerable stream) + { + double maxMagnitude = 0.0d; + Complex max = Complex.Zero; + bool any = false; + + foreach (var d in stream) + { + double magnitude = d.Magnitude; + if (double.IsNaN(magnitude)) + { + return new Complex(double.NaN, double.NaN); + } + + if (magnitude > maxMagnitude || magnitude == maxMagnitude && d.Phase > max.Phase) + { + maxMagnitude = magnitude; + max = d; + } + + any = true; + } + + return any ? max : new Complex(double.NaN, double.NaN); + } + + /// + /// Returns the largest absolute value from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static Complex32 MaximumMagnitudePhase(IEnumerable stream) + { + float maxMagnitude = 0.0f; + Complex32 max = Complex32.Zero; + bool any = false; + + foreach (var d in stream) + { + float magnitude = d.Magnitude; + if (float.IsNaN(magnitude)) + { + return new Complex32(float.NaN, float.NaN); + } + + if (magnitude > maxMagnitude || magnitude == maxMagnitude && d.Phase > max.Phase) + { + maxMagnitude = magnitude; + max = d; + } + + any = true; + } + + return any ? max : new Complex32(float.NaN, float.NaN); + } + + /// + /// Estimates the arithmetic sample mean from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double Mean(IEnumerable stream) + { + double mean = 0; + ulong m = 0; + bool any = false; + + foreach (var d in stream) + { + mean += (d - mean) / ++m; + any = true; + } + + return any ? mean : double.NaN; + } + + /// + /// Estimates the arithmetic sample mean from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double Mean(IEnumerable stream) + { + return Mean(stream.Select(x => (double)x)); + } + + /// + /// Evaluates the geometric mean of the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double GeometricMean(IEnumerable stream) + { + ulong m = 0; + double sum = 0; + + foreach (var d in stream) + { + sum += Math.Log(d); + m++; + } + + return m > 0 ? Math.Exp(sum / m) : double.NaN; + } + + /// + /// Evaluates the geometric mean of the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double GeometricMean(IEnumerable stream) + { + return GeometricMean(stream.Select(x => (double)x)); + } + + /// + /// Evaluates the harmonic mean of the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double HarmonicMean(IEnumerable stream) + { + ulong m = 0; + double sum = 0; + + foreach (var d in stream) + { + sum += 1.0 / d; + m++; + } + + return m > 0 ? m / sum : double.NaN; + } + + /// + /// Evaluates the harmonic mean of the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double HarmonicMean(IEnumerable stream) + { + return HarmonicMean(stream.Select(x => (double)x)); + } + + /// + /// Estimates the unbiased population variance from the provided samples as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double Variance(IEnumerable samples) + { + double variance = 0; + double sum = 0; + ulong count = 0; + + using (var iterator = samples.GetEnumerator()) + { + if (iterator.MoveNext()) + { + count++; + sum = iterator.Current; + } + + while (iterator.MoveNext()) + { + count++; + double xi = iterator.Current; + sum += xi; + double diff = (count * xi) - sum; + variance += (diff * diff) / (count * (count - 1)); + } + } + + return count > 1 ? variance / (count - 1) : double.NaN; + } + + /// + /// Estimates the unbiased population variance from the provided samples as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double Variance(IEnumerable samples) + { + return Variance(samples.Select(x => (double)x)); + } + + /// + /// Evaluates the population variance from the full population provided as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double PopulationVariance(IEnumerable population) + { + double variance = 0; + double sum = 0; + ulong count = 0; + + using (var iterator = population.GetEnumerator()) + { + if (iterator.MoveNext()) + { + count++; + sum = iterator.Current; + } + + while (iterator.MoveNext()) + { + count++; + double xi = iterator.Current; + sum += xi; + double diff = (count * xi) - sum; + variance += (diff * diff) / (count * (count - 1)); + } + } + + return variance / count; + } + + /// + /// Evaluates the population variance from the full population provided as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double PopulationVariance(IEnumerable population) + { + return PopulationVariance(population.Select(x => (double)x)); + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double StandardDeviation(IEnumerable samples) + { + return Math.Sqrt(Variance(samples)); + } + + /// + /// Estimates the unbiased population standard deviation from the provided samples as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double StandardDeviation(IEnumerable samples) + { + return Math.Sqrt(Variance(samples)); + } + + /// + /// Evaluates the population standard deviation from the full population provided as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double PopulationStandardDeviation(IEnumerable population) + { + return Math.Sqrt(PopulationVariance(population)); + } + + /// + /// Evaluates the population standard deviation from the full population provided as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double PopulationStandardDeviation(IEnumerable population) + { + return Math.Sqrt(PopulationVariance(population)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population variance from the provided samples as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN, and NaN for variance if data has less than two entries or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static Tuple MeanVariance(IEnumerable samples) + { + double mean = 0; + double variance = 0; + double sum = 0; + ulong count = 0; + + using (var iterator = samples.GetEnumerator()) + { + if (iterator.MoveNext()) + { + count++; + sum = mean = iterator.Current; + } + + while (iterator.MoveNext()) + { + count++; + double xi = iterator.Current; + sum += xi; + double diff = (count * xi) - sum; + variance += (diff * diff) / (count * (count - 1)); + mean += (xi - mean) / count; + } + } + + return new Tuple( + count > 0 ? mean : double.NaN, + count > 1 ? variance / (count - 1) : double.NaN); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population variance from the provided samples as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN, and NaN for variance if data has less than two entries or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static Tuple MeanVariance(IEnumerable samples) + { + return MeanVariance(samples.Select(x => (double)x)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population standard deviation from the provided samples as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN, and NaN for standard deviation if data has less than two entries or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static Tuple MeanStandardDeviation(IEnumerable samples) + { + var meanVariance = MeanVariance(samples); + return new Tuple(meanVariance.Item1, Math.Sqrt(meanVariance.Item2)); + } + + /// + /// Estimates the arithmetic sample mean and the unbiased population standard deviation from the provided samples as enumerable sequence, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN for mean if data is empty or any entry is NaN, and NaN for standard deviation if data has less than two entries or if any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static Tuple MeanStandardDeviation(IEnumerable samples) + { + return MeanStandardDeviation(samples.Select(x => (double)x)); + } + + /// + /// Estimates the unbiased population covariance from the provided two sample enumerable sequences, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// First sample stream. + /// Second sample stream. + public static double Covariance(IEnumerable samples1, IEnumerable samples2) + { + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + var n = 0; + var mean1 = 0.0; + var mean2 = 0.0; + var comoment = 0.0; + + using (var s1 = samples1.GetEnumerator()) + using (var s2 = samples2.GetEnumerator()) + { + while (s1.MoveNext()) + { + if (!s2.MoveNext()) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var mean2Prev = mean2; + n++; + mean1 += (s1.Current - mean1) / n; + mean2 += (s2.Current - mean2) / n; + comoment += (s1.Current - mean1) * (s2.Current - mean2Prev); + } + + if (s2.MoveNext()) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + } + + return n > 1 ? comoment / (n - 1) : double.NaN; + } + + /// + /// Estimates the unbiased population covariance from the provided two sample enumerable sequences, in a single pass without memoization. + /// On a dataset of size N will use an N-1 normalizer (Bessel's correction). + /// Returns NaN if data has less than two entries or if any entry is NaN. + /// + /// First sample stream. + /// Second sample stream. + public static double Covariance(IEnumerable samples1, IEnumerable samples2) + { + return Covariance(samples1.Select(x => (double)x), samples2.Select(x => (double)x)); + } + + /// + /// Evaluates the population covariance from the full population provided as two enumerable sequences, in a single pass without memoization. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// First population stream. + /// Second population stream. + public static double PopulationCovariance(IEnumerable population1, IEnumerable population2) + { + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + var n = 0; + var mean1 = 0.0; + var mean2 = 0.0; + var comoment = 0.0; + + using (var p1 = population1.GetEnumerator()) + using (var p2 = population2.GetEnumerator()) + { + while (p1.MoveNext()) + { + if (!p2.MoveNext()) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + var mean2Prev = mean2; + n++; + mean1 += (p1.Current - mean1) / n; + mean2 += (p2.Current - mean2) / n; + comoment += (p1.Current - mean1) * (p2.Current - mean2Prev); + } + + if (p2.MoveNext()) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + } + + return comoment / n; + } + + /// + /// Evaluates the population covariance from the full population provided as two enumerable sequences, in a single pass without memoization. + /// On a dataset of size N will use an N normalizer and would thus be biased if applied to a subset. + /// Returns NaN if data is empty or if any entry is NaN. + /// + /// First population stream. + /// Second population stream. + public static double PopulationCovariance(IEnumerable population1, IEnumerable population2) + { + return PopulationCovariance(population1.Select(x => (double)x), population2.Select(x => (double)x)); + } + + /// + /// Estimates the root mean square (RMS) also known as quadratic mean from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double RootMeanSquare(IEnumerable stream) + { + double mean = 0; + ulong m = 0; + bool any = false; + + foreach (var d in stream) + { + mean += (d * d - mean) / ++m; + any = true; + } + + return any ? Math.Sqrt(mean) : double.NaN; + } + + /// + /// Estimates the root mean square (RMS) also known as quadratic mean from the enumerable, in a single pass without memoization. + /// Returns NaN if data is empty or any entry is NaN. + /// + /// Sample stream, no sorting is assumed. + public static double RootMeanSquare(IEnumerable stream) + { + return RootMeanSquare(stream.Select(x => (double)x)); + } + + /// + /// Calculates the entropy of a stream of double values. + /// Returns NaN if any of the values in the stream are NaN. + /// + /// The input stream to evaluate. + /// + public static double Entropy(IEnumerable stream) + { + // http://en.wikipedia.org/wiki/Shannon_entropy + + var index = new Dictionary(); + + // count the number of occurrences of each item in the stream + int totalCount = 0; + foreach (double value in stream) + { + if (double.IsNaN(value)) + { + return double.NaN; + } + + double currentValueCount; + if (index.TryGetValue(value, out currentValueCount)) + { + index[value] = ++currentValueCount; + } + else + { + index.Add(value, 1); + } + + ++totalCount; + } + + // calculate the entropy of the stream + double entropy = 0; + foreach (var item in index) + { + double p = item.Value / totalCount; + entropy += p * Math.Log(p, 2); + } + + return -entropy; + } +} diff --git a/MathNet.Numerics/Threading/CommonParallel.cs b/MathNet.Numerics/Threading/CommonParallel.cs new file mode 100644 index 0000000..8372000 --- /dev/null +++ b/MathNet.Numerics/Threading/CommonParallel.cs @@ -0,0 +1,362 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; + +#if !NETSTANDARD1_3 +using System.Threading.Tasks; +using System.Collections.Concurrent; +#endif + +namespace MathNet.Numerics.Threading; + +/// +/// Used to simplify parallel code, particularly between the .NET 4.0 and Silverlight Code. +/// +internal static class CommonParallel +{ + static ParallelOptions CreateParallelOptions() + { + return new ParallelOptions + { + MaxDegreeOfParallelism = Control.MaxDegreeOfParallelism, + TaskScheduler = Control.TaskScheduler, + }; + } + + /// + /// Executes a for loop in which iterations may run in parallel. + /// + /// The start index, inclusive. + /// The end index, exclusive. + /// The body to be invoked for each iteration range. + public static void For(int fromInclusive, int toExclusive, Action body) + { + For(fromInclusive, toExclusive, Math.Max(1, (toExclusive - fromInclusive) / Control.MaxDegreeOfParallelism), body); + } + + /// + /// Executes a for loop in which iterations may run in parallel. + /// + /// The start index, inclusive. + /// The end index, exclusive. + /// The partition size for splitting work into smaller pieces. + /// The body to be invoked for each iteration range. + public static void For(int fromInclusive, int toExclusive, int rangeSize, Action body) + { + if (body == null) + { + throw new ArgumentNullException(nameof(body)); + } + + if (fromInclusive < 0) + { + throw new ArgumentOutOfRangeException(nameof(fromInclusive)); + } + + if (fromInclusive > toExclusive) + { + throw new ArgumentOutOfRangeException(nameof(toExclusive)); + } + + if (rangeSize < 1) + { + throw new ArgumentOutOfRangeException(nameof(rangeSize)); + } + + var length = toExclusive - fromInclusive; + + // Special case: nothing to do + if (length <= 0) + { + return; + } + + // Special case: not worth to parallelize, inline + if (Control.MaxDegreeOfParallelism < 2 || (rangeSize * 2) > length) + { + body(fromInclusive, toExclusive); + return; + } + + // Common case + Parallel.ForEach( + Partitioner.Create(fromInclusive, toExclusive, rangeSize), + CreateParallelOptions(), + range => body(range.Item1, range.Item2)); + } + + /// + /// Executes each of the provided actions inside a discrete, asynchronous task. + /// + /// An array of actions to execute. + /// The actions array contains a null element. + /// At least one invocation of the actions threw an exception. + public static void Invoke(params Action[] actions) + { + // Special case: no action + if (actions.Length == 0) + { + return; + } + + // Special case: single action, inline + if (actions.Length == 1) + { + actions[0](); + return; + } + + // Special case: straight execution without parallelism + if (Control.MaxDegreeOfParallelism < 2) + { + for (int i = 0; i < actions.Length; i++) + { + actions[i](); + } + + return; + } + + // Common case + Parallel.Invoke( + CreateParallelOptions(), + actions); + } + + /// + /// Selects an item (such as Max or Min). + /// + /// Starting index of the loop. + /// Ending index of the loop + /// The function to select items over a subset. + /// The function to select the item of selection from the subsets. + /// The selected value. + public static T Aggregate(int fromInclusive, int toExclusive, Func select, Func reduce) + { + if (select == null) + { + throw new ArgumentNullException(nameof(select)); + } + + if (reduce == null) + { + throw new ArgumentNullException(nameof(reduce)); + } + + // Special case: no action + if (fromInclusive >= toExclusive) + { + return reduce(new T[0]); + } + + // Special case: single action, inline + if (fromInclusive == (toExclusive - 1)) + { + return reduce(new[] { select(fromInclusive) }); + } + + // Special case: straight execution without parallelism + if (Control.MaxDegreeOfParallelism < 2) + { + var mapped = new T[toExclusive - fromInclusive]; + for (int k = 0; k < mapped.Length; k++) + { + mapped[k] = select(k + fromInclusive); + } + + return reduce(mapped); + } + + // Common case + var intermediateResults = new List(); + var syncLock = new object(); + Parallel.ForEach( + Partitioner.Create(fromInclusive, toExclusive), + CreateParallelOptions(), + () => new List(), + (range, loop, localData) => + { + var mapped = new T[range.Item2 - range.Item1]; + for (int k = 0; k < mapped.Length; k++) + { + mapped[k] = select(k + range.Item1); + } + + localData.Add(reduce(mapped)); + return localData; + }, + localResult => + { + lock (syncLock) + { + intermediateResults.Add(reduce(localResult.ToArray())); + } + }); + return reduce(intermediateResults.ToArray()); + } + + /// + /// Selects an item (such as Max or Min). + /// + /// The array to iterate over. + /// The function to select items over a subset. + /// The function to select the item of selection from the subsets. + /// The selected value. + public static TOut Aggregate(T[] array, Func select, Func reduce) + { + if (select == null) + { + throw new ArgumentNullException(nameof(select)); + } + + if (reduce == null) + { + throw new ArgumentNullException(nameof(reduce)); + } + + // Special case: no action + if (array == null || array.Length == 0) + { + return reduce(new TOut[0]); + } + + // Special case: single action, inline + if (array.Length == 1) + { + return reduce(new[] { select(0, array[0]) }); + } + + // Special case: straight execution without parallelism + if (Control.MaxDegreeOfParallelism < 2) + { + var mapped = new TOut[array.Length]; + for (int k = 0; k < mapped.Length; k++) + { + mapped[k] = select(k, array[k]); + } + + return reduce(mapped); + } + + // Common case + var intermediateResults = new List(); + var syncLock = new object(); + Parallel.ForEach( + Partitioner.Create(0, array.Length), + CreateParallelOptions(), + () => new List(), + (range, loop, localData) => + { + var mapped = new TOut[range.Item2 - range.Item1]; + for (int k = 0; k < mapped.Length; k++) + { + mapped[k] = select(k + range.Item1, array[k + range.Item1]); + } + + localData.Add(reduce(mapped)); + return localData; + }, + localResult => + { + lock (syncLock) + { + intermediateResults.Add(reduce(localResult.ToArray())); + } + }); + + return reduce(intermediateResults.ToArray()); + } + + /// + /// Selects an item (such as Max or Min). + /// + /// Starting index of the loop. + /// Ending index of the loop + /// The function to select items over a subset. + /// The function to select the item of selection from the subsets. + /// Default result of the reduce function on an empty set. + /// The selected value. + public static T Aggregate(int fromInclusive, int toExclusive, Func select, Func reducePair, T reduceDefault) + { + return Aggregate(fromInclusive, toExclusive, select, results => + { + if (results == null || results.Length == 0) + { + return reduceDefault; + } + + if (results.Length == 1) + { + return results[0]; + } + + T result = results[0]; + for (int i = 1; i < results.Length; i++) + { + result = reducePair(result, results[i]); + } + + return result; + }); + } + + /// + /// Selects an item (such as Max or Min). + /// + /// The array to iterate over. + /// The function to select items over a subset. + /// The function to select the item of selection from the subsets. + /// Default result of the reduce function on an empty set. + /// The selected value. + public static TOut Aggregate(T[] array, Func select, Func reducePair, TOut reduceDefault) + { + return Aggregate(array, select, results => + { + if (results == null || results.Length == 0) + { + return reduceDefault; + } + + if (results.Length == 1) + { + return results[0]; + } + + TOut result = results[0]; + for (int i = 1; i < results.Length; i++) + { + result = reducePair(result, results[i]); + } + + return result; + }); + } +} diff --git a/MathNet.Numerics/Trigonometry.cs b/MathNet.Numerics/Trigonometry.cs new file mode 100644 index 0000000..72428d2 --- /dev/null +++ b/MathNet.Numerics/Trigonometry.cs @@ -0,0 +1,836 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +using Complex = System.Numerics.Complex; + +namespace MathNet.Numerics; + +/// +/// Double-precision trigonometry toolkit. +/// +public static class Trig +{ + /// + /// Constant to convert a degree to grad. + /// + private const double DegreeToGradConstant = 10.0 / 9.0; + + /// + /// Converts a degree (360-periodic) angle to a grad (400-periodic) angle. + /// + /// The degree to convert. + /// The converted grad angle. + public static double DegreeToGrad(double degree) + { + return degree * DegreeToGradConstant; + } + + /// + /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle. + /// + /// The degree to convert. + /// The converted radian angle. + public static double DegreeToRadian(double degree) + { + return degree * Constants.Degree; + } + + /// + /// Converts a grad (400-periodic) angle to a degree (360-periodic) angle. + /// + /// The grad to convert. + /// The converted degree. + public static double GradToDegree(double grad) + { + return grad * 0.9; + } + + /// + /// Converts a grad (400-periodic) angle to a radian (2*Pi-periodic) angle. + /// + /// The grad to convert. + /// The converted radian. + public static double GradToRadian(double grad) + { + return grad * Constants.Grad; + } + + /// + /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle. + /// + /// The radian to convert. + /// The converted degree. + public static double RadianToDegree(double radian) + { + return radian / Constants.Degree; + } + + /// + /// Converts a radian (2*Pi-periodic) angle to a grad (400-periodic) angle. + /// + /// The radian to convert. + /// The converted grad. + public static double RadianToGrad(double radian) + { + return radian / Constants.Grad; + } + + /// + /// Normalized Sinc function. sinc(x) = sin(pi*x)/(pi*x). + /// + public static double Sinc(double x) + { + double z = Math.PI * x; + return z.AlmostEqual(0.0, 15) ? 1.0 : Math.Sin(z) / z; + } + + /// + /// Trigonometric Sine of an angle in radian, or opposite / hypotenuse. + /// + /// The angle in radian. + /// The sine of the radian angle. + public static double Sin(double radian) + { + return Math.Sin(radian); + } + + /// + /// Trigonometric Sine of a Complex number. + /// + /// The complex value. + /// The sine of the complex number. + public static Complex Sin(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Sin(value.Real), 0.0); + } + + return new Complex( + Sin(value.Real) * Cosh(value.Imaginary), + Cos(value.Real) * Sinh(value.Imaginary)); + } + + /// + /// Trigonometric Cosine of an angle in radian, or adjacent / hypotenuse. + /// + /// The angle in radian. + /// The cosine of an angle in radian. + public static double Cos(double radian) + { + return Math.Cos(radian); + } + + /// + /// Trigonometric Cosine of a Complex number. + /// + /// The complex value. + /// The cosine of a complex number. + public static Complex Cos(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Cos(value.Real), 0.0); + } + + return new Complex( + Cos(value.Real) * Cosh(value.Imaginary), + -Sin(value.Real) * Sinh(value.Imaginary)); + } + + /// + /// Trigonometric Tangent of an angle in radian, or opposite / adjacent. + /// + /// The angle in radian. + /// The tangent of the radian angle. + public static double Tan(double radian) + { + return Math.Tan(radian); + } + + /// + /// Trigonometric Tangent of a Complex number. + /// + /// The complex value. + /// The tangent of the complex number. + public static Complex Tan(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Tan(value.Real), 0.0); + } + + // tan(z) = - j*tanh(j*z) + + Complex z = Tanh(new Complex(-value.Imaginary, value.Real)); + return new Complex(z.Imaginary, -z.Real); + } + + /// + /// Trigonometric Cotangent of an angle in radian, or adjacent / opposite. Reciprocal of the tangent. + /// + /// The angle in radian. + /// The cotangent of an angle in radian. + public static double Cot(double radian) + { + return 1 / Math.Tan(radian); + } + + /// + /// Trigonometric Cotangent of a Complex number. + /// + /// The complex value. + /// The cotangent of the complex number. + public static Complex Cot(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Cot(value.Real), 0d); + } + + // cot(z) = - j*coth(-j*z) + + Complex z = Coth(new Complex(value.Imaginary, -value.Real)); + return new Complex(z.Imaginary, -z.Real); + } + + /// + /// Trigonometric Secant of an angle in radian, or hypotenuse / adjacent. Reciprocal of the cosine. + /// + /// The angle in radian. + /// The secant of the radian angle. + public static double Sec(double radian) + { + return 1 / Math.Cos(radian); + } + + /// + /// Trigonometric Secant of a Complex number. + /// + /// The complex value. + /// The secant of the complex number. + public static Complex Sec(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Sec(value.Real), 0d); + } + + var cosr = Cos(value.Real); + var sinhi = Sinh(value.Imaginary); + var denom = (cosr * cosr) + (sinhi * sinhi); + + return new Complex(cosr * Cosh(value.Imaginary) / denom, Sin(value.Real) * sinhi / denom); + } + + /// + /// Trigonometric Cosecant of an angle in radian, or hypotenuse / opposite. Reciprocal of the sine. + /// + /// The angle in radian. + /// Cosecant of an angle in radian. + public static double Csc(double radian) + { + return 1 / Math.Sin(radian); + } + + /// + /// Trigonometric Cosecant of a Complex number. + /// + /// The complex value. + /// The cosecant of a complex number. + public static Complex Csc(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Csc(value.Real), 0d); + } + + var sinr = Sin(value.Real); + var sinhi = Sinh(value.Imaginary); + var denom = (sinr * sinr) + (sinhi * sinhi); + + return new Complex(sinr * Cosh(value.Imaginary) / denom, -Cos(value.Real) * sinhi / denom); + } + + /// + /// Trigonometric principal Arc Sine in radian + /// + /// The opposite for a unit hypotenuse (i.e. opposite / hypotenuse). + /// The angle in radian. + public static double Asin(double opposite) + { + return Math.Asin(opposite); + } + + /// + /// Trigonometric principal Arc Sine of this Complex number. + /// + /// The complex value. + /// The arc sine of a complex number. + public static Complex Asin(this Complex value) + { + if (value.Imaginary > 0 || value.Imaginary == 0d && value.Real < 0) + { + return -Asin(-value); + } + + return -Complex.ImaginaryOne * ((1 - value.Square()).SquareRoot() + (Complex.ImaginaryOne * value)).Ln(); + } + + /// + /// Trigonometric principal Arc Cosine in radian + /// + /// The adjacent for a unit hypotenuse (i.e. adjacent / hypotenuse). + /// The angle in radian. + public static double Acos(double adjacent) + { + return Math.Acos(adjacent); + } + + /// + /// Trigonometric principal Arc Cosine of this Complex number. + /// + /// The complex value. + /// The arc cosine of a complex number. + public static Complex Acos(this Complex value) + { + if (value.Imaginary < 0 || value.Imaginary == 0d && value.Real > 0) + { + return Constants.Pi - Acos(-value); + } + + return -Complex.ImaginaryOne * (value + (Complex.ImaginaryOne * (1 - value.Square()).SquareRoot())).Ln(); + } + + /// + /// Trigonometric principal Arc Tangent in radian + /// + /// The opposite for a unit adjacent (i.e. opposite / adjacent). + /// The angle in radian. + public static double Atan(double opposite) + { + return Math.Atan(opposite); + } + + /// + /// Trigonometric principal Arc Tangent of this Complex number. + /// + /// The complex value. + /// The arc tangent of a complex number. + public static Complex Atan(this Complex value) + { + var iz = new Complex(-value.Imaginary, value.Real); // I*this + return new Complex(0, 0.5) * ((1 - iz).Ln() - (1 + iz).Ln()); + } + + /// + /// Trigonometric principal Arc Cotangent in radian + /// + /// The adjacent for a unit opposite (i.e. adjacent / opposite). + /// The angle in radian. + public static double Acot(double adjacent) + { + return Math.Atan(1 / adjacent); + } + + /// + /// Trigonometric principal Arc Cotangent of this Complex number. + /// + /// The complex value. + /// The arc cotangent of a complex number. + public static Complex Acot(this Complex value) + { + if (value.IsZero()) + { + return Constants.PiOver2; + } + + var inv = Complex.ImaginaryOne / value; + return (Complex.ImaginaryOne * 0.5) * ((1.0 - inv).Ln() - (1.0 + inv).Ln()); + } + + /// + /// Trigonometric principal Arc Secant in radian + /// + /// The hypotenuse for a unit adjacent (i.e. hypotenuse / adjacent). + /// The angle in radian. + public static double Asec(double hypotenuse) + { + return Math.Acos(1 / hypotenuse); + } + + /// + /// Trigonometric principal Arc Secant of this Complex number. + /// + /// The complex value. + /// The arc secant of a complex number. + public static Complex Asec(this Complex value) + { + var inv = 1 / value; + return -Complex.ImaginaryOne * (inv + (Complex.ImaginaryOne * (1 - inv.Square()).SquareRoot())).Ln(); + } + + /// + /// Trigonometric principal Arc Cosecant in radian + /// + /// The hypotenuse for a unit opposite (i.e. hypotenuse / opposite). + /// The angle in radian. + public static double Acsc(double hypotenuse) + { + return Math.Asin(1 / hypotenuse); + } + + /// + /// Trigonometric principal Arc Cosecant of this Complex number. + /// + /// The complex value. + /// The arc cosecant of a complex number. + public static Complex Acsc(this Complex value) + { + var inv = 1 / value; + return -Complex.ImaginaryOne * ((Complex.ImaginaryOne * inv) + (1 - inv.Square()).SquareRoot()).Ln(); + } + + /// + /// Hyperbolic Sine + /// + /// The hyperbolic angle, i.e. the area of the hyperbolic sector. + /// The hyperbolic sine of the angle. + public static double Sinh(double angle) + { + return (Math.Exp(angle) - Math.Exp(-angle)) / 2; + } + + /// + /// Hyperbolic Sine of a Complex number. + /// + /// The complex value. + /// The hyperbolic sine of a complex number. + public static Complex Sinh(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Sinh(value.Real), 0.0); + } + + // sinh(x + j y) = sinh(x)*cos(y) + j*cosh(x)*sin(y) + // if x > huge, sinh(x + jy) = sign(x)*exp(|x|)/2*cos(y) + j*exp(|x|)/2*sin(y) + + if (Math.Abs(value.Real) >= 22.0) // Taken from the msun library in FreeBSD + { + double h = Math.Exp(Math.Abs(value.Real)) * 0.5; + return new Complex( + Math.Sign(value.Real) * h * Cos(value.Imaginary), + h * Sin(value.Imaginary)); + } + + return new Complex( + Sinh(value.Real) * Cos(value.Imaginary), + Cosh(value.Real) * Sin(value.Imaginary)); + } + + /// + /// Hyperbolic Cosine + /// + /// The hyperbolic angle, i.e. the area of the hyperbolic sector. + /// The hyperbolic Cosine of the angle. + public static double Cosh(double angle) + { + return (Math.Exp(angle) + Math.Exp(-angle)) / 2; + } + + /// + /// Hyperbolic Cosine of a Complex number. + /// + /// The complex value. + /// The hyperbolic cosine of a complex number. + public static Complex Cosh(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Cosh(value.Real), 0.0); + } + + // cosh(x + j*y) = cosh(x)*cos(y) + j*sinh(x)*sin(y) + // if x > huge, cosh(x + j*y) = exp(|x|)/2*cos(y) + j*sign(x)*exp(|x|)/2*sin(y) + + if (Math.Abs(value.Real) >= 22.0) // Taken from the msun library in FreeBSD + { + double h = Math.Exp(Math.Abs(value.Real)) * 0.5; + return new Complex( + h * Cos(value.Imaginary), + Math.Sign(value.Real) * h * Sin(value.Imaginary)); + } + + return new Complex( + Cosh(value.Real) * Cos(value.Imaginary), + Sinh(value.Real) * Sin(value.Imaginary)); + } + + /// + /// Hyperbolic Tangent in radian + /// + /// The hyperbolic angle, i.e. the area of the hyperbolic sector. + /// The hyperbolic tangent of the angle. + public static double Tanh(double angle) + { + if (angle > 19.1) + { + return 1.0; + } + + if (angle < -19.1) + { + return -1; + } + + var e1 = Math.Exp(angle); + var e2 = Math.Exp(-angle); + return (e1 - e2) / (e1 + e2); + } + + /// + /// Hyperbolic Tangent of a Complex number. + /// + /// The complex value. + /// The hyperbolic tangent of a complex number. + public static Complex Tanh(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Tanh(value.Real), 0.0); + } + + // tanh(x + j*y) = (cosh(x)*sinh(x)/cos^2(y) + j*tan(y))/(1 + sinh^2(x)/cos^2(y)) + // if |x| > huge, tanh(z) = sign(x) + j*4*cos(y)*sin(y)*exp(-2*|x|) + // if exp(-|x|) = 0, tanh(z) = sign(x) + // if tan(y) = +/- oo or 1/cos^2(y) = 1 + tan^2(y) = oo, tanh(z) = cosh(x)/sinh(x) + // + // The algorithm is based on Kahan. + + if (Math.Abs(value.Real) >= 22.0) // Taken from the msun library in FreeBSD + { + double e = Math.Exp(-Math.Abs(value.Real)); + return e == 0.0 + ? new Complex(Math.Sign(value.Real), 0.0) + : new Complex(Math.Sign(value.Real), 4.0 * Math.Cos(value.Imaginary) * Math.Sin(value.Imaginary) * e * e); + } + + double tani = Tan(value.Imaginary); + double beta = 1 + tani * tani; // beta = 1/cos^2(y) = 1 + t^2 + double sinhr = Sinh(value.Real); + double coshr = Cosh(value.Real); + + if (double.IsInfinity(tani)) + return new Complex(coshr / sinhr, 0.0); + + double denom = 1.0 + beta * sinhr * sinhr; + return new Complex(beta * coshr * sinhr / denom, tani / denom); + } + + /// + /// Hyperbolic Cotangent + /// + /// The hyperbolic angle, i.e. the area of the hyperbolic sector. + /// The hyperbolic cotangent of the angle. + public static double Coth(double angle) + { + if (angle > 19.115) + { + return 1.0; + } + + if (angle < -19.115) + { + return -1; + } + + var e1 = Math.Exp(angle); + var e2 = Math.Exp(-angle); + return (e1 + e2) / (e1 - e2); + } + + /// + /// Hyperbolic Cotangent of a Complex number. + /// + /// The complex value. + /// The hyperbolic cotangent of a complex number. + public static Complex Coth(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Coth(value.Real), 0.0); + } + + // Coth(z) = 1/tanh(z) + + return Complex.One / Tanh(value); + } + + /// + /// Hyperbolic Secant + /// + /// The hyperbolic angle, i.e. the area of the hyperbolic sector. + /// The hyperbolic secant of the angle. + public static double Sech(double angle) + { + return 1 / Cosh(angle); + } + + /// + /// Hyperbolic Secant of a Complex number. + /// + /// The complex value. + /// The hyperbolic secant of a complex number. + public static Complex Sech(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Sech(value.Real), 0.0); + } + + // sech(x + j*y) = (cosh(x)/cos(y) - j*sinh(x)*tan(y)/cos(y))/(1 + sinh^2(x)/cos^2(y)) + // if |x| > huge, sech(z) = 4*cosh(x)*cos(y)*exp(-2*|x|) - j*4*sinh(x)*tan(y)*cos(y)*exp(-2*|x|) + // if exp(-|x|) = 0, sech(z) = 0 + // if tan(y) = +/- oo or 1/cos^2(y) = 1 + tan^2(y) = oo, sech(z) = -j*sign(tan(y))/sinh(x) + // + // The algorithm is based on Kahan. + + double tani = Tan(value.Imaginary); + double cosi = Cos(value.Imaginary); + double beta = 1.0 + tani * tani; + double sinhr = Math.Sinh(value.Real); + double coshr = Math.Cosh(value.Real); + + if (Math.Abs(value.Real) >= 22.0) // Taken from the msun library in FreeBSD + { + double e = Math.Exp(-Math.Abs(value.Real)); + return e == 0.0 + ? new Complex(0, 0) + : new Complex(4.0 * coshr * cosi * e * e, -4.0 * sinhr * tani * cosi * e * e); + } + + if (double.IsInfinity(tani)) + { + return new Complex(0.0, -Math.Sign(tani) / sinhr); + } + + double denom = 1.0 + beta * sinhr * sinhr; + return new Complex(coshr / cosi / denom, -sinhr * tani / cosi / denom); + } + + /// + /// Hyperbolic Cosecant + /// + /// The hyperbolic angle, i.e. the area of the hyperbolic sector. + /// The hyperbolic cosecant of the angle. + public static double Csch(double angle) + { + return 1 / Sinh(angle); + } + + /// + /// Hyperbolic Cosecant of a Complex number. + /// + /// The complex value. + /// The hyperbolic cosecant of a complex number. + public static Complex Csch(this Complex value) + { + if (value.IsReal()) + { + return new Complex(Csch(value.Real), 0.0); + } + + // csch(x + j*y) = (sinh(x)*cot(y)/sin(y) - j*cosh(x)/sin(y))/(1 + sinh^2(x)/sin^2(y)) + // if |x| > huge, csch(z) = 4*sinh(x)*cot(y)*sin(y)*exp(-2*|x|) - j*4*cosh(x)*sin(y)*exp(-2*|x|) + // if exp(-|x|) = 0, csch(z) = 0 + // if cot(y) = +/- oo or 1/sin^2(x) = 1 + cot^2(x) = oo, csch(z) = sign(cot(y))/sinh(x) + // + // The algorithm is based on Kahan. + + double coti = Cot(value.Imaginary); + double sini = Sin(value.Imaginary); + double beta = 1 + coti * coti; + double sinhr = Sinh(value.Real); + double coshr = Cosh(value.Real); + + if (Math.Abs(value.Real) >= 22.0) // Taken from the msun library in FreeBSD + { + double e = Math.Exp(-Math.Abs(value.Real)); + return e == 0.0 + ? new Complex(0, 0) + : new Complex(4.0 * sinhr * coti * sini * e * e, -4.0 * coshr * sini * e * e); + } + + if (double.IsInfinity(coti)) + { + return new Complex(Math.Sign(coti) / sinhr, 0.0); + } + + double denom = 1.0 + beta * sinhr * sinhr; + return new Complex(sinhr * coti / sini / denom, -coshr / sini / denom); + } + + /// + /// Hyperbolic Area Sine + /// + /// The real value. + /// The hyperbolic angle, i.e. the area of its hyperbolic sector. + public static double Asinh(double value) + { + // asinh(x) = Sign(x) * ln(|x| + sqrt(x*x + 1)) + // if |x| > huge, asinh(x) ~= Sign(x) * ln(2|x|) + + if (Math.Abs(value) >= 268435456.0) // 2^28, taken from freeBSD + return Math.Sign(value) * (Math.Log(Math.Abs(value)) + Math.Log(2.0)); + + return Math.Sign(value) * Math.Log(Math.Abs(value) + Math.Sqrt((value * value) + 1)); + } + + /// + /// Hyperbolic Area Sine of this Complex number. + /// + /// The complex value. + /// The hyperbolic arc sine of a complex number. + public static Complex Asinh(this Complex value) + { + return (value + (value.Square() + 1).SquareRoot()).Ln(); + } + + /// + /// Hyperbolic Area Cosine + /// + /// The real value. + /// The hyperbolic angle, i.e. the area of its hyperbolic sector. + public static double Acosh(double value) + { + // acosh(x) = ln(x + sqrt(x*x - 1)) + // if |x| >= 2^28, acosh(x) ~ ln(x) + ln(2) + + if (Math.Abs(value) >= 268435456.0) // 2^28, taken from freeBSD + return Math.Log(value) + Math.Log(2.0); + + return Math.Log(value + (Math.Sqrt(value - 1) * Math.Sqrt(value + 1)), Math.E); + } + + /// + /// Hyperbolic Area Cosine of this Complex number. + /// + /// The complex value. + /// The hyperbolic arc cosine of a complex number. + public static Complex Acosh(this Complex value) + { + return (value + ((value - 1).SquareRoot() * (value + 1).SquareRoot())).Ln(); + } + + /// + /// Hyperbolic Area Tangent + /// + /// The real value. + /// The hyperbolic angle, i.e. the area of its hyperbolic sector. + public static double Atanh(double value) + { + return 0.5 * Math.Log((1 + value) / (1 - value), Math.E); + } + + /// + /// Hyperbolic Area Tangent of this Complex number. + /// + /// The complex value. + /// The hyperbolic arc tangent of a complex number. + public static Complex Atanh(this Complex value) + { + return 0.5 * ((1 + value).Ln() - (1 - value).Ln()); + } + + /// + /// Hyperbolic Area Cotangent + /// + /// The real value. + /// The hyperbolic angle, i.e. the area of its hyperbolic sector. + public static double Acoth(double value) + { + return 0.5 * Math.Log((value + 1) / (value - 1), Math.E); + } + + /// + /// Hyperbolic Area Cotangent of this Complex number. + /// + /// The complex value. + /// The hyperbolic arc cotangent of a complex number. + public static Complex Acoth(this Complex value) + { + var inv = 1.0 / value; + return 0.5 * ((1.0 + inv).Ln() - (1.0 - inv).Ln()); + } + + /// + /// Hyperbolic Area Secant + /// + /// The real value. + /// The hyperbolic angle, i.e. the area of its hyperbolic sector. + public static double Asech(double value) + { + return Acosh(1 / value); + } + + /// + /// Hyperbolic Area Secant of this Complex number. + /// + /// The complex value. + /// The hyperbolic arc secant of a complex number. + public static Complex Asech(this Complex value) + { + var inv = 1 / value; + return (inv + ((inv - 1).SquareRoot() * (inv + 1).SquareRoot())).Ln(); + } + + /// + /// Hyperbolic Area Cosecant + /// + /// The real value. + /// The hyperbolic angle, i.e. the area of its hyperbolic sector. + public static double Acsch(double value) + { + return Asinh(1 / value); + } + + /// + /// Hyperbolic Area Cosecant of this Complex number. + /// + /// The complex value. + /// The hyperbolic arc cosecant of a complex number. + public static Complex Acsch(this Complex value) + { + var inv = 1 / value; + return (inv + (inv.Square() + 1).SquareRoot()).Ln(); + } +} diff --git a/MathNet.Numerics/Window.cs b/MathNet.Numerics/Window.cs new file mode 100644 index 0000000..126d960 --- /dev/null +++ b/MathNet.Numerics/Window.cs @@ -0,0 +1,444 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// +// Copyright (c) 2009-2013 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace MathNet.Numerics; + +public static class Window +{ + /// + /// Hamming window. Named after Richard Hamming. + /// Symmetric version, useful e.g. for filter design purposes. + /// + public static double[] Hamming(int width) + { + const double a = 0.53836; + const double b = -0.46164; + + double phaseStep = (2.0 * Math.PI) / (width - 1.0); + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a + b * Math.Cos(i * phaseStep); + } + + return w; + } + + /// + /// Hamming window. Named after Richard Hamming. + /// Periodic version, useful e.g. for FFT purposes. + /// + public static double[] HammingPeriodic(int width) + { + const double a = 0.53836; + const double b = -0.46164; + + double phaseStep = (2.0 * Math.PI) / width; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a + b * Math.Cos(i * phaseStep); + } + + return w; + } + + /// + /// Hann window. Named after Julius von Hann. + /// Symmetric version, useful e.g. for filter design purposes. + /// + public static double[] Hann(int width) + { + double phaseStep = (2.0 * Math.PI) / (width - 1.0); + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = 0.5 - 0.5 * Math.Cos(i * phaseStep); + } + + return w; + } + + /// + /// Hann window. Named after Julius von Hann. + /// Periodic version, useful e.g. for FFT purposes. + /// + public static double[] HannPeriodic(int width) + { + double phaseStep = (2.0 * Math.PI) / width; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = 0.5 - 0.5 * Math.Cos(i * phaseStep); + } + + return w; + } + + /// + /// Cosine window. + /// Symmetric version, useful e.g. for filter design purposes. + /// + public static double[] Cosine(int width) + { + double phaseStep = Math.PI / (width - 1.0); + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = Math.Sin(i * phaseStep); + } + + return w; + } + + /// + /// Cosine window. + /// Periodic version, useful e.g. for FFT purposes. + /// + public static double[] CosinePeriodic(int width) + { + double phaseStep = Math.PI / width; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = Math.Sin(i * phaseStep); + } + + return w; + } + + /// + /// Lanczos window. + /// Symmetric version, useful e.g. for filter design purposes. + /// + public static double[] Lanczos(int width) + { + double phaseStep = 2.0 / (width - 1.0); + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = Trig.Sinc(i * phaseStep - 1.0); + } + + return w; + } + + /// + /// Lanczos window. + /// Periodic version, useful e.g. for FFT purposes. + /// + public static double[] LanczosPeriodic(int width) + { + double phaseStep = 2.0 / width; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = Trig.Sinc(i * phaseStep - 1.0); + } + + return w; + } + + /// + /// Gauss window. + /// + public static double[] Gauss(int width, double sigma) + { + double a = (width - 1) / 2.0; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + double exponent = (i - a) / (sigma * a); + w[i] = Math.Exp(-0.5 * exponent * exponent); + } + + return w; + } + + /// + /// Blackman window. + /// + public static double[] Blackman(int width) + { + const double alpha = 0.16; + const double a = 0.5 - 0.5 * alpha; + const double b = 0.5 * alpha; + + int last = width - 1; + double c = 2.0 * Math.PI / last; + double d = 2.0 * c; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a + - 0.5 * Math.Cos(i * c) + + b * Math.Cos(i * d); + } + + return w; + } + + /// + /// Blackman-Harris window. + /// + public static double[] BlackmanHarris(int width) + { + const double a = 0.35875; + const double b = -0.48829; + const double c = 0.14128; + const double d = -0.01168; + + int last = width - 1; + double e = 2.0 * Math.PI / last; + double f = 2.0 * e; + double g = 3.0 * e; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a + + b * Math.Cos(e * i) + + c * Math.Cos(f * i) + + d * Math.Cos(g * i); + } + + return w; + } + + /// + /// Blackman-Nuttall window. + /// + public static double[] BlackmanNuttall(int width) + { + const double a = 0.3635819; + const double b = -0.4891775; + const double c = 0.1365995; + const double d = -0.0106411; + + int last = width - 1; + double e = 2.0 * Math.PI / last; + double f = 2.0 * e; + double g = 3.0 * e; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a + + b * Math.Cos(e * i) + + c * Math.Cos(f * i) + + d * Math.Cos(g * i); + } + + return w; + } + + /// + /// Bartlett window. + /// + public static double[] Bartlett(int width) + { + int last = width - 1; + double a = 2.0 / last; + double b = last / 2.0; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a * (b - Math.Abs(i - b)); + } + + return w; + } + + /// + /// Bartlett-Hann window. + /// + public static double[] BartlettHann(int width) + { + const double a = 0.62; + const double b = -0.48; + const double c = -0.38; + + int last = width - 1; + double d = 1.0 / last; + double e = 2.0 * Math.PI / last; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a + + b * Math.Abs(i * d - 0.5) + + c * Math.Cos(i * e); + } + + return w; + } + + /// + /// Nuttall window. + /// + public static double[] Nuttall(int width) + { + const double a = 0.355768; + const double b = -0.487396; + const double c = 0.144232; + const double d = -0.012604; + + int last = width - 1; + double e = 2.0 * Math.PI / last; + double f = 2.0 * e; + double g = 3.0 * e; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a + + b * Math.Cos(e * i) + + c * Math.Cos(f * i) + + d * Math.Cos(g * i); + } + + return w; + } + + /// + /// Flat top window. + /// + public static double[] FlatTop(int width) + { + const double a = 1.0; + const double b = -1.93; + const double c = 1.29; + const double d = -0.388; + const double e = 0.032; + + int last = width - 1; + double f = 2.0 * Math.PI / last; + double g = 2.0 * f; + double h = 3.0 * f; + double k = 4.0 * f; + + double[] w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a + + b * Math.Cos(f * i) + + c * Math.Cos(g * i) + + d * Math.Cos(h * i) + + e * Math.Cos(k * i); + } + + return w; + } + + /// + /// Uniform rectangular (Dirichlet) window. + /// + public static double[] Dirichlet(int width) + { + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = 1.0; + } + + return w; + } + + /// + /// Triangular window. + /// + public static double[] Triangular(int width) + { + double a = 2.0 / width; + double b = width / 2.0; + double c = (width - 1) / 2.0; + + var w = new double[width]; + for (int i = 0; i < w.Length; i++) + { + w[i] = a * (b - Math.Abs(i - c)); + } + + return w; + } + + /// + /// Tukey tapering window. A rectangular window bounded + /// by half a cosine window on each side. + /// + /// Width of the window + /// Fraction of the window occupied by the cosine parts + public static double[] Tukey(int width, double r = 0.5) + { + + if (r <= 0) + { + return Generate.Repeat(width, 1.0); + } + else if (r >= 1) + { + return Hann(width); + } + + var w = new double[width]; + var period = (width - 1) * r; + var step = 2 * Math.PI / period; + var b1 = (int)Math.Floor((width - 1) * r * 0.5 + 1); + var b2 = width - b1; + for (var i = 0; i < b1; i++) + { + w[i] = (1 - Math.Cos(i * step)) * 0.5; + } + + for (var i = b1; i < b2; i++) + { + w[i] = 1; + } + + for (var i = b2; i < width; i++) + { + w[i] = w[width - i - 1]; + } + + return w; + } +} diff --git a/MathNet.Numerics/paket.references b/MathNet.Numerics/paket.references new file mode 100644 index 0000000..739f7d9 --- /dev/null +++ b/MathNet.Numerics/paket.references @@ -0,0 +1,6 @@ +System.Runtime framework:netstandard1.3 +System.Runtime.Numerics framework:netstandard1.3 +System.Runtime.Serialization.Xml framework:netstandard1.3 +System.Runtime.Serialization.Primitives framework:netstandard1.3 +System.Runtime.Serialization.Formatters framework:netstandard1.3 +System.Security.Cryptography.Algorithms framework:netstandard1.3 diff --git a/O2DESNet.UnitTests/HourCounter_Tests.cs b/O2DESNet.UnitTests/HourCounter_Tests.cs index e41fd9c..8f8c117 100644 --- a/O2DESNet.UnitTests/HourCounter_Tests.cs +++ b/O2DESNet.UnitTests/HourCounter_Tests.cs @@ -1,116 +1,112 @@ using NUnit.Framework; + +using O2DESNet.HourCounters; + using System; -namespace O2DESNet.UnitTests +namespace O2DESNet.UnitTests; + +[TestFixture] +public class HourCounter_Tests { - public class HourCounter_Tests + [Test] + public void Pause_Should_Not_Affect_Update_Last_Count() { - [Test] - public void Pause_Should_Not_Affect_Update_Last_Count() - { - TestSandbox sb = new TestSandbox(); - var hc = sb.HC; - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(1); - sb.Run(TimeSpan.FromHours(1)); - hc.Pause(); - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(2); - sb.Run(TimeSpan.FromHours(1)); - hc.Resume(); - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(0); - sb.Run(TimeSpan.FromHours(5)); - hc.ObserveCount(0); - if (hc.AverageCount != 0.375) Assert.Fail(); - sb.Dispose(); - } - [Test] - public void Pause_Should_Affect_Total_Increment() - { - TestSandbox sb = new TestSandbox(); - var hc = sb.HC; - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(1); - sb.Run(TimeSpan.FromHours(1)); - hc.Pause(); - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(2); - sb.Run(TimeSpan.FromHours(1)); - hc.Resume(); - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(0); - sb.Run(TimeSpan.FromHours(5)); - hc.ObserveCount(0); - if (hc.TotalIncrement != 1) Assert.Fail(); - if (hc.TotalDecrement != 2) Assert.Fail(); - sb.Dispose(); - } - [Test] - public void TotalIncrement_At_ClockTime_Equals_LastTime() - { - TestSandbox sb = new TestSandbox(); - var hc = sb.HC; - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(1); - hc.ObserveChange(1); - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveChange(1); - hc.ObserveChange(-1); - if (hc.TotalIncrement != 3) Assert.Fail(); - if (hc.TotalDecrement != 1) Assert.Fail(); - sb.Dispose(); - } - [Test] - public void LogFiles() - { - TestSandbox sb = new TestSandbox(); - var hc = sb.HC; - hc.LogFile = "hc.csv"; - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(1); - sb.Run(TimeSpan.FromHours(1)); - hc.Pause(); - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(2); - sb.Run(TimeSpan.FromHours(1)); - hc.Resume(); - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(0); - sb.Run(TimeSpan.FromHours(5)); - hc.ObserveCount(0); - sb.Dispose(); - } - [Test] - public void AverageCount_With_UpdateToClockTime_Count() - { - TestSandbox sb = new TestSandbox(); - var hc = sb.HC; - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(1); - sb.Run(TimeSpan.FromHours(1)); - hc.Pause(); - sb.Run(TimeSpan.FromHours(1)); /// paused - hc.ObserveCount(2); - sb.Run(TimeSpan.FromHours(1)); /// paused - hc.Resume(); - sb.Run(TimeSpan.FromHours(1)); - hc.ObserveCount(0); - sb.Run(TimeSpan.FromHours(5)); - hc.ObserveCount(0); - sb.Run(TimeSpan.FromHours(8)); - if (hc.AverageCount != 0.375 / 2) Assert.Fail(); - if (hc.TotalHours != 16) Assert.Fail(); - sb.Dispose(); - } + TestSandbox sb = new(); + var hc = sb.HC; + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(1); + sb.Run(TimeSpan.FromHours(1)); + hc.Pause(); + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(2); + sb.Run(TimeSpan.FromHours(1)); + hc.Resume(); + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(0); + sb.Run(TimeSpan.FromHours(5)); + hc.ObserveCount(0); + if (hc.AverageCount != 0.375) + Assert.Fail(); + sb.Dispose(); + } + + [Test] + public void Pause_Should_Affect_Total_Increment() + { + TestSandbox sb = new(); + var hc = sb.HC; + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(1); + sb.Run(TimeSpan.FromHours(1)); + hc.Pause(); + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(2); + sb.Run(TimeSpan.FromHours(1)); + hc.Resume(); + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(0); + sb.Run(TimeSpan.FromHours(5)); + hc.ObserveCount(0); + if (hc.TotalIncrement != 1) + Assert.Fail(); + if (hc.TotalDecrement != 2) + Assert.Fail(); + sb.Dispose(); + } + + [Test] + public void TotalIncrement_At_ClockTime_Equals_LastTime() + { + TestSandbox sb = new(); + var hc = sb.HC; + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(1); + hc.ObserveChange(1); + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveChange(1); + hc.ObserveChange(-1); + if (hc.TotalIncrement != 3) + Assert.Fail(); + if (hc.TotalDecrement != 1) + Assert.Fail(); + sb.Dispose(); + } + + [Test] + public void AverageCount_With_UpdateToClockTime_Count() + { + TestSandbox sb = new(); + var hc = sb.HC; + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(1); + sb.Run(TimeSpan.FromHours(1)); + hc.Pause(); + sb.Run(TimeSpan.FromHours(1)); + /// paused + hc.ObserveCount(2); + sb.Run(TimeSpan.FromHours(1)); + /// paused + hc.Resume(); + sb.Run(TimeSpan.FromHours(1)); + hc.ObserveCount(0); + sb.Run(TimeSpan.FromHours(5)); + hc.ObserveCount(0); + sb.Run(TimeSpan.FromHours(8)); + if (hc.AverageCount != 0.375 / 2) + Assert.Fail(); + if (hc.TotalHours != 16) + Assert.Fail(); + sb.Dispose(); + } - class TestSandbox : Sandbox + internal class TestSandbox : Sandbox + { + public HourCounter HC { get; private set; } + public TestSandbox() : base(id: nameof(TestSandbox), seed: 0) { - public HourCounter HC { get; private set; } - public TestSandbox() - { - HC = AddHourCounter(); - } + TestContext.Out.WriteLine("AddHourCounter"); + HC = AddHourCounter(); } } } \ No newline at end of file diff --git a/O2DESNet.UnitTests/O2DESNet.UnitTests.csproj b/O2DESNet.UnitTests/O2DESNet.UnitTests.csproj index fbaeab2..a9b6bba 100644 --- a/O2DESNet.UnitTests/O2DESNet.UnitTests.csproj +++ b/O2DESNet.UnitTests/O2DESNet.UnitTests.csproj @@ -1,21 +1,41 @@  - netcoreapp3.1 - + net9.0 + enable + latest false + true + + + + + bin\ + false + false + false + + + + + en-US + + false + + + + en-US - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/O2DESNet.UnitTests/PatternGenerator_Tests.cs b/O2DESNet.UnitTests/PatternGenerator_Tests.cs index fe3f708..27e9db5 100644 --- a/O2DESNet.UnitTests/PatternGenerator_Tests.cs +++ b/O2DESNet.UnitTests/PatternGenerator_Tests.cs @@ -1,178 +1,282 @@ -using NUnit.Framework; +using Microsoft.Extensions.Logging; + +using NUnit.Framework; + using O2DESNet.Standard; + +using Serilog; +using Serilog.Events; + using System; using System.Collections.Generic; -using System.Diagnostics; -namespace O2DESNet.UnitTests +namespace O2DESNet.UnitTests; + +/// +/// Test suite for validating the behavior of the Standard.PatternGenerator under a variety of +/// seasonality configurations and lifecycle scenarios. The tests focus on two core aspects: +/// (1) statistical consistency of the long‑run average event rate and (2) robustness of the +/// start/stop lifecycle. +/// +/// Overview +/// - The PatternGenerator emits events whose inter‑arrival process is governed by a baseline +/// MeanHourlyRate and optionally modulated by seasonal factors at different calendar granularities +/// (hour of day, day of week, day of month, month of year, year) and by custom user‑defined +/// periodicities. +/// - Each test constructs a PatternGenerator.Statics configuration, starts the generator, and runs +/// until a certain number of events have been generated (except the on/off test which exercises +/// lifecycle without asserting rate accuracy). +/// - The helper Test method compares the expected elapsed time, derived from the configured +/// baseline rate and number of generated events, against the observed simulation clock time. The +/// relative difference must stay within a small tolerance, accounting for stochastic variance. +/// +/// What is validated +/// - NoSeasonality: Verifies that, with only MeanHourlyRate set, the observed time to produce N +/// events is consistent with the reciprocal rate (1 / MeanHourlyRate) times the event count. +/// - HoursInDay, DaysInWeek, DaysInMonth, MonthsInYear, Years: Ensures that enabling a single +/// seasonal dimension (with lists sized 24, 7, 31, 12, or a finite year list) still yields the +/// correct long‑run average rate when integrated across the seasonal cycle. +/// - Combined_HoursInDay_DaysInWeek: Ensures multiple seasonal dimensions can be enabled together +/// without breaking the average‑rate property. +/// - Customized: Uses CustomizedSeasonalFactors to validate arbitrary user‑defined periods and +/// factor sequences behave consistently with the baseline rate in the long run. +/// - Customized_On_and_Off: Exercises Start/End transitions mid‑simulation to ensure the generator +/// can be paused and resumed safely, and that advancing the clock while stopped does not cause +/// errors. This test is primarily about robustness (no assertion on average rate). +/// +/// How the checks work +/// - After generating N events (by repeatedly calling Run(1)), the test computes: +/// expected = (1 / MeanHourlyRate) * Count +/// observed = ClockTime.TotalHours +/// diff = expected / observed - 1 +/// The assertion gates on |diff| being smaller than a tolerance (typically 0.05, 0.04 for the +/// most volatile case). Tolerances vary slightly to accommodate variance introduced by strong +/// seasonal modulation and finite sample sizes. +/// +/// Reproducibility and logging +/// - A fixed seed is used to keep tests reproducible. +/// - Serilog is configured in SetUp to log to console and a file, and bridged into +/// Microsoft.Extensions.Logging so test output can be correlated with simulation steps when +/// diagnosing failures. +/// +/// Scope +/// - These tests target average‑rate and lifecycle behavior. They do not attempt to verify the +/// exact distributional form of inter‑arrival times beyond the long‑run rate implied by the input +/// configuration, nor do they validate a particular combination rule across seasonal dimensions +/// beyond its effect on long‑run averages. +/// +[TestFixture] +public class PatternGenerator_Tests { - public class PatternGenerator_Tests + private Microsoft.Extensions.Logging.ILogger _logger; + private LogEventLevel _minLevel = LogEventLevel.Information; + + [SetUp] + public void Init() { - [Test] - public void NoSeasonality() - { - Debug.WriteLine("Seasonality - None"); - var diff = Test(new PatternGenerator.Statics - { - MeanHourlyRate = 1, - }, 1000); - if (Math.Abs(diff) > 0.05) Assert.Fail(); - } - - [Test] - public void HoursInDay() + // Comment out the following code to switch log file + //ConfigureSerilog("Logs\\log-PatternGenerator_Tests.txt"); + } + + private void ConfigureSerilog(string filePath) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Is(_minLevel) + .WriteTo.Console() // Output to console + .WriteTo.File(filePath, + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 31, + restrictedToMinimumLevel: _minLevel, + shared: true) // Output to file + .Enrich.FromLogContext() + .CreateLogger(); + + _logger = new LoggerFactory() + .AddSerilog(Log.Logger) + .CreateLogger(); + } + + [Test] + public void EventRate_NoSeasonality_MatchesExpectedAverage() + { + TestContext.Out.WriteLine("Seasonality - None"); + var diff = Test(new PatternGenerator.Statics { - Debug.WriteLine("Seasonality - Hours in Day"); - var diff = Test(new PatternGenerator.Statics - { - MeanHourlyRate = 15, - SeasonalFactors_HoursOfDay = new List - { - 1, 2, 3, 3, 3, 3, 3, 10, - 10, 10, 10, 9, 9, 8, 8, 8, - 7, 6, 5, 4, 3, 2, 1, 0, - }, - }, 1000); - if (Math.Abs(diff) > 0.04) Assert.Fail(); - } - - [Test] - public void DaysInWeek() + MeanHourlyRate = 1, + }, 1000); + Assert.That(Math.Abs(diff), Is.LessThanOrEqualTo(0.05), + $"Absolute relative difference {Math.Abs(diff):P2} exceeded tolerance 5%."); + } + + [Test] + public void EventRate_WithHourlySeasonality_MatchesExpectedAverage() + { + TestContext.Out.WriteLine("Seasonality - Hours in Day"); + var statics = new PatternGenerator.Statics { - Debug.WriteLine("Seasonality - Days in Week"); - var diff = Test(new PatternGenerator.Statics - { - MeanHourlyRate = 1, - SeasonalFactors_DaysOfWeek = new List - { - 1, 2, 3, 3, 1, 0, 0, - }, - }, 1000); - if (Math.Abs(diff) > 0.05) Assert.Fail(); - } - - [Test] - public void Combined_HoursInDay_DaysInWeek() + MeanHourlyRate = 15, + }; + statics.SeasonalFactors_HoursOfDay.AddRange( + [ + 1, 2, 3, 3, 3, 3, 3, 10, + 10, 10, 10, 9, 9, 8, 8, 8, + 7, 6, 5, 4, 3, 2, 1, 0, + ]); + var diff = Test(statics, 1000); + Assert.That(Math.Abs(diff), Is.LessThanOrEqualTo(0.04), + $"Absolute relative difference {Math.Abs(diff):P2} exceeded tolerance 4%."); + } + + [Test] + public void EventRate_WithWeeklySeasonality_MatchesExpectedAverage() + { + TestContext.Out.WriteLine("Seasonality - Days in Week"); + var statics = new PatternGenerator.Statics { - Debug.WriteLine("Seasonality - Combined_HoursInDay_DaysInWeek"); - var diff = Test(new PatternGenerator.Statics - { - MeanHourlyRate = 1, - SeasonalFactors_HoursOfDay = new List - { - 1, 2, 3, 3, 3, 3, 3, 10, - 10, 10, 10, 9, 9, 8, 8, 8, - 7, 6, 5, 4, 3, 2, 1, 0, - }, - SeasonalFactors_DaysOfWeek = new List - { - 1, 2, 3, 3, 1, 0, 0, - }, - }, 1000); - if (Math.Abs(diff) > 0.05) Assert.Fail(); - } - - [Test] - public void DaysInMonth() + MeanHourlyRate = 1, + }; + statics.SeasonalFactors_DaysOfWeek.AddRange( + [ + 1, 2, 3, 3, 1, 0, 0, + ]); + var diff = Test(statics, 1000); + Assert.That(Math.Abs(diff), Is.LessThanOrEqualTo(0.05), + $"Absolute relative difference {Math.Abs(diff):P2} exceeded tolerance 5%."); + } + + [Test] + public void EventRate_WithHourlyAndWeeklySeasonality_MatchesExpectedAverage() + { + TestContext.Out.WriteLine("Seasonality - Combined_HoursInDay_DaysInWeek"); + var statics = new PatternGenerator.Statics { - Debug.WriteLine("Seasonality - DaysInMonth"); - var diff = Test(new PatternGenerator.Statics - { - MeanHourlyRate = 0.5, - SeasonalFactors_DaysOfMonth = new List - { - 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, - 4, 4, 4, 4, 4, 4, 4, - 3, 3, 3, 3, 3, 3, 3, - }, - }, 2000); - if (Math.Abs(diff) > 0.05) Assert.Fail(); - } - - [Test] - public void MonthsInYear() + MeanHourlyRate = 1, + }; + statics.SeasonalFactors_HoursOfDay.AddRange( + [ + 1, 2, 3, 3, 3, 3, 3, 10, + 10, 10, 10, 9, 9, 8, 8, 8, + 7, 6, 5, 4, 3, 2, 1, 0, + ]); + statics.SeasonalFactors_DaysOfWeek.AddRange( + [ + 1, 2, 3, 3, 1, 0, 0, + ]); + var diff = Test(statics, 1000); + Assert.That(Math.Abs(diff), Is.LessThanOrEqualTo(0.05), + $"Absolute relative difference {Math.Abs(diff):P2} exceeded tolerance 5%."); + } + + [Test] + public void EventRate_WithDayOfMonthSeasonality_MatchesExpectedAverage() + { + TestContext.Out.WriteLine("Seasonality - DaysInMonth"); + var statics = new PatternGenerator.Statics { - Debug.WriteLine("Seasonality - MonthsInYear"); - var diff = Test(new PatternGenerator.Statics - { - MeanHourlyRate = 0.05, - SeasonalFactors_MonthsOfYear = new List - { - 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, - }, - }, 4000); - if (Math.Abs(diff) > 0.05) Assert.Fail(); - } - - [Test] - public void Years() + MeanHourlyRate = 0.5, + }; + statics.SeasonalFactors_DaysOfMonth.AddRange( + [ + 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, + 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, + ]); + var diff = Test(statics, 2000); + Assert.That(Math.Abs(diff), Is.LessThanOrEqualTo(0.05), + $"Absolute relative difference {Math.Abs(diff):P2} exceeded tolerance 5%."); + } + + [Test] + public void EventRate_WithMonthOfYearSeasonality_MatchesExpectedAverage() + { + TestContext.Out.WriteLine("Seasonality - MonthsInYear"); + var statics = new PatternGenerator.Statics { - Debug.WriteLine("Seasonality - Years"); - var diff = Test(new PatternGenerator.Statics - { - MeanHourlyRate = 0.05, - SeasonalFactors_Years = new List - { - 1, 2, 3, - }, - }, 5000); - if (Math.Abs(diff) > 0.05) Assert.Fail(); - } - - [Test] - public void Customized() + MeanHourlyRate = 0.05, + }; + statics.SeasonalFactors_MonthsOfYear.AddRange( + [ + 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, + ]); + var diff = Test(statics, 4000); + Assert.That(Math.Abs(diff), Is.LessThanOrEqualTo(0.05), + $"Absolute relative difference {Math.Abs(diff):P2} exceeded tolerance 5%."); + } + + // Fix for CS0200: Use collection initializer to add elements to the read-only property instead of assignment. + [Test] + public void EventRate_WithYearSeasonality_MatchesExpectedAverage() + { + TestContext.Out.WriteLine("Seasonality - Years"); + var statics = new PatternGenerator.Statics { - Debug.WriteLine("Seasonality - Customized"); - var diff = Test(new PatternGenerator.Statics - { - MeanHourlyRate = 0.5, - CustomizedSeasonalFactors = new List<(TimeSpan, List)> - { - (TimeSpan.FromHours(100), new List { 1, 3, 9 }), - (TimeSpan.FromHours(1000), new List { 1, 3, 9 }), - }, - }, 3000); - if (Math.Abs(diff) > 0.05) Assert.Fail(); - } - - [Test] - public void Customized_On_and_Off() + MeanHourlyRate = 0.05, + }; + statics.SeasonalFactors_Years.AddRange([1.0, 2.0, 3.0]); + var diff = Test(statics, 5000); + Assert.That(Math.Abs(diff), Is.LessThanOrEqualTo(0.05), + $"Absolute relative difference {Math.Abs(diff):P2} exceeded tolerance 5%."); + } + + [Test] + public void EventRate_WithCustomizedSeasonalFactors_MatchesExpectedAverage() + { + TestContext.Out.WriteLine("Seasonality - Customized"); + var statics = new PatternGenerator.Statics { - Debug.WriteLine("Seasonality - Customized_On_and_Off"); - Test_On_and_Off(new PatternGenerator.Statics - { - MeanHourlyRate = 0.5, - CustomizedSeasonalFactors = new List<(TimeSpan, List)> - { - (TimeSpan.FromHours(100), new List { 1, 3, 9 }), - (TimeSpan.FromHours(1000), new List { 1, 3, 9 }), - }, - }, 100); - } - - private double Test(PatternGenerator.Statics assets, int nEvents) + MeanHourlyRate = 0.5, + }; + statics.CustomizedSeasonalFactors.Add((TimeSpan.FromHours(100), new List { 1, 3, 9 })); + statics.CustomizedSeasonalFactors.Add((TimeSpan.FromHours(1000), new List { 1, 3, 9 })); + var diff = Test(statics, 3000); + Assert.That(Math.Abs(diff), Is.LessThanOrEqualTo(0.05), + $"Absolute relative difference {Math.Abs(diff):P2} exceeded tolerance 5%."); + } + + [Test] + public void Lifecycle_CustomizedSeasonality_StartStop_ResumesAndGeneratesExpectedEvents() + { + const int nEvents = 100; + _logger?.LogInformation("Seasonality - Customized_On_and_Off"); + var statics = new PatternGenerator.Statics { - var gen = new PatternGenerator(assets, 0); - gen.Start(); - for(int i = 0; i < nEvents; i++) gen.Run(1); + MeanHourlyRate = 0.5, + }; + statics.CustomizedSeasonalFactors.Add((TimeSpan.FromHours(100), new List { 1, 3, 9 })); + statics.CustomizedSeasonalFactors.Add((TimeSpan.FromHours(1000), new List { 1, 3, 9 })); + Test_On_and_Off(statics, nEvents); + } - var expected = 1 / assets.MeanHourlyRate * gen.Count; - var observed = (gen.ClockTime - DateTime.MinValue).TotalHours; - var diff = expected / observed - 1; - return diff; - } + private double Test(PatternGenerator.Statics assets, int nEvents) + { + var gen = new PatternGenerator(_logger, assets, id: nameof(PatternGenerator), seed: 0); + gen.Start(); + for (int i = 0; i < nEvents; i++) + gen.Run(1); - private void Test_On_and_Off(PatternGenerator.Statics config, int nEvents) - { - var gen = new PatternGenerator(config, 0); - gen.Start(); - gen.Run(nEvents / 2); - gen.End(); - gen.Run(TimeSpan.FromDays(3)); - gen.Start(); - gen.Run(nEvents / 2); - } + var expected = 1 / assets.MeanHourlyRate * gen.Count; + var observed = gen.ClockTime.TotalHours; + var diff = expected / observed - 1; + return diff; + } + + private void Test_On_and_Off(PatternGenerator.Statics config, int nEvents) + { + var gen = new PatternGenerator(_logger, config, id: nameof(PatternGenerator), seed: 0); + gen.Start(); + Assert.That(gen.IsOn, Is.True, "Generator should be ON after Start()."); + gen.Run(nEvents / 2); + Assert.That(gen.Count, Is.EqualTo(nEvents / 2), "Generator should produce the requested number of events before pausing."); + gen.End(); + Assert.That(gen.IsOn, Is.False, "Generator should be OFF after End()."); + Assert.That(gen.ClockTime > TimeSpan.Zero, Is.True, "ClockTime should have advanced after producing events."); + gen.Run(TimeSpan.FromDays(3)); + gen.Start(); + Assert.That(gen.IsOn, Is.True, "Generator should be ON after resuming Start()."); + gen.Run(nEvents / 2); + Assert.That(gen.Count, Is.EqualTo(nEvents), "Generator should produce the total expected number of events after resume."); + gen.End(); } } diff --git a/O2DESNet.UnitTests/PhaseTracker_Tests.cs b/O2DESNet.UnitTests/PhaseTracker_Tests.cs index 979395b..0887d7f 100644 --- a/O2DESNet.UnitTests/PhaseTracker_Tests.cs +++ b/O2DESNet.UnitTests/PhaseTracker_Tests.cs @@ -1,54 +1,65 @@ using NUnit.Framework; + using System; -namespace O2DESNet.UnitTests +namespace O2DESNet.UnitTests; + +[TestFixture] +public class PhaseTracer_Tests { - public class PhaseTracer_Tests + [Test] + public void PhaseTracer_at_MinDateTime() { - [Test] - public void PhaseTracer_at_MinDateTime() - { - var pr = new PhaseTracer("Idle"); - pr.UpdPhase("Busy1", DateTime.MinValue.AddMinutes(1.2)); - pr.UpdPhase("Busy2", DateTime.MinValue.AddMinutes(2)); - pr.UpdPhase("Idle", DateTime.MinValue.AddMinutes(2.5)); - pr.UpdPhase("Busy2", DateTime.MinValue.AddMinutes(2.9)); - if (Diff(pr.GetProportion("Idle", DateTime.MinValue.AddMinutes(3)), 1.6 / 3)) Assert.Fail(); - if (Diff(pr.GetProportion("Busy1", DateTime.MinValue.AddMinutes(3)), 0.8 / 3)) Assert.Fail(); - if (Diff(pr.GetProportion("Busy2", DateTime.MinValue.AddMinutes(3)), 0.6 / 3)) Assert.Fail(); - if (Diff(pr.GetProportion("Other", DateTime.MinValue.AddMinutes(3)), 0)) Assert.Fail(); - } + var pr = new PhaseTracer("Idle"); + pr.UpdPhase("Busy1", TimeSpan.FromMinutes(1.2)); + pr.UpdPhase("Busy2", TimeSpan.FromMinutes(2)); + pr.UpdPhase("Idle", TimeSpan.FromMinutes(2.5)); + pr.UpdPhase("Busy2", TimeSpan.FromMinutes(2.9)); + if (Diff(pr.GetProportion("Idle", TimeSpan.FromMinutes(3)), 1.6 / 3)) + Assert.Fail(); + if (Diff(pr.GetProportion("Busy1", TimeSpan.FromMinutes(3)), 0.8 / 3)) + Assert.Fail(); + if (Diff(pr.GetProportion("Busy2", TimeSpan.FromMinutes(3)), 0.6 / 3)) + Assert.Fail(); + if (Diff(pr.GetProportion("Other", TimeSpan.FromMinutes(3)), 0)) + Assert.Fail(); + } - [Test] - public void PhaseTracer_at_Non_MinDateTime() - { - var pr = new PhaseTracer("Idle", new DateTime(1, 1, 1, 0, 1, 0)); - pr.UpdPhase("Busy1", DateTime.MinValue.AddMinutes(1.2)); - pr.UpdPhase("Busy2", DateTime.MinValue.AddMinutes(2)); - pr.UpdPhase("Idle", DateTime.MinValue.AddMinutes(2.5)); - pr.UpdPhase("Busy2", DateTime.MinValue.AddMinutes(2.9)); - if (Diff(pr.GetProportion("Idle", DateTime.MinValue.AddMinutes(3)), 0.6 / 2)) Assert.Fail(); - if (Diff(pr.GetProportion("Busy1", DateTime.MinValue.AddMinutes(3)), 0.8 / 2)) Assert.Fail(); - if (Diff(pr.GetProportion("Busy2", DateTime.MinValue.AddMinutes(3)), 0.6 / 2)) Assert.Fail(); - } + [Test] + public void PhaseTracer_at_Non_MinDateTime() + { + var pr = new PhaseTracer("Idle", TimeSpan.FromMinutes(1)); + pr.UpdPhase("Busy1", TimeSpan.FromMinutes(1.2)); + pr.UpdPhase("Busy2", TimeSpan.FromMinutes(2)); + pr.UpdPhase("Idle", TimeSpan.FromMinutes(2.5)); + pr.UpdPhase("Busy2", TimeSpan.FromMinutes(2.9)); + if (Diff(pr.GetProportion("Idle", TimeSpan.FromMinutes(3)), 0.6 / 2)) + Assert.Fail(); + if (Diff(pr.GetProportion("Busy1", TimeSpan.FromMinutes(3)), 0.8 / 2)) + Assert.Fail(); + if (Diff(pr.GetProportion("Busy2", TimeSpan.FromMinutes(3)), 0.6 / 2)) + Assert.Fail(); + } - [Test] - public void PhaseTracer_with_WarmUp() - { - var pr = new PhaseTracer("Idle"); - pr.UpdPhase("Busy1", DateTime.MinValue.AddMinutes(1.2)); - pr.WarmedUp(DateTime.MinValue.AddMinutes(1.5)); - pr.UpdPhase("Busy2", DateTime.MinValue.AddMinutes(2)); - pr.UpdPhase("Idle", DateTime.MinValue.AddMinutes(2.5)); - pr.UpdPhase("Busy2", DateTime.MinValue.AddMinutes(2.9)); - if (Diff(pr.GetProportion("Idle", DateTime.MinValue.AddMinutes(3)), 0.4 / 1.5)) Assert.Fail(); - if (Diff(pr.GetProportion("Busy1", DateTime.MinValue.AddMinutes(3)), 0.5 / 1.5)) Assert.Fail(); - if (Diff(pr.GetProportion("Busy2", DateTime.MinValue.AddMinutes(3)), 0.6 / 1.5)) Assert.Fail(); - } + [Test] + public void PhaseTracer_with_WarmUp() + { + var pr = new PhaseTracer("Idle"); + pr.UpdPhase("Busy1", TimeSpan.FromMinutes(1.2)); + pr.WarmedUp(TimeSpan.FromMinutes(1.5)); + pr.UpdPhase("Busy2", TimeSpan.FromMinutes(2)); + pr.UpdPhase("Idle", TimeSpan.FromMinutes(2.5)); + pr.UpdPhase("Busy2", TimeSpan.FromMinutes(2.9)); + if (Diff(pr.GetProportion("Idle", TimeSpan.FromMinutes(3)), 0.4 / 1.5)) + Assert.Fail(); + if (Diff(pr.GetProportion("Busy1", TimeSpan.FromMinutes(3)), 0.5 / 1.5)) + Assert.Fail(); + if (Diff(pr.GetProportion("Busy2", TimeSpan.FromMinutes(3)), 0.6 / 1.5)) + Assert.Fail(); + } - private static bool Diff(double x1, double x2, int decimals = 12) - { - return Math.Round(x1, decimals) != Math.Round(x2, decimals); - } + private static bool Diff(double x1, double x2, int decimals = 12) + { + return Math.Round(x1, decimals) != Math.Round(x2, decimals); } } diff --git a/O2DESNet.UnitTests/PmPathTests/ControlPoint.cs b/O2DESNet.UnitTests/PmPathTests/ControlPoint.cs new file mode 100644 index 0000000..1b5d153 --- /dev/null +++ b/O2DESNet.UnitTests/PmPathTests/ControlPoint.cs @@ -0,0 +1,16 @@ +using System.Numerics; + +namespace O2DESNet.UnitTests.PmPathTests; + +public readonly record struct ControlPoint +{ + public ControlPointId Id { get; init; } + public string Name { get; } + public Vector2 Start { get; init; } + public Vector2 End { get; init; } + + public ControlPoint(ControlPointId id, string name, Vector2 start, Vector2 end) + { + (Id, Name, Start, End) = (id, name, start, end); + } +} diff --git a/O2DESNet.UnitTests/PmPathTests/ControlPointId.cs b/O2DESNet.UnitTests/PmPathTests/ControlPointId.cs new file mode 100644 index 0000000..641bf88 --- /dev/null +++ b/O2DESNet.UnitTests/PmPathTests/ControlPointId.cs @@ -0,0 +1,125 @@ +using O2DESNet.Standard; + +using System; + +namespace O2DESNet.UnitTests.PmPathTests; + +public readonly record struct ControlPointId +{ + // Private Fields + private static readonly string _emptyNormalized = Guid.Empty.ToString("n"); + private readonly string _value = _emptyNormalized; + + // Public Properties + /// + /// A sentinel empty identifier equal to in the normalized "n" format. + /// + public static ControlPointId Empty { get; } = new(Guid.Empty); + + /// + /// Gets a value indicating whether this identifier is the empty value. + /// + public bool IsEmpty => Value == Empty.Value; + + /// + /// Normalized GUID string (format "n"). Defaults to when using default(EntityId). + /// + public string Value + { + get => _value; + } + + // Constructors + /// + /// Create a new from a . + /// + public ControlPointId(Guid guid) + { + _value = guid.ToString("n"); + } + + /// + /// Create a new from a GUID string. The value is validated and normalized to format "n". + /// + /// Any valid GUID string in supported formats (e.g., "D", "N", "B", "P", "X"). + /// Thrown when is not a valid GUID. + public ControlPointId(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("EntityId value cannot be null or whitespace.", nameof(value)); + } + + if (!Guid.TryParse(value, out var guid)) + { + throw new ArgumentException("EntityId value must be a valid GUID string.", nameof(value)); + } + + _value = guid.ToString("n"); + } + + // Public Methods + /// + /// Generate a new unique . + /// + public static EntityId New() => new(Guid.NewGuid()); + + /// + /// Factory method to create an from a . + /// + public static EntityId Create(Guid guid) => new(guid); + + /// + /// Factory method to create an from a GUID string. The input is validated and normalized. + /// + /// Thrown when is not a valid GUID. + public static EntityId Create(string value) => new(value); + + /// + public override string ToString() => Value; + + /// + /// Try to parse a string into an . + /// + /// The input string. Any valid GUID format is accepted. + /// The resulting identifier when parsing succeeds; otherwise . + /// if parsing succeeds; otherwise . + public static bool TryParse(string? value, out EntityId id) + { + if (Guid.TryParse(value, out var guid)) + { + id = new EntityId(guid); + return true; + } + + id = default; + return false; + } + + /// + /// Parse a string into an , throwing if invalid. + /// + /// The input string. Any valid GUID format is accepted. + /// Thrown when is not a valid GUID. + public static ControlPointId Parse(string value) => new(value); + + /// + /// Implicit conversion from to . + /// + public static implicit operator ControlPointId(Guid value) => new(value); + + /// + /// Implicit conversion from to . + /// + public static implicit operator ControlPointId(string value) => new(value); + + /// + /// Explicit conversion from to . + /// + public static explicit operator Guid(ControlPointId id) => Guid.Parse(id.Value); + + /// + /// Explicit conversion from to . + /// + public static explicit operator string(ControlPointId id) => id.Value; +} diff --git a/O2DESNet.UnitTests/PmPathTests/PmPath.cs b/O2DESNet.UnitTests/PmPathTests/PmPath.cs new file mode 100644 index 0000000..ff91059 --- /dev/null +++ b/O2DESNet.UnitTests/PmPathTests/PmPath.cs @@ -0,0 +1,38 @@ +using System; +using System.Numerics; + +namespace O2DESNet.UnitTests.PmPathTests; + +public readonly record struct PmPath +{ + /// + /// The maximum allowed number of vehicles on the path segment + /// + public int Capacity { get; } + public ControlPoint Start { get; } + public ControlPoint End { get; } + /// + /// Number of lanes per path segment. Each lane has the same capacity. + /// + /// + /// A two-lane path with a capacity of 5 vehicles per lane would have a total effective capacity of 10 vehicles. + /// + public int NumberOfLanes { get; } = 1; // Default is always 1 lane + public int EffectiveCapacity { get; } + public double Length { get; } + + public PmPath(int capacity, ControlPoint start, ControlPoint end, int numberOfLanes) + { + if (numberOfLanes < 1) + throw new ArgumentOutOfRangeException(nameof(numberOfLanes), "Number of lanes must be at least 1"); + + Capacity = capacity; + Start = start; + End = end; + NumberOfLanes = numberOfLanes; + EffectiveCapacity = capacity * numberOfLanes; + Length = PathLength(); + } + + private double PathLength() => Vector2.Distance(Start.End, End.Start); +} diff --git a/O2DESNet.UnitTests/PmPathTests/PmPathTraffic_Tests.cs b/O2DESNet.UnitTests/PmPathTests/PmPathTraffic_Tests.cs new file mode 100644 index 0000000..2ad203e --- /dev/null +++ b/O2DESNet.UnitTests/PmPathTests/PmPathTraffic_Tests.cs @@ -0,0 +1,437 @@ +using Microsoft.Extensions.Logging; + +using NUnit.Framework; + +using O2DESNet.Standard; + +using Serilog; +using Serilog.Events; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace O2DESNet.UnitTests.PmPathTests; + +[TestFixture] +public partial class PmPathTraffic_Tests +{ + private Microsoft.Extensions.Logging.ILogger? _logger; + private LogEventLevel _minLevel = LogEventLevel.Information; + + [SetUp] + public void Init() + { + // Comment out the following code to switch log file + ConfigureSerilog("Logs\\log-O2DES-Library-Tests.txt"); + } + + private void ConfigureSerilog(string filePath) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Is(_minLevel) + .WriteTo.Console() // Output to console + .WriteTo.File(filePath, + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 31, + restrictedToMinimumLevel: _minLevel, + shared: true) // Output to file + .Enrich.FromLogContext() + .CreateLogger(); + + _logger = new LoggerFactory() + .AddSerilog(Log.Logger) + .CreateLogger(); + } + + [Test] + public void PmPath_Module_Single_Capacity_Test() + { + // Arrange + var path1 = new PmPathSingleCapacity("Path1", 0); + path1.OnArrive += (s, e) => + { + var vehicle = e.Vehicle as Vehicle; + var path = s as PmPathSingleCapacity; + _logger?.LogInformation($"{vehicle?.Name} arrived at {path?.Id} at {path?.ClockTime}"); + }; + + path1.OnProcess += (s, e) => + { + var vehicle = e.Vehicle as Vehicle; + var path = s as PmPathSingleCapacity; + _logger?.LogInformation($"{vehicle?.Name} processing at {path?.Id} at {path?.ClockTime}"); + }; + + path1.OnDepart += (s, e) => + { + var vehicle = e.Vehicle as Vehicle; + var path = s as PmPathSingleCapacity; + _logger?.LogInformation($"{vehicle?.Name} departed from {path?.Id} at {path?.ClockTime}"); + }; + + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle1"), TimeSpan.FromSeconds(1)); + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle2"), TimeSpan.FromSeconds(5)); + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle3"), TimeSpan.FromSeconds(25)); + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle4"), TimeSpan.FromSeconds(27)); + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle5"), TimeSpan.FromSeconds(30)); + + path1.Run(15); + + path1.Dispose(); + + } + + [Test] + public void PmPath_Module_N_Capacity_Test() + { + double vehicleLength = 5; // meters + double vehicleAllowedGapDistance = 2; // meters + int capacity = 2; + + // Arrange + var path1 = new PmPathMultiCapacity("Path1", 0, vehicleLength, vehicleAllowedGapDistance, capacity); + + _logger?.LogInformation("Begin two-capacity path validation"); + _logger?.LogInformation($"Configured Capacity={path1.Capacity}, ProcessDuration={path1.ProcessDuration}, MinHeadway={path1.MinHeadwayTime}"); + + // Collect events for verification + var arrivals = new List<(Vehicle vehicle, TimeSpan time)>(); + var starts = new List<(Vehicle vehicle, TimeSpan time)>(); + var departures = new List<(Vehicle vehicle, TimeSpan time)>(); + + path1.OnArrive += (s, e) => + { + var vehicle = e.Vehicle as Vehicle; + var path = (PmPathMultiCapacity)s!; + arrivals.Add((vehicle!, path.ClockTime)); + _logger?.LogInformation($"Event: {vehicle?.Name} arrived at {path.ClockTime}"); + }; + + path1.OnProcess += (s, e) => + { + var vehicle = e.Vehicle as Vehicle; + var path = (PmPathMultiCapacity)s!; + starts.Add((vehicle!, path.ClockTime)); + _logger?.LogInformation($"Event: {vehicle?.Name} started at {path.ClockTime}"); + }; + + path1.OnDepart += (s, e) => + { + var vehicle = e.Vehicle as Vehicle; + var path = (PmPathMultiCapacity)s!; + departures.Add((vehicle!, path.ClockTime)); + _logger?.LogInformation($"Event: {vehicle?.Name} departed at {path.ClockTime}"); + }; + + // Enqueue several vehicles densely to exercise N-capacity with spacing + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle1"), TimeSpan.FromSeconds(1)); + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle2"), TimeSpan.FromSeconds(5)); + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle3"), TimeSpan.FromSeconds(25)); + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle4"), TimeSpan.FromSeconds(27)); + path1.RequestToEnter(new Vehicle(VehicleId.New(), "Vehicle5"), TimeSpan.FromSeconds(30)); + + // Run enough events to process all + path1.Run(40); + + _logger?.LogInformation("Validation step: counts should match the number of vehicles"); + Assert.That(arrivals.Count, Is.EqualTo(5), "All vehicles should arrive"); + Assert.That(starts.Count, Is.EqualTo(5), "All vehicles should start processing"); + Assert.That(departures.Count, Is.EqualTo(5), "All vehicles should depart"); + + // Build lookup for times + var startMap = starts.ToDictionary(x => x.vehicle, x => x.time); + var departMap = departures.ToDictionary(x => x.vehicle, x => x.time); + + _logger?.LogInformation("Validation step: each depart time equals start + ProcessDuration"); + foreach (var (vehicle, startTime) in starts) + { + Assert.That(departMap.ContainsKey(vehicle), $"Missing depart for {vehicle.Name}"); + var departTime = departMap[vehicle]; + _logger?.LogInformation($"Check duration for {vehicle.Name}: start={startTime}, depart={departTime}"); + Assert.That(departTime - startTime, Is.EqualTo(path1.ProcessDuration), $"Process duration mismatch for {vehicle.Name}"); + } + + _logger?.LogInformation("Validation step: headway between consecutive starts >= MinHeadway"); + var orderedStarts = starts.OrderBy(x => x.time).ToList(); + for (int i = 1; i < orderedStarts.Count; i++) + { + var prev = orderedStarts[i - 1]; + var curr = orderedStarts[i]; + var delta = curr.time - prev.time; + _logger?.LogInformation($"Headway {prev.vehicle.Name}->{curr.vehicle.Name}: {delta} (min {path1.MinHeadwayTime})"); + Assert.That(delta >= path1.MinHeadwayTime, $"Headway violated between {prev.vehicle.Name} and {curr.vehicle.Name}: {delta} < {path1.MinHeadwayTime}"); + } + + _logger?.LogInformation("Validation step: concurrency never exceeds Capacity"); + var changes = new List<(TimeSpan t, int d)>(); + changes.AddRange(starts.Select(s => (s.time, +1))); + changes.AddRange(departures.Select(d => (d.time, -1))); + var ordered = changes + .OrderBy(c => c.t) + .ThenBy(c => c.d) // departures (-1) before starts (+1) at the same timestamp + .ToList(); + + int concurrent = 0; + int maxConcurrent = 0; + foreach (var (t, d) in ordered) + { + concurrent += d; + if (concurrent > maxConcurrent) + maxConcurrent = concurrent; + + Assert.That(concurrent, Is.LessThanOrEqualTo(path1.Capacity), $"Capacity exceeded (>{path1.Capacity}) at {t}"); + } + + _logger?.LogInformation($"Max concurrency observed: {maxConcurrent}"); + Assert.That(maxConcurrent, Is.LessThanOrEqualTo(path1.Capacity), $"Max concurrency should be <= {path1.Capacity}"); + + _logger?.LogInformation("N-capacity path validation completed successfully"); + + path1.Dispose(); + } + + class PmPathTrafficNetwork : Sandbox + { + public event EventHandler? OnArrive; + public event EventHandler? OnProcess; + public event EventHandler? OnDepart; + + // Private Fields + private readonly double _vehicleLengthMeters; + private readonly double _vehicleAllowedGapMeters; + private readonly double _speedMetersPerSecond = 1d; // assumed constant speed along path + private readonly TimeSpan _processDuration = TimeSpan.FromSeconds(10); + private readonly int _capacity; + private readonly List _serverNexts; + + // Reserve headway based on the last planned start time + private TimeSpan? _lastPlannedStartTime; + + // Public Properties + public TimeSpan ProcessDuration => _processDuration; + public TimeSpan MinHeadwayTime => MinHeadway(); + public int Capacity => _capacity; + + public PmPathTrafficNetwork(string id, int seed, double vehicleLength, double vehicleAllowedGapDistance, int capacity) : base(id, seed) + { + if (capacity <= 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive"); + + _vehicleLengthMeters = vehicleLength; + _vehicleAllowedGapMeters = vehicleAllowedGapDistance; + _capacity = capacity; + _serverNexts = Enumerable.Repeat(TimeSpan.Zero, _capacity).ToList(); + } + + public void RequestToEnter(Vehicle vehicle, TimeSpan timestamp) + { + Schedule(() => Arrive(vehicle), timestamp); + } + + private TimeSpan MinHeadway() + { + var seconds = (_vehicleLengthMeters + _vehicleAllowedGapMeters) / _speedMetersPerSecond; + return TimeSpan.FromSeconds(seconds); + } + + private static TimeSpan Max(TimeSpan a, TimeSpan b) + { + return a >= b ? a : b; + } + + private static TimeSpan Max(TimeSpan a, TimeSpan b, TimeSpan c) + { + return Max(Max(a, b), c); + } + + private void Arrive(IEntity vehicle) + { + OnArrive?.Invoke(this, new VehicleArrivedEventArgs(vehicle, null)); + + var headwayEarliest = _lastPlannedStartTime is null ? TimeSpan.Zero : _lastPlannedStartTime.Value + MinHeadway(); + // Find the earliest available server index and time + int earliestIdx = 0; + var earliestServerTime = _serverNexts[0]; + for (int i = 1; i < _serverNexts.Count; i++) + { + if (_serverNexts[i] < earliestServerTime) + { + earliestServerTime = _serverNexts[i]; + earliestIdx = i; + } + } + + var startTime = Max(ClockTime, headwayEarliest, earliestServerTime); + var delay = startTime - ClockTime; + + // Reserve capacity now by assigning the earlier-available server + _serverNexts[earliestIdx] = startTime + _processDuration; + + // Reserve headway for subsequent arrivals + _lastPlannedStartTime = startTime; + + Schedule(() => Process(vehicle), delay); + } + + private void Process(IEntity vehicle) + { + OnProcess?.Invoke(this, new VehicleProcessedEventArgs(vehicle, null)); + Schedule(() => Depart(vehicle), _processDuration); + } + + private void Depart(IEntity vehicle) + { + OnDepart?.Invoke(this, new VehicleDepartedEventArgs(vehicle, null)); + } + } + + class PmPathMultiCapacity : Sandbox + { + public event EventHandler? OnArrive; + public event EventHandler? OnProcess; + public event EventHandler? OnDepart; + + // Private Fields + private readonly double _vehicleLengthMeters; + private readonly double _vehicleAllowedGapMeters; + private readonly double _speedMetersPerSecond = 1d; // assumed constant speed along path + private readonly TimeSpan _processDuration = TimeSpan.FromSeconds(10); + private readonly int _capacity; + private readonly List _serverNexts; + + // Reserve headway based on the last planned start time + private TimeSpan? _lastPlannedStartTime; + + // Public Properties + public TimeSpan ProcessDuration => _processDuration; + public TimeSpan MinHeadwayTime => MinHeadway(); + public int Capacity => _capacity; + + public PmPathMultiCapacity(string id, int seed, double vehicleLength, double vehicleAllowedGapDistance, int capacity) : base(id, seed) + { + if (capacity <= 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive"); + + _vehicleLengthMeters = vehicleLength; + _vehicleAllowedGapMeters = vehicleAllowedGapDistance; + _capacity = capacity; + _serverNexts = Enumerable.Repeat(TimeSpan.Zero, _capacity).ToList(); + } + + public void RequestToEnter(Vehicle vehicle, TimeSpan timestamp) + { + Schedule(() => Arrive(vehicle), timestamp); + } + + private TimeSpan MinHeadway() + { + var seconds = (_vehicleLengthMeters + _vehicleAllowedGapMeters) / _speedMetersPerSecond; + return TimeSpan.FromSeconds(seconds); + } + + private static TimeSpan Max(TimeSpan a, TimeSpan b) + { + return a >= b ? a : b; + } + + private static TimeSpan Max(TimeSpan a, TimeSpan b, TimeSpan c) + { + return Max(Max(a, b), c); + } + + private void Arrive(Vehicle vehicle) + { + OnArrive?.Invoke(this, new VehicleArrivedEventArgs(vehicle, null)); + + var headwayEarliest = _lastPlannedStartTime is null ? TimeSpan.Zero : _lastPlannedStartTime.Value + MinHeadway(); + // Find the earliest available server index and time + int earliestIdx = 0; + var earliestServerTime = _serverNexts[0]; + for (int i = 1; i < _serverNexts.Count; i++) + { + if (_serverNexts[i] < earliestServerTime) + { + earliestServerTime = _serverNexts[i]; + earliestIdx = i; + } + } + + var startTime = Max(ClockTime, headwayEarliest, earliestServerTime); + var delay = startTime - ClockTime; + + // Reserve capacity now by assigning the earlier-available server + _serverNexts[earliestIdx] = startTime + _processDuration; + + // Reserve headway for subsequent arrivals + _lastPlannedStartTime = startTime; + + Schedule(() => Process(vehicle), delay); + } + + private void Process(Vehicle vehicle) + { + OnProcess?.Invoke(this, new VehicleProcessedEventArgs(vehicle, null)); + Schedule(() => Depart(vehicle), _processDuration); + } + + private void Depart(Vehicle vehicle) + { + OnDepart?.Invoke(this, new VehicleDepartedEventArgs(vehicle, null)); + } + } + + class PmPathSingleCapacity : Sandbox + { + public event EventHandler? OnArrive; + public event EventHandler? OnProcess; + public event EventHandler? OnDepart; + + // Private Fields + private TimeSpan _nextAvailableTime = TimeSpan.Zero; + private readonly int _capacity; + + // Public Properties + public int Capacity => _capacity; + + public PmPathSingleCapacity(string id, int seed, int capacity = 1) : base(id, seed) + { + if (capacity <= 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive"); + + _capacity = capacity; + } + + public void RequestToEnter(Vehicle vehicle, TimeSpan timestamp) + { + Schedule(() => Arrive(vehicle), timestamp); + } + + private void Arrive(Vehicle vehicle) + { + var processDuration = TimeSpan.FromSeconds(10); + OnArrive?.Invoke(this, new VehicleArrivedEventArgs(vehicle, null)); + + // Compute earliest start time respecting single-capacity processing + var startTime = ClockTime < _nextAvailableTime ? _nextAvailableTime : ClockTime; + var delay = startTime - ClockTime; // relative delay from now + + // Reserve capacity for this job so subsequent arrivals see the updated availability + _nextAvailableTime = startTime + processDuration; + + Schedule(() => Process(vehicle), delay); + } + + private void Process(Vehicle vehicle) + { + var processDuration = TimeSpan.FromSeconds(10); + OnProcess?.Invoke(this, new VehicleProcessedEventArgs(vehicle, null)); + Schedule(() => Depart(vehicle), processDuration); + } + + private void Depart(Vehicle vehicle) + { + OnDepart?.Invoke(this, new VehicleDepartedEventArgs(vehicle, null)); + } + } +} \ No newline at end of file diff --git a/O2DESNet.UnitTests/PmPathTests/Vehicle.cs b/O2DESNet.UnitTests/PmPathTests/Vehicle.cs new file mode 100644 index 0000000..30c9525 --- /dev/null +++ b/O2DESNet.UnitTests/PmPathTests/Vehicle.cs @@ -0,0 +1,60 @@ +using O2DESNet.Standard; + +using System; + +namespace O2DESNet.UnitTests.PmPathTests; + +public sealed class VehicleId : IEquatable +{ + // Private Fields + private readonly Guid _value; + + // Public Properties + public Guid Value => _value; + + // Constructors + public VehicleId() + { + _value = Guid.NewGuid(); + } + + public VehicleId(Guid value) + { + _value = value; + } + + public static VehicleId New() => new(Guid.NewGuid()); + + // Public Methods + public override string ToString() => _value.ToString("n"); + + public bool Equals(VehicleId? other) + { + if (other is null) + { + return false; + } + + return _value.Equals(other._value); + } + + public override bool Equals(object? obj) => obj is VehicleId other && Equals(other); + + public override int GetHashCode() => _value.GetHashCode(); + + public static bool operator ==(VehicleId? left, VehicleId? right) => Equals(left, right); + + public static bool operator !=(VehicleId? left, VehicleId? right) => !Equals(left, right); +} + +public class Vehicle : Entity +{ + private readonly string _name; + + public string Name => _name; + + public Vehicle(VehicleId id, string name) : base(id) + { + _name = name; + } +} diff --git a/O2DESNet.UnitTests/PmPathTests/VehicleEventArgs.cs b/O2DESNet.UnitTests/PmPathTests/VehicleEventArgs.cs new file mode 100644 index 0000000..122f743 --- /dev/null +++ b/O2DESNet.UnitTests/PmPathTests/VehicleEventArgs.cs @@ -0,0 +1,21 @@ +using O2DESNet.Standard; + +using System; + +namespace O2DESNet.UnitTests.PmPathTests; + +public abstract class VehicleEventArgs : EventArgs +{ + public IEntity Vehicle { get; } + public PmPath? Path { get; } + + protected VehicleEventArgs(IEntity vehicle, PmPath? pmPath) + { + Vehicle = vehicle; + Path = pmPath; + } +} + +public class VehicleArrivedEventArgs(IEntity vehicle, PmPath? pmPath) : VehicleEventArgs(vehicle, pmPath); +public class VehicleProcessedEventArgs(IEntity vehicle, PmPath? pmPath) : VehicleEventArgs(vehicle, pmPath); +public class VehicleDepartedEventArgs(IEntity vehicle, PmPath? pmPath) : VehicleEventArgs(vehicle, pmPath); diff --git a/O2DESNet.UnitTests/RandomVariableTests/Categorical/UniformTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Categorical/UniformTests.cs index dc09558..b75e764 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Categorical/UniformTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Categorical/UniformTests.cs @@ -1,50 +1,55 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; + using O2DESNet.RandomVariables.Categorical; + using System; using System.Collections.Generic; -using System.Diagnostics; namespace O2DESNet.UnitTests.RandomVariableTests.Categorical { - [TestClass] + [TestFixture] public class UniformTests { - [TestMethod] + [Test] public void TestMeanAndVariacneConsistency() { - List numList = new List() { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + List numList = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const int numSamples = 100000; double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Uniform uniform = new Uniform(); + RunningStat rs = new(); + Random defaultrs = new(); + Uniform uniform = new(); uniform.Candidates = numList; rs.Clear(); - mean = 50; stdev = 0; + mean = 50; + stdev = 0; for (int i = 0; i < numSamples; ++i) { rs.Push(uniform.Sample(defaultrs)); } + PrintResult.CompareMeanAndVariance("uniform categorical", mean, stdev * stdev, rs.Mean(), rs.Variance()); } - [TestMethod] + + [Test] public void TestUniformRVCategoricalGenericObjectSampleMethod() { - List numList = new List() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - Uniform uniform = new Uniform(); - Random rs = new Random(); + List numList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + Uniform uniform = new(); + Random rs = new(); uniform.Candidates = numList; for (int i = 0; i < 20; i++) { var tmep = uniform.Sample(rs); - Debug.WriteLine(tmep); + TestContext.Out.WriteLine(tmep); } } - [TestMethod] + + [Test] public void TestUniformRVCategoricalCostumizedObjectSampleMethod() { - Random rs = new Random(); - List students = new List(); + Random rs = new(); + List students = []; for (int i = 0; i < 20; i++) { var s = new student(); @@ -52,12 +57,13 @@ public void TestUniformRVCategoricalCostumizedObjectSampleMethod() s.name = "a" + Convert.ToString(i); students.Add(s); } - Uniform uniform = new Uniform(); + + Uniform uniform = new(); uniform.Candidates = students; for (int i = 0; i < 20; i++) { var temp = uniform.Sample(rs); - Debug.WriteLine(temp.name + " " + temp.id); + TestContext.Out.WriteLine(temp.name + " " + temp.id); } } } @@ -67,5 +73,4 @@ public class student { public int id { get; set; } public string name { get; set; } - } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Continuous/BetaTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Continuous/BetaTests.cs index 33d7a51..96f401f 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Continuous/BetaTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Continuous/BetaTests.cs @@ -1,60 +1,62 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; + using O2DESNet.RandomVariables.Continuous; + using System; -namespace O2DESNet.UnitTests.RandomVariableTests.Continuous +namespace O2DESNet.UnitTests.RandomVariableTests.Continuous; + +[TestFixture] +public class BetaTests { - [TestClass] - public class BetaTests + [Test] + public void TestMeanAndVariacneConsistency() { - [TestMethod] - public void TestMeanAndVariacneConsistency() + const int numSamples = 100000; + double mean, variance; + + RunningStat rs = new(); + Random defaultrs = new(); + Beta beta = new(); + rs.Clear(); + //double a = 2, b = 70; + double a = 2, b = 10; + mean = a / (a + b); + variance = a * b / (a + b) / (a + b) / (a + b + 1); + beta.Mean = mean; + beta.StandardDeviation = Math.Sqrt(variance); + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, variance; - - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Beta beta = new Beta(); - rs.Clear(); - //double a = 2, b = 70; - double a = 2, b = 10; - mean = a / (a + b); - variance = a * b / (a + b) / (a + b) / (a + b + 1); - beta.Mean = mean; - beta.StandardDeviation = Math.Sqrt(variance); - for (int i = 0; i < numSamples; ++i) - { - rs.Push(beta.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("Beta", mean, variance, rs.Mean(), rs.Variance()); + rs.Push(beta.Sample(defaultrs)); } - [TestMethod] - public void TestMeanAndVariacneConsistency_Mean() + PrintResult.CompareMeanAndVariance("Beta", mean, variance, rs.Mean(), rs.Variance()); + } + + [Test] + public void TestMeanAndVariacneConsistency_Mean() + { + const int numSamples = 100000; + double mean, variance; + + RunningStat rs = new(); + Random defaultrs = new(); + Beta beta = new(); + rs.Clear(); + //double a = 2, b = 70; + + //mean = a / (a + b); + //variance = mean * (1 - mean) / (a + b + 1); + mean = 0.1; + variance = 0.1 * 0.1; + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, variance; - - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Beta beta = new Beta(); - rs.Clear(); - //double a = 2, b = 70; - - //mean = a / (a + b); - //variance = mean * (1 - mean) / (a + b + 1); - mean = 0.1; variance = 0.1 * 0.1; - for (int i = 0; i < numSamples; ++i) - { - - beta.Mean = mean; - beta.StandardDeviation = Math.Sqrt(variance); - rs.Push(beta.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("Beta", mean, variance, rs.Mean(), rs.Variance()); + + beta.Mean = mean; + beta.StandardDeviation = Math.Sqrt(variance); + rs.Push(beta.Sample(defaultrs)); } + PrintResult.CompareMeanAndVariance("Beta", mean, variance, rs.Mean(), rs.Variance()); } - } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Continuous/ExponentialTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Continuous/ExponentialTests.cs index 57c7d20..06edf0a 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Continuous/ExponentialTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Continuous/ExponentialTests.cs @@ -1,32 +1,32 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; + using O2DESNet.RandomVariables.Continuous; + using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -namespace O2DESNet.UnitTests.RandomVariableTests.Continuous +namespace O2DESNet.UnitTests.RandomVariableTests.Continuous; + +[TestFixture] +public class ExponentialTests { - [TestClass] - public class ExponentialTests + [Test] + public void TestMeanAndVariacneConsistency() { - [TestMethod] - public void TestMeanAndVariacneConsistency() + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + Exponential exponential = new(); + rs.Clear(); + mean = 2; + stdev = 2; + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Exponential exponential = new Exponential(); - rs.Clear(); - mean = 2; stdev = 2; - for (int i = 0; i < numSamples; ++i) - { - exponential.StandardDeviation = 2; - //exponential.Mean = mean; - rs.Push(exponential.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("exponential", mean, stdev * stdev, rs.Mean(), rs.Variance()); + exponential.StandardDeviation = 2; + //exponential.Mean = mean; + rs.Push(exponential.Sample(defaultrs)); } + + PrintResult.CompareMeanAndVariance("exponential", mean, stdev * stdev, rs.Mean(), rs.Variance()); } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Continuous/GammaTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Continuous/GammaTests.cs index 035baba..e10ae74 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Continuous/GammaTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Continuous/GammaTests.cs @@ -1,51 +1,57 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; +using NUnit.Framework; + using O2DESNet.RandomVariables.Continuous; -namespace O2DESNet.UnitTests.RandomVariableTests.Continuous +using System; + +namespace O2DESNet.UnitTests.RandomVariableTests.Continuous; + +[TestFixture] +public class GammaTests { - [TestClass] - public class GammaTests + [Test] + public void TestMeanAndVariacneConsistency() { - [TestMethod] - public void TestMeanAndVariacneConsistency() + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + Gamma gamma = new(); + rs.Clear(); + mean = 2; + stdev = 5; + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Gamma gamma = new Gamma(); - rs.Clear(); - mean = 2; stdev = 5; - for (int i = 0; i < numSamples; ++i) - { - gamma.Mean = mean; - gamma.StandardDeviation = stdev; - rs.Push(gamma.Sample(defaultrs));//yy - } - PrintResult.CompareMeanAndVariance("gamma", mean, stdev * stdev, rs.Mean(), rs.Variance()); // TODO: result not consistent need to fix the bug + gamma.Mean = mean; + gamma.StandardDeviation = stdev; + rs.Push(gamma.Sample(defaultrs));//yy } - [TestMethod] - public void TestMeanAndVariacneConsistency_Shape() + + PrintResult.CompareMeanAndVariance("gamma", mean, stdev * stdev, rs.Mean(), rs.Variance()); // TODO: result not consistent need to fix the bug + } + [Test] + public void TestMeanAndVariacneConsistency_Shape() + { + const int numSamples = 100000; + double mean, stdev; + double alpha, beta; + RunningStat rs = new(); + Random defaultrs = new(); + Gamma gamma = new(); + rs.Clear(); + alpha = 4; + beta = 4; + mean = alpha / beta; + stdev = Math.Sqrt(alpha / beta / beta); + + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - double alpha, beta; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Gamma gamma = new Gamma(); - rs.Clear(); - alpha = 4; beta = 4; - mean = alpha / beta; stdev = Math.Sqrt(alpha / beta / beta); - - for (int i = 0; i < numSamples; ++i) - { - - gamma.Mean = mean; - gamma.StandardDeviation = stdev; - rs.Push(gamma.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("gamma", mean, stdev * stdev, rs.Mean(), rs.Variance()); // TODO: result not consistent need to fix the bug + + gamma.Mean = mean; + gamma.StandardDeviation = stdev; + rs.Push(gamma.Sample(defaultrs)); } + + PrintResult.CompareMeanAndVariance("gamma", mean, stdev * stdev, rs.Mean(), rs.Variance()); // TODO: result not consistent need to fix the bug } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Continuous/LogNormalTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Continuous/LogNormalTests.cs index b684472..00345a1 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Continuous/LogNormalTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Continuous/LogNormalTests.cs @@ -1,53 +1,58 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; + using O2DESNet.RandomVariables.Continuous; + using System; -namespace O2DESNet.UnitTests.RandomVariableTests.Continuous +namespace O2DESNet.UnitTests.RandomVariableTests.Continuous; + +[TestFixture] +public class LogNormalTests { - [TestClass] - public class LogNormalTests + [Test] + public void TestMeanAndVariacneConsistency() { - [TestMethod] - public void TestMeanAndVariacneConsistency() + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + LogNormal logNormal = new(); + rs.Clear(); + mean = 2; + stdev = 5; + + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - LogNormal logNormal = new LogNormal(); - rs.Clear(); - mean = 2; stdev = 5; - - for (int i = 0; i < numSamples; ++i) - { - - logNormal.Mean = mean; - logNormal.StandardDeviation = stdev; - rs.Push(logNormal.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("logNormal", mean, stdev * stdev, rs.Mean(), rs.Variance()); + + logNormal.Mean = mean; + logNormal.StandardDeviation = stdev; + rs.Push(logNormal.Sample(defaultrs)); } - [TestMethod] - public void TestMeanAndVariacneConsistency_MuSigma() + PrintResult.CompareMeanAndVariance("logNormal", mean, stdev * stdev, rs.Mean(), rs.Variance()); + } + + [Test] + public void TestMeanAndVariacneConsistency_MuSigma() + { + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + LogNormal logNormal = new(); + rs.Clear(); + mean = 2; + stdev = 5; + var muTemp = Math.Log(mean) - 0.5 * Math.Log(1 + stdev * stdev / mean / mean); + var sigmaTemp = Math.Sqrt(Math.Log(1 + stdev * stdev / mean / mean)); + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - LogNormal logNormal = new LogNormal(); - rs.Clear(); - mean = 2; stdev = 5; - var muTemp = Math.Log(mean) - 0.5 * Math.Log(1 + stdev * stdev / mean / mean); - var sigmaTemp = Math.Sqrt(Math.Log(1 + stdev * stdev / mean / mean)); - for (int i = 0; i < numSamples; ++i) - { - logNormal.Mu = muTemp; - logNormal.Sigma = sigmaTemp; - - rs.Push(logNormal.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("logNormal", mean, stdev * stdev, rs.Mean(), rs.Variance()); + logNormal.Mu = muTemp; + logNormal.Sigma = sigmaTemp; + + rs.Push(logNormal.Sample(defaultrs)); } + + PrintResult.CompareMeanAndVariance("logNormal", mean, stdev * stdev, rs.Mean(), rs.Variance()); } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Continuous/NormalTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Continuous/NormalTests.cs index 3ad99ee..d17f4c7 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Continuous/NormalTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Continuous/NormalTests.cs @@ -1,50 +1,55 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; + using O2DESNet.RandomVariables.Continuous; + using System; -namespace O2DESNet.UnitTests.RandomVariableTests.Continuous +namespace O2DESNet.UnitTests.RandomVariableTests.Continuous; + +[TestFixture] +public class NormalTests { - [TestClass] - public class NormalTests + [Test] + public void TestMeanAndVariacneConsistency_Std() { - [TestMethod] - public void TestMeanAndVariacneConsistency_Std() + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + Normal normal = new(); + rs.Clear(); + mean = 2; + stdev = 5; + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Normal normal = new Normal(); - rs.Clear(); - mean = 2; stdev = 5; - for (int i = 0; i < numSamples; ++i) - { - - normal.Mean = mean; - normal.StandardDeviation = stdev; - rs.Push(normal.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("normal", mean, stdev * stdev, rs.Mean(), rs.Variance()); + + normal.Mean = mean; + normal.StandardDeviation = stdev; + rs.Push(normal.Sample(defaultrs)); } - [TestMethod] - public void TestMeanAndVariacneConsistency_CV() + + PrintResult.CompareMeanAndVariance("normal", mean, stdev * stdev, rs.Mean(), rs.Variance()); + } + [Test] + public void TestMeanAndVariacneConsistency_CV() + { + const int numSamples = 100000; + double mean, stdev, cv; + RunningStat rs = new(); + Random defaultrs = new(); + Normal normal = new(); + rs.Clear(); + mean = 2; + stdev = 5; + cv = stdev / Math.Abs(mean); + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev, cv; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Normal normal = new Normal(); - rs.Clear(); - mean = 2; stdev = 5; - cv = stdev / Math.Abs(mean); - for (int i = 0; i < numSamples; ++i) - { - - normal.Mean = mean; - normal.CV = cv; - rs.Push(normal.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("normal", mean, stdev * stdev, rs.Mean(), rs.Variance()); + + normal.Mean = mean; + normal.CV = cv; + rs.Push(normal.Sample(defaultrs)); } + + PrintResult.CompareMeanAndVariance("normal", mean, stdev * stdev, rs.Mean(), rs.Variance()); } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Continuous/TriangularTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Continuous/TriangularTests.cs index 9aaddf2..1634054 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Continuous/TriangularTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Continuous/TriangularTests.cs @@ -1,29 +1,34 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; + using O2DESNet.RandomVariables.Continuous; + using System; -namespace O2DESNet.UnitTests.RandomVariableTests.Continuous +namespace O2DESNet.UnitTests.RandomVariableTests.Continuous; + +[TestFixture] +public class TriangularTests { - [TestClass] - public class TriangularTests + [Test] + public void TestMeanAndVariacneConsistency() { - [TestMethod] - public void TestMeanAndVariacneConsistency() + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + Triangular tri = new(); + rs.Clear(); + var a = tri.LowerBound; + var b = tri.UpperBound; + var c = tri.Mode; + mean = (a + b + c) / 3; + stdev = Math.Sqrt((a * a + b * b + c * c - a * b - a * c - b * c) / 18); + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Triangular tri = new Triangular(); - rs.Clear(); - var a = tri.LowerBound; var b = tri.UpperBound; var c = tri.Mode; - mean = (a + b + c) / 3; stdev = Math.Sqrt((a * a + b * b + c * c - a * b - a * c - b * c) / 18); - for (int i = 0; i < numSamples; ++i) - { - rs.Push(tri.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("Triangular", mean, stdev * stdev, rs.Mean(), rs.Variance()); + rs.Push(tri.Sample(defaultrs)); } + + PrintResult.CompareMeanAndVariance("Triangular", mean, stdev * stdev, rs.Mean(), rs.Variance()); } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Continuous/UniformTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Continuous/UniformTests.cs index 27ff5fb..7bdeaf8 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Continuous/UniformTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Continuous/UniformTests.cs @@ -1,50 +1,51 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; + using O2DESNet.RandomVariables.Continuous; + using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -namespace O2DESNet.UnitTests.RandomVariableTests.Continuous +namespace O2DESNet.UnitTests.RandomVariableTests.Continuous; + +[TestFixture] +public class UniformTests { - [TestClass] - public class UniformTests + [Test] + public void TestMeanAndVarianceConsistency() { - [TestMethod] - public void TestMeanAndVariacneConsistency() + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + Uniform uniform = new(); + rs.Clear(); + var a = uniform.UpperBound; + var b = uniform.LowerBound; + mean = (a + b) / 2; + stdev = Math.Sqrt((b - a) * (b - a) / 12); + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Uniform uniform = new Uniform(); - rs.Clear(); - var a = uniform.UpperBound; - var b = uniform.LowerBound; - mean = (a + b) / 2; stdev = Math.Sqrt((b - a) * (b - a) / 12); - for (int i = 0; i < numSamples; ++i) - { - rs.Push(uniform.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("uniform", mean, stdev * stdev, rs.Mean(), rs.Variance()); - } - [TestMethod] - public void IfLowerBoundLarger() - { - Random rs = new Random(); - Uniform uniform = new Uniform(); - uniform.UpperBound = 12; - uniform.LowerBound = 11; - Assert.AreEqual(uniform.UpperBound, 12); - uniform.UpperBound = 10; - Assert.AreEqual(uniform.UpperBound,10); - Debug.WriteLine(" " + uniform.UpperBound); - uniform.LowerBound = 13; - Assert.AreEqual(uniform.UpperBound,uniform.LowerBound); - Debug.WriteLine(uniform.Sample(rs)); - Debug.WriteLine(uniform.UpperBound); - Debug.WriteLine(uniform.LowerBound); + rs.Push(uniform.Sample(defaultrs)); } + + PrintResult.CompareMeanAndVariance("uniform", mean, stdev * stdev, rs.Mean(), rs.Variance()); + } + + [Test] + public void IfLowerBoundLarger() + { + Random rs = new(); + Uniform uniform = new(); + uniform.UpperBound = 12; + uniform.LowerBound = 11; + Assert.That(uniform.UpperBound, Is.EqualTo(12)); + uniform.UpperBound = 10; + Assert.That(uniform.UpperBound, Is.EqualTo(10)); + TestContext.Out.WriteLine(" " + uniform.UpperBound); + uniform.LowerBound = 13; + Assert.That(uniform.UpperBound, Is.EqualTo(uniform.LowerBound)); + TestContext.Out.WriteLine(uniform.Sample(rs)); + TestContext.Out.WriteLine(uniform.UpperBound); + TestContext.Out.WriteLine(uniform.LowerBound); } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Discrete/PoissonTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Discrete/PoissonTests.cs index 86fb809..eacc251 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Discrete/PoissonTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Discrete/PoissonTests.cs @@ -1,30 +1,32 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; + using O2DESNet.RandomVariables.Discrete; + using System; -namespace O2DESNet.UnitTests.RandomVariableTests.Discrete +namespace O2DESNet.UnitTests.RandomVariableTests.Discrete; + +[TestFixture] +public class PoissonTests { - [TestClass] - public class PoissonTests + [Test] + public void TestMeanAndVariacneConsistency() { - [TestMethod] - public void TestMeanAndVariacneConsistency() + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + Poisson poisson = new(); + rs.Clear(); + mean = 2000; + stdev = Math.Sqrt(2000); + poisson.Lambda = 2000; + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Poisson poisson = new Poisson(); - rs.Clear(); - mean = 2000; stdev = Math.Sqrt(2000); - poisson.Lambda = 2000; - for (int i = 0; i < numSamples; ++i) - { - rs.Push(poisson.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("Poisson Discrete", mean, stdev * stdev, rs.Mean(), rs.Variance()); + rs.Push(poisson.Sample(defaultrs)); } + PrintResult.CompareMeanAndVariance("Poisson Discrete", mean, stdev * stdev, rs.Mean(), rs.Variance()); } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/Discrete/UniformTests.cs b/O2DESNet.UnitTests/RandomVariableTests/Discrete/UniformTests.cs index 20212b1..a597ce8 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/Discrete/UniformTests.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/Discrete/UniformTests.cs @@ -1,50 +1,54 @@ -using MathNet.Numerics.Distributions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json.Bson; +using NUnit.Framework; + using O2DESNet.RandomVariables.Discrete; -using RDotNet; + using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -namespace O2DESNet.UnitTests.RandomVariableTests.Discrete +namespace O2DESNet.UnitTests.RandomVariableTests.Discrete; + +[TestFixture] +public class UniformTests { - [TestClass] - public class UniformTests + [Test] + public void TestMeanAndVarianceConsistency() { - [TestMethod] - public void TestMeanAndVariacneConsistency() + const int numSamples = 100000; + double mean, stdev; + RunningStat rs = new(); + Random defaultrs = new(); + Uniform uniform = new(); + + rs.Clear(); + + var a = Convert.ToDouble(uniform.UpperBound); + var b = Convert.ToDouble(uniform.LowerBound); + mean = (a + b) / 2; + stdev = Math.Sqrt(0.25); + + for (int i = 0; i < numSamples; ++i) { - const int numSamples = 100000; - double mean, stdev; - RunningStat rs = new RunningStat(); - Random defaultrs = new Random(); - Uniform uniform = new Uniform(); - - rs.Clear(); - var a = Convert.ToDouble(uniform.UpperBound); - var b = Convert.ToDouble(uniform.LowerBound); - mean = (a + b) / 2; stdev = Math.Sqrt(0.25); - for (int i = 0; i < numSamples; ++i) - { - - rs.Push(uniform.Sample(defaultrs)); - } - PrintResult.CompareMeanAndVariance("uniform", mean, stdev * stdev, rs.Mean(), rs.Variance()); - Assert.IsTrue(Math.Abs(mean - rs.Mean()) < 0.1); - Assert.IsTrue(Math.Abs(stdev * stdev - rs.Variance()) < 0.1); + + rs.Push(uniform.Sample(defaultrs)); } - [TestMethod] - public void TestGetterOfMeanAndVariance() + PrintResult.CompareMeanAndVariance("uniform", mean, stdev * stdev, rs.Mean(), rs.Variance()); + using (Assert.EnterMultipleScope()) { - Uniform uniform = new Uniform(); - Debug.WriteLine(uniform.Mean); - Debug.WriteLine(uniform.StandardDeviation); - Assert.AreEqual(uniform.Mean, 0.5); - Assert.AreEqual(uniform.StandardDeviation, 0.5); + Assert.That(Math.Abs(mean - rs.Mean()), Is.LessThan(0.1)); + Assert.That(Math.Abs(stdev * stdev - rs.Variance()), Is.LessThan(0.1)); } + } + [Test] + public void TestGetterOfMeanAndVariance() + { + Uniform uniform = new(); + TestContext.Out.WriteLine(uniform.Mean); + TestContext.Out.WriteLine(uniform.StandardDeviation); + using (Assert.EnterMultipleScope()) + { + Assert.That(uniform.Mean, Is.EqualTo(0.5)); + Assert.That(uniform.StandardDeviation, Is.EqualTo(0.5)); + } } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/PrintResult.cs b/O2DESNet.UnitTests/RandomVariableTests/PrintResult.cs index 649581f..bd06c23 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/PrintResult.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/PrintResult.cs @@ -1,22 +1,21 @@ -using System.Diagnostics; +using NUnit.Framework; -namespace O2DESNet.UnitTests.RandomVariableTests +namespace O2DESNet.UnitTests.RandomVariableTests; + +public static class PrintResult { - public static class PrintResult + public static void CompareMeanAndVariance + ( + string name, + double expectedMean, + double expectedVariance, + double computedMean, + double computedVariance + ) { - public static void CompareMeanAndVariance - ( - string name, - double expectedMean, - double expectedVariance, - double computedMean, - double computedVariance - ) - { - Debug.WriteLine("Testing {0}", name); - Debug.WriteLine("Expected mean: {0}, computed mean: {1}", expectedMean, computedMean); - Debug.WriteLine("Expected variance: {0}, computed variance: {1}", expectedVariance, computedVariance); - Debug.WriteLine(""); - } + TestContext.Out.WriteLine("Testing {0}", name); + TestContext.Out.WriteLine("Expected mean: {0}, computed mean: {1}", expectedMean, computedMean); + TestContext.Out.WriteLine("Expected variance: {0}, computed variance: {1}", expectedVariance, computedVariance); + TestContext.Out.WriteLine(""); } } diff --git a/O2DESNet.UnitTests/RandomVariableTests/RunningStat.cs b/O2DESNet.UnitTests/RandomVariableTests/RunningStat.cs index 2edec92..d302695 100644 --- a/O2DESNet.UnitTests/RandomVariableTests/RunningStat.cs +++ b/O2DESNet.UnitTests/RandomVariableTests/RunningStat.cs @@ -1,63 +1,60 @@ using System; -using System.Collections.Generic; -using System.Text; -namespace O2DESNet.UnitTests.RandomVariableTests +namespace O2DESNet.UnitTests.RandomVariableTests; + +public class RunningStat { - public class RunningStat + public int m_n; + public double m_oldM, m_newM, m_oldS, m_newS; + + public RunningStat() { - public int m_n; - public double m_oldM, m_newM, m_oldS, m_newS; + m_n = 0; + } - public RunningStat() - { - m_n = 0; - } + public void Clear() + { + m_n = 0; + } - public void Clear() - { - m_n = 0; - } + public void Push(double x) + { + m_n++; - public void Push(double x) + // See Knuth TAOCP vol 2, 3rd edition, page 232 + if (m_n == 1) { - m_n++; - - // See Knuth TAOCP vol 2, 3rd edition, page 232 - if (m_n == 1) - { - m_oldM = m_newM = x; - m_oldS = 0.0; - } - else - { - m_newM = m_oldM + (x - m_oldM) / m_n; - m_newS = m_oldS + (x - m_oldM) * (x - m_newM); - - // set up for next iteration - m_oldM = m_newM; - m_oldS = m_newS; - } + m_oldM = m_newM = x; + m_oldS = 0.0; } - - public int NumDataValues() + else { - return m_n; - } + m_newM = m_oldM + (x - m_oldM) / m_n; + m_newS = m_oldS + (x - m_oldM) * (x - m_newM); - public double Mean() - { - return (m_n > 0) ? m_newM : 0.0; + // set up for next iteration + m_oldM = m_newM; + m_oldS = m_newS; } + } - public double Variance() - { - return ((m_n > 1) ? m_newS / (m_n - 1) : 0.0); - } + public int NumDataValues() + { + return m_n; + } - public double StandardDeviation() - { - return Math.Sqrt(Variance()); - } + public double Mean() + { + return (m_n > 0) ? m_newM : 0.0; + } + + public double Variance() + { + return ((m_n > 1) ? m_newS / (m_n - 1) : 0.0); + } + + public double StandardDeviation() + { + return Math.Sqrt(Variance()); } } diff --git a/O2DESNet.UnitTests/Simulator_Tests.cs b/O2DESNet.UnitTests/Simulator_Tests.cs index c0bb5a9..c3b5287 100644 --- a/O2DESNet.UnitTests/Simulator_Tests.cs +++ b/O2DESNet.UnitTests/Simulator_Tests.cs @@ -1,19 +1,94 @@ -using NUnit.Framework; +using Microsoft.Extensions.Logging; + +using NUnit.Framework; + using O2DESNet.Standard; + +using Serilog; +using Serilog.Events; + using System; -namespace O2DESNet.UnitTests +namespace O2DESNet.UnitTests; + +/// +/// Minimal, infrastructure-focused unit tests for the core simulation engine to ensure that +/// advancing simulation time behaves correctly and that logging is set up consistently for +/// diagnostics. +/// +/// Purpose +/// - Validates that calling Run with a TimeSpan advances the simulation clock by the exact +/// duration requested when no intervening events preempt that horizon. This is the most +/// fundamental contract of the Simulator: time progression is monotonic and precise. +/// - Uses a simple Standard.Server instance as a lightweight concrete simulator host so the test +/// exercises the shared scheduling and clock-keeping mechanics rather than domain logic. +/// +/// What is covered +/// - ClockTime_Advance: Constructs a Server with deterministic seed and capacity 1, advances the +/// simulation by two hours using Run(TimeSpan.FromHours(2)), and asserts that ClockTime equals the +/// same two-hour span. This acts as a smoke test that the base engine increments time correctly +/// when asked to advance by a fixed interval. +/// +/// Test scaffolding and logging +/// - SetUp configures Serilog to write to console and a rolling file, then bridges it into +/// Microsoft.Extensions.Logging so diagnostics remain consistent across components. While the +/// assertion is simple, logs are useful when investigating integration or environment issues. +/// - A fixed seed is supplied to the simulator to keep behaviors reproducible across runs, even +/// though this specific test does not rely on randomness. +/// +/// Scope and non-goals +/// - This class intentionally avoids validating operational semantics of servers, queues, or event +/// processing policies. Those concerns are covered by dedicated tests elsewhere (for example, +/// queueing or pattern generation tests). Here, the focus is the simulator infrastructure: time +/// advancement and basic lifecycle via disposal. +/// - Future extensions may include verifying pause/resume semantics, advancing with pending events, +/// and ensuring that advancing by zero or negative durations is handled as specified by the +/// engine’s contract. +/// +[TestFixture] +public class Simulator_Tests { - public class Simulator_Tests + private Microsoft.Extensions.Logging.ILogger? _logger; + private LogEventLevel _minLevel = LogEventLevel.Information; + + [SetUp] + public void Init() + { + // Comment out the following code to switch log file + //ConfigureSerilog("Logs\\log-Simulator_Tests.txt"); + } + + private void ConfigureSerilog(string filePath) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Is(_minLevel) + .WriteTo.Console() // Output to console + .WriteTo.File(filePath, + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 31, + restrictedToMinimumLevel: _minLevel, + shared: true) // Output to file + .Enrich.FromLogContext() + .CreateLogger(); + + _logger = new LoggerFactory() + .AddSerilog(Log.Logger) + .CreateLogger(); + } + + [Test] + public void Run_WithTimeSpan_AdvancesClockTimeByDuration() { - [Test] - public void ClockTime_Advance() - { - using (var sim = new Server(new Server.Statics { Capacity = 1 }, 0)) - { - sim.Run(TimeSpan.FromHours(2)); - if (sim.ClockTime != new DateTime(1, 1, 1, 2, 0, 0)) Assert.Fail(); - } - } + var statics = new Server.Statics { Capacity = 1 }; + using var sim = new Server(_logger, statics, id: nameof(Server), seed: 0); + + // Assert initial clock time is zero + Assert.That(sim.ClockTime, Is.EqualTo(TimeSpan.Zero)); + + var advanceBy = TimeSpan.FromHours(2); + sim.Run(advanceBy); + + // Assert clock time advanced exactly by the requested duration + Assert.That(sim.ClockTime, Is.EqualTo(advanceBy)); } } diff --git a/O2DESNet.UnitTests/TandemQueue.cs b/O2DESNet.UnitTests/TandemQueue.cs deleted file mode 100644 index 8329396..0000000 --- a/O2DESNet.UnitTests/TandemQueue.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NUnit.Framework; -using O2DESNet.Demos; -using System; -using System.Diagnostics; - -namespace O2DESNet.UnitTests -{ - public class TandemQueue_Tests - { - [Test] - public void Test1() - { - for (int seed = 0; seed < 3; seed++) - { - var q = new TandemQueue(3, 5, 5, 2, seed); - var sw = new Stopwatch(); - sw.Start(); - q.WarmUp(TimeSpan.FromHours(1000)); - q.Run(TimeSpan.FromHours(20000)); - sw.Stop(); - Debug.WriteLine("q1:{0:F4}\tq2:{1:F4}\ts1:{2:F4}\ts2:{3:F4}\tT:{4:F2}hrs\t{5}ms", - q.AvgNQueueing1, q.AvgNQueueing2, q.AvgNServing1, q.AvgNServing2, - q.AvgHoursInSystem, sw.ElapsedMilliseconds); - } - } - } -} diff --git a/O2DESNet.UnitTests/TandemQueue_Tests.cs b/O2DESNet.UnitTests/TandemQueue_Tests.cs new file mode 100644 index 0000000..e2fe8fb --- /dev/null +++ b/O2DESNet.UnitTests/TandemQueue_Tests.cs @@ -0,0 +1,128 @@ +using Microsoft.Extensions.Logging; + +using NUnit.Framework; + +using O2DESNet.Demos; + +using Serilog; +using Serilog.Events; + +using System; +using System.Diagnostics; + +namespace O2DESNet.UnitTests; + +/// +/// Long-run, smoke, and regression tests for the demo tandem queue model (O2DESNet.Demos.TandemQueue). +/// +/// Purpose +/// - Exercises a two-station tandem queue over an extended horizon to ensure the end-to-end event +/// scheduling, queueing, and statistics-accumulation logic operate without runtime errors across +/// multiple random seeds. +/// - Provides quick feedback on performance counters (e.g., average numbers in queue/serving and +/// average time in system) after a warm-up period. While no analytical assertions are made here, +/// the logged KPIs serve as baselines for spotting regressions after refactors or dependency +/// upgrades. +/// +/// Test design +/// - For each of several seeds, the test constructs a TandemQueue instance with a fixed set of +/// model parameters (e.g., capacities and/or rate parameters as defined by the demo), performs a +/// warm-up of 1,000 simulated hours to let transient effects dissipate, then runs 20,000 hours to +/// collect stable long-run averages. +/// - A Stopwatch captures wall-clock execution time to give a rough indication of simulation +/// throughput on the current environment; this is purely informational. +/// - Key performance indicators are emitted via Serilog: AvgNQueueing1/2, AvgNServing1/2, and +/// AvgHoursInSystem. Consumers can compare these metrics across commits or environments to detect +/// unintentional behavioral shifts. +/// +/// Logging and reproducibility +/// - Serilog is configured to log to console and a rolling file. Logs are bridged to +/// Microsoft.Extensions.Logging so downstream components share a consistent logging facade. +/// - Deterministic seeds are used to make runs reproducible. Differences in KPIs across runs with +/// the same seed may indicate subtle changes in scheduling semantics or random number generation. +/// +/// Scope and non-goals +/// - This class is intentionally non-assertive about numerical targets; it is intended as a +/// stability/regression harness and performance smoke test rather than a strict correctness proof +/// against closed-form queueing results. Analytical validation should live in complementary tests +/// that assert specific expectations for known parameterizations. +/// - The test focuses on the demo model’s integration with the simulation engine (event loop, +/// statistics, timing) rather than micro-level unit behavior of individual components. +/// +public class TandemQueue_Tests +{ + private Microsoft.Extensions.Logging.ILogger? _logger = null; + private LogEventLevel _minLevel = LogEventLevel.Information; + + [SetUp] + public void Init() + { + // Comment out the following code to switch log file + //ConfigureSerilog("Logs\\log-TandemQueue_Tests.txt"); + } + + private void ConfigureSerilog(string filePath) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Is(_minLevel) + .WriteTo.Console() // Output to console + .WriteTo.File(filePath, + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 31, + restrictedToMinimumLevel: _minLevel, + shared: true) // Output to file + .Enrich.FromLogContext() + .CreateLogger(); + + _logger = new LoggerFactory() + .AddSerilog(Log.Logger) + .CreateLogger(); + } + + [Test] + public void TandemQueue_LongRun_MetricsWithinBounds() + { + for (int seed = 0; seed < 3; seed++) + { + var q = new TandemQueue(_logger, 3, 5, 5, 2, seed); + var sw = new Stopwatch(); + sw.Start(); + q.WarmUp(TimeSpan.FromHours(1000)); + q.Run(TimeSpan.FromHours(20000)); + sw.Stop(); + + // Log for regression visibility + _logger?.LogInformation("q1:{0:F4}\tq2:{1:F4}\ts1:{2:F4}\ts2:{3:F4}\tT:{4:F2}hrs\t{5}ms", + q.AvgNQueueing1, q.AvgNQueueing2, q.AvgNServing1, q.AvgNServing2, + q.AvgHoursInSystem, sw.ElapsedMilliseconds); + + // Deterministic, physics-based invariants that must always hold + Assert.Multiple(() => + { + // Queue 2 has finite capacity; averages must respect capacity bounds + Assert.That(q.BufferQueueSize, Is.EqualTo(2)); + Assert.That(q.AvgNQueueing2, Is.GreaterThanOrEqualTo(0)); + Assert.That(q.AvgNQueueing2, Is.LessThanOrEqualTo(q.BufferQueueSize)); + + // Queue 1 is unbounded but averages must be non-negative + Assert.That(q.AvgNQueueing1, Is.GreaterThanOrEqualTo(0)); + + // Each server has capacity 1; average in service must be within [0, 1] + Assert.That(q.AvgNServing1, Is.GreaterThanOrEqualTo(0)); + Assert.That(q.AvgNServing1, Is.LessThanOrEqualTo(1)); + Assert.That(q.AvgNServing2, Is.GreaterThanOrEqualTo(0)); + Assert.That(q.AvgNServing2, Is.LessThanOrEqualTo(1)); + + // Time in system must be non-negative + Assert.That(q.AvgHoursInSystem, Is.GreaterThanOrEqualTo(0)); + + // Ensure no NaNs are produced in metrics + Assert.That(double.IsNaN(q.AvgNQueueing1), Is.False); + Assert.That(double.IsNaN(q.AvgNQueueing2), Is.False); + Assert.That(double.IsNaN(q.AvgNServing1), Is.False); + Assert.That(double.IsNaN(q.AvgNServing2), Is.False); + Assert.That(double.IsNaN(q.AvgHoursInSystem), Is.False); + }); + } + } +} diff --git a/O2DESNet.UnitTests/Version_3.cs b/O2DESNet.UnitTests/Version_3.cs deleted file mode 100644 index 35edf1d..0000000 --- a/O2DESNet.UnitTests/Version_3.cs +++ /dev/null @@ -1,108 +0,0 @@ -using NUnit.Framework; -using O2DESNet.Distributions; -using O2DESNet.Standard; -using O2DESNet.Demos; -using System; -using System.Diagnostics; - -namespace O2DESNet.UnitTests -{ - public class Version_3 - { - [Test] - public void WarmedUp() - { - var a = new A(); - a.WarmUp(TimeSpan.FromHours(1)); - } - - [Test] - public void Generator() - { - var gen = new Generator.Statics { InterArrivalTime = - rs => Exponential.Sample(rs, TimeSpan.FromMinutes(3)) }.Sandbox(); - gen.Run(TimeSpan.FromHours(0.5)); - gen.Start(); - gen.Run(TimeSpan.FromHours(2)); - gen.End(); - gen.Run(TimeSpan.FromHours(0.5)); - gen.Start(); - gen.Run(TimeSpan.FromHours(1)); - } - - [Test] - public void MMnQueue_Atomic() - { - for (int seed = 0; seed < 3; seed++) - { - var q = new MMnQueue_Atomic(4, 5, 1, seed); - var sw = new Stopwatch(); - sw.Start(); - q.WarmUp(TimeSpan.FromHours(1000)); - q.Run(TimeSpan.FromHours(20000)); - sw.Stop(); - Debug.WriteLine("{0:F4}\t{1:F4}\t{2:F4}\t{3}ms", - q.AvgNQueueing, q.AvgNServing, q.AvgHoursInSystem, sw.ElapsedMilliseconds); - } - } - - [Test] - public void MMnQueue_Modular() - { - for (int seed = 0; seed < 3; seed++) - { - var q = new MMnQueue_Modular(4, 5, 1, seed); - var sw = new Stopwatch(); - sw.Start(); - q.WarmUp(TimeSpan.FromHours(1000)); - q.Run(TimeSpan.FromHours(20000)); - sw.Stop(); - Debug.WriteLine("{0:F4}\t{1:F4}\t{2:F4}\t{3}ms", - q.AvgNQueueing, q.AvgNServing, q.AvgHoursInSystem, sw.ElapsedMilliseconds); - } - } - - private class Assets : IAssets - { - public string Id { get { return GetType().Name; } } - } - private class A : Sandbox - { - public A() : base(new Assets()) { AddChild(new B()); AddChild(new C()); } - public override void Dispose() { } - protected override void WarmedUpHandler() - { - Debug.WriteLine("A WarmedUp"); - } - } - private class B : Sandbox - { - public B() : base(new Assets()) { AddChild(new D()); } - public override void Dispose() { } - protected override void WarmedUpHandler() - { - Debug.WriteLine("B WarmedUp"); - } - } - private class C : Sandbox - { - public C() : base(new Assets()) { } - public override void Dispose() { } - protected override void WarmedUpHandler() - { - Debug.WriteLine("C WarmedUp"); - } - } - private class D : Sandbox - { - public D() : base(new Assets()) { } - public override void Dispose() { } - protected override void WarmedUpHandler() - { - Debug.WriteLine("D WarmedUp"); - } - } - } - - -} diff --git a/O2DESNet.UnitTests/Version_3_Tests.cs b/O2DESNet.UnitTests/Version_3_Tests.cs new file mode 100644 index 0000000..d2547e7 --- /dev/null +++ b/O2DESNet.UnitTests/Version_3_Tests.cs @@ -0,0 +1,225 @@ +using Microsoft.Extensions.Logging; + +using NUnit.Framework; + +using O2DESNet.Demos; +using O2DESNet.Distributions; +using O2DESNet.Standard; + +using Serilog; +using Serilog.Events; + +using System; +using System.Diagnostics; + +namespace O2DESNet.UnitTests; + +/// +/// Version 3 functional and integration tests for O2DESNet that exercise the updated Sandbox +/// APIs, warm-up lifecycle, generator module behavior, and two flavors of M/M/n queue demos. +/// +/// Goals +/// - Validate core lifecycle hooks introduced or refined in V3 (e.g., WarmUp and WarmedUpHandler) +/// across a hierarchy of nested Sandbox instances, confirming that warm-up propagates correctly +/// and that user overrides are invoked once the warm-up horizon elapses. +/// - Smoke and regression-test the Standard.Generator in Sandbox form, including Start/End cycles +/// and running across mixed phases (pre-start, running, paused), ensuring that event scheduling +/// and clock advancement align with expectations. +/// - Exercise two reference queueing implementations (MMnQueue_Atomic and MMnQueue_Modular) over a +/// long horizon to surface integration issues in the event loop, statistics accumulation +/// (HourCounter), and logging, while providing baseline KPIs for future comparisons. +/// +/// Test coverage overview +/// - WarmedUp: Builds a small Sandbox hierarchy A → {B → D, C}. Calling WarmUp triggers the +/// framework to advance to the specified duration and then invoke WarmedUpHandler on each module. +/// The handlers log a message per module (A/B/C/D), demonstrating successful propagation and hook +/// execution. +/// - Generator: Creates a Generator.Statics with an exponential inter-arrival time and hosts it in a +/// Sandbox via .Sandbox(...). It mixes Run(durations) with Start/End to exercise transitions +/// between idle, active, and paused phases without throwing, and with expected clock progression. +/// - MMnQueue_Atomic and MMnQueue_Modular: For several seeds, each queue demo is warmed up for 1,000 +/// simulated hours and then run for 20,000 hours. The tests log long-run metrics +/// (AvgNQueueing, AvgNServing, AvgHoursInSystem) and elapsed wall-clock time. These are intended +/// as stability and performance baselines rather than strict analytical assertions. +/// +/// Instrumentation and reproducibility +/// - Serilog is configured to write to console and a rolling file and is bridged into +/// Microsoft.Extensions.Logging for consistent diagnostics across modules. +/// - A deterministic seed is supplied wherever applicable to keep runs reproducible. +/// +/// Scope and non-goals +/// - These tests focus on lifecycle correctness (warm-up and start/stop), scheduler stability, +/// and aggregate statistics over long runs. They do not assert closed-form queueing results or the +/// exact distributions of inter-arrival/service times; such analytical validation can be covered +/// in dedicated unit tests. +/// - The nested Sandbox classes (A/B/C/D) are minimal scaffolding to validate V3 warm-up semantics +/// and the WarmedUpHandler override path. +/// +[TestFixture] +public class Version_3_Tests +{ + private Microsoft.Extensions.Logging.ILogger _logger; + private LogEventLevel _minLevel = LogEventLevel.Information; + + [SetUp] + public void Init() + { + // Comment out the following code to switch log file + //ConfigureSerilog("Logs\\log-Version_3.txt"); + } + + private void ConfigureSerilog(string filePath) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Is(_minLevel) + .WriteTo.Console() // Output to console + .WriteTo.File(filePath, + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 31, + restrictedToMinimumLevel: _minLevel, + shared: true) // Output to file + .Enrich.FromLogContext() + .CreateLogger(); + + _logger = new LoggerFactory() + .AddSerilog(Log.Logger) + .CreateLogger(); + } + + [Test] + public void WarmedUp() + { + var a = new A(_logger); + var ok = a.WarmUp(TimeSpan.FromHours(1)); + Assert.That(a.ClockTime, Is.EqualTo(TimeSpan.FromHours(1))); + } + + [Test] + public void Generator() + { + var gen = new Generator.Statics + { + InterArrivalTime = + rs => Exponential.Sample(rs, TimeSpan.FromMinutes(3)) + }.Sandbox(logger: _logger, seed: 0); + gen.Run(TimeSpan.FromHours(0.5)); + gen.Start(); + gen.Run(TimeSpan.FromHours(2)); + gen.End(); + gen.Run(TimeSpan.FromHours(0.5)); + gen.Start(); + gen.Run(TimeSpan.FromHours(1)); + Assert.That(gen.ClockTime, Is.EqualTo(TimeSpan.FromHours(4))); + } + + [Test] + public void MMnQueue_Atomic() + { + for (int seed = 0; seed < 3; seed++) + { + var q = new MMnQueue_Atomic(_logger, 4, 5, 1, seed); + var sw = new Stopwatch(); + sw.Start(); + var warmOk = q.WarmUp(TimeSpan.FromHours(1000)); + var runOk = q.Run(TimeSpan.FromHours(20000)); + sw.Stop(); + q.Logger?.LogInformation("{0:F4}\t{1:F4}\t{2:F4}\t{3}ms", + q.AvgNQueueing, q.AvgNServing, q.AvgHoursInSystem, sw.ElapsedMilliseconds); + + Assert.That(warmOk && runOk, Is.True); + Assert.That(q.ClockTime, Is.EqualTo(TimeSpan.FromHours(21000))); + Assert.That(double.IsFinite(q.AvgNQueueing), Is.True); + Assert.That(q.AvgNQueueing, Is.GreaterThanOrEqualTo(0)); + Assert.That(double.IsFinite(q.AvgNServing), Is.True); + Assert.That(q.AvgNServing, Is.GreaterThanOrEqualTo(0)); + Assert.That(double.IsFinite(q.AvgHoursInSystem), Is.True); + Assert.That(q.AvgHoursInSystem, Is.GreaterThanOrEqualTo(0)); + } + } + + [Test] + public void MMnQueue_Modular() + { + for (int seed = 0; seed < 3; seed++) + { + var q = new MMnQueue_Modular(_logger, 4, 5, 1, seed); + var sw = new Stopwatch(); + sw.Start(); + var warmOk = q.WarmUp(TimeSpan.FromHours(1000)); + var runOk = q.Run(TimeSpan.FromHours(20000)); + sw.Stop(); + q.Logger?.LogInformation("{0:F4}\t{1:F4}\t{2:F4}\t{3}ms", + q.AvgNQueueing, q.AvgNServing, q.AvgHoursInSystem, sw.ElapsedMilliseconds); + + Assert.That(warmOk && runOk, Is.True); + Assert.That(q.ClockTime, Is.EqualTo(TimeSpan.FromHours(21000))); + Assert.That(double.IsFinite(q.AvgNQueueing), Is.True); + Assert.That(q.AvgNQueueing, Is.GreaterThanOrEqualTo(0)); + Assert.That(double.IsFinite(q.AvgNServing), Is.True); + Assert.That(q.AvgNServing, Is.GreaterThanOrEqualTo(0)); + Assert.That(double.IsFinite(q.AvgHoursInSystem), Is.True); + Assert.That(q.AvgHoursInSystem, Is.GreaterThanOrEqualTo(0)); + } + } + + private class Assets : IAssets + { + public string Id => GetType().Name; + } + + private class A : Sandbox + { + public A(Microsoft.Extensions.Logging.ILogger logger) + : base(logger, new Assets(), id: nameof(A), seed: 0) + { + AddChild(new B(logger)); + AddChild(new C(logger)); + } + + public override void Dispose() { } + + protected override void WarmedUpHandler() + { + Logger?.LogInformation("A WarmedUp"); + } + } + + private class B : Sandbox + { + public B(Microsoft.Extensions.Logging.ILogger logger) + : base(logger, new Assets(), id: nameof(B), seed: 0) { AddChild(new D(logger)); } + + public override void Dispose() { } + + protected override void WarmedUpHandler() + { + Logger?.LogInformation("B WarmedUp"); + } + } + + private class C : Sandbox + { + public C(Microsoft.Extensions.Logging.ILogger logger) + : base(logger, new Assets(), id: nameof(C), seed: 0) { } + + public override void Dispose() { } + + protected override void WarmedUpHandler() + { + Logger?.LogInformation("C WarmedUp"); + } + } + + private class D : Sandbox + { + public D(Microsoft.Extensions.Logging.ILogger logger) + : base(logger, new Assets(), id: nameof(D), seed: 0) { } + + public override void Dispose() { } + + protected override void WarmedUpHandler() + { + Logger?.LogInformation("D WarmedUp"); + } + } +} diff --git a/O2DESNet/O2DESNet.nuspec b/O2DESNet.nuspec similarity index 97% rename from O2DESNet/O2DESNet.nuspec rename to O2DESNet.nuspec index 20026e3..7fef84a 100644 --- a/O2DESNet/O2DESNet.nuspec +++ b/O2DESNet.nuspec @@ -1,17 +1,17 @@ - - - - $id$ - $version$ - $title$ - $author$ - $author$ - false - $description$ - https://opensource.org/licenses/MIT - http://www.o2des.net - http://www.o2des.net/wp-content/uploads/2020/05/o2des.png - Copyright 2015 - 2020 - O2DES.Net Discrete-Event Simulation - - + + + + $id$ + $version$ + $title$ + $author$ + $author$ + false + $description$ + https://opensource.org/licenses/MIT + http://www.o2des.net + http://www.o2des.net/wp-content/uploads/2020/05/o2des.png + Copyright 2015 - 2020 + O2DES.Net Discrete-Event Simulation + + diff --git a/O2DESNet.sln b/O2DESNet.sln index 267fad6..9074ce4 100644 --- a/O2DESNet.sln +++ b/O2DESNet.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.156 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36414.22 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "O2DESNet", "O2DESNet\O2DESNet.csproj", "{2430D47F-0E33-4835-859A-E4C948C1C0EB}" EndProject @@ -9,23 +9,45 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "O2DESNet.UnitTests", "O2DES EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{32B8B2A1-A5A0-4F94-A023-08C233045D92}" ProjectSection(SolutionItems) = preProject + .github\copilot-instructions.md = .github\copilot-instructions.md + O2DESNet.nuspec = O2DESNet.nuspec README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathNet.Numerics", "MathNet.Numerics\MathNet.Numerics.csproj", "{C229CACE-A616-6B95-9ECC-6D42F7BEC40E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2430D47F-0E33-4835-859A-E4C948C1C0EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2430D47F-0E33-4835-859A-E4C948C1C0EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2430D47F-0E33-4835-859A-E4C948C1C0EB}.Debug|x64.ActiveCfg = Debug|x64 + {2430D47F-0E33-4835-859A-E4C948C1C0EB}.Debug|x64.Build.0 = Debug|x64 {2430D47F-0E33-4835-859A-E4C948C1C0EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {2430D47F-0E33-4835-859A-E4C948C1C0EB}.Release|Any CPU.Build.0 = Release|Any CPU + {2430D47F-0E33-4835-859A-E4C948C1C0EB}.Release|x64.ActiveCfg = Release|x64 + {2430D47F-0E33-4835-859A-E4C948C1C0EB}.Release|x64.Build.0 = Release|x64 {3D80B354-FB3B-47FE-AFA3-6D04F4C27653}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3D80B354-FB3B-47FE-AFA3-6D04F4C27653}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D80B354-FB3B-47FE-AFA3-6D04F4C27653}.Debug|x64.ActiveCfg = Debug|Any CPU + {3D80B354-FB3B-47FE-AFA3-6D04F4C27653}.Debug|x64.Build.0 = Debug|Any CPU {3D80B354-FB3B-47FE-AFA3-6D04F4C27653}.Release|Any CPU.ActiveCfg = Release|Any CPU {3D80B354-FB3B-47FE-AFA3-6D04F4C27653}.Release|Any CPU.Build.0 = Release|Any CPU + {3D80B354-FB3B-47FE-AFA3-6D04F4C27653}.Release|x64.ActiveCfg = Release|Any CPU + {3D80B354-FB3B-47FE-AFA3-6D04F4C27653}.Release|x64.Build.0 = Release|Any CPU + {C229CACE-A616-6B95-9ECC-6D42F7BEC40E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C229CACE-A616-6B95-9ECC-6D42F7BEC40E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C229CACE-A616-6B95-9ECC-6D42F7BEC40E}.Debug|x64.ActiveCfg = Debug|x64 + {C229CACE-A616-6B95-9ECC-6D42F7BEC40E}.Debug|x64.Build.0 = Debug|x64 + {C229CACE-A616-6B95-9ECC-6D42F7BEC40E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C229CACE-A616-6B95-9ECC-6D42F7BEC40E}.Release|Any CPU.Build.0 = Release|Any CPU + {C229CACE-A616-6B95-9ECC-6D42F7BEC40E}.Release|x64.ActiveCfg = Release|x64 + {C229CACE-A616-6B95-9ECC-6D42F7BEC40E}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/O2DESNet/Assets.cs b/O2DESNet/Assets.cs index 4bb8351..9d5cdc4 100644 --- a/O2DESNet/Assets.cs +++ b/O2DESNet/Assets.cs @@ -1,7 +1,6 @@ -namespace O2DESNet +namespace O2DESNet; + +public interface IAssets { - public interface IAssets - { - string Id { get; } - } + string Id { get; } } \ No newline at end of file diff --git a/O2DESNet/Demos/IMMnQueue.cs b/O2DESNet/Demos/IMMnQueue.cs index a8e1ff4..9207726 100644 --- a/O2DESNet/Demos/IMMnQueue.cs +++ b/O2DESNet/Demos/IMMnQueue.cs @@ -1,31 +1,30 @@ -namespace O2DESNet.Demos +namespace O2DESNet.Demos; + +public interface IMMnQueue { - public interface IMMnQueue - { - /// - /// Expected number to arrive per hour - /// - double HourlyArrivalRate { get; } - /// - /// Average number to serve per hour by one server - /// - double HourlyServiceRate { get; } - /// - /// Number of servers - /// - int NServers { get; } + /// + /// Expected number to arrive per hour + /// + double HourlyArrivalRate { get; } + /// + /// Average number to serve per hour by one server + /// + double HourlyServiceRate { get; } + /// + /// Number of servers + /// + int NServers { get; } - /// - /// Average number of loads queueing - /// - double AvgNQueueing { get; } - /// - /// Average number of loads serving - /// - double AvgNServing { get; } - /// - /// Average hours a load spends in system (cycle time) - /// - double AvgHoursInSystem { get; } - } + /// + /// Average number of loads queueing + /// + double AvgNQueueing { get; } + /// + /// Average number of loads serving + /// + double AvgNServing { get; } + /// + /// Average hours a load spends in system (cycle time) + /// + double AvgHoursInSystem { get; } } diff --git a/O2DESNet/Demos/LoadEntity.cs b/O2DESNet/Demos/LoadEntity.cs new file mode 100644 index 0000000..761aa3a --- /dev/null +++ b/O2DESNet/Demos/LoadEntity.cs @@ -0,0 +1,8 @@ +using O2DESNet.Standard; + +namespace O2DESNet.Demos; + +public class LoadEntity : Entity +{ + public LoadEntity(EntityId id) : base(id) { } +} diff --git a/O2DESNet/Demos/MMnQueue_Atomic.cs b/O2DESNet/Demos/MMnQueue_Atomic.cs index a9bb123..fd284f2 100644 --- a/O2DESNet/Demos/MMnQueue_Atomic.cs +++ b/O2DESNet/Demos/MMnQueue_Atomic.cs @@ -1,77 +1,96 @@ -using O2DESNet; +using Microsoft.Extensions.Logging; + using O2DESNet.Distributions; +using O2DESNet.HourCounters; + using System; -namespace O2DESNet.Demos +namespace O2DESNet.Demos; + +public class MMnQueue_Atomic : Sandbox, IMMnQueue { - public class MMnQueue_Atomic : Sandbox, IMMnQueue + #region Static Properties + public double HourlyArrivalRate { get; private set; } + public double HourlyServiceRate { get; private set; } + public int NServers { get; private set; } + #endregion + + #region Dynamic Properties / Methods + public double AvgNQueueing => HC_InQueue.AverageCount; + public double AvgNServing => HC_InServer.AverageCount; + public double AvgHoursInSystem => HC_InSystem.AverageDuration.TotalHours; + + private HourCounter HC_InServer { get; set; } + private HourCounter HC_InQueue { get; set; } + private HourCounter HC_InSystem { get; set; } + #endregion + + #region Events + private void Arrive() { - #region Static Properties - public double HourlyArrivalRate { get; private set; } - public double HourlyServiceRate { get; private set; } - public int NServers { get; private set; } - #endregion - - #region Dynamic Properties / Methods - public double AvgNQueueing { get { return HC_InQueue.AverageCount; } } - public double AvgNServing { get { return HC_InServer.AverageCount; } } - public double AvgHoursInSystem { get { return HC_InSystem.AverageDuration.TotalHours; } } - - private HourCounter HC_InServer { get; set; } - private HourCounter HC_InQueue { get; set; } - private HourCounter HC_InSystem { get; set; } - #endregion - - #region Events - private void Arrive() - { - Log("Arrive"); - HC_InSystem.ObserveChange(1, ClockTime); - - if (HC_InServer.LastCount < NServers) Start(); - else - { - Log("Enqueue"); - HC_InQueue.ObserveChange(1, ClockTime); - } - Schedule(Arrive, Exponential.Sample(DefaultRS, TimeSpan.FromHours(1 / HourlyArrivalRate))); - } + Logger?.LogInformation("Arrive"); + HC_InSystem.ObserveChange(1, ClockTime); - private void Start() + if (HC_InServer.LastCount < NServers) + Start(); + else { - Log("Start"); - HC_InServer.ObserveChange(1, ClockTime); - Schedule(Depart, Exponential.Sample(DefaultRS, TimeSpan.FromHours(1 / HourlyServiceRate))); + Logger?.LogInformation("Enqueue"); + HC_InQueue.ObserveChange(1, ClockTime); } - private void Depart() + Schedule(Arrive, Exponential.Sample(DefaultRS, TimeSpan.FromHours(1 / HourlyArrivalRate))); + } + + private void Start() + { + Logger?.LogInformation("Start"); + HC_InServer.ObserveChange(1, ClockTime); + Schedule(Depart, Exponential.Sample(DefaultRS, TimeSpan.FromHours(1 / HourlyServiceRate))); + } + + private void Depart() + { + Logger?.LogInformation("Depart"); + HC_InServer.ObserveChange(-1, ClockTime); + HC_InSystem.ObserveChange(-1, ClockTime); + + if (HC_InQueue.LastCount > 0) { - Log("Depart"); - HC_InServer.ObserveChange(-1, ClockTime); - HC_InSystem.ObserveChange(-1, ClockTime); - - if (HC_InQueue.LastCount > 0) - { - Log("Dequeue"); - HC_InQueue.ObserveChange(-1, ClockTime); - Start(); - } + Logger?.LogInformation("Dequeue"); + HC_InQueue.ObserveChange(-1, ClockTime); + Start(); } - #endregion + } + #endregion - public MMnQueue_Atomic(double hourlyArrivalRate, double hourlyServiceRate, int nServers, int seed = 0) - : base(seed) - { - HourlyArrivalRate = hourlyArrivalRate; - HourlyServiceRate = hourlyServiceRate; - NServers = nServers; + public MMnQueue_Atomic(double hourlyArrivalRate, double hourlyServiceRate, int nServers, int seed = 0) + : base(nameof(MMnQueue_Atomic), seed) + { + HourlyArrivalRate = hourlyArrivalRate; + HourlyServiceRate = hourlyServiceRate; + NServers = nServers; - HC_InServer = AddHourCounter(); - HC_InQueue = AddHourCounter(); - HC_InSystem = AddHourCounter(); + HC_InServer = AddHourCounter(); + HC_InQueue = AddHourCounter(); + HC_InSystem = AddHourCounter(); - /// Initial event - Arrive(); - } + /// Initial event + Arrive(); + } + + public MMnQueue_Atomic(ILogger logger, double hourlyArrivalRate, double hourlyServiceRate, int nServers, int seed = 0) + : base(logger, nameof(MMnQueue_Atomic), seed) + { + HourlyArrivalRate = hourlyArrivalRate; + HourlyServiceRate = hourlyServiceRate; + NServers = nServers; + + HC_InServer = AddHourCounter(); + HC_InQueue = AddHourCounter(); + HC_InSystem = AddHourCounter(); + + /// Initial event + Arrive(); } } diff --git a/O2DESNet/Demos/MMnQueue_Modular.cs b/O2DESNet/Demos/MMnQueue_Modular.cs index 6935716..a975d1f 100644 --- a/O2DESNet/Demos/MMnQueue_Modular.cs +++ b/O2DESNet/Demos/MMnQueue_Modular.cs @@ -1,77 +1,86 @@ -using O2DESNet; +using Microsoft.Extensions.Logging; + using O2DESNet.Distributions; +using O2DESNet.HourCounters; using O2DESNet.Standard; + using System; -namespace O2DESNet.Demos +namespace O2DESNet.Demos; + +public class MMnQueue_Modular : Sandbox, IMMnQueue { - public class MMnQueue_Modular : Sandbox, IMMnQueue + #region Static Properties + public double HourlyArrivalRate { get; private set; } + public double HourlyServiceRate { get; private set; } + public int NServers => (int)Server.Capacity; + #endregion + + #region Dynamic Properties + public double AvgNQueueing => Queue.AvgNQueueing; + public double AvgNServing => Server.AvgNServing; + public double AvgHoursInSystem => HC_InSystem.AverageDuration.TotalHours; + + private IGenerator Generator { get; set; } + private IQueue Queue { get; set; } + private IServer Server { get; set; } + private HourCounter HC_InSystem { get; set; } + #endregion + + #region Events / Methods + private void Arrive() { - #region Static Properties - public double HourlyArrivalRate { get; private set; } - public double HourlyServiceRate { get; private set; } - public int NServers { get { return (int)Server.Capacity; } } - #endregion - - #region Dynamic Properties - public double AvgNQueueing { get { return Queue.AvgNQueueing; } } - public double AvgNServing { get { return Server.AvgNServing; } } - public double AvgHoursInSystem { get { return HC_InSystem.AverageDuration.TotalHours; } } - - private IGenerator Generator { get; set; } - private IQueue Queue { get; set; } - private IServer Server { get; set; } - private HourCounter HC_InSystem { get; set; } - #endregion - - #region Events / Methods - private void Arrive() - { - Log("Arrive"); - HC_InSystem.ObserveChange(1, ClockTime); - } + Logger?.LogInformation("Arrive"); + HC_InSystem.ObserveChange(1, ClockTime); + } - private void Depart() - { - Log("Depart"); - HC_InSystem.ObserveChange(-1, ClockTime); - } - #endregion + private void Depart() + { + Logger?.LogInformation("Depart"); + HC_InSystem.ObserveChange(-1, ClockTime); + } + #endregion - public MMnQueue_Modular(double hourlyArrivalRate, double hourlyServiceRate, int nServers, int seed = 0) - : base(seed) - { - HourlyArrivalRate = hourlyArrivalRate; - HourlyServiceRate = hourlyServiceRate; + public MMnQueue_Modular(double hourlyArrivalRate, double hourlyServiceRate, int nServers, int seed) + : this(logger: null, hourlyArrivalRate, hourlyServiceRate, nServers, seed) { } - Generator = AddChild(new Generator(new Generator.Statics - { - InterArrivalTime = rs => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyArrivalRate)) - }, DefaultRS.Next())); + public MMnQueue_Modular(ILogger? logger, double hourlyArrivalRate, double hourlyServiceRate, int nServers, int seed) + : base(logger: logger, id: nameof(MMnQueue_Modular), seed) + { + HourlyArrivalRate = hourlyArrivalRate; + HourlyServiceRate = hourlyServiceRate; - Queue = AddChild(new Queue(double.PositiveInfinity, DefaultRS.Next())); + Generator = AddChild(new Generator(logger: logger, new Generator.Statics + { + InterArrivalTime = rs => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyArrivalRate)) + }, id: nameof(Generator), DefaultRS.Next())); - Server = AddChild(new Server(new Server.Statics - { - Capacity = nServers, - ServiceTime = (rs, load) => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyServiceRate)), - }, DefaultRS.Next())); + Queue = AddChild(new Queue(logger: logger, double.PositiveInfinity, id: nameof(Queue), DefaultRS.Next())); - Generator.OnArrive += () => Queue.RqstEnqueue(new Load()); - Generator.OnArrive += Arrive; + Server = AddChild(new Server(logger: logger, new Server.Statics + { + Capacity = nServers, + ServiceTime = (rs, load) => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyServiceRate)), + }, id: nameof(Server), DefaultRS.Next())); + + Generator.OnArrive += () => Queue.RqstEnqueue(new LoadEntity(EntityId.New())); + Generator.OnArrive += Arrive; - Queue.OnEnqueued += Server.RqstStart; - Server.OnStarted += Queue.Dequeue; + Queue.OnEnqueued += Server.RqstStart; + Server.OnStarted += Queue.Dequeue; - Server.OnReadyToDepart += Server.Depart; - Server.OnReadyToDepart += load => Depart(); + Server.OnReadyToDepart += Server.Depart; + Server.OnReadyToDepart += load => Depart(); - HC_InSystem = AddHourCounter(); + HC_InSystem = AddHourCounter(); - /// Initial event - Generator.Start(); - } + /// Initial event + Generator.Start(); + } - public override void Dispose() { } + public override void Dispose() + { + base.Dispose(); + // No extra unmanaged resources; base disposes children and hour counters. } } diff --git a/O2DESNet/Demos/TandemQueue.cs b/O2DESNet/Demos/TandemQueue.cs index b75a5b3..5bae1c2 100644 --- a/O2DESNet/Demos/TandemQueue.cs +++ b/O2DESNet/Demos/TandemQueue.cs @@ -1,101 +1,115 @@ -using O2DESNet; +using Microsoft.Extensions.Logging; + using O2DESNet.Distributions; +using O2DESNet.HourCounters; using O2DESNet.Standard; + using System; -namespace O2DESNet.Demos +namespace O2DESNet.Demos; + +public class TandemQueue : Sandbox { - public class TandemQueue : Sandbox + #region Static Properties + public double HourlyArrivalRate { get; private set; } + public double HourlyServiceRate1 { get; private set; } + public double HourlyServiceRate2 { get; private set; } + public int BufferQueueSize => (int)Queue2.Capacity; + #endregion + + #region Dynamic Properties + public double AvgNQueueing1 => Queue1.AvgNQueueing; + public double AvgNQueueing2 => Queue2.AvgNQueueing; + public double AvgNServing1 => Server1.AvgNServing; + public double AvgNServing2 => Server2.AvgNServing; + public double AvgHoursInSystem => HcInSystem.AverageDuration.TotalHours; + + private readonly IGenerator Generator; + private readonly IQueue Queue1; + private readonly IServer Server1; + private readonly IQueue Queue2; + private readonly IServer Server2; + private readonly HourCounter HcInSystem; + #endregion + + #region Events / Methods + private void Arrive() { - #region Static Properties - public double HourlyArrivalRate { get; private set; } - public double HourlyServiceRate1 { get; private set; } - public double HourlyServiceRate2 { get; private set; } - public int BufferQueueSize { get { return (int)Queue2.Capacity; } } - #endregion - - #region Dynamic Properties - public double AvgNQueueing1 { get { return Queue1.AvgNQueueing; } } - public double AvgNQueueing2 { get { return Queue2.AvgNQueueing; } } - public double AvgNServing1 { get { return Server1.AvgNServing; } } - public double AvgNServing2 { get { return Server2.AvgNServing; } } - public double AvgHoursInSystem { get { return HcInSystem.AverageDuration.TotalHours; } } - - private readonly IGenerator Generator; - private readonly IQueue Queue1; - private readonly IServer Server1; - private readonly IQueue Queue2; - private readonly IServer Server2; - private readonly HourCounter HcInSystem; - #endregion - - #region Events / Methods - private void Arrive() - { - Log("Arrive"); - HcInSystem.ObserveChange(1, ClockTime); - } + Logger?.LogInformation("Arrive"); + HcInSystem.ObserveChange(1, ClockTime); + } - private void Depart() - { - Log("Depart"); - HcInSystem.ObserveChange(-1, ClockTime); - } - #endregion - - /// Hour arrival rate to the system - /// Hourly service rate of Server 1 - /// Hourly service rate of Server 2 - /// Buffer queue (Queue 2) capacity - public TandemQueue(double arrRate, double svcRate1, double svcRate2, int bufferQSize, int seed = 0) - : base(seed) - { - HourlyArrivalRate = arrRate; - HourlyServiceRate1 = svcRate1; - HourlyServiceRate2 = svcRate2; + private void Depart() + { + Logger?.LogInformation("Depart"); + HcInSystem.ObserveChange(-1, ClockTime); + } + #endregion + + /// Hour arrival rate to the system + /// Hourly service rate of Server 1 + /// Hourly service rate of Server 2 + /// Buffer queue (Queue 2) capacity + public TandemQueue(double arrRate, double svcRate1, double svcRate2, int bufferQSize, int seed) + : this(null, arrRate, svcRate1, svcRate2, bufferQSize, seed) { } + + /// Hour arrival rate to the system + /// Hourly service rate of Server 1 + /// Hourly service rate of Server 2 + /// Buffer queue (Queue 2) capacity + public TandemQueue(ILogger? logger, double arrRate, double svcRate1, double svcRate2, int bufferQSize, int seed) + : base(logger, nameof(TandemQueue), seed) + { + HourlyArrivalRate = arrRate; + HourlyServiceRate1 = svcRate1; + HourlyServiceRate2 = svcRate2; - Generator = AddChild(new Generator(new Generator.Statics - { - InterArrivalTime = rs => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyArrivalRate)) - }, DefaultRS.Next())); + Generator = AddChild(new Generator(logger, new Generator.Statics + { + InterArrivalTime = rs => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyArrivalRate)) + }, nameof(TandemQueue), DefaultRS.Next())); - Queue1 = AddChild(new Queue(double.PositiveInfinity, DefaultRS.Next(), id: "Queue1")); + Queue1 = AddChild(new Queue(logger, double.PositiveInfinity, id: "Queue1", DefaultRS.Next())); - Server1 = AddChild(new Server(new Server.Statics - { - Capacity = 1, - ServiceTime = (rs, load) => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyServiceRate1)), - }, DefaultRS.Next(), id: "Server1")); + Server1 = AddChild(new Server(logger, new Server.Statics + { + Capacity = 1, + ServiceTime = (rs, load) => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyServiceRate1)), + }, id: "Server1", DefaultRS.Next())); - Queue2 = AddChild(new Queue(bufferQSize, DefaultRS.Next(), id: "Queue2")); + Queue2 = AddChild(new Queue(logger, bufferQSize, id: "Queue2", DefaultRS.Next())); - Server2 = AddChild(new Server(new Server.Statics - { - Capacity = 1, - ServiceTime = (rs, load) => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyServiceRate2)), - }, DefaultRS.Next(), id: "Server2")); + Server2 = AddChild(new Server(logger, new Server.Statics + { + Capacity = 1, + ServiceTime = (rs, load) => Exponential.Sample(rs, TimeSpan.FromHours(1 / HourlyServiceRate2)), + }, id: "Server2", DefaultRS.Next())); - Generator.OnArrive += () => Queue1.RqstEnqueue(new Load()); - Generator.OnArrive += Arrive; + Generator.OnArrive += () => Queue1.RqstEnqueue(new LoadEntity(EntityId.New())); + Generator.OnArrive += Arrive; - Queue1.OnEnqueued += Server1.RqstStart; - Server1.OnStarted += Queue1.Dequeue; + Queue1.OnEnqueued += Server1.RqstStart; + Server1.OnStarted += Queue1.Dequeue; - Server1.OnReadyToDepart += Queue2.RqstEnqueue; - Queue2.OnEnqueued += Server1.Depart; + Server1.OnReadyToDepart += Queue2.RqstEnqueue; + Queue2.OnEnqueued += Server1.Depart; - Queue2.OnEnqueued += Server2.RqstStart; - Server2.OnStarted += Queue2.Dequeue; + Queue2.OnEnqueued += Server2.RqstStart; + Server2.OnStarted += Queue2.Dequeue; - Server2.OnReadyToDepart += Server2.Depart; - Server2.OnReadyToDepart += load => Depart(); + Server2.OnReadyToDepart += Server2.Depart; + Server2.OnReadyToDepart += load => Depart(); - HcInSystem = AddHourCounter(); + HcInSystem = AddHourCounter(); - /// Initial event - Generator.Start(); - } + /// Initial event + Generator.Start(); + } - public override void Dispose() { } + public override void Dispose() + { + base.Dispose(); + // No explicit event fields to unsubscribe here because we used inline lambdas when wiring components. + // Children and hour counters are disposed by base. } } diff --git a/O2DESNet/Distributions/Beta.cs b/O2DESNet/Distributions/Beta.cs index 4fdeb7b..fb57e8b 100644 --- a/O2DESNet/Distributions/Beta.cs +++ b/O2DESNet/Distributions/Beta.cs @@ -1,58 +1,65 @@ using System; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class Beta { - public static class Beta + /// + /// + /// + /// + /// + /// coefficient of variation + /// + public static double Sample(Random rs, double mean, double cv) + { + if (mean < 0) + throw new Exception("Negative mean not applicable"); + if (cv < 0) + throw new Exception("Negative coefficient of variation not applicable for beta distribution"); + if (mean == 0) + return 0; + if (cv == 0) + return mean; + var stddev = cv * mean; + var a = mean * mean * (1 - mean) / stddev / stddev - mean; + var b = (1 - mean) * (1 - mean) * mean / stddev / stddev + mean - 1; + return MathNet.Numerics.Distributions.Beta.Sample(rs, a, b); + } + /// + /// + /// + /// + /// coefficient of variation + /// + /// + public static double CDF(double mean, double cv, double x) + { + if (mean <= 0) + throw new Exception("Zero or negative mean not applicable"); + if (cv <= 0) + throw new Exception("Zero or negative coefficient of variation not applicable for beta distribution"); + var sigma = cv * mean; + var a = mean * mean * (1 - mean) / sigma / sigma - mean; + var b = (1 - mean) * (1 - mean) * mean / sigma / sigma + mean - 1; + return MathNet.Numerics.Distributions.Beta.CDF(a, b, x); + } + /// + /// + /// + /// + /// coefficient of variation + /// + /// + public static double InvCDF(double mean, double cv, double p) { - /// - /// - /// - /// - /// - /// coefficient of variation - /// - public static double Sample(Random rs, double mean, double cv) - { - if (mean < 0) throw new Exception("Negative mean not applicable"); - if (cv < 0) throw new Exception("Negative coefficient of variation not applicable for beta distribution"); - if (mean == 0) return 0; - if (cv == 0) return mean; - var stddev = cv * mean; - var a = mean * mean * (1 - mean) / stddev / stddev - mean; - var b = (1 - mean) * (1 - mean) * mean / stddev / stddev + mean - 1; - return MathNet.Numerics.Distributions.Beta.Sample(rs, a, b); - } - /// - /// - /// - /// - /// coefficient of variation - /// - /// - public static double CDF(double mean, double cv, double x) - { - if (mean <= 0) throw new Exception("Zero or negative mean not applicable"); - if (cv <= 0) throw new Exception("Zero or negative coefficient of variation not applicable for beta distribution"); - var sigma = cv * mean; - var a = mean * mean * (1 - mean) / sigma / sigma - mean; - var b = (1 - mean) * (1 - mean) * mean / sigma / sigma + mean - 1; - return MathNet.Numerics.Distributions.Beta.CDF(a, b, x); - } - /// - /// - /// - /// - /// coefficient of variation - /// - /// - public static double InvCDF(double mean, double cv, double p) - { - if (mean <= 0) throw new Exception("Zero or negative mean not applicable"); - if (cv <= 0) throw new Exception("Zero or negative coefficient of variation not applicable for beta distribution"); - var sigma = cv * mean; - var a = mean * mean * (1 - mean) / sigma / sigma - mean; - var b = (1 - mean) * (1 - mean) * mean / sigma / sigma + mean - 1; - return MathNet.Numerics.Distributions.Beta.InvCDF(a, b, p); - } + if (mean <= 0) + throw new Exception("Zero or negative mean not applicable"); + if (cv <= 0) + throw new Exception("Zero or negative coefficient of variation not applicable for beta distribution"); + var sigma = cv * mean; + var a = mean * mean * (1 - mean) / sigma / sigma - mean; + var b = (1 - mean) * (1 - mean) * mean / sigma / sigma + mean - 1; + return MathNet.Numerics.Distributions.Beta.InvCDF(a, b, p); } } diff --git a/O2DESNet/Distributions/Empirical.cs b/O2DESNet/Distributions/Empirical.cs index c09edb3..b290451 100644 --- a/O2DESNet/Distributions/Empirical.cs +++ b/O2DESNet/Distributions/Empirical.cs @@ -2,24 +2,25 @@ using System.Collections.Generic; using System.Linq; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class Empirical { - public static class Empirical + public static int Sample(Random rs, IEnumerable ratios) { - public static int Sample(Random rs, IEnumerable ratios) - { - var threshold = rs.NextDouble() * ratios.Sum(); - for (int i = 0; i < ratios.Count(); i++) - { - var v = ratios.ElementAt(i); - if (threshold < v) return i; - threshold -= v; - } - return -1; - } - public static T Sample(Random rs, Dictionary ratios) + var threshold = rs.NextDouble() * ratios.Sum(); + for (int i = 0; i < ratios.Count(); i++) { - return ratios.Keys.ElementAt(Sample(rs, ratios.Values)); + var v = ratios.ElementAt(i); + if (threshold < v) + return i; + threshold -= v; } + + return -1; + } + public static T Sample(Random rs, Dictionary ratios) + { + return ratios.Keys.ElementAt(Sample(rs, ratios.Values)); } } diff --git a/O2DESNet/Distributions/Exponential.cs b/O2DESNet/Distributions/Exponential.cs index f251e26..8253766 100644 --- a/O2DESNet/Distributions/Exponential.cs +++ b/O2DESNet/Distributions/Exponential.cs @@ -1,27 +1,26 @@ using System; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class Exponential { - public static class Exponential + public static double Sample(Random rs, double mean) { - public static double Sample(Random rs, double mean) - { - return MathNet.Numerics.Distributions.Exponential.Sample(rs, 1 / mean); - } + return MathNet.Numerics.Distributions.Exponential.Sample(rs, 1 / mean); + } - public static double CDF(double mean, double x) - { - return MathNet.Numerics.Distributions.Exponential.CDF(1 / mean, x); - } + public static double CDF(double mean, double x) + { + return MathNet.Numerics.Distributions.Exponential.CDF(1 / mean, x); + } - public static double InvCDF(double mean, double p) - { - return MathNet.Numerics.Distributions.Exponential.InvCDF(1 / mean, p); - } + public static double InvCDF(double mean, double p) + { + return MathNet.Numerics.Distributions.Exponential.InvCDF(1 / mean, p); + } - public static TimeSpan Sample(Random rs, TimeSpan mean) - { - return TimeSpan.FromDays(Sample(rs, mean.TotalDays)); - } + public static TimeSpan Sample(Random rs, TimeSpan mean) + { + return TimeSpan.FromDays(Sample(rs, mean.TotalDays)); } } \ No newline at end of file diff --git a/O2DESNet/Distributions/Gamma.cs b/O2DESNet/Distributions/Gamma.cs index bcf1505..6d65dfc 100644 --- a/O2DESNet/Distributions/Gamma.cs +++ b/O2DESNet/Distributions/Gamma.cs @@ -1,37 +1,40 @@ using System; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class Gamma { - public static class Gamma + public static double Sample(Random rs, double mean, double cv) { - public static double Sample(Random rs, double mean, double cv) - { - if (mean == 0) return 0; - if (cv == 0) return mean; - var k = 1 / cv / cv; - var lambda = k / mean; - return MathNet.Numerics.Distributions.Gamma.Sample(rs, k, lambda); - } + if (mean == 0) + return 0; + if (cv == 0) + return mean; + var k = 1 / cv / cv; + var lambda = k / mean; + return MathNet.Numerics.Distributions.Gamma.Sample(rs, k, lambda); + } - public static double CDF(double mean, double cv, double x) - { - if (cv == 0) return x >= mean ? 1 : 0; - var k = 1 / cv / cv; - var lambda = k / mean; - return MathNet.Numerics.Distributions.Gamma.CDF(k, lambda, x); - } + public static double CDF(double mean, double cv, double x) + { + if (cv == 0) + return x >= mean ? 1 : 0; + var k = 1 / cv / cv; + var lambda = k / mean; + return MathNet.Numerics.Distributions.Gamma.CDF(k, lambda, x); + } - public static double InvCDF(double mean, double cv, double p) - { - if (cv == 0) return mean; - var k = 1 / cv / cv; - var lambda = k / mean; - return MathNet.Numerics.Distributions.Gamma.InvCDF(k, lambda, p); - } + public static double InvCDF(double mean, double cv, double p) + { + if (cv == 0) + return mean; + var k = 1 / cv / cv; + var lambda = k / mean; + return MathNet.Numerics.Distributions.Gamma.InvCDF(k, lambda, p); + } - public static TimeSpan Sample(Random rs, TimeSpan mean, double cv) - { - return TimeSpan.FromDays(Sample(rs, mean.TotalDays, cv)); - } + public static TimeSpan Sample(Random rs, TimeSpan mean, double cv) + { + return TimeSpan.FromDays(Sample(rs, mean.TotalDays, cv)); } } \ No newline at end of file diff --git a/O2DESNet/Distributions/LogNormal.cs b/O2DESNet/Distributions/LogNormal.cs index 3f60d52..ddbae98 100644 --- a/O2DESNet/Distributions/LogNormal.cs +++ b/O2DESNet/Distributions/LogNormal.cs @@ -1,52 +1,59 @@ using System; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class LogNormal { - public static class LogNormal + /// + /// parameters definition + /// + /// + /// + /// coefficient of variation + /// + public static double Sample(Random rs, double mean, double cv) + { + if (mean < 0) + throw new Exception("Negative mean not applicable"); + if (cv < 0) + throw new Exception("Negative coefficient of variation not applicable for log normal distribution"); + if (mean == 0) + return 0; + if (cv == 0) + return mean; + var stddev = cv * mean; + return MathNet.Numerics.Distributions.LogNormal.Sample(rs, mean, stddev); + } + /// + /// + /// + /// + /// coefficient of variation + /// + /// + public static double CDF(double mean, double cv, double x) + { + if (cv == 0) + return x >= mean ? 1 : 0; + if (mean <= 0) + throw new Exception("Zero or negative mean not applicable"); + var stddev = cv * mean; + return MathNet.Numerics.Distributions.LogNormal.CDF(mean, stddev, x); + } + /// + /// + /// + /// + /// coefficient of variation + /// + /// + public static double InvCDF(double mean, double cv, double p) { - /// - /// parameters definition - /// - /// - /// - /// coefficient of variation - /// - public static double Sample(Random rs, double mean, double cv) - { - if (mean < 0) throw new Exception("Negative mean not applicable"); - if (cv < 0) throw new Exception("Negative coefficient of variation not applicable for log normal distribution"); - if (mean == 0) return 0; - if (cv == 0) return mean; - var stddev = cv * mean; - return MathNet.Numerics.Distributions.LogNormal.Sample(rs, mean, stddev); - } - /// - /// - /// - /// - /// coefficient of variation - /// - /// - public static double CDF(double mean, double cv, double x) - { - if (cv == 0) return x >= mean ? 1 : 0; - if (mean <= 0) throw new Exception("Zero or negative mean not applicable"); - var stddev = cv * mean; - return MathNet.Numerics.Distributions.LogNormal.CDF(mean, stddev, x); - } - /// - /// - /// - /// - /// coefficient of variation - /// - /// - public static double InvCDF(double mean, double cv, double p) - { - if (cv == 0) return mean; - if (mean <= 0) throw new Exception("Zero or negative mean not applicable"); - var stddev = cv * mean; - return MathNet.Numerics.Distributions.LogNormal.InvCDF(mean, stddev, p); - } + if (cv == 0) + return mean; + if (mean <= 0) + throw new Exception("Zero or negative mean not applicable"); + var stddev = cv * mean; + return MathNet.Numerics.Distributions.LogNormal.InvCDF(mean, stddev, p); } } diff --git a/O2DESNet/Distributions/Normal.cs b/O2DESNet/Distributions/Normal.cs index 80043c6..408ac22 100644 --- a/O2DESNet/Distributions/Normal.cs +++ b/O2DESNet/Distributions/Normal.cs @@ -1,52 +1,59 @@ using System; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class Normal { - public static class Normal + /// + /// parameters definition + /// + /// + /// + /// coefficient of variation + /// + public static double Sample(Random rs, double mean, double cv) + { + if (mean < 0) + throw new Exception("Negative mean not applicable"); + if (cv < 0) + throw new Exception("Negative coefficient of variation not applicable for normal distribution"); + if (mean == 0) + return 0; + if (cv == 0) + return mean; + var stddev = cv * mean; + return MathNet.Numerics.Distributions.Normal.Sample(rs, mean, stddev); + } + /// + /// + /// + /// + /// coefficient of variation + /// + /// + public static double CDF(double mean, double cv, double x) + { + if (mean <= 0) + throw new Exception("Zero or negative mean not applicable"); + if (cv <= 0) + throw new Exception("Zero or negative coefficient of variation not applicable for normal distribution"); + var stddev = cv * mean; + return MathNet.Numerics.Distributions.Normal.CDF(mean, stddev, x); + } + /// + /// + /// + /// + /// coefficient of variation + /// probability + /// + public static double InvCDF(double mean, double cv, double p) { - /// - /// parameters definition - /// - /// - /// - /// coefficient of variation - /// - public static double Sample(Random rs, double mean, double cv) - { - if (mean < 0) throw new Exception("Negative mean not applicable"); - if (cv < 0) throw new Exception("Negative coefficient of variation not applicable for normal distribution"); - if (mean == 0) return 0; - if (cv == 0) return mean; - var stddev = cv * mean; - return MathNet.Numerics.Distributions.Normal.Sample(rs, mean, stddev); - } - /// - /// - /// - /// - /// coefficient of variation - /// - /// - public static double CDF(double mean, double cv, double x) - { - if (mean <= 0) throw new Exception("Zero or negative mean not applicable"); - if (cv <= 0) throw new Exception("Zero or negative coefficient of variation not applicable for normal distribution"); - var stddev = cv * mean; - return MathNet.Numerics.Distributions.Normal.CDF(mean, stddev, x); - } - /// - /// - /// - /// - /// coefficient of variation - /// probability - /// - public static double InvCDF(double mean, double cv, double p) - { - if (mean <= 0) throw new Exception("Zero or negative mean not applicable"); - if (cv <= 0) throw new Exception("Zero or negative coefficient of variation not applicable for normal distribution"); - var stddev = cv * mean; - return MathNet.Numerics.Distributions.Normal.InvCDF(mean, stddev, p); - } + if (mean <= 0) + throw new Exception("Zero or negative mean not applicable"); + if (cv <= 0) + throw new Exception("Zero or negative coefficient of variation not applicable for normal distribution"); + var stddev = cv * mean; + return MathNet.Numerics.Distributions.Normal.InvCDF(mean, stddev, p); } } diff --git a/O2DESNet/Distributions/Poisson.cs b/O2DESNet/Distributions/Poisson.cs index b5eb604..dc4e6fc 100644 --- a/O2DESNet/Distributions/Poisson.cs +++ b/O2DESNet/Distributions/Poisson.cs @@ -1,17 +1,16 @@ using System; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class Poisson { - public static class Poisson + public static int Sample(Random rs, double lambda) { - public static int Sample(Random rs, double lambda) - { - return MathNet.Numerics.Distributions.Poisson.Sample(rs, lambda); - } + return MathNet.Numerics.Distributions.Poisson.Sample(rs, lambda); + } - public static double CDF(double lambda, double x) - { - return MathNet.Numerics.Distributions.Poisson.CDF(lambda, x); - } + public static double CDF(double lambda, double x) + { + return MathNet.Numerics.Distributions.Poisson.CDF(lambda, x); } } diff --git a/O2DESNet/Distributions/Triangular.cs b/O2DESNet/Distributions/Triangular.cs index d7eb9bc..6df6bea 100644 --- a/O2DESNet/Distributions/Triangular.cs +++ b/O2DESNet/Distributions/Triangular.cs @@ -1,28 +1,27 @@ using System; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class Triangular { - public static class Triangular + /// + /// parameters definition + /// + /// + /// + /// + /// + /// + public static double Sample(Random rs, double lower, double upper, double mode) + { + return MathNet.Numerics.Distributions.Triangular.Sample(rs, lower, upper, mode); + } + public static double CDF(double lower, double upper, double mode, double x) + { + return MathNet.Numerics.Distributions.Triangular.CDF(lower, upper, mode, x); + } + public static double InvCDF(double lower, double upper, double mode, double p) { - /// - /// parameters definition - /// - /// - /// - /// - /// - /// - public static double Sample(Random rs, double lower, double upper, double mode) - { - return MathNet.Numerics.Distributions.Triangular.Sample(rs, lower, upper, mode); - } - public static double CDF(double lower, double upper, double mode, double x) - { - return MathNet.Numerics.Distributions.Triangular.CDF(lower, upper, mode, x); - } - public static double InvCDF(double lower, double upper, double mode, double p) - { - return MathNet.Numerics.Distributions.Triangular.InvCDF(lower, upper, mode, p); - } + return MathNet.Numerics.Distributions.Triangular.InvCDF(lower, upper, mode, p); } } diff --git a/O2DESNet/Distributions/Uniform.cs b/O2DESNet/Distributions/Uniform.cs index 688cd06..dfa76a5 100644 --- a/O2DESNet/Distributions/Uniform.cs +++ b/O2DESNet/Distributions/Uniform.cs @@ -2,24 +2,24 @@ using System.Collections.Generic; using System.Linq; -namespace O2DESNet.Distributions +namespace O2DESNet.Distributions; + +public static class Uniform { - public static class Uniform + public static double Sample(Random rs, double lowerbound, double upperbound) { - public static double Sample(Random rs, double lowerbound, double upperbound) - { - return lowerbound + (upperbound - lowerbound) * rs.NextDouble(); - } + return lowerbound + (upperbound - lowerbound) * rs.NextDouble(); + } - public static TimeSpan Sample(Random rs, TimeSpan lowerbound, TimeSpan upperbound) - { - return TimeSpan.FromMinutes(Sample(rs, lowerbound.TotalMinutes, upperbound.TotalMinutes)); - } + public static TimeSpan Sample(Random rs, TimeSpan lowerbound, TimeSpan upperbound) + { + return TimeSpan.FromMinutes(Sample(rs, lowerbound.TotalMinutes, upperbound.TotalMinutes)); + } - public static T Sample(Random rs, IEnumerable candidates) - { - if (candidates.Count() == 0) return default(T); - return candidates.ElementAt(rs.Next(candidates.Count())); - } + public static T? Sample(Random rs, IEnumerable candidates) + { + if (candidates.Count() == 0) + return default(T); + return candidates.ElementAt(rs.Next(candidates.Count())); } } \ No newline at end of file diff --git a/O2DESNet/Event.cs b/O2DESNet/Event.cs deleted file mode 100644 index cf4ba9f..0000000 --- a/O2DESNet/Event.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace O2DESNet -{ - public class Event : IDisposable - { - private static int _count = 0; - internal int Index { get; private set; } = _count++; - internal string Tag { get; private set; } - internal Sandbox Owner { get; private set; } - internal DateTime ScheduledTime { get; private set; } - internal Action Action { get; private set; } - - internal Event(Sandbox owner, Action action, DateTime scheduledTime, string tag = null) - { - Owner = owner; - Action = action; - ScheduledTime = scheduledTime; - Tag = tag; - } - internal void Invoke() { Action.Invoke(); } - public override string ToString() - { - return string.Format("{0}#{1}", Tag, Index); - } - - public void Dispose() - { - } - } - internal sealed class EventComparer : IComparer - { - private static readonly EventComparer _instance = new EventComparer(); - private EventComparer() { } - public static EventComparer Instance { get { return _instance; } } - public int Compare(Event x, Event y) - { - int compare = x.ScheduledTime.CompareTo(y.ScheduledTime); - if (compare == 0) return x.Index.CompareTo(y.Index); - return compare; - } - } -} diff --git a/O2DESNet/HourCounter.cs b/O2DESNet/HourCounter.cs deleted file mode 100644 index bc0d39d..0000000 --- a/O2DESNet/HourCounter.cs +++ /dev/null @@ -1,401 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace O2DESNet -{ - public interface IReadOnlyHourCounter - { - DateTime LastTime { get; } - double LastCount { get; } - bool Paused { get; } - /// - /// Total number of increment observed - /// - double TotalIncrement { get; } - /// - /// Total number of decrement observed - /// - double TotalDecrement { get; } - double IncrementRate { get; } - double DecrementRate { get; } - /// - /// Total number of hours since the initial time. - /// - double TotalHours { get; } - double WorkingTimeRatio { get; } - /// - /// The cumulative count value on time in unit of hours - /// - double CumValue { get; } - /// - /// The average count on observation period - /// - double AverageCount { get; } - /// - /// Average timespan that a load stays in the activity, if it is a stationary process, - /// i.e., decrement rate == increment rate - /// It is 0 at the initial status, i.e., decrement rate is NaN (no decrement observed). - /// - TimeSpan AverageDuration { get; } - string LogFile { get; set; } - } - public interface IHourCounter : IReadOnlyHourCounter - { - void ObserveCount(double count, DateTime clockTime); - void ObserveChange(double count, DateTime clockTime); - void Pause(); - void Pause(DateTime clockTime); - void Resume(DateTime clockTime); - } - public class ReadOnlyHourCounter : IReadOnlyHourCounter, IDisposable - { - public DateTime LastTime { get { return HourCounter.LastTime; } } - - public double LastCount { get { return HourCounter.LastCount; } } - - public bool Paused { get { return HourCounter.Paused; } } - - public double TotalIncrement { get { return HourCounter.TotalIncrement; } } - - public double TotalDecrement { get { return HourCounter.TotalDecrement; } } - - public double IncrementRate { get { return HourCounter.IncrementRate; } } - - public double DecrementRate { get { return HourCounter.DecrementRate; } } - - public double TotalHours { get { return HourCounter.TotalHours; } } - - public double WorkingTimeRatio { get { return HourCounter.WorkingTimeRatio; } } - - public double CumValue { get { return HourCounter.CumValue; } } - - public double AverageCount { get { return HourCounter.AverageCount; } } - - public TimeSpan AverageDuration { get { return HourCounter.AverageDuration; } } - public string LogFile - { - get { return HourCounter.LogFile; } - set { HourCounter.LogFile = value; } - } - - private readonly HourCounter HourCounter; - internal ReadOnlyHourCounter(HourCounter hourCounter) - { - HourCounter = hourCounter; - } - - public void Dispose() { } - } - public class HourCounter : IHourCounter, IDisposable - { - private ISandbox _sandbox; - private DateTime _initialTime; - public DateTime LastTime { get; private set; } - public double LastCount { get; private set; } - /// - /// Total number of increment observed - /// - public double TotalIncrement { get; private set; } - /// - /// Total number of decrement observed - /// - public double TotalDecrement { get; private set; } - /// - /// Total number of hours since the initial time. - /// - public double TotalHours { get; private set; } - private void UpdateToClockTime() - { - if (LastTime != _sandbox.ClockTime) ObserveCount(LastCount); - } - public double WorkingTimeRatio - { - get - { - UpdateToClockTime(); - if (LastTime == _initialTime) return 0; - return TotalHours / (LastTime - _initialTime).TotalHours; - } - } - /// - /// The cumulative count value (integral) on time in unit of hours - /// - public double CumValue { get; private set; } - /// - /// The average count on observation period - /// - public double AverageCount - { - get - { - UpdateToClockTime(); - if (TotalHours == 0) return LastCount; return CumValue / TotalHours; - } - } - /// - /// Average timespan that a load stays in the activity, if it is a stationary process, - /// i.e., decrement rate == increment rate - /// It is 0 at the initial status, i.e., decrement rate is NaN (no decrement observed). - /// - public TimeSpan AverageDuration - { - get - { - UpdateToClockTime(); - double hours = AverageCount / DecrementRate; - if (double.IsNaN(hours) || double.IsInfinity(hours)) hours = 0; - return TimeSpan.FromHours(hours); - } - } - public bool Paused { get; private set; } - - #region For history keeping - private Dictionary _history; - public bool KeepHistory { get; private set; } - /// - /// Scatter points of (time in hours, count) - /// - public List> History - { - get - { - if (!KeepHistory) return null; - return _history.OrderBy(i => i.Key).Select(i => new Tuple((i.Key - _initialTime).TotalHours, i.Value)).ToList(); - } - } - #endregion - - internal HourCounter(ISandbox sandbox, bool keepHistory = false) - { - Init(sandbox, DateTime.MinValue, keepHistory); - } - internal HourCounter(ISandbox sandbox, DateTime initialTime, bool keepHistory = false) - { - Init(sandbox, initialTime, keepHistory); - } - private void Init(ISandbox sandbox, DateTime initialTime, bool keepHistory) - { - _sandbox = sandbox; - _initialTime = initialTime; - LastTime = initialTime; - LastCount = 0; - TotalIncrement = 0; - TotalDecrement = 0; - TotalHours = 0; - CumValue = 0; - KeepHistory = keepHistory; - if (KeepHistory) _history = new Dictionary(); - } - public void ObserveCount(double count) - { - var clockTime = _sandbox.ClockTime; - if (clockTime < LastTime) - throw new Exception("Time of new count cannot be earlier than current time."); - if (!Paused) - { - var hours = (clockTime - LastTime).TotalHours; - TotalHours += hours; - CumValue += hours * LastCount; - if (count > LastCount) TotalIncrement += count - LastCount; - else TotalDecrement += LastCount - count; - - if (!HoursForCount.ContainsKey(LastCount)) HoursForCount.Add(LastCount, 0); - HoursForCount[LastCount] += hours; - } - if (_logFile != null) - { - using (var sw = new StreamWriter(_logFile, append: true)) - { - sw.Write("{0},{1}", TotalHours, LastCount); - if (Paused) sw.Write(",Paused"); - sw.WriteLine(); - if (count != LastCount) - { - sw.Write("{0},{1}", TotalHours, count); - if (Paused) sw.Write(",Paused"); - sw.WriteLine(); - } - }; - } - LastTime = clockTime; - LastCount = count; - if (KeepHistory) _history[clockTime] = count; - } - /// - /// Remove parameter clockTime as since Version 3.6, according to Issue 1 - /// - public void ObserveCount(double count, DateTime clockTime) - { - CheckClockTime(clockTime); - ObserveCount(count); - } - public void ObserveChange(double change) { ObserveCount(LastCount + change); } - /// - /// Remove parameter clockTime as since Version 3.6, according to Issue 1 - /// - public void ObserveChange(double change, DateTime clockTime) - { - CheckClockTime(clockTime); - ObserveChange(change); - } - public void Pause() - { - var clockTime = _sandbox.ClockTime; - if (Paused) return; - ObserveCount(LastCount, clockTime); - Paused = true; - if (_logFile != null) - { - using (var sw = new StreamWriter(_logFile, append: true)) - { - sw.WriteLine("{0},{1},Paused", TotalHours, LastCount); - }; - } - } - /// - /// Remove parameter clockTime as since Version 3.6, according to Issue 1 - /// - public void Pause(DateTime clockTime) - { - CheckClockTime(clockTime); - Pause(); - } - public void Resume() - { - if (!Paused) return; - LastTime = _sandbox.ClockTime; - Paused = false; - if (_logFile != null) - { - using (var sw = new StreamWriter(_logFile, append: true)) - { - sw.WriteLine("{0},{1},Paused", TotalHours, LastCount); - sw.WriteLine("{0},{1}", TotalHours, LastCount); - }; - } - } - /// - /// Remove parameter clockTime as since Version 3.6, according to Issue 1 - /// - public void Resume(DateTime clockTime) - { - CheckClockTime(clockTime); - Resume(); - } - private void CheckClockTime(DateTime clockTime) - { - if (clockTime != _sandbox.ClockTime) throw new Exception("ClockTime is not consistent with the Sandbox."); - } - - public double IncrementRate - { - get - { - UpdateToClockTime(); - return TotalIncrement / TotalHours; - } - } - public double DecrementRate - { - get - { - UpdateToClockTime(); - return TotalDecrement / TotalHours; - } - } - internal void WarmedUp() - { - - // all reset except the last count - _initialTime = _sandbox.ClockTime; - LastTime = _sandbox.ClockTime; - TotalIncrement = 0; - TotalDecrement = 0; - TotalHours = 0; - CumValue = 0; - HoursForCount = new Dictionary(); - } - - public Dictionary HoursForCount = new Dictionary(); - private void SortHoursForCount() { HoursForCount = HoursForCount.OrderBy(i => i.Key).ToDictionary(i => i.Key, i => i.Value); } - /// - /// Get the percentile of count values on time, i.e., the count value that with x-percent of time the observation is not higher than it. - /// - /// values between 0 and 100 - public double Percentile(double ratio) - { - SortHoursForCount(); - var threashold = HoursForCount.Sum(i => i.Value) * ratio / 100; - foreach (var i in HoursForCount) - { - threashold -= i.Value; - if (threashold <= 0) return i.Key; - } - return double.PositiveInfinity; - } - /// - /// Statistics for the amount of time spent at each range of count values - /// - /// width of the count value interval - /// A dictionary map from [the lowerbound value of each interval] to the array of [total hours observed], [probability], [cumulated probability] - public Dictionary Histogram(double countInterval) // interval -> { observation, probability, cumulative probability} - { - SortHoursForCount(); - var histogram = new Dictionary(); - if (HoursForCount.Count > 0) - { - double countLb = 0; - double cumHours = 0; - foreach (var i in HoursForCount) - { - if (i.Key > countLb + countInterval || i.Equals(HoursForCount.Last())) - { - if (cumHours > 0) histogram.Add(countLb, new double[] { cumHours, 0, 0 }); - countLb += countInterval; - cumHours = i.Value; - } - else - { - cumHours += i.Value; - } - } - } - var sum = histogram.Sum(h => h.Value[0]); - double cum = 0; - foreach (var h in histogram) - { - cum += h.Value[0]; - h.Value[1] = h.Value[0] / sum; // probability - h.Value[2] = cum / sum; // cum. prob. - } - return histogram; - } - - private string _logFile; - public string LogFile - { - get { return _logFile; } - set - { - _logFile = value; - if (_logFile != null) - using (var sw = new StreamWriter(_logFile)) - { - sw.WriteLine("Hours,Count,Remark"); - sw.WriteLine("{0},{1}", TotalHours, LastCount); - }; - } - } - - private ReadOnlyHourCounter ReadOnly { get; set; } = null; - public ReadOnlyHourCounter AsReadOnly() - { - if (ReadOnly == null) ReadOnly = new ReadOnlyHourCounter(this); - return ReadOnly; - } - - public void Dispose() { } - } -} diff --git a/O2DESNet/HourCounters/HourCounter.cs b/O2DESNet/HourCounters/HourCounter.cs new file mode 100644 index 0000000..a3480f8 --- /dev/null +++ b/O2DESNet/HourCounters/HourCounter.cs @@ -0,0 +1,450 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace O2DESNet.HourCounters; + +public class HourCounter : IHourCounter, IDisposable +{ + // Private fields + private readonly ILogger? _logger; + private ISandbox _sandbox; + private TimeSpan _initialTime; + private Dictionary _history = []; + + // Backing fields for properties with private setters + private TimeSpan _lastTime; + private double _lastCount; + private double _totalIncrement; + private double _totalDecrement; + private double _totalHours; + private double _cumValue; + private bool _paused; + private bool _keepHistory; + private ReadOnlyHourCounter? _readOnly; + + // Private properties + private ReadOnlyHourCounter? ReadOnly => _readOnly; + + public ILogger? Logger => _logger; + + // Public properties and fields + /// + /// Last observed simulation time (from the sandbox clock) when the counter was most recently updated. + /// Used as the left boundary of the next accumulation interval until another observation occurs. + /// + public TimeSpan LastTime => _lastTime; + + /// + /// The most recent observed count value. + /// Represents the level held since until the next observation is recorded. + /// + public double LastCount => _lastCount; + + /// + /// Total amount of positive changes observed across the whole observation period. + /// This increases when a new observation is larger than the previous + /// + public double TotalIncrement => _totalIncrement; + + /// + /// Total amount of negative changes observed across the whole observation period. + /// This increases when a new observation is smaller than the previous + /// + public double TotalDecrement => _totalDecrement; + + /// + /// Total number of hours that have elapsed (and were accumulated) since the initial time. + /// This value does not increase while the counter is . + /// + public double TotalHours => _totalHours; + + /// + /// Ratio of accumulated working time to wall-clock time since initialization. + /// Computed as TotalHours divided by (LastTime - initialTime).TotalHours; returns 0 if no time has elapsed. + /// + public double WorkingTimeRatio + { + get + { + UpdateToClockTime(); + if (LastTime == _initialTime) + return 0; + return TotalHours / (LastTime - _initialTime).TotalHours; + } + } + + /// + /// The cumulative time-weighted integral of the count (in hours * count). + /// Each interval contributes duration (in hours) multiplied by the held during that interval. + /// + public double CumValue => _cumValue; + + /// + /// The time-average of the count over the observation period. + /// Equals if no hours have been accumulated yet (i.e., TotalHours == 0). + /// + public double AverageCount + { + get + { + UpdateToClockTime(); + if (TotalHours == 0) + return LastCount; + return CumValue / TotalHours; + } + } + + /// + /// Average timespan that a load stays in the activity, assuming a stationary process + /// (i.e., decrement rate == increment rate). Computed as AverageCount / DecrementRate (Little’s law). + /// Returns 0 when undefined (e.g., no decrements observed yet). + /// + public TimeSpan AverageDuration + { + get + { + UpdateToClockTime(); + double hours = AverageCount / DecrementRate; + if (double.IsNaN(hours) || double.IsInfinity(hours)) + hours = 0; + return TimeSpan.FromHours(hours); + } + } + + /// + /// Indicates whether the counter is currently paused. While paused, time is not accumulated and + /// counts are not integrated into . + /// + public bool Paused => _paused; + + /// + /// Indicates whether this counter keeps a timestamped history of observed counts. + /// When enabled, will expose the recorded scatter points. + /// + public bool KeepHistory => _keepHistory; + + /// + /// Scatter points of (time since initialization, count) representing history entries. + /// Prefer this strongly-typed struct over tuples for readability and maintainability. + /// Returns null when is false to avoid unnecessary memory usage. + /// + public List? History + { + get + { + if (!_keepHistory) + return null; + return _history + .OrderBy(i => i.Key) + .Select(i => new HourCounterHistoryPoint(i.Key - _initialTime, i.Value)) + .ToList(); + } + } + + /// + /// Average increment rate per hour across the observation period. + /// Calculated as TotalIncrement / TotalHours. Updates to the current clock time before evaluation. + /// + public double IncrementRate + { + get + { + UpdateToClockTime(); + return TotalIncrement / TotalHours; + } + } + + /// + /// Average decrement rate per hour across the observation period. + /// Calculated as TotalDecrement / TotalHours. Updates to the current clock time before evaluation. + /// + public double DecrementRate + { + get + { + UpdateToClockTime(); + return TotalDecrement / TotalHours; + } + } + + /// + /// Distribution of total hours spent at each distinct count value. + /// Key = count value, Value = total hours observed at that value. + /// + public Dictionary HoursForCount = []; + + // Constructors + internal HourCounter(ISandbox sandbox, bool keepHistory = false) + { + if(sandbox is null) + throw new ArgumentNullException(nameof(sandbox)); + _sandbox = sandbox; + _logger = null; + Init(sandbox, TimeSpan.Zero, keepHistory); + } + + internal HourCounter(ILogger? logger, ISandbox sandbox, bool keepHistory = false) + { + if (sandbox is null) + throw new ArgumentNullException(nameof(sandbox)); + _sandbox = sandbox; + _logger = logger; + Init(sandbox, TimeSpan.Zero, keepHistory); + } + + internal HourCounter(ISandbox sandbox, TimeSpan initialTime, bool keepHistory = false) + { + if (sandbox is null) + throw new ArgumentNullException(nameof(sandbox)); + _sandbox = sandbox; + _logger = null; + Init(sandbox, initialTime, keepHistory); + } + + internal HourCounter(ILogger? logger, ISandbox sandbox, TimeSpan initialTime, bool keepHistory = false) + { + if (sandbox is null) + throw new ArgumentNullException(nameof(sandbox)); + _sandbox = sandbox; + _logger = logger; + Init(sandbox, initialTime, keepHistory); + } + + private void Init(ISandbox sandbox, TimeSpan initialTime, bool keepHistory) + { + _sandbox = sandbox; + _initialTime = initialTime; + _lastTime = initialTime; + _lastCount = 0; + _totalIncrement = 0; + _totalDecrement = 0; + _totalHours = 0; + _cumValue = 0; + _keepHistory = keepHistory; + if (_keepHistory) + _history = []; + } + + // Public methods + /// + /// Observe the specified count at the current sandbox clock time. + /// Updates accumulated hours, time-weighted cumulative value, increment/decrement totals, and optional history logging. + /// Throws if the current clock time is earlier than the previous observation time. + /// + /// The new observed count value. + public void ObserveCount(double count) + { + var clockTime = _sandbox.ClockTime; + if (clockTime < LastTime) + throw new Exception("Time of new count cannot be earlier than current time."); + if (!Paused) + { + var hours = (clockTime - LastTime).TotalHours; + _totalHours += hours; + _cumValue += hours * LastCount; + if (count > LastCount) + _totalIncrement += count - LastCount; + else + _totalDecrement += LastCount - count; + + if (!HoursForCount.ContainsKey(LastCount)) + HoursForCount.Add(LastCount, 0); + HoursForCount[LastCount] += hours; + } + + // Log current point and, if changed, the new point + _logger?.LogInformation("HourCounter sample: Hours={Hours}, Count={Count}, Paused={Paused}", TotalHours, LastCount, Paused); + if (count != LastCount) + { + _logger?.LogInformation("HourCounter sample: Hours={Hours}, Count={Count}, Paused={Paused}", TotalHours, count, Paused); + } + + _lastTime = clockTime; + _lastCount = count; + if (KeepHistory) + _history[clockTime] = count; + } + + /// + /// Observe the specified count with a consistency check on the supplied clock time. + /// The provided clockTime must match the sandbox's ClockTime. Kept for backward compatibility since v3.6 (Issue 1). + /// + /// The new observed count value. + /// The time that must match the sandbox clock. + public void ObserveCount(double count, TimeSpan clockTime) + { + CheckClockTime(clockTime); + ObserveCount(count); + } + + /// + /// Observe a change (delta) to the current count at the current sandbox clock time. + /// Equivalent to calling ObserveCount(LastCount + change). + /// + /// The delta to apply to the last observed count. + public void ObserveChange(double change) => ObserveCount(LastCount + change); + + /// + /// Observe a change (delta) with a consistency check on the supplied clock time. + /// The provided clockTime must match the sandbox's ClockTime. Kept for backward compatibility since v3.6 (Issue 1). + /// + /// The delta to apply to the last observed count. + /// The time that must match the sandbox clock. + public void ObserveChange(double change, TimeSpan clockTime) + { + CheckClockTime(clockTime); + ObserveChange(change); + } + + /// + /// Pause the counter at the current sandbox clock time. + /// Finalizes the current accumulation interval and prevents further accumulation until Resume is called. + /// Also logs the paused state. + /// + public void Pause() + { + var clockTime = _sandbox.ClockTime; + if (Paused) + return; + ObserveCount(LastCount, clockTime); + _paused = true; + _logger?.LogInformation("HourCounter paused: Hours={Hours}, Count={Count}", TotalHours, LastCount); + } + + /// + /// Pause the counter with a consistency check on the supplied clock time. + /// The provided clockTime must match the sandbox's ClockTime. Kept for backward compatibility since v3.6 (Issue 1). + /// + /// The time that must match the sandbox clock. + public void Pause(TimeSpan clockTime) + { + CheckClockTime(clockTime); + Pause(); + } + + /// + /// Resume the counter at the current sandbox clock time. + /// Sets LastTime to the current clock time and enables accumulation for subsequent observations. + /// Also logs the resume action. + /// + public void Resume() + { + if (!Paused) + return; + _lastTime = _sandbox.ClockTime; + _paused = false; + _logger?.LogInformation("HourCounter resumed: Hours={Hours}, Count={Count}", TotalHours, LastCount); + } + + /// + /// Resume the counter with a consistency check on the supplied clock time. + /// The provided clockTime must match the sandbox's ClockTime. Kept for backward compatibility since v3.6 (Issue 1). + /// + /// The time that must match the sandbox clock. + public void Resume(TimeSpan clockTime) + { + CheckClockTime(clockTime); + Resume(); + } + + internal void WarmedUp() + { + + // all reset except the last count + _initialTime = _sandbox.ClockTime; + _lastTime = _sandbox.ClockTime; + _totalIncrement = 0; + _totalDecrement = 0; + _totalHours = 0; + _cumValue = 0; + HoursForCount = []; + } + + /// + /// Get the percentile of count values on time, i.e., the count value that with x-percent of time the observation is not higher than it. + /// Uses the time spent at each distinct count value as weights. + /// + /// Percentile between 0 and 100 (e.g., 50 for median). + public double Percentile(double ratio) + { + SortHoursForCount(); + var threshold = HoursForCount.Sum(i => i.Value) * ratio / 100; + foreach (var i in HoursForCount) + { + threshold -= i.Value; + if (threshold <= 0) + return i.Key; + } + + return double.PositiveInfinity; + } + + /// + /// Build a histogram of the time spent within ranges of count values. + /// Returns a dictionary mapping the lower bound of each interval to an array: { total hours observed, probability, cumulative probability }. + /// + /// Width of the count value interval (bin size). + /// A dictionary map: lower bound -> [hours observed, probability, cumulative probability]. + public Dictionary Histogram(double countInterval) // interval -> { observation, probability, cumulative probability} + { + SortHoursForCount(); + var histogram = new Dictionary(); + if (HoursForCount.Count > 0) + { + double countLb = 0; + double cumHours = 0; + foreach (var i in HoursForCount) + { + if (i.Key > countLb + countInterval || i.Equals(HoursForCount.Last())) + { + if (cumHours > 0) + histogram.Add(countLb, new double[] { cumHours, 0, 0 }); + countLb += countInterval; + cumHours = i.Value; + } + else + { + cumHours += i.Value; + } + } + } + + var sum = histogram.Sum(h => h.Value[0]); + double cum = 0; + foreach (var h in histogram) + { + cum += h.Value[0]; + h.Value[1] = h.Value[0] / sum; // probability + h.Value[2] = cum / sum; // cum. prob. + } + + return histogram; + } + + public ReadOnlyHourCounter AsReadOnly() + { + if (_readOnly == null) + _readOnly = new ReadOnlyHourCounter(this); + return _readOnly; + } + + public void Dispose() { } + + // Private methods + private void UpdateToClockTime() + { + if (LastTime != _sandbox.ClockTime) + ObserveCount(LastCount); + } + + + private void CheckClockTime(TimeSpan clockTime) + { + if (clockTime != _sandbox.ClockTime) + throw new Exception("ClockTime is not consistent with the Sandbox."); + } + + private void SortHoursForCount() => HoursForCount = HoursForCount.OrderBy(i => i.Key).ToDictionary(i => i.Key, i => i.Value); +} diff --git a/O2DESNet/HourCounters/HourCounterHistoryPoint.cs b/O2DESNet/HourCounters/HourCounterHistoryPoint.cs new file mode 100644 index 0000000..ba6433d --- /dev/null +++ b/O2DESNet/HourCounters/HourCounterHistoryPoint.cs @@ -0,0 +1,19 @@ +using System; + +namespace O2DESNet.HourCounters; + +/// +/// Represents a single history point for HourCounter, capturing the time since initialization and the observed count. +/// Immutable value type for efficient storage and clear semantics compared to tuples. +/// +public readonly struct HourCounterHistoryPoint +{ + public TimeSpan HoursSinceInitial { get; } + public double Count { get; } + + public HourCounterHistoryPoint(TimeSpan hoursSinceInitial, double count) + { + HoursSinceInitial = hoursSinceInitial; + Count = count; + } +} diff --git a/O2DESNet/HourCounters/IHourCounter.cs b/O2DESNet/HourCounters/IHourCounter.cs new file mode 100644 index 0000000..a94b85e --- /dev/null +++ b/O2DESNet/HourCounters/IHourCounter.cs @@ -0,0 +1,12 @@ +using System; + +namespace O2DESNet.HourCounters; + +public interface IHourCounter : IReadOnlyHourCounter +{ + void ObserveCount(double count, TimeSpan clockTime); + void ObserveChange(double count, TimeSpan clockTime); + void Pause(); + void Pause(TimeSpan clockTime); + void Resume(TimeSpan clockTime); +} diff --git a/O2DESNet/HourCounters/IReadOnlyHourCounter.cs b/O2DESNet/HourCounters/IReadOnlyHourCounter.cs new file mode 100644 index 0000000..d7d2b7c --- /dev/null +++ b/O2DESNet/HourCounters/IReadOnlyHourCounter.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Logging; +using System; + +namespace O2DESNet.HourCounters; + +public interface IReadOnlyHourCounter +{ + TimeSpan LastTime { get; } + double LastCount { get; } + bool Paused { get; } + /// + /// Total number of increment observed + /// + double TotalIncrement { get; } + /// + /// Total number of decrement observed + /// + double TotalDecrement { get; } + double IncrementRate { get; } + double DecrementRate { get; } + /// + /// Total number of hours since the initial time. + /// + double TotalHours { get; } + double WorkingTimeRatio { get; } + /// + /// The cumulative count value on time in unit of hours + /// + double CumValue { get; } + /// + /// The average count on observation period + /// + double AverageCount { get; } + /// + /// Average timespan that a load stays in the activity, if it is a stationary process, + /// i.e., decrement rate == increment rate + /// It is 0 at the initial status, i.e., decrement rate is NaN (no decrement observed). + /// + TimeSpan AverageDuration { get; } + ILogger? Logger { get; } +} diff --git a/O2DESNet/HourCounters/ReadOnlyHourCounter.cs b/O2DESNet/HourCounters/ReadOnlyHourCounter.cs new file mode 100644 index 0000000..59cf1eb --- /dev/null +++ b/O2DESNet/HourCounters/ReadOnlyHourCounter.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Logging; +using System; + +namespace O2DESNet.HourCounters; + +public class ReadOnlyHourCounter : IReadOnlyHourCounter, IDisposable +{ + public TimeSpan LastTime => HourCounter.LastTime; + + public double LastCount => HourCounter.LastCount; + + public bool Paused => HourCounter.Paused; + + public double TotalIncrement => HourCounter.TotalIncrement; + + public double TotalDecrement => HourCounter.TotalDecrement; + + public double IncrementRate => HourCounter.IncrementRate; + + public double DecrementRate => HourCounter.DecrementRate; + + public double TotalHours => HourCounter.TotalHours; + + public double WorkingTimeRatio => HourCounter.WorkingTimeRatio; + + public double CumValue => HourCounter.CumValue; + + public double AverageCount => HourCounter.AverageCount; + + public TimeSpan AverageDuration => HourCounter.AverageDuration; + + public ILogger? Logger => HourCounter.Logger; + + private readonly HourCounter HourCounter; + internal ReadOnlyHourCounter(HourCounter hourCounter) + { + HourCounter = hourCounter; + } + + public void Dispose() { } +} diff --git a/O2DESNet/ISandbox.cs b/O2DESNet/ISandbox.cs new file mode 100644 index 0000000..7907370 --- /dev/null +++ b/O2DESNet/ISandbox.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Logging; + +using System; +using System.Collections.Immutable; + +namespace O2DESNet; + +public interface ISandbox : IDisposable +{ + string Id { get; } + ILogger? Logger { get; } + int Seed { get; } + + ISandbox? Parent { get; } + IImmutableList Children { get; } + TimeSpan ClockTime { get; } + + void UpdateRandomSeed(int seed); + bool Run(); + bool Run(int eventCount); + bool Run(TimeSpan duration); + bool Run(double speed); + bool WarmUp(TimeSpan period); +} diff --git a/O2DESNet/Internals/Event.cs b/O2DESNet/Internals/Event.cs new file mode 100644 index 0000000..c05a450 --- /dev/null +++ b/O2DESNet/Internals/Event.cs @@ -0,0 +1,44 @@ +using System; + +namespace O2DESNet.Internals; + +[System.Diagnostics.DebuggerDisplay("{_owner.Id}#{_tag}#{_index}")] +internal class Event +{ + private readonly Sandbox _owner = null!; + private readonly int _index; + private readonly string? _tag; + private readonly Action? _action; + private readonly TimeSpan _timestamp; + + internal int Index => _index; + + internal string? Tag => _tag; + + internal Sandbox Owner => _owner; + + internal TimeSpan Timestamp => _timestamp; + + internal Action? Action => _action; + + private Event() { } + + internal Event(Sandbox owner, int eventIndex, Action action, TimeSpan timestamp, string? tag) + { + _owner = owner ?? throw new ArgumentNullException(nameof(owner), "Owner of this event instance cannot be null"); + _index = eventIndex; + _action = action; + _timestamp = timestamp; + _tag = tag; + } + + internal void Invoke() + { + _action?.Invoke(); + } + + public override string ToString() + { + return $"{_owner.Id}#{_tag}#{_index}"; + } +} diff --git a/O2DESNet/Internals/EventComparer.cs b/O2DESNet/Internals/EventComparer.cs new file mode 100644 index 0000000..c83a3b2 --- /dev/null +++ b/O2DESNet/Internals/EventComparer.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace O2DESNet.Internals; + +internal sealed class EventComparer : IComparer +{ + private static readonly EventComparer _instance = new(); + + private EventComparer() { } + + public static EventComparer Instance => _instance; + + public int Compare(Event x, Event y) + { + int compare = x.Timestamp.CompareTo(y.Timestamp); + + if (compare == 0) + return x.Index.CompareTo(y.Index); + + return compare; + } +} diff --git a/O2DESNet/Internals/EventIndexer.cs b/O2DESNet/Internals/EventIndexer.cs new file mode 100644 index 0000000..de7d4b0 --- /dev/null +++ b/O2DESNet/Internals/EventIndexer.cs @@ -0,0 +1,17 @@ +namespace O2DESNet; + +internal class EventIndexer +{ + private static int _eventIndex = 1; + + public static int GenerateIndex() + { + _eventIndex++; + return _eventIndex; + } + + public static void Reset() + { + _eventIndex = 0; + } +} diff --git a/O2DESNet/Internals/FutureEventList.cs b/O2DESNet/Internals/FutureEventList.cs new file mode 100644 index 0000000..6a04b17 --- /dev/null +++ b/O2DESNet/Internals/FutureEventList.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace O2DESNet.Internals; + +internal class FutureEventList : SortedSet +{ + private readonly Sandbox _sandbox = null!; + + private FutureEventList() { } + + public FutureEventList(Sandbox sandbox) : base(EventComparer.Instance) + { + _sandbox = sandbox ?? throw new ArgumentNullException(nameof(sandbox)); + } + + public Event Add(Action action, TimeSpan scheduledTime, string? tag) + { + var e = new Event(_sandbox, EventIndexer.GenerateIndex(), action, scheduledTime, tag); + base.Add(e); + return e; + } + + public void Reset() + { + base.Clear(); + EventIndexer.Reset(); + } +} diff --git a/O2DESNet/O2DESNet.csproj b/O2DESNet/O2DESNet.csproj index 3b0651c..8497c66 100644 --- a/O2DESNet/O2DESNet.csproj +++ b/O2DESNet/O2DESNet.csproj @@ -1,11 +1,17 @@  - 1.0.0.0 - $(BUILD_BUILDNUMBER) netstandard2.1 - 9.0 + O2DESNet + O2DESNet + latest + disable + enable true + AnyCPU;x64 + + + Li Haobin A framework for Object-Oriented Discrete Event Simulation ISEM Department, National University of Singapore @@ -18,9 +24,50 @@ O2DES.Net Discrete-Event Simulation + + 4.0.0.0 + $(BUILD_BUILDNUMBER) + + + + + bin\ + false + false + false + + + + + en-US + + false + + + + en-US + + + + + + + + + + + + + + + + + + + + - - + diff --git a/O2DESNet/PhaseTracker.cs b/O2DESNet/PhaseTracker.cs index 420f182..dea27a6 100644 --- a/O2DESNet/PhaseTracker.cs +++ b/O2DESNet/PhaseTracker.cs @@ -2,70 +2,76 @@ using System.Collections.Generic; using System.Linq; -namespace O2DESNet +namespace O2DESNet; + +public class PhaseTracer { - public class PhaseTracer + private TimeSpan _initialTime; + private int _lastPhaseIndex; + private Dictionary _indices = []; + private int GetPhaseIndex(string phase) { - private DateTime _initialTime; - private int _lastPhaseIndex; - private Dictionary _indices = new Dictionary(); - private int GetPhaseIndex(string phase) + if (!_indices.ContainsKey(phase)) { - if (!_indices.ContainsKey(phase)) - { - _indices.Add(phase, AllPhases.Count); - AllPhases.Add(phase); - TimeSpans.Add(new TimeSpan()); - } - return _indices[phase]; + _indices.Add(phase, AllPhases.Count); + AllPhases.Add(phase); + TimeSpans.Add(new TimeSpan()); } - public DateTime LastTime { get; private set; } - public List AllPhases { get; private set; } = new List(); - public string LastPhase - { - get { return AllPhases[_lastPhaseIndex]; } - private set { _lastPhaseIndex = GetPhaseIndex(value); } - } + return _indices[phase]; + } - public List> History { get; private set; } = new List>(); - public bool HistoryOn { get; private set; } - /// - /// TimeSpans at all phases - /// - public List TimeSpans { get; private set; } = new List(); - public PhaseTracer(string initPhase, DateTime? initialTime = null, bool historyOn = false) - { - if (initialTime == null) initialTime = DateTime.MinValue; - _initialTime = initialTime.Value; - LastTime = _initialTime; - LastPhase = initPhase; - HistoryOn = historyOn; - if (HistoryOn) History = new List> { new Tuple(LastTime, _lastPhaseIndex) }; - } - public void UpdPhase(string phase, DateTime clockTime) - { - var duration = clockTime - LastTime; - TimeSpans[_lastPhaseIndex] += duration; - if (HistoryOn) History.Add(new Tuple(clockTime, GetPhaseIndex(phase))); - LastPhase = phase; - LastTime = clockTime; - } - public void WarmedUp(DateTime clockTime) - { - _initialTime = clockTime; - LastTime = clockTime; - if (HistoryOn) History = new List> { new Tuple(clockTime, _lastPhaseIndex) }; - TimeSpans = TimeSpans.Select(ts => new TimeSpan()).ToList(); - } - public double GetProportion(string phase, DateTime clockTime) - { - if (!_indices.ContainsKey(phase)) return 0; - double timespan; - timespan = TimeSpans[_indices[phase]].TotalHours; - if (phase.Equals(LastPhase)) timespan += (clockTime - LastTime).TotalHours; - double sum = (clockTime - _initialTime).TotalHours; - return timespan / sum; - } + public TimeSpan LastTime { get; private set; } + public List AllPhases { get; private set; } = []; + public string LastPhase + { + get { return AllPhases[_lastPhaseIndex]; } + private set { _lastPhaseIndex = GetPhaseIndex(value); } + } + + public List> History { get; private set; } = []; + public bool HistoryOn { get; private set; } + /// + /// TimeSpans at all phases + /// + public List TimeSpans { get; private set; } = []; + public PhaseTracer(string initPhase, TimeSpan? initialTime = null, bool historyOn = false) + { + if (initialTime == null) + initialTime = TimeSpan.Zero; + _initialTime = initialTime.Value; + LastTime = _initialTime; + LastPhase = initPhase; + HistoryOn = historyOn; + if (HistoryOn) + History = [new Tuple(LastTime, _lastPhaseIndex)]; + } + public void UpdPhase(string phase, TimeSpan clockTime) + { + var duration = clockTime - LastTime; + TimeSpans[_lastPhaseIndex] += duration; + if (HistoryOn) + History.Add(new Tuple(clockTime, GetPhaseIndex(phase))); + LastPhase = phase; + LastTime = clockTime; + } + public void WarmedUp(TimeSpan clockTime) + { + _initialTime = clockTime; + LastTime = clockTime; + if (HistoryOn) + History = [new Tuple(clockTime, _lastPhaseIndex)]; + TimeSpans = TimeSpans.Select(ts => new TimeSpan()).ToList(); + } + public double GetProportion(string phase, TimeSpan clockTime) + { + if (!_indices.ContainsKey(phase)) + return 0; + double timespan; + timespan = TimeSpans[_indices[phase]].TotalHours; + if (phase.Equals(LastPhase)) + timespan += (clockTime - LastTime).TotalHours; + double sum = (clockTime - _initialTime).TotalHours; + return sum == 0 ? 0 : timespan / sum; } } diff --git a/O2DESNet/Pointer.cs b/O2DESNet/Pointer.cs index f899a4c..7536548 100644 --- a/O2DESNet/Pointer.cs +++ b/O2DESNet/Pointer.cs @@ -1,41 +1,40 @@ using System; -namespace O2DESNet +namespace O2DESNet; + +public struct Pointer { - public struct Pointer + public double X { get; } + public double Y { get; } + public double Angle { get; } + public bool Flipped { get; } + public Pointer(double x = 0, double y = 0, double angle = 0, bool flipped = false) { - public double X { get; } - public double Y { get; } - public double Angle { get; } - public bool Flipped { get; } - public Pointer(double x = 0, double y = 0, double angle = 0, bool flipped = false) - { - X = x; - Y = y; - Angle = angle; - Flipped = flipped; - } + X = x; + Y = y; + Angle = angle; + Flipped = flipped; + } - /// - /// Super position of two pointer - /// - public static Pointer operator *(Pointer inner, Pointer outter) - { - var radius = outter.Angle / 180 * Math.PI; - return new Pointer( - x: inner.X * Math.Cos(radius) - inner.Y * Math.Sin(radius) + outter.X, - y: inner.Y * Math.Cos(radius) + inner.X * Math.Sin(radius) + outter.Y, - angle: (outter.Angle + inner.Angle) % 360, - flipped: outter.Flipped ^ inner.Flipped - ); - } - /// - /// Get the inner pointer - /// - public static Pointer operator /(Pointer product, Pointer outter) - { - return product * new Pointer(x: -outter.X, y: -outter.Y) - * new Pointer(angle: -outter.Angle, flipped: outter.Flipped); - } + /// + /// Super position of two pointer + /// + public static Pointer operator *(Pointer inner, Pointer outter) + { + var radius = outter.Angle / 180 * Math.PI; + return new Pointer( + x: inner.X * Math.Cos(radius) - inner.Y * Math.Sin(radius) + outter.X, + y: inner.Y * Math.Cos(radius) + inner.X * Math.Sin(radius) + outter.Y, + angle: (outter.Angle + inner.Angle) % 360, + flipped: outter.Flipped ^ inner.Flipped + ); + } + /// + /// Get the inner pointer + /// + public static Pointer operator /(Pointer product, Pointer outter) + { + return product * new Pointer(x: -outter.X, y: -outter.Y) + * new Pointer(angle: -outter.Angle, flipped: outter.Flipped); } } \ No newline at end of file diff --git a/O2DESNet/RandomVariables/Categorical/Uniform.cs b/O2DESNet/RandomVariables/Categorical/Uniform.cs index 315d5d1..0c24d0d 100644 --- a/O2DESNet/RandomVariables/Categorical/Uniform.cs +++ b/O2DESNet/RandomVariables/Categorical/Uniform.cs @@ -2,53 +2,53 @@ using System.Collections.Generic; using System.Linq; -namespace O2DESNet.RandomVariables.Categorical +namespace O2DESNet.RandomVariables.Categorical; + +public class Uniform : ICategoricalRandomVariable { - public class Uniform : ICategoricalRandomVariable - { - private readonly string NotSupported - = $"Categorical random variable (mean and standard deviation) for {nameof(Uniform)} not available"; + private readonly string NotSupported + = $"Categorical random variable (mean and standard deviation) for {nameof(Uniform)} not available"; - /// - /// Gets or sets the candidates. - /// - public IEnumerable Candidates { get; set; } + /// + /// Gets or sets the candidates. + /// + public IEnumerable? Candidates { get; set; } - /// - /// Gets or sets the mean. - /// - /// - /// Categorical random variable standard deviation not available for or/> - /// Categorical random variable standard deviation not available for - /// - public double Mean - { - get => throw new NotImplementedException(NotSupported); - set => throw new NotImplementedException(NotSupported); - } + /// + /// Gets or sets the mean. + /// + /// + /// Categorical random variable standard deviation not available for or/> + /// Categorical random variable standard deviation not available for + /// + public double Mean + { + get => throw new NotImplementedException(NotSupported); + set => throw new NotImplementedException(NotSupported); + } - /// - /// Gets or sets the standard deviation. - /// - /// - /// Categorical random variable standard deviation not available for or/> - /// Categorical random variable standard deviation not available for - /// - public double StandardDeviation - { - get => throw new NotImplementedException(NotSupported); - set => throw new NotImplementedException(NotSupported); - } + /// + /// Gets or sets the standard deviation. + /// + /// + /// Categorical random variable standard deviation not available for or/> + /// Categorical random variable standard deviation not available for + /// + public double StandardDeviation + { + get => throw new NotImplementedException(NotSupported); + set => throw new NotImplementedException(NotSupported); + } - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample value as - public T Sample(Random rs) - { - if (Candidates.Count() == 0) return default; - return Candidates.ElementAt(rs.Next(Candidates.Count())); - } + /// + /// Samples the specified random generator. + /// + /// The random generator. + /// Sample value as + public T? Sample(Random rs) + { + if (Candidates.Count() == 0) + return default; + return Candidates.ElementAt(rs.Next(Candidates.Count())); } } diff --git a/O2DESNet/RandomVariables/Continuous/Beta.cs b/O2DESNet/RandomVariables/Continuous/Beta.cs index 1788fea..6cd4d84 100644 --- a/O2DESNet/RandomVariables/Continuous/Beta.cs +++ b/O2DESNet/RandomVariables/Continuous/Beta.cs @@ -1,176 +1,174 @@ using System; -using O2DESNet.RandomVariables.Categorical; +namespace O2DESNet.RandomVariables.Continuous; -namespace O2DESNet.RandomVariables.Continuous +public class Beta : IContinuousRandomVariable { - public class Beta : IContinuousRandomVariable + private double mean = 0.5d; + private double cv = Math.Sqrt(3d) / 3d; // value: 0.57735026918962573105... + private double std = Math.Sqrt(1d / 12d); // value: 0.28867513459481286552... + private double alpha = 1d; + private double beta = 1d; + + /// + /// Gets or sets the mean value. + /// + /// + /// None positive mean value not applicable for beta distribution + /// or + /// Mean value of beta distribution should not exceed 1 (one) + /// + /// + /// The setting of mean and standard deviation will derive illegal alpha value + /// + public double Mean { - private double mean = 0.5d; - private double cv = Math.Sqrt(3d) / 3d; // value: 0.57735026918962573105... - private double std = Math.Sqrt(1d / 12d); // value: 0.28867513459481286552... - private double alpha = 1d; - private double beta = 1d; - - /// - /// Gets or sets the mean value. - /// - /// - /// None positive mean value not applicable for beta distribution - /// or - /// Mean value of beta distribution should not exceed 1 (one) - /// - /// - /// The setting of mean and standard deviation will derive illegal alpha value - /// - public double Mean + get { - get - { - return mean; - } - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException("None positive mean value not applicable for beta distribution"); - - if (value > 1) - throw new ArgumentOutOfRangeException("Mean value of beta distribution should not exceed 1 (one)"); - - mean = value; - cv = std / mean; - var alphaTemp = mean * mean * (1d - mean) / std / std - mean; - var betaTemp = (1d - mean) * (1d - mean) * mean / std / std + mean - 1d; - - if (alphaTemp > 0d) - alpha = alphaTemp; - else - throw new ArgumentException("The setting of mean and standard deviation will derive illegal alpha value"); - - if (betaTemp > 0d) - beta = betaTemp; - else - throw new ArgumentException("The setting of mean and standard deviation will derive illegal alpha value"); - } + return mean; } - - /// - /// Gets or sets the standard deviation value. - /// - /// - /// Negative standard deviation not applicable - /// - /// - /// The setting of mean and standard deviation will derive illegal alpha value - /// - public double StandardDeviation + set { - get - { - return std; - } - set - { - if (value < 0d) - throw new ArgumentOutOfRangeException("Negative standard deviation not applicable"); - - std = value; - cv = std / mean; - - var alphaTemp = mean * mean * (1d - mean) / std / std - mean; - var betaTemp = (1 - mean) * (1d - mean) * mean / std / std + mean - 1d; - - if (alphaTemp > 0d) - alpha = alphaTemp; - else - throw new ArgumentException("The setting of mean and standard deviation will derive illegal alpha value"); - - if (betaTemp > 0d) - beta = betaTemp; - else - throw new ArgumentException("The setting of mean and standard deviation will derive illegal alpha value"); - } + if (value <= 0) + throw new ArgumentOutOfRangeException("None positive mean value not applicable for beta distribution"); + + if (value > 1) + throw new ArgumentOutOfRangeException("Mean value of beta distribution should not exceed 1 (one)"); + + mean = value; + cv = std / mean; + var alphaTemp = mean * mean * (1d - mean) / std / std - mean; + var betaTemp = (1d - mean) * (1d - mean) * mean / std / std + mean - 1d; + + if (alphaTemp > 0d) + alpha = alphaTemp; + else + throw new ArgumentException("The setting of mean and standard deviation will derive illegal alpha value"); + + if (betaTemp > 0d) + beta = betaTemp; + else + throw new ArgumentException("The setting of mean and standard deviation will derive illegal alpha value"); } + } - /// - /// Gets or sets the Coefficient of Variation. - /// - /// - /// Negative coefficient variation not applicable - /// - public double CV + /// + /// Gets or sets the standard deviation value. + /// + /// + /// Negative standard deviation not applicable + /// + /// + /// The setting of mean and standard deviation will derive illegal alpha value + /// + public double StandardDeviation + { + get { - get - { - return cv; - } - set - { - if (value < 0d) - throw new ArgumentOutOfRangeException("Negative coefficient variation not applicable"); - - cv = value; - std = cv * mean; - } + return std; + } + set + { + if (value < 0d) + throw new ArgumentOutOfRangeException("Negative standard deviation not applicable"); + + std = value; + cv = std / mean; + + var alphaTemp = mean * mean * (1d - mean) / std / std - mean; + var betaTemp = (1 - mean) * (1d - mean) * mean / std / std + mean - 1d; + + if (alphaTemp > 0d) + alpha = alphaTemp; + else + throw new ArgumentException("The setting of mean and standard deviation will derive illegal alpha value"); + + if (betaTemp > 0d) + beta = betaTemp; + else + throw new ArgumentException("The setting of mean and standard deviation will derive illegal alpha value"); } + } - /// - /// Gets or sets the alpha value. - /// - /// - /// A (-) negative or (0) zero alpha value not applicable - /// - public double AlphaValue + /// + /// Gets or sets the Coefficient of Variation. + /// + /// + /// Negative coefficient variation not applicable + /// + public double CV + { + get { - get - { - return alpha; - } - set - { - if (value <= 0d) - throw new ArgumentOutOfRangeException("A negative or zero alpha value not applicable"); - - alpha = value; - mean = alpha / (alpha + beta); - std = Math.Sqrt(alpha * beta / (alpha + beta) * (alpha + beta) / (alpha + beta + 1)); - cv = std / mean; - } + return cv; } + set + { + if (value < 0d) + throw new ArgumentOutOfRangeException("Negative coefficient variation not applicable"); - /// - /// Gets or sets the beta value. - /// - /// - /// negative or zero beta value not applicable - /// - public double BetaValue + cv = value; + std = cv * mean; + } + } + + /// + /// Gets or sets the alpha value. + /// + /// + /// A (-) negative or (0) zero alpha value not applicable + /// + public double AlphaValue + { + get { - get - { - return beta; - } - set - { - if (value <= 0d) - throw new ArgumentOutOfRangeException("negative or zero beta value not applicable"); - - beta = value; - mean = alpha / (alpha + beta); - std = Math.Sqrt(alpha * beta / (alpha + beta) * (alpha + beta) / (alpha + beta + 1)); - cv = std / mean; - } + return alpha; } + set + { + if (value <= 0d) + throw new ArgumentOutOfRangeException("A negative or zero alpha value not applicable"); - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample Value - public double Sample(Random rs) + alpha = value; + mean = alpha / (alpha + beta); + std = Math.Sqrt(alpha * beta / (alpha + beta) * (alpha + beta) / (alpha + beta + 1)); + cv = std / mean; + } + } + + /// + /// Gets or sets the beta value. + /// + /// + /// negative or zero beta value not applicable + /// + public double BetaValue + { + get { - if (cv == 0d) return Mean; - return MathNet.Numerics.Distributions.Beta.Sample(rs, AlphaValue, BetaValue); + return beta; } + set + { + if (value <= 0d) + throw new ArgumentOutOfRangeException("negative or zero beta value not applicable"); + + beta = value; + mean = alpha / (alpha + beta); + std = Math.Sqrt(alpha * beta / (alpha + beta) * (alpha + beta) / (alpha + beta + 1)); + cv = std / mean; + } + } + + /// + /// Samples the specified random generator. + /// + /// The random generator. + /// Sample Value + public double Sample(Random rs) + { + if (cv == 0d) + return Mean; + return MathNet.Numerics.Distributions.Beta.Sample(rs, AlphaValue, BetaValue); } } diff --git a/O2DESNet/RandomVariables/Continuous/Exponential.cs b/O2DESNet/RandomVariables/Continuous/Exponential.cs index a970d59..d6fc3a6 100644 --- a/O2DESNet/RandomVariables/Continuous/Exponential.cs +++ b/O2DESNet/RandomVariables/Continuous/Exponential.cs @@ -1,84 +1,83 @@ using System; -namespace O2DESNet.RandomVariables.Continuous +namespace O2DESNet.RandomVariables.Continuous; + +public class Exponential : IContinuousRandomVariable { - public class Exponential : IContinuousRandomVariable - { - private double lambda = 1d; - private double mean = 1d; - private double std = 1d; + private double lambda = 1d; + private double mean = 1d; + private double std = 1d; - /// - /// Gets or sets the lambda. - /// - /// - /// negative or zero arrival rate not applicable - /// - public double Lambda + /// + /// Gets or sets the lambda. + /// + /// + /// negative or zero arrival rate not applicable + /// + public double Lambda + { + get { - get - { - return lambda; - } - set - { - if (value <= 0d) - throw new ArgumentOutOfRangeException("negative or zero arrival rate not applicable"); - - lambda = value; - mean = 1d / lambda; - std = mean; - } + return lambda; } - - /// - /// Gets or sets the mean value. - /// - /// - /// negative or zero mean value not applicable - /// - public double Mean + set { - get { return mean; } - set - { - if (value <= 0d) - throw new ArgumentOutOfRangeException("negative or zero mean value not applicable"); + if (value <= 0d) + throw new ArgumentOutOfRangeException("negative or zero arrival rate not applicable"); - mean = value; - std = mean; - lambda = 1d / mean; - } + lambda = value; + mean = 1d / lambda; + std = mean; } + } - /// - /// Gets or sets the standard deviation value. - /// - /// - /// negative or zero standard deviation not applicable - /// - public double StandardDeviation + /// + /// Gets or sets the mean value. + /// + /// + /// negative or zero mean value not applicable + /// + public double Mean + { + get { return mean; } + set { - get { return std; } - set - { - if (value <= 0d) - throw new ArgumentOutOfRangeException("negative or zero standard deviation not applicable"); + if (value <= 0d) + throw new ArgumentOutOfRangeException("negative or zero mean value not applicable"); - std = value; - mean = std; - lambda = 1d / mean; - } + mean = value; + std = mean; + lambda = 1d / mean; } + } - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample value - public double Sample(Random rs) + /// + /// Gets or sets the standard deviation value. + /// + /// + /// negative or zero standard deviation not applicable + /// + public double StandardDeviation + { + get { return std; } + set { - return MathNet.Numerics.Distributions.Exponential.Sample(rs, Lambda); + if (value <= 0d) + throw new ArgumentOutOfRangeException("negative or zero standard deviation not applicable"); + + std = value; + mean = std; + lambda = 1d / mean; } } + + /// + /// Samples the specified random generator. + /// + /// The random generator. + /// Sample value + public double Sample(Random rs) + { + return MathNet.Numerics.Distributions.Exponential.Sample(rs, Lambda); + } } diff --git a/O2DESNet/RandomVariables/Continuous/Gamma.cs b/O2DESNet/RandomVariables/Continuous/Gamma.cs index ccbc19f..742aeb2 100644 --- a/O2DESNet/RandomVariables/Continuous/Gamma.cs +++ b/O2DESNet/RandomVariables/Continuous/Gamma.cs @@ -1,145 +1,146 @@ using System; -namespace O2DESNet.RandomVariables.Continuous +namespace O2DESNet.RandomVariables.Continuous; + +public class Gamma : IContinuousRandomVariable { - public class Gamma : IContinuousRandomVariable - { - private double mean = 1; - private double std = 1; - private double cv = 1; - private double alpha = 1; - private double beta = 1; + private double mean = 1; + private double std = 1; + private double cv = 1; + private double alpha = 1; + private double beta = 1; - /// - /// Gets or sets the mean value. - /// - /// - /// None positive mean value not applicable for beta distribution - /// - public double Mean + /// + /// Gets or sets the mean value. + /// + /// + /// None positive mean value not applicable for beta distribution + /// + public double Mean + { + get { - get - { - return mean; - } - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException("None positive mean value not applicable for beta distribution"); - - mean = value; - cv = std / mean; - alpha = mean * mean / std / std; - beta = mean / std / std; - } + return mean; } - - /// - /// Gets or sets the standard deviation value. - /// - /// - /// A negative standard deviation not applicable - /// - public double StandardDeviation + set { - get - { - return std; - } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException("A negative standard deviation not applicable"); + if (value <= 0) + throw new ArgumentOutOfRangeException("None positive mean value not applicable for beta distribution"); - std = value; - cv = std / mean; - alpha = mean * mean / std / std; - beta = mean / std / std; - } + mean = value; + cv = std / mean; + alpha = mean * mean / std / std; + beta = mean / std / std; } + } - /// - /// Coefficient of Variation [CV = σ/μ] - /// - /// - /// A negative coefficient variation not applicable - /// - private double CV + /// + /// Gets or sets the standard deviation value. + /// + /// + /// A negative standard deviation not applicable + /// + public double StandardDeviation + { + get { - get - { - return cv; - } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException("A negative coefficient variation not applicable"); + return std; + } + set + { + if (value < 0) + throw new ArgumentOutOfRangeException("A negative standard deviation not applicable"); - cv = value; - std = cv * mean; - alpha = 1 / cv / cv; - beta = mean / std / std; - } + std = value; + cv = std / mean; + alpha = mean * mean / std / std; + beta = mean / std / std; } + } - /// - /// Gets or sets the alpha. - /// Shape of Gamma distribution, refer to - /// - /// A negative or zero alpha value not applicable - public double Alpha + /// + /// Coefficient of Variation [CV = σ/μ] + /// + /// + /// A negative coefficient variation not applicable + /// + private double CV + { + get + { + return cv; + } + set { - get - { - return alpha; - } - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException("A negative or zero alpha value not applicable"); + if (value < 0) + throw new ArgumentOutOfRangeException("A negative coefficient variation not applicable"); - alpha = value; - mean = alpha / beta; - std = Math.Sqrt(alpha / beta / beta); - cv = std / mean; - } + cv = value; + std = cv * mean; + alpha = 1 / cv / cv; + beta = mean / std / std; } + } - /// - /// Gets or sets the beta. - /// Rate of Gamma distribution, refer to - /// - /// - /// A negative or zero beta value not applicable - /// - public double Beta + /// + /// Gets or sets the alpha. + /// Shape of Gamma distribution, refer to + /// + /// A negative or zero alpha value not applicable + public double Alpha + { + get + { + return alpha; + } + set { - get - { - return beta; - } - set - { - if (value <= 0) - throw new ArgumentOutOfRangeException("A negative or zero beta value not applicable"); + if (value <= 0) + throw new ArgumentOutOfRangeException("A negative or zero alpha value not applicable"); - beta = value; - mean = alpha / beta; - std = Math.Sqrt(alpha / beta / beta); - cv = std / mean; - } + alpha = value; + mean = alpha / beta; + std = Math.Sqrt(alpha / beta / beta); + cv = std / mean; } + } - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample value - public double Sample(Random rs) + /// + /// Gets or sets the beta. + /// Rate of Gamma distribution, refer to + /// + /// + /// A negative or zero beta value not applicable + /// + public double Beta + { + get { - if (Mean == 0) return 0; - if (CV == 0) return Mean; - return MathNet.Numerics.Distributions.Gamma.Sample(rs, Alpha, Beta); + return beta; } + set + { + if (value <= 0) + throw new ArgumentOutOfRangeException("A negative or zero beta value not applicable"); + + beta = value; + mean = alpha / beta; + std = Math.Sqrt(alpha / beta / beta); + cv = std / mean; + } + } + + /// + /// Samples the specified random generator. + /// + /// The random generator. + /// Sample value + public double Sample(Random rs) + { + if (Mean == 0) + return 0; + if (CV == 0) + return Mean; + return MathNet.Numerics.Distributions.Gamma.Sample(rs, Alpha, Beta); } } diff --git a/O2DESNet/RandomVariables/Continuous/LogNormal.cs b/O2DESNet/RandomVariables/Continuous/LogNormal.cs index b383a3a..45229d6 100644 --- a/O2DESNet/RandomVariables/Continuous/LogNormal.cs +++ b/O2DESNet/RandomVariables/Continuous/LogNormal.cs @@ -1,143 +1,141 @@ using System; -namespace O2DESNet.RandomVariables.Continuous +namespace O2DESNet.RandomVariables.Continuous; + +public class LogNormal : IContinuousRandomVariable { - public class LogNormal : IContinuousRandomVariable - { - private double mean = Math.Exp(0.5d); // value: 1.6487212707001281941643355821... - private double std = Math.Sqrt(Math.E * Math.E - Math.E); // value: 2.1611974158950877367146858887... - private double cv = Math.Sqrt(Math.E - 1); // value: 1.3108324944320861593638483100... - private double mu = 0d; - private double sigma = 1d; + private double mean = Math.Exp(0.5d); // value: 1.6487212707001281941643355821... + private double std = Math.Sqrt(Math.E * Math.E - Math.E); // value: 2.1611974158950877367146858887... + private double cv = Math.Sqrt(Math.E - 1); // value: 1.3108324944320861593638483100... + private double mu = 0d; + private double sigma = 1d; - /// - /// Gets or sets the mean value. - /// Expectation of LogNormal random variable. - /// - /// - /// None positive mean value is not applicable for beta distribution - /// - public double Mean + /// + /// Gets or sets the mean value. + /// Expectation of LogNormal random variable. + /// + /// + /// None positive mean value is not applicable for beta distribution + /// + public double Mean + { + get + { + return mean; + } + set { - get - { - return mean; - } - set - { - if (value <= 0d) - throw new ArgumentOutOfRangeException("None positive mean value is not applicable for beta distribution"); + if (value <= 0d) + throw new ArgumentOutOfRangeException("None positive mean value is not applicable for beta distribution"); - mean = value; - mu = Math.Log(mean) - 0.5d * Math.Log(1d + std * std / mean / mean); - sigma = Math.Sqrt(Math.Log(1d + std * std / mean / mean)); + mean = value; + mu = Math.Log(mean) - 0.5d * Math.Log(1d + std * std / mean / mean); + sigma = Math.Sqrt(Math.Log(1d + std * std / mean / mean)); - if (value == 0d) - cv = double.MaxValue; - else - cv = std / mean; - } + if (value == 0d) + cv = double.MaxValue; + else + cv = std / mean; } + } - /// - /// Gets or sets the standard deviation value. - /// - /// - /// A negative standard deviation is not applicable - /// - public double StandardDeviation + /// + /// Gets or sets the standard deviation value. + /// + /// + /// A negative standard deviation is not applicable + /// + public double StandardDeviation + { + get { - get - { - return std; - } - set - { - if (value < 0d) - throw new ArgumentOutOfRangeException("A negative standard deviation is not applicable"); + return std; + } + set + { + if (value < 0d) + throw new ArgumentOutOfRangeException("A negative standard deviation is not applicable"); - std = value; - mu = Math.Log(mean) - 0.5d * Math.Log(1d + std * std / mean / mean); - sigma = Math.Sqrt(Math.Log(1d + std * std / mean / mean)); + std = value; + mu = Math.Log(mean) - 0.5d * Math.Log(1d + std * std / mean / mean); + sigma = Math.Sqrt(Math.Log(1d + std * std / mean / mean)); - if (mean != 0d) - cv = std / mean; - } + if (mean != 0d) + cv = std / mean; } + } - /// - /// Gets or sets the Coefficient of Variation. - /// - /// - /// A negative coefficient of variation is not applicable for log normal distribution - /// - public double CV + /// + /// Gets or sets the Coefficient of Variation. + /// + /// + /// A negative coefficient of variation is not applicable for log normal distribution + /// + public double CV + { + get + { + return cv; + } + set { - get - { - return cv; - } - set - { - if (value < 0d) - throw new ArgumentOutOfRangeException("A negative coefficient of variation is not applicable for log normal distribution"); + if (value < 0d) + throw new ArgumentOutOfRangeException("A negative coefficient of variation is not applicable for log normal distribution"); - cv = value; - std = cv * mean; - } + cv = value; + std = cv * mean; } + } - /// - /// The log-scale(mu) of the distribution - /// - public double Mu + /// + /// The log-scale(mu) of the distribution + /// + public double Mu + { + get { - get - { - return mu; - } - set - { - mu = value; - mean = Math.Exp(mu + sigma * sigma / 2d); - std = Math.Sqrt((Math.Exp(sigma * sigma) - 1) * Math.Exp(2d * mu + sigma * sigma)); - cv = std / mean; - } + return mu; } - - - /// - /// Gets or sets the sigma. - /// - /// - /// A negative shape parameter is not applicable - /// - public double Sigma + set { - get - { - return sigma; - } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException("A negative shape parameter is not applicable"); - - sigma = value; - mean = Math.Exp(mu + sigma * sigma / 2d); - std = Math.Sqrt((Math.Exp(sigma * sigma) - 1d) * Math.Exp(2d * mu + sigma * sigma)); - cv = std / mean; - } + mu = value; + mean = Math.Exp(mu + sigma * sigma / 2d); + std = Math.Sqrt((Math.Exp(sigma * sigma) - 1) * Math.Exp(2d * mu + sigma * sigma)); + cv = std / mean; } + } - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample value - public double Sample(Random rs) + /// + /// Gets or sets the sigma. + /// + /// + /// A negative shape parameter is not applicable + /// + public double Sigma + { + get + { + return sigma; + } + set { - return MathNet.Numerics.Distributions.LogNormal.Sample(rs, Mu, Sigma); + if (value < 0) + throw new ArgumentOutOfRangeException("A negative shape parameter is not applicable"); + + sigma = value; + mean = Math.Exp(mu + sigma * sigma / 2d); + std = Math.Sqrt((Math.Exp(sigma * sigma) - 1d) * Math.Exp(2d * mu + sigma * sigma)); + cv = std / mean; } } + + /// + /// Samples the specified random generator. + /// + /// The random generator. + /// Sample value + public double Sample(Random rs) + { + return MathNet.Numerics.Distributions.LogNormal.Sample(rs, Mu, Sigma); + } } diff --git a/O2DESNet/RandomVariables/Continuous/Normal.cs b/O2DESNet/RandomVariables/Continuous/Normal.cs index 3b53247..3447a94 100644 --- a/O2DESNet/RandomVariables/Continuous/Normal.cs +++ b/O2DESNet/RandomVariables/Continuous/Normal.cs @@ -1,87 +1,86 @@ using System; -namespace O2DESNet.RandomVariables.Continuous +namespace O2DESNet.RandomVariables.Continuous; + +public class Normal : IContinuousRandomVariable { - public class Normal : IContinuousRandomVariable - { - private double mean = 1d; - private double std = 1d; - private double cv = 1d; + private double mean = 1d; + private double std = 1d; + private double cv = 1d; - /// - /// Gets or sets the mean value. - /// - public double Mean + /// + /// Gets or sets the mean value. + /// + public double Mean + { + get { - get - { - return mean; - } - set - { - mean = value; - - if (value == 0d) - cv = double.MaxValue; - else - cv = std / Math.Abs(mean); - } + return mean; } - /// - /// standard deviation - /// - /// - /// A negative standard deviation not applicable - /// - public double StandardDeviation + set { - get - { - return std; - } - set - { - if (value < 0d) - throw new Exception("A negative standard deviation is not applicable"); + mean = value; - std = value; - if (mean != 0d) - cv = std / Math.Abs(mean); - } + if (value == 0d) + cv = double.MaxValue; + else + cv = std / Math.Abs(mean); } - - /// - /// Gets or sets the Coefficient of Variation. - /// - /// - /// A negative coefficient of variation is not applicable - /// - public double CV + } + /// + /// standard deviation + /// + /// + /// A negative standard deviation not applicable + /// + public double StandardDeviation + { + get + { + return std; + } + set { - get - { - return cv; - } - set - { - if (value < 0d) - throw new Exception("A negative coefficient of variation is not applicable"); + if (value < 0d) + throw new Exception("A negative standard deviation is not applicable"); - cv = value; - std = cv * Math.Abs(mean); - } + std = value; + if (mean != 0d) + cv = std / Math.Abs(mean); } + } - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample value - public double Sample(Random rs) + /// + /// Gets or sets the Coefficient of Variation. + /// + /// + /// A negative coefficient of variation is not applicable + /// + public double CV + { + get + { + return cv; + } + set { - if (cv == 0d) return mean; - return MathNet.Numerics.Distributions.Normal.Sample(rs, mean, std); + if (value < 0d) + throw new Exception("A negative coefficient of variation is not applicable"); + + cv = value; + std = cv * Math.Abs(mean); } - + } + + /// + /// Samples the specified random generator. + /// + /// The random generator. + /// Sample value + public double Sample(Random rs) + { + if (cv == 0d) + return mean; + return MathNet.Numerics.Distributions.Normal.Sample(rs, mean, std); } } diff --git a/O2DESNet/RandomVariables/Continuous/Triangular.cs b/O2DESNet/RandomVariables/Continuous/Triangular.cs index 347de34..e3332b3 100644 --- a/O2DESNet/RandomVariables/Continuous/Triangular.cs +++ b/O2DESNet/RandomVariables/Continuous/Triangular.cs @@ -1,120 +1,124 @@ using System; -namespace O2DESNet.RandomVariables.Continuous +namespace O2DESNet.RandomVariables.Continuous; + +public class Triangular : IContinuousRandomVariable { - public class Triangular : IContinuousRandomVariable - { - private double lowerBound = 0d; - private double upperBound = 1d; - private double mode = 0.5d; - private double mean = 0.5d; - private double std = Math.Sqrt(0.75d / 18d); // value: 0.20412414523193150861501976578438... + private double lowerBound = 0d; + private double upperBound = 1d; + private double mode = 0.5d; + private double mean = 0.5d; + private double std = Math.Sqrt(0.75d / 18d); // value: 0.20412414523193150861501976578438... - /// - /// Gets or sets the lower bound. - /// - public double LowerBound + /// + /// Gets or sets the lower bound. + /// + public double LowerBound + { + get { - get - { - return lowerBound; - } - set - { - if (value > UpperBound) UpperBound = value; - if (value > Mode) Mode = value; - lowerBound = value; - mean = (lowerBound + upperBound + mode) / 3d; - std = Math.Sqrt(((lowerBound) * (lowerBound) + - (upperBound) * (upperBound) + - (mode) * (mode) - - (lowerBound) * (upperBound) - - (lowerBound) * (mode) - - (upperBound) * (mode)) / 18d); - } + return lowerBound; } - - - /// - /// Gets or sets the upper bound. - /// - public double UpperBound + set { - get - { - return upperBound; - } - set - { - if (value < LowerBound) LowerBound = value; - if (value < Mode) Mode = value; - upperBound = value; - mean = (lowerBound + upperBound + mode) / 3d; - std = Math.Sqrt(((lowerBound) * (lowerBound) + - (upperBound) * (upperBound) + - (mode) * (mode) - - (lowerBound) * (upperBound) - - (lowerBound) * (mode) - - (upperBound) * (mode)) / 18d); - } + if (value > UpperBound) + UpperBound = value; + if (value > Mode) + Mode = value; + lowerBound = value; + mean = (lowerBound + upperBound + mode) / 3d; + std = Math.Sqrt(((lowerBound) * (lowerBound) + + (upperBound) * (upperBound) + + (mode) * (mode) - + (lowerBound) * (upperBound) - + (lowerBound) * (mode) - + (upperBound) * (mode)) / 18d); } + } - /// - /// Gets or sets the mode of the triangle distribution - /// - public double Mode + /// + /// Gets or sets the upper bound. + /// + public double UpperBound + { + get { - get - { - return mode; - } - set - { - if (value < LowerBound) LowerBound = value; - if (value > UpperBound) UpperBound = value; - mode = value; - mean = (lowerBound + upperBound + mode) / 3d; - std = Math.Sqrt(((lowerBound) * (lowerBound) + - (upperBound) * (upperBound) + - (mode) * (mode) - - (lowerBound) * (upperBound) - - (lowerBound) * (mode) - - (upperBound) * (mode)) / 18d); - } + return upperBound; } - - /// - /// Gets or sets the mean value. - /// - /// - /// Users not allowed to define triangular random variable by setting mean value - /// - public double Mean + set { - get => mean; - set => throw new ArgumentException("Users not allowed to define triangular random variable by setting mean value"); + if (value < LowerBound) + LowerBound = value; + if (value < Mode) + Mode = value; + upperBound = value; + mean = (lowerBound + upperBound + mode) / 3d; + std = Math.Sqrt(((lowerBound) * (lowerBound) + + (upperBound) * (upperBound) + + (mode) * (mode) - + (lowerBound) * (upperBound) - + (lowerBound) * (mode) - + (upperBound) * (mode)) / 18d); } + } - /// - /// Gets or sets the standard deviation value. - /// - /// - /// Users not allowed to define triangular random variable by setting standard deviation value - /// - public double StandardDeviation + /// + /// Gets or sets the mode of the triangle distribution + /// + public double Mode + { + get { - get => std; - set => throw new ArgumentException("Users not allowed to define triangular random variable by setting standard deviation value"); + return mode; } - - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample value - public double Sample(Random rs) + set { - return MathNet.Numerics.Distributions.Triangular.Sample(rs, LowerBound, UpperBound, Mode); + if (value < LowerBound) + LowerBound = value; + if (value > UpperBound) + UpperBound = value; + mode = value; + mean = (lowerBound + upperBound + mode) / 3d; + std = Math.Sqrt(((lowerBound) * (lowerBound) + + (upperBound) * (upperBound) + + (mode) * (mode) - + (lowerBound) * (upperBound) - + (lowerBound) * (mode) - + (upperBound) * (mode)) / 18d); } } + + /// + /// Gets or sets the mean value. + /// + /// + /// Users not allowed to define triangular random variable by setting mean value + /// + public double Mean + { + get => mean; + set => throw new ArgumentException("Users not allowed to define triangular random variable by setting mean value"); + } + + /// + /// Gets or sets the standard deviation value. + /// + /// + /// Users not allowed to define triangular random variable by setting standard deviation value + /// + public double StandardDeviation + { + get => std; + set => throw new ArgumentException("Users not allowed to define triangular random variable by setting standard deviation value"); + } + + /// + /// Samples the specified random generator. + /// + /// The random generator. + /// Sample value + public double Sample(Random rs) + { + return MathNet.Numerics.Distributions.Triangular.Sample(rs, LowerBound, UpperBound, Mode); + } } diff --git a/O2DESNet/RandomVariables/Continuous/Uniform.cs b/O2DESNet/RandomVariables/Continuous/Uniform.cs index 5d10780..defce29 100644 --- a/O2DESNet/RandomVariables/Continuous/Uniform.cs +++ b/O2DESNet/RandomVariables/Continuous/Uniform.cs @@ -1,82 +1,83 @@ using System; -namespace O2DESNet.RandomVariables.Continuous +namespace O2DESNet.RandomVariables.Continuous; + +public class Uniform : IContinuousRandomVariable { - public class Uniform : IContinuousRandomVariable - { - private double lowerBound = 0d; - private double upperBound = 1d; - private double mean = 0.5d; - private double std = 0.5d; + private double lowerBound = 0d; + private double upperBound = 1d; + private double mean = 0.5d; + private double std = 0.5d; - /// - /// Gets or sets the lower bound. - /// - public double LowerBound + /// + /// Gets or sets the lower bound. + /// + public double LowerBound + { + get { - get - { - return lowerBound; - } - set - { - if (value > UpperBound) UpperBound = value; - lowerBound = value; - mean = (lowerBound + upperBound) / 2d; - std = (upperBound - lowerBound) * (upperBound - lowerBound) / 12d; - } + return lowerBound; } - - /// - /// Gets or sets the upper bound. - /// - public double UpperBound + set { - get - { - return upperBound; - } - set - { - if (value < LowerBound) LowerBound = value; - upperBound = value; - mean = (lowerBound + upperBound) / 2d; - std = (upperBound - lowerBound) * (upperBound - lowerBound) / 12d; - } + if (value > UpperBound) + UpperBound = value; + lowerBound = value; + mean = (lowerBound + upperBound) / 2d; + std = (upperBound - lowerBound) * (upperBound - lowerBound) / 12d; } + } - /// - /// Gets or sets the mean value. - /// - /// - /// Users not allowed to define continuous uniform random variable by setting mean value - /// - public double Mean + /// + /// Gets or sets the upper bound. + /// + public double UpperBound + { + get { - get => mean; - set => throw new ArgumentException("Users not allowed to define continuous uniform random variable by setting mean value"); + return upperBound; } - - /// - /// Gets or sets the standard deviation value. - /// - /// - /// Users not allowed to define continuous uniform random variable by setting standard deviation value - /// - public double StandardDeviation + set { - get => std; - set => throw new ArgumentException("Users not allowed to define continuous uniform random variable by setting standard deviation value"); + if (value < LowerBound) + LowerBound = value; + upperBound = value; + mean = (lowerBound + upperBound) / 2d; + std = (upperBound - lowerBound) * (upperBound - lowerBound) / 12d; } + } - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample value - public double Sample(Random rs) - { - return lowerBound + (upperBound - lowerBound) * rs.NextDouble(); - } + /// + /// Gets or sets the mean value. + /// + /// + /// Users not allowed to define continuous uniform random variable by setting mean value + /// + public double Mean + { + get => mean; + set => throw new ArgumentException("Users not allowed to define continuous uniform random variable by setting mean value"); + } + + /// + /// Gets or sets the standard deviation value. + /// + /// + /// Users not allowed to define continuous uniform random variable by setting standard deviation value + /// + public double StandardDeviation + { + get => std; + set => throw new ArgumentException("Users not allowed to define continuous uniform random variable by setting standard deviation value"); + } + + /// + /// Samples the specified random generator. + /// + /// The random generator. + /// Sample value + public double Sample(Random rs) + { + return lowerBound + (upperBound - lowerBound) * rs.NextDouble(); } } diff --git a/O2DESNet/RandomVariables/Discrete/Poisson.cs b/O2DESNet/RandomVariables/Discrete/Poisson.cs index c84f37f..a1b6201 100644 --- a/O2DESNet/RandomVariables/Discrete/Poisson.cs +++ b/O2DESNet/RandomVariables/Discrete/Poisson.cs @@ -1,81 +1,80 @@ using System; -namespace O2DESNet.RandomVariables.Discrete +namespace O2DESNet.RandomVariables.Discrete; + +public class Poisson : IDiscreteRandomVariable { - public class Poisson : IDiscreteRandomVariable - { - private double _lambda = 1d; - private double _mean = 1d; - private double _std = 1d; + private double _lambda = 1d; + private double _mean = 1d; + private double _std = 1d; - /// - /// Gets or sets the lambda (Arrival Rate). - /// - /// - /// A negative lambda (arrival rate) is not applicable - /// - public double Lambda + /// + /// Gets or sets the lambda (Arrival Rate). + /// + /// + /// A negative lambda (arrival rate) is not applicable + /// + public double Lambda + { + get { return _lambda; } + set { - get { return _lambda; } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException("A negative lambda (arrival rate) is not applicable"); + if (value < 0) + throw new ArgumentOutOfRangeException("A negative lambda (arrival rate) is not applicable"); - _lambda = value; - _mean = value; - _std = Math.Sqrt(value); - } + _lambda = value; + _mean = value; + _std = Math.Sqrt(value); } + } - /// - /// Gets or sets the mean. - /// - /// - /// A negative mean value is not applicable - /// - public double Mean + /// + /// Gets or sets the mean. + /// + /// + /// A negative mean value is not applicable + /// + public double Mean + { + get { return _mean; } + set { - get { return _mean; } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException("A negative mean value is not applicable"); + if (value < 0) + throw new ArgumentOutOfRangeException("A negative mean value is not applicable"); - _mean = value; - _lambda = value; - _std = Math.Sqrt(value); - } + _mean = value; + _lambda = value; + _std = Math.Sqrt(value); } + } - /// - /// Gets or sets the standard deviation. - /// - /// - /// A negative standard deviation is not applicable - /// - public double StandardDeviation + /// + /// Gets or sets the standard deviation. + /// + /// + /// A negative standard deviation is not applicable + /// + public double StandardDeviation + { + get { return _std; } + set { - get { return _std; } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException("A negative standard deviation is not applicable"); + if (value < 0) + throw new ArgumentOutOfRangeException("A negative standard deviation is not applicable"); - _std = value; - _mean = value * value; - _lambda = value * value; - } + _std = value; + _mean = value * value; + _lambda = value * value; } + } - /// - /// Samples the specified rs. - /// - /// The rs. - /// Sample value - public int Sample(Random rs) - { - return MathNet.Numerics.Distributions.Poisson.Sample(rs, Lambda); - } + /// + /// Samples the specified rs. + /// + /// The rs. + /// Sample value + public int Sample(Random rs) + { + return MathNet.Numerics.Distributions.Poisson.Sample(rs, Lambda); } } diff --git a/O2DESNet/RandomVariables/Discrete/Uniform.cs b/O2DESNet/RandomVariables/Discrete/Uniform.cs index d634f07..6382234 100644 --- a/O2DESNet/RandomVariables/Discrete/Uniform.cs +++ b/O2DESNet/RandomVariables/Discrete/Uniform.cs @@ -1,158 +1,160 @@ using System; -namespace O2DESNet.RandomVariables.Discrete +namespace O2DESNet.RandomVariables.Discrete; + +public class Uniform : IDiscreteRandomVariable { - public class Uniform : IDiscreteRandomVariable - { - private int lowerBound = 0; - private int upperBound = 1; - private double mean = 0.5d; - private double std = 0.5d; + private int lowerBound = 0; + private int upperBound = 1; + private double mean = 0.5d; + private double std = 0.5d; - /// - /// Gets or sets the lower bound. - /// - /// - /// Nothing between lower bound and upper bound if IncludeBound property is set to 'false' - /// - public int LowerBound + /// + /// Gets or sets the lower bound. + /// + /// + /// Nothing between lower bound and upper bound if IncludeBound property is set to 'false' + /// + public int LowerBound + { + get { return lowerBound; } + set { - get { return lowerBound; } - set + lowerBound = value; + if (value > UpperBound) { - lowerBound = value; - if (value > UpperBound) + UpperBound = value; + mean = value; + std = 0d; + } + else + { + double tempSquareSum = 0d; + mean = (lowerBound + upperBound) / 2d; + double n = upperBound - lowerBound + 1d; + + if (IncludeBound) { - UpperBound = value; - mean = value; - std = 0d; + for (int i = lowerBound; i <= upperBound; i++) + tempSquareSum += (i - mean) * (i - mean); + std = Math.Sqrt(tempSquareSum / n); } else { - double tempSquareSum = 0d; - mean = (lowerBound + upperBound) / 2d; - double n = upperBound - lowerBound + 1d; + if (upperBound - lowerBound <= 1) + throw new ArgumentOutOfRangeException("Nothing between lower bound and upper bound if IncludeBound property is set to 'false'"); - if (IncludeBound) - { - for (int i = lowerBound; i <= upperBound; i++) - tempSquareSum += (i - mean) * (i - mean); - std = Math.Sqrt(tempSquareSum / n); - } - else - { - if (upperBound - lowerBound <= 1) - throw new ArgumentOutOfRangeException("Nothing between lower bound and upper bound if IncludeBound property is set to 'false'"); - - for (int i = lowerBound+1; i <= upperBound-1; i++) tempSquareSum += (i - mean) * (i - mean); - std = Math.Sqrt(tempSquareSum / n); - } + for (int i = lowerBound + 1; i <= upperBound - 1; i++) + tempSquareSum += (i - mean) * (i - mean); + std = Math.Sqrt(tempSquareSum / n); } } } + } - /// - /// Gets or sets the upper bound. - /// - /// - /// Nothing between lower bound and upper bound if IncludeBound property is set to 'false' - /// - public int UpperBound + /// + /// Gets or sets the upper bound. + /// + /// + /// Nothing between lower bound and upper bound if IncludeBound property is set to 'false' + /// + public int UpperBound + { + get { return upperBound; } + set { - get { return upperBound; } - set + upperBound = value; + if (value < LowerBound) { - upperBound = value; - if (value < LowerBound) - { - LowerBound = value; - mean = value; - std = 0d; + LowerBound = value; + mean = value; + std = 0d; + } + else + { + mean = (lowerBound + upperBound) / 2d; + double tempSquareSum = 0d; + double n = upperBound - lowerBound + 1d; + + if (IncludeBound) + { + + for (int i = lowerBound; i <= upperBound; i++) + tempSquareSum += (i - mean) * (i - mean); + std = Math.Sqrt(tempSquareSum / n); } else { - mean = (lowerBound + upperBound) / 2d; - double tempSquareSum = 0d; - double n = upperBound - lowerBound + 1d; - - if (IncludeBound) - { - - for (int i = lowerBound; i <= upperBound; i++) - tempSquareSum += (i - mean) * (i - mean); - std = Math.Sqrt(tempSquareSum / n); - } - else - { - if (upperBound - lowerBound <= 1) - throw new ArgumentOutOfRangeException("Nothing between lower bound and upper bound if IncludeBound property is set to 'false'"); + if (upperBound - lowerBound <= 1) + throw new ArgumentOutOfRangeException("Nothing between lower bound and upper bound if IncludeBound property is set to 'false'"); - for (int i = lowerBound + 1; i <= upperBound - 1; i++) tempSquareSum += (i - mean) * (i - mean); - std = Math.Sqrt(tempSquareSum / n); - } + for (int i = lowerBound + 1; i <= upperBound - 1; i++) + tempSquareSum += (i - mean) * (i - mean); + std = Math.Sqrt(tempSquareSum / n); } } } + } - /// - /// Gets or sets the mean. - /// - /// - /// Users not allowed to define discrete uniform random variable by setting mean value - /// - public double Mean - { - get => mean; - set => throw new ArgumentException("Users not allowed to define discrete uniform random variable by setting mean value"); - } + /// + /// Gets or sets the mean. + /// + /// + /// Users not allowed to define discrete uniform random variable by setting mean value + /// + public double Mean + { + get => mean; + set => throw new ArgumentException("Users not allowed to define discrete uniform random variable by setting mean value"); + } - /// - /// Gets or sets the standard deviation. - /// - /// - /// Users not allowed to define discrete uniform random variable by setting standard deviation value - /// - public double StandardDeviation - { - get => std; - set => throw new ArgumentException("Users not allowed to define discrete uniform random variable by setting standard deviation value"); - } + /// + /// Gets or sets the standard deviation. + /// + /// + /// Users not allowed to define discrete uniform random variable by setting standard deviation value + /// + public double StandardDeviation + { + get => std; + set => throw new ArgumentException("Users not allowed to define discrete uniform random variable by setting standard deviation value"); + } - /// - /// Gets or sets a value indicating whether [include bound]. - /// - /// - /// true if [include bound]; otherwise, false. - /// - public bool IncludeBound { get; set; } = true; + /// + /// Gets or sets a value indicating whether [include bound]. + /// + /// + /// true if [include bound]; otherwise, false. + /// + public bool IncludeBound { get; set; } = true; - /// - /// Samples the specified rs. - /// - /// The rs. - /// Sample value - public int Sample(Random rs) + /// + /// Samples the specified rs. + /// + /// The rs. + /// Sample value + public int Sample(Random rs) + { + int temp; + if (IncludeBound) { - int temp; - if (IncludeBound) + int dummyLowerBound = LowerBound - 1; + int dummyUpperBound = UpperBound + 1; + temp = dummyLowerBound; + while (temp == dummyLowerBound || temp == dummyUpperBound) { - int dummyLowerBound = LowerBound - 1; - int dummyUpperBound = UpperBound + 1; - temp = dummyLowerBound; - while (temp == dummyLowerBound || temp == dummyUpperBound) - { - temp = Convert.ToInt32(Math.Round(dummyLowerBound + (dummyUpperBound - dummyLowerBound) * rs.NextDouble())); - } + temp = Convert.ToInt32(Math.Round(dummyLowerBound + (dummyUpperBound - dummyLowerBound) * rs.NextDouble())); } - else + } + else + { + temp = LowerBound; + while (temp == LowerBound || temp == UpperBound) { - temp = LowerBound; - while (temp == LowerBound || temp == UpperBound) - { - temp = Convert.ToInt32(Math.Round(LowerBound + (UpperBound - LowerBound) * rs.NextDouble())); - } + temp = Convert.ToInt32(Math.Round(LowerBound + (UpperBound - LowerBound) * rs.NextDouble())); } - return temp; } + + return temp; } } diff --git a/O2DESNet/RandomVariables/IRandomVariable.cs b/O2DESNet/RandomVariables/IRandomVariable.cs index 74eea34..ed58cfa 100644 --- a/O2DESNet/RandomVariables/IRandomVariable.cs +++ b/O2DESNet/RandomVariables/IRandomVariable.cs @@ -1,43 +1,42 @@ using System; -namespace O2DESNet.RandomVariables -{ - /// - /// Categorical Random Variables Interface - /// - public interface ICategoricalRandomVariable : IRandomVariable { } +namespace O2DESNet.RandomVariables; + +/// +/// Categorical Random Variables Interface +/// +public interface ICategoricalRandomVariable : IRandomVariable { } + +/// +/// Continuous Random Variables Interface +/// +public interface IContinuousRandomVariable : IRandomVariable { } +/// +/// Discrete Random Variables Interface +/// +public interface IDiscreteRandomVariable : IRandomVariable { } + +/// +/// Random Variables Interface +/// +/// +public interface IRandomVariable +{ /// - /// Continuous Random Variables Interface + /// Gets or sets the mean value. /// - public interface IContinuousRandomVariable : IRandomVariable { } + double Mean { get; set; } /// - /// Discrete Random Variables Interface + /// Gets or sets the standard deviation value. /// - public interface IDiscreteRandomVariable : IRandomVariable { } + double StandardDeviation { get; set; } /// - /// Random Variables Interface + /// Samples the specified random generator. /// - /// - public interface IRandomVariable - { - /// - /// Gets or sets the mean value. - /// - double Mean { get; set; } - - /// - /// Gets or sets the standard deviation value. - /// - double StandardDeviation { get; set; } - - /// - /// Samples the specified random generator. - /// - /// The random generator. - /// Sample value as - T Sample(Random rs); - } + /// The random generator. + /// Sample value as + T Sample(Random rs); } diff --git a/O2DESNet/ReadOnlyExtensions.cs b/O2DESNet/ReadOnlyExtensions.cs deleted file mode 100644 index fa9ad2b..0000000 --- a/O2DESNet/ReadOnlyExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -namespace O2DESNet -{ - public static class ReadOnlyExtensions - { - public static IReadOnlyList AsReadOnly(this HashSet hashSet) - { - return hashSet.ToList().AsReadOnly(); - } - public static IReadOnlyList AsReadOnly(this ICollection collection, Func asReadOnly) - { - return collection.Select(i => asReadOnly(i)).ToList().AsReadOnly(); - } - public static IReadOnlyDictionary AsReadOnly(this Dictionary dict) - { - return AsReadOnly(dict, i => i); - } - public static IReadOnlyDictionary> AsReadOnly(this Dictionary> dict) - { - return AsReadOnly(dict, list => (IReadOnlyList)list.AsReadOnly()); - } - public static IReadOnlyDictionary> AsReadOnly(this Dictionary> dict) - { - return AsReadOnly(dict, hashSet => (IReadOnlyList)hashSet.ToList().AsReadOnly()); - } - public static IReadOnlyDictionary AsReadOnly(this Dictionary dict, Func asReadOnly) - { - return new ReadOnlyDictionary(dict.ToDictionary(i => i.Key, i => asReadOnly(i.Value))); - } - public static IReadOnlyDictionary AsReadOnly(this Dictionary dict, Func keyAsReadOnly, Func valueAsReadOnly) - { - return new ReadOnlyDictionary(dict.ToDictionary(i => keyAsReadOnly(i.Key), i => valueAsReadOnly(i.Value))); - } - - public static IReadOnlyDictionary ToReadOnlyDictionary - (this IEnumerable enumerable, Func keySelector, Func elementSelector) - { - return enumerable.ToDictionary(keySelector, elementSelector).AsReadOnly(); - } - } -} diff --git a/O2DESNet/Sandbox.cs b/O2DESNet/Sandbox.cs index f6d7d70..b73973b 100644 --- a/O2DESNet/Sandbox.cs +++ b/O2DESNet/Sandbox.cs @@ -1,249 +1,252 @@ -using System; +using Microsoft.Extensions.Logging; + +using O2DESNet.HourCounters; +using O2DESNet.Internals; + +using System; using System.Collections.Generic; -using System.IO; +using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; -namespace O2DESNet +namespace O2DESNet; + +public abstract class Sandbox : Sandbox where TAssets : IAssets { - public interface ISandbox : IDisposable - { - int Index { get; } - string Id { get; } - Pointer Pointer { get; } - int Seed { get; } - ISandbox Parent { get; } - IReadOnlyList Children { get; } - DateTime ClockTime { get; } - DateTime? HeadEventTime { get; } - string LogFile { get; set; } - bool DebugMode { get; set; } - bool Run(); - bool Run(int eventCount); - bool Run(DateTime terminate); - bool Run(TimeSpan duration); - bool Run(double speed); - bool WarmUp(DateTime till); - bool WarmUp(TimeSpan period); - } - - public abstract class Sandbox : Sandbox - where TAssets : IAssets - { - public TAssets Assets { get; private set; } - public Sandbox(TAssets assets, int seed = 0, string id = null, Pointer pointer = new Pointer()) - : base(seed, id, pointer) { Assets = assets; } - } - - public abstract class Sandbox : ISandbox - { - private static int _count = 0; - /// - /// Unique index in sequence for all module instances - /// - public int Index { get; private set; } - /// - /// Tag of the instance of the module - /// - public string Id { get; private set; } - public Pointer Pointer { get; private set; } - protected Random DefaultRS { get; private set; } - private int _seed; - public int Seed { get { return _seed; } set { _seed = value; DefaultRS = new Random(_seed); } } - - #region Future Event List - internal SortedSet FutureEventList = new SortedSet(EventComparer.Instance); - /// - /// Schedule an event to be invoked at the specified clock-time - /// - protected void Schedule(Action action, DateTime clockTime, string tag = null) - { - FutureEventList.Add(new Event(this, action, clockTime, tag)); - } - /// - /// Schedule an event to be invoked after the specified time delay - /// - protected void Schedule(Action action, TimeSpan delay, string tag = null) - { - FutureEventList.Add(new Event(this, action, ClockTime + delay, tag)); - } - /// - /// Schedule an event at the current clock time. - /// - protected void Schedule(Action action, string tag = null) - { - FutureEventList.Add(new Event(this, action, ClockTime, tag)); - } - #endregion - #region Simulation Run Control - internal Event HeadEvent - { - get - { - var headEvent = FutureEventList.FirstOrDefault(); - foreach(Sandbox child in Children_List) - { - var childHeadEvent = child.HeadEvent; - if (headEvent == null || (childHeadEvent != null && - EventComparer.Instance.Compare(childHeadEvent, headEvent) < 0)) - headEvent = childHeadEvent; - } - return headEvent; - } - } - private DateTime _clockTime = DateTime.MinValue; - public DateTime ClockTime - { - get - { - if (Parent == null) return _clockTime; - return Parent.ClockTime; - } - } - public DateTime? HeadEventTime - { - get - { - var head = HeadEvent; - if (head == null) return null; - return head.ScheduledTime; - } - } - public bool Run() - { - if (Parent != null) return Parent.Run(); - var head = HeadEvent; - if (head == null) return false; - head.Owner.FutureEventList.Remove(head); - _clockTime = head.ScheduledTime; - head.Invoke(); - return true; - } - public bool Run(TimeSpan duration) - { - if (Parent != null) return Parent.Run(duration); - return Run(ClockTime.Add(duration)); - } - public bool Run(DateTime terminate) - { - if (Parent != null) return Parent.Run(terminate); - while (true) - { - var head = HeadEvent; - if (HeadEvent != null && HeadEvent.ScheduledTime <= terminate) Run(); - else - { - _clockTime = terminate; - return head != null; /// if the simulation can be continued - } - } - } - public bool Run(int eventCount) - { - if (Parent != null) return Parent.Run(eventCount); - while (eventCount-- > 0) - if (!Run()) return false; - return true; - } - private DateTime? _realTimeForLastRun = null; - public bool Run(double speed) - { - if (Parent != null) return Parent.Run(speed); - var rtn = true; - if (_realTimeForLastRun != null) - rtn = Run(terminate: ClockTime.AddSeconds((DateTime.Now - _realTimeForLastRun.Value).TotalSeconds * speed)); - _realTimeForLastRun = DateTime.Now; - return rtn; - } - #endregion + private readonly TAssets _assets; - #region Children - Sub-modules - public ISandbox Parent { get; private set; } = null; - private readonly List Children_List = new List(); - public IReadOnlyList Children { get { return Children_List.AsReadOnly(); } } - protected TSandbox AddChild(TSandbox child) where TSandbox : Sandbox - { - Children_List.Add(child); - child.Parent = this; - OnWarmedUp += child.OnWarmedUp; - return child; - } - protected IReadOnlyList HourCounters { get { return HourCounters_List.AsReadOnly(); } } - private readonly List HourCounters_List = new List(); - protected HourCounter AddHourCounter(bool keepHistory = false) - { - var hc = new HourCounter(this, keepHistory); - HourCounters_List.Add(hc); - OnWarmedUp += () => hc.WarmedUp(); - return hc; - } - #endregion - - public Sandbox(int seed = 0, string id = null, Pointer pointer = new Pointer()) - { - Seed = seed; - Index = ++_count; - Id = id; - Pointer = pointer; - OnWarmedUp += WarmedUpHandler; - } + public TAssets Assets => _assets; - public override string ToString() - { - var str = Id; - if (str == null || str.Length == 0) str = GetType().Name; - str += "#" + Index.ToString(); - return str; - } + public Sandbox(TAssets assets, string id, int seed) : base(id, seed) + { + _assets = assets; + } + + public Sandbox(ILogger? logger, TAssets assets, string id, int seed) : base(logger, id, seed) + { + _assets = assets; + } +} + +public abstract class Sandbox : ISandbox +{ + #region Private Fields + private readonly string _id; + private readonly ILogger? _logger; + private readonly FutureEventList _futureEventList; + private readonly List _children = []; + private readonly List _hourCounters = []; + + private int _seed; + private TimeSpan _clockTime = TimeSpan.Zero; + private Stopwatch? _rtStopwatch = null; + // Use a private delegate to handle WarmedUp callbacks instead of Action + private delegate void WarmUpHandler(); + private WarmUpHandler? OnWarmedUp; + private ISandbox? _parent; + private Random _defaultRS; + #endregion + + private FutureEventList FutureEventList => _futureEventList; + + /// + /// Tag of the instance of the module + /// + public string Id => _id; + + public ILogger? Logger => _logger; + + protected Random DefaultRS => _defaultRS; + + public int Seed => _seed; + + public Sandbox(string id, int seed) + { + _futureEventList = new FutureEventList(this); + + _seed = seed; + _id = id; + _defaultRS = new Random(_seed); + + OnWarmedUp += WarmedUpHandler; + } + + public Sandbox(ILogger? logger, string id, int seed) : this(id, seed) + { + _logger = logger; + } + + private void SetParent(ISandbox parent) + { + _parent = parent; + } + + public void UpdateRandomSeed(int seed) + { + _seed = seed; + _defaultRS = new Random(_seed); + } - #region Warm-Up - public bool WarmUp(TimeSpan period) + /// + /// Schedule an event to be invoked after the specified time delay + /// + protected void Schedule(Action action, TimeSpan delay, string? tag = null) + { + _futureEventList.Add(action, ClockTime + delay, tag); + } + + /// + /// Schedule an event at the current clock time. + /// + protected void Schedule(Action action, string? tag) + { + _futureEventList.Add(action, ClockTime, tag); + } + + #region Simulation Run Control + internal Event GetHeadEvent() + { + var headEvent = _futureEventList.FirstOrDefault(); + foreach (Sandbox child in _children.Cast()) { - if (Parent != null) return Parent.WarmUp(period); - return WarmUp(ClockTime + period); + var childHeadEvent = child.GetHeadEvent(); + if (headEvent is null || (childHeadEvent is not null && + EventComparer.Instance.Compare(childHeadEvent, headEvent) < 0)) + headEvent = childHeadEvent; } - public bool WarmUp(DateTime till) + + return headEvent; + } + + public TimeSpan ClockTime + { + get { - if (Parent != null) return Parent.WarmUp(till); - var result = Run(till); - OnWarmedUp.Invoke(); - return result; // to be continued + if (Parent == null) + return _clockTime; + return Parent.ClockTime; } - private Action OnWarmedUp; - protected virtual void WarmedUpHandler() { } - #endregion + } + + public bool Run() + { + if (Parent is not null) + return Parent.Run(); + + var head = GetHeadEvent(); + + if (head is null) + return false; + + head.Owner.FutureEventList.Remove(head); + + _clockTime = head.Timestamp; + + head.Invoke(); + + return true; + } + + public bool Run(TimeSpan duration) + { + if (Parent != null) + return Parent.Run(duration); - #region For Logging - private string _logFile; - public string LogFile + return RunUntil(ClockTime + duration); + } + + private bool RunUntil(TimeSpan terminate) + { + while (true) { - get { return _logFile; } - set + var head = GetHeadEvent(); + + if (GetHeadEvent() is not null && GetHeadEvent().Timestamp <= terminate) { - _logFile = value; if (_logFile != null) using (var sw = new StreamWriter(_logFile)) { }; + Run(); } - } - protected void Log(params object[] args) - { - var timeStr = ClockTime.ToString("y/M/d H:mm:ss.fff"); - if (LogFile != null) + else { - using (var sw = new StreamWriter(LogFile, true)) - { - sw.Write("{0}\t{1}\t", timeStr, Id); - foreach (var arg in args) sw.Write("{0}\t", arg); - sw.WriteLine(); - } + _clockTime = terminate; + return head != null; /// if the simulation can be continued } } + } - public bool DebugMode { get; set; } = false; - #endregion + public bool Run(int eventCount) + { + if (Parent is not null) + return Parent.Run(eventCount); - public virtual void Dispose() + while (eventCount-- > 0) + if (!Run()) + return false; + + return true; + } + + public bool Run(double speed) + { + if (Parent is not null) + return Parent.Run(speed); + + var rtn = true; + + if (_rtStopwatch is not null) { - foreach (var child in Children_List) child.Dispose(); - foreach (var hc in HourCounters_List) hc.Dispose(); + var elapsed = _rtStopwatch.Elapsed.TotalSeconds; + rtn = RunUntil(ClockTime + TimeSpan.FromSeconds(elapsed * speed)); } + + _rtStopwatch = Stopwatch.StartNew(); + + return rtn; + } + #endregion + + #region Children - Sub-modules + public ISandbox? Parent => _parent; + public IImmutableList Children => _children.ToImmutableList(); + public TSandbox AddChild(TSandbox child) where TSandbox : Sandbox + { + _children.Add(child); + child.SetParent(this); + OnWarmedUp += child.OnWarmedUp; + return child; + } + + public IImmutableList HourCounters => _hourCounters.ToImmutableList(); + + protected HourCounter AddHourCounter(bool keepHistory = false) + { + var hc = new HourCounter(Logger, this, keepHistory); + _hourCounters.Add(hc); + OnWarmedUp += () => hc.WarmedUp(); + return hc; + } + #endregion + + #region Warm-Up + public bool WarmUp(TimeSpan duration) + { + if (Parent != null) + return Parent.WarmUp(duration); + var result = RunUntil(ClockTime + duration); + OnWarmedUp?.Invoke(); + return result; // to be continued + } + protected virtual void WarmedUpHandler() { } + #endregion + + public override string ToString() => + string.IsNullOrEmpty(Id) ? GetType().Name : Id; + + public virtual void Dispose() + { + foreach (var child in _children) + child.Dispose(); + foreach (var hc in _hourCounters) + hc.Dispose(); } } diff --git a/O2DESNet/SimulationBuilder.cs b/O2DESNet/SimulationBuilder.cs new file mode 100644 index 0000000..5411086 --- /dev/null +++ b/O2DESNet/SimulationBuilder.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using Serilog; +using Serilog.Events; + +namespace O2DESNet; + +/// +/// Fluent builder for configuring and returning an ISandbox instance. +/// Usage: +/// var sim = new SimulationBuilder(rootSandbox) +/// .AddSimulationStatics(myStatics) +/// .AddLogger() +/// .Build(); +/// +/// A marker startup type for the simulation configuration pipeline. +public sealed class SimulationBuilder where TStartup : class +{ + private readonly ISandbox _root; + private readonly Dictionary _statics = new Dictionary(); + + // Logging configuration + private bool _enableConsole = true; + private string _filePath; + private LogEventLevel _minLevel = LogEventLevel.Information; + private bool _loggerConfigured; + + public SimulationBuilder(ISandbox root) + { + _root = root ?? throw new ArgumentNullException(nameof(root)); + } + + /// + /// Adds a statics/configuration object that implements IAssets. + /// + public SimulationBuilder AddSimulationStatics(TStatics statics) + where TStatics : class, IAssets + { + if (statics == null) + throw new ArgumentNullException(nameof(statics)); + _statics[typeof(TStatics)] = statics; + return this; + } + + /// + /// Configure Serilog logger. By default writes to Console. + /// Set filePath to also write logs into a rolling file. + /// + public SimulationBuilder AddLogger(string filePath = null, bool writeToConsole = true, LogEventLevel minLevel = LogEventLevel.Information) + { + _filePath = filePath; + _enableConsole = writeToConsole; + _minLevel = minLevel; + _loggerConfigured = true; + return this; + } + + /// + /// Try to get a statics object previously added. + /// + public bool TryGetStatics(out TStatics result) where TStatics : class, IAssets + { + object value; + if (_statics.TryGetValue(typeof(TStatics), out value)) + { + result = (TStatics)value; + return true; + } + + result = null; + return false; + } + + /// + /// Finalize configuration and return the configured root ISandbox. + /// + public ISandbox Build() + { + if (_loggerConfigured) + { + ConfigureSerilog(); + } + + return _root; + } + + private void ConfigureSerilog() + { + var cfg = new LoggerConfiguration() + .MinimumLevel.Is(_minLevel) + .Enrich.FromLogContext(); + + if (_enableConsole) + { + cfg = cfg.WriteTo.Console(); + } + + if (!string.IsNullOrWhiteSpace(_filePath)) + { + // Ensure directory exists to avoid exceptions + var directory = Path.GetDirectoryName(_filePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + cfg = cfg.WriteTo.File( + _filePath, + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 31, + restrictedToMinimumLevel: _minLevel, + shared: true); + } + + Log.Logger = cfg.CreateLogger(); + } +} diff --git a/O2DESNet/Standard/Entity.Generic.cs b/O2DESNet/Standard/Entity.Generic.cs new file mode 100644 index 0000000..cdaeb9f --- /dev/null +++ b/O2DESNet/Standard/Entity.Generic.cs @@ -0,0 +1,39 @@ +namespace O2DESNet.Standard; + +/// +/// Generic abstract entity base that supports strongly typed identifiers while still implementing the non-generic IEntity. +/// +/// The identifier value type. +public abstract class Entity : IEntity +{ + // Private Fields + private static int _index = 0; + private readonly TId _id; + + // Public Properties + public int Index => _index; + + /// + /// Strongly typed identifier. + /// + public TId Id => _id; + + // Explicit implementation for the non-generic abstraction, exposes Id as string + string IEntity.Id => _id?.ToString() ?? string.Empty; + + // Constructors + protected Entity(TId id) + { + _id = id; + _index++; + } + + protected Entity(TId id, int index) + : this(id) + { + _index = index; + } + + // Public Methods + public override string ToString() => $"{_id}#{_index}"; +} diff --git a/O2DESNet/Standard/Entity.cs b/O2DESNet/Standard/Entity.cs new file mode 100644 index 0000000..e7f82f3 --- /dev/null +++ b/O2DESNet/Standard/Entity.cs @@ -0,0 +1,29 @@ +namespace O2DESNet.Standard; + +public abstract class Entity : IEntity +{ + private static int _index = 0; + private readonly EntityId _id; + + public Entity(EntityId id) + { + _id = id; + _index++; + } + + public Entity(EntityId id, int index) + : this(id) + { + _index = index; + } + + public int Index => _index; + + // Expose primitive identifier per IEntity while keeping strong type internally + public string Id => _id.ToString(); + + // Give derived classes access to the strong typed id without leaking it into the interface contract + protected EntityId StrongId => _id; + + public override string ToString() => $"{_id}#{_index}"; +} diff --git a/O2DESNet/Standard/EntityId.cs b/O2DESNet/Standard/EntityId.cs new file mode 100644 index 0000000..79f7491 --- /dev/null +++ b/O2DESNet/Standard/EntityId.cs @@ -0,0 +1,154 @@ +using System; + +namespace O2DESNet.Standard; + +/// +/// Strongly typed unique identifier for an entity, normalized to a GUID string in the compact "n" format (32 digits, no hyphens). +/// +/// +/// The underlying value is stored as a Guid rendered with format specifier "n", e.g., 3f2504e04f8911d39a0c0305e82c3301. +/// +/// +/// +/// // Create a new unique id +/// var id = EntityId.New(); +/// +/// // Recommended: Create from factory methods +/// var fromGuid = EntityId.Create(Guid.NewGuid()); +/// var fromString = EntityId.Create("3f2504e04f8911d39a0c0305e82c3301"); +/// +/// // Use as string +/// string s = (string)id; // explicit cast to string +/// string s2 = id.ToString(); // normalized "n" format +/// +/// // Retrieve Guid value +/// Guid g = (Guid)fromString; // explicit cast to Guid +/// +/// // Parse helpers +/// var parsed = EntityId.Parse(s); +/// if (EntityId.TryParse(s, out var tryId)) +/// { +/// // use tryId +/// } +/// +/// +public readonly record struct EntityId +{ + // Private Fields + private static readonly string _emptyNormalized = Guid.Empty.ToString("n"); + private readonly string _value = _emptyNormalized; + + // Public Properties + /// + /// A sentinel empty identifier equal to in the normalized "n" format. + /// + public static EntityId Empty { get; } = new(Guid.Empty); + + /// + /// Gets a value indicating whether this identifier is the empty value. + /// + public bool IsEmpty => Value == Empty.Value; + + /// + /// Normalized GUID string (format "n"). Defaults to when using default(EntityId). + /// + public string Value + { + get => _value; + } + + // Constructors + /// + /// Create a new from a . + /// + public EntityId(Guid guid) + { + _value = guid.ToString("n"); + } + + /// + /// Create a new from a GUID string. The value is validated and normalized to format "n". + /// + /// Any valid GUID string in supported formats (e.g., "D", "N", "B", "P", "X"). + /// Thrown when is not a valid GUID. + public EntityId(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("EntityId value cannot be null or whitespace.", nameof(value)); + } + + if (!Guid.TryParse(value, out var guid)) + { + throw new ArgumentException("EntityId value must be a valid GUID string.", nameof(value)); + } + + _value = guid.ToString("n"); + } + + // Public Methods + /// + /// Generate a new unique . + /// + public static EntityId New() => new(Guid.NewGuid()); + + /// + /// Factory method to create an from a . + /// + public static EntityId Create(Guid guid) => new(guid); + + /// + /// Factory method to create an from a GUID string. The input is validated and normalized. + /// + /// Thrown when is not a valid GUID. + public static EntityId Create(string value) => new(value); + + /// + public override string ToString() => Value; + + /// + /// Try to parse a string into an . + /// + /// The input string. Any valid GUID format is accepted. + /// The resulting identifier when parsing succeeds; otherwise . + /// if parsing succeeds; otherwise . + public static bool TryParse(string? value, out EntityId id) + { + if (Guid.TryParse(value, out var guid)) + { + id = new EntityId(guid); + return true; + } + + id = default; + return false; + } + + /// + /// Parse a string into an , throwing if invalid. + /// + /// The input string. Any valid GUID format is accepted. + /// Thrown when is not a valid GUID. + public static EntityId Parse(string value) => new(value); + + /// + /// Implicit conversion from to . + /// + public static implicit operator EntityId(Guid value) => new(value); + + /// + /// Implicit conversion from to . + /// + public static implicit operator EntityId(string value) => new(value); + + /// + /// Explicit conversion from to . + /// + public static explicit operator Guid(EntityId id) => Guid.Parse(id.Value); + + /// + /// Explicit conversion from to . + /// + public static explicit operator string(EntityId id) => id.Value; +} + diff --git a/O2DESNet/Standard/Generator.cs b/O2DESNet/Standard/Generator.cs index 604a889..efae448 100644 --- a/O2DESNet/Standard/Generator.cs +++ b/O2DESNet/Standard/Generator.cs @@ -1,85 +1,149 @@ -using System; -using System.Diagnostics; +using Microsoft.Extensions.Logging; -namespace O2DESNet.Standard +using System; +using System.Linq; + +namespace O2DESNet.Standard; + +/// +/// A generic event generator that produces Arrive events according to a provided inter-arrival time sampler. +/// +/// Big picture +/// - Users supply a function InterArrivalTime(Random) that yields the time gap until the next arrival. +/// - Start() enables generation and schedules the next arrival; End() disables further generation. +/// - Each Arrive event increments Count and immediately schedules the next arrival using the sampler. +/// - WarmUp() resets Count via the sandbox warm-up mechanism, keeping statistics separate if desired. +/// +public class Generator : Sandbox, IGenerator { - public class Generator : Sandbox, IGenerator + /// + /// Static configuration for the generator: an RNG-based inter-arrival time sampler. + /// + public class Statics : IAssets { - public class Statics : IAssets - { - public string Id { get { return GetType().Name; } } - public Func InterArrivalTime { get; set; } - public Generator Sandbox(int seed = 0) { return new Generator(this, seed); } - } + /// + /// Identifier of the configuration. Defaults to the type name. + /// + public string Id => GetType().Name; + /// + /// Function to sample the inter-arrival time using the provided RNG. Must be set before Start(). + /// + public Func? InterArrivalTime { get; set; } + public Generator Sandbox(ILogger? logger, int seed) => new(logger, this, nameof(Generator), seed); + } - #region Dyanmic Properties - public DateTime? StartTime { get; private set; } - public bool IsOn { get; private set; } - public int Count { get; private set; } // number of loads generated - #endregion + #region Dyanmic Properties + /// + /// The clock time when Start() was called, null before the generator is started. + /// + public TimeSpan? StartTime { get; private set; } - #region Events - public void Start() - { - if (!IsOn) - { - Log("Start"); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tStart", ClockTime, this); - if (Assets.InterArrivalTime == null) throw new Exception("Inter-arrival time is null"); - IsOn = true; - StartTime = ClockTime; - Count = 0; - ScheduleToArrive(); - } - } + /// + /// Indicates whether the generator is actively producing arrivals. + /// + public bool IsOn { get; private set; } + /// + /// Total number of arrivals generated since last warm-up/reset. + /// + public int Count { get; private set; } // number of loads generated + #endregion - public void End() + #region Events + /// + /// Start generating arrivals. Schedules the first arrival using the inter-arrival sampler. + /// + public void Start() + { + if (!IsOn) { - if (IsOn) - { - Log("End"); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tEnd", ClockTime, this); - IsOn = false; - } + Logger?.LogInformation("Start"); + Logger?.LogDebug($"{ClockTime}:\t{this}\tStart"); + if (Assets.InterArrivalTime == null) + throw new Exception("Inter-arrival time is null"); + IsOn = true; + StartTime = ClockTime; + Count = 0; + ScheduleToArrive(); } + } - private void ScheduleToArrive() + /// + /// Stop generating further arrivals. Pending scheduled candidates will be ignored when fired (due to IsOn check). + /// + public void End() + { + if (IsOn) { - Schedule(Arrive, Assets.InterArrivalTime(DefaultRS)); + Logger?.LogInformation("End"); + Logger?.LogDebug($"{ClockTime}:\t{this}\tEnd"); + IsOn = false; } + } + + /// + /// Schedule the next arrival according to the provided inter-arrival sampler. + /// + private void ScheduleToArrive() + { + Schedule(Arrive, Assets.InterArrivalTime(DefaultRS)); + } - private void Arrive() + /// + /// Arrival event handler. Increments Count, schedules the next arrival, and emits OnArrive. + /// + private void Arrive() + { + if (IsOn) { - if (IsOn) - { - Log("Arrive"); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tArrive", ClockTime, this); + Logger?.LogInformation("Arrive"); + Logger?.LogDebug($"{ClockTime}:\t{this}\tArrive"); - Count++; - ScheduleToArrive(); - OnArrive.Invoke(); - } + Count++; + ScheduleToArrive(); + OnArrive.Invoke(); } + } - public event Action OnArrive = () => { }; - #endregion + /// + /// Event raised whenever an arrival is realized. + /// + public event Action OnArrive = () => { }; + #endregion - public Generator(Statics assets, int seed = 0, string id = null) - : base(assets, seed, id) - { - IsOn = false; - Count = 0; - } + /// + /// Convenience constructor without logger. + /// + public Generator(Statics assets, string id, int seed) + : this(null, assets, id, seed) { } - protected override void WarmedUpHandler() - { - Count = 0; - } + /// + /// Initializes dynamic state. + /// + public Generator(ILogger? logger, Statics assets, string id, int seed) + : base(logger, assets, id, seed) + { + IsOn = false; + Count = 0; + } + + /// + /// Reset dynamic counters after warm-up. + /// + protected override void WarmedUpHandler() + { + Count = 0; + } - public override void Dispose() + /// + /// Unsubscribe listeners to avoid leaks and dispose base resources. + /// + public override void Dispose() + { + base.Dispose(); + var handlers = OnArrive.GetInvocationList().Cast(); + foreach (Action i in handlers) { - foreach (Action i in OnArrive.GetInvocationList()) OnArrive -= i; + OnArrive -= i; } - } } diff --git a/O2DESNet/Standard/IEntity.cs b/O2DESNet/Standard/IEntity.cs new file mode 100644 index 0000000..a295507 --- /dev/null +++ b/O2DESNet/Standard/IEntity.cs @@ -0,0 +1,15 @@ +namespace O2DESNet.Standard; + +public interface IEntity +{ + int Index { get; } + + // Expose primitive identifier to avoid dependency on EntityId in abstractions + string Id { get; } +} + +// Generic abstraction to opt-in to strongly typed identifiers without coupling the base interface +public interface IEntity : IEntity +{ + new TId Id { get; } +} diff --git a/O2DESNet/Standard/IGenerator.cs b/O2DESNet/Standard/IGenerator.cs index 27869d3..343a657 100644 --- a/O2DESNet/Standard/IGenerator.cs +++ b/O2DESNet/Standard/IGenerator.cs @@ -1,26 +1,25 @@ using System; -namespace O2DESNet.Standard +namespace O2DESNet.Standard; + +public interface IGenerator : ISandbox { - public interface IGenerator : ISandbox - { - DateTime? StartTime { get; } - bool IsOn { get; } - /// - /// Number of loads generated - /// - int Count { get; } - /// - /// Input event - Start - /// - void Start(); - /// - /// Input event - End - /// - void End(); - /// - /// Output event - Arrive - /// - event Action OnArrive; - } + TimeSpan? StartTime { get; } + bool IsOn { get; } + /// + /// Number of loads generated + /// + int Count { get; } + /// + /// Input event - Start + /// + void Start(); + /// + /// Input event - End + /// + void End(); + /// + /// Output event - Arrive + /// + event Action OnArrive; } diff --git a/O2DESNet/Standard/ILoad.cs b/O2DESNet/Standard/ILoad.cs deleted file mode 100644 index 2308d13..0000000 --- a/O2DESNet/Standard/ILoad.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace O2DESNet.Standard -{ - public interface ILoad - { - int Index { get; } - string Id { get; } - } -} diff --git a/O2DESNet/Standard/IQueue.cs b/O2DESNet/Standard/IQueue.cs index 3140e39..2dfbf99 100644 --- a/O2DESNet/Standard/IQueue.cs +++ b/O2DESNet/Standard/IQueue.cs @@ -1,25 +1,24 @@ using System; using System.Collections.Generic; -namespace O2DESNet.Standard +namespace O2DESNet.Standard; + +public interface IQueue : ISandbox { - public interface IQueue : ISandbox - { - double Capacity { get; } + double Capacity { get; } - IReadOnlyList PendingToEnqueue { get; } - IReadOnlyList Queueing { get; } - int Occupancy { get; } - double Vacancy { get; } - double Utilization { get; } - /// - /// Average number of loads queueing - /// - double AvgNQueueing { get; } + IReadOnlyList PendingToEnqueue { get; } + IReadOnlyList Queueing { get; } + int Occupancy { get; } + double Vacancy { get; } + double Utilization { get; } + /// + /// Average number of loads queueing + /// + double AvgNQueueing { get; } - void RqstEnqueue(ILoad load); - void Dequeue(ILoad load); + void RqstEnqueue(IEntity load); + void Dequeue(IEntity load); - event Action OnEnqueued; - } + event Action OnEnqueued; } diff --git a/O2DESNet/Standard/IServer.cs b/O2DESNet/Standard/IServer.cs index 7a48af2..a65832f 100644 --- a/O2DESNet/Standard/IServer.cs +++ b/O2DESNet/Standard/IServer.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; -namespace O2DESNet.Standard +namespace O2DESNet.Standard; + +public interface IServer : ISandbox { - public interface IServer : ISandbox - { - double Capacity { get; } - int Occupancy { get; } - double Vacancy { get; } - double AvgNServing { get; } - double AvgNOccupying { get; } - /// - /// Utilization only consider serving loads (active) - /// - double UtilServing { get; } - /// - /// Utilization including both serving and served loads (active + passive) - /// - double UtilOccupying { get; } - IReadOnlyList PendingToStart { get; } - IReadOnlyList Serving { get; } - IReadOnlyList PendingToDepart { get; } + double Capacity { get; } + int Occupancy { get; } + double Vacancy { get; } + double AvgNServing { get; } + double AvgNOccupying { get; } + /// + /// Utilization only consider serving loads (active) + /// + double UtilServing { get; } + /// + /// Utilization including both serving and served loads (active + passive) + /// + double UtilOccupying { get; } + IReadOnlyList PendingToStart { get; } + IReadOnlyList Serving { get; } + IReadOnlyList PendingToDepart { get; } - void RqstStart(ILoad load); - void Depart(ILoad load); + void RqstStart(IEntity load); + void Depart(IEntity load); - event Action OnStarted; - event Action OnReadyToDepart; - } + event Action OnStarted; + event Action OnReadyToDepart; } diff --git a/O2DESNet/Standard/Load.cs b/O2DESNet/Standard/Load.cs deleted file mode 100644 index 84939ea..0000000 --- a/O2DESNet/Standard/Load.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace O2DESNet.Standard -{ - public class Load : ILoad - { - private static int _count = 0; - public int Index { get; private set; } = _count++; - public virtual string Id { get { return string.Format("{0}#{1}", GetType().Name, Index); } } - public override string ToString() { return Id; } - } -} diff --git a/O2DESNet/Standard/PatternGenerator.cs b/O2DESNet/Standard/PatternGenerator.cs index f785de5..e7960ac 100644 --- a/O2DESNet/Standard/PatternGenerator.cs +++ b/O2DESNet/Standard/PatternGenerator.cs @@ -1,216 +1,356 @@ -using O2DESNet.Distributions; +using Microsoft.Extensions.Logging; + +using O2DESNet.Distributions; + using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -namespace O2DESNet.Standard +namespace O2DESNet.Standard; + +/// +/// A nonhomogeneous arrival generator that produces Arrive events according to a time-varying rate. +/// +/// Big picture +/// - The baseline input is : the mean arrival rate (per hour) without seasonality. +/// - Seasonality is modeled multiplicatively across multiple calendars: hour-of-day (24), day-of-week (7), day-of-month (31), +/// month-of-year (12), year (arbitrary list), plus any number of customized cycles (interval + list of factors). +/// - Each factor list is normalized (see ) so its average equals 1.0. Values represent relative intensity. +/// - The instantaneous rate at a given clock time is the product of the baseline rate and the active factors from all dimensions. +/// - For simulation efficiency, the process uses a thinning algorithm: it samples inter-arrival gaps from an exponential +/// distribution with parameter equal to a precomputed peak hourly rate (an upper bound), then accepts or rejects candidates +/// based on the ratio of current-factor to max-factor for each seasonal dimension. +/// +/// The generator exposes a simple interface: call to begin generating arrivals and to stop. +/// When an arrival is realized, is invoked, and is incremented. +/// +public class PatternGenerator : Sandbox, IGenerator { - public class PatternGenerator : Sandbox, IGenerator + /// + /// Static configuration (assets) for . + /// Includes baseline mean rate and seasonal factor lists. All seasonality dimensions are optional. + /// + public class Statics : IAssets { - public class Statics : IAssets + /// + /// Identifier of the configuration. Defaults to the type name. + /// + public string Id => GetType().Name; + /// + /// Baseline mean arrival rate (per hour). If no seasonal factors are provided, the inter-arrival time + /// follows an exponential distribution with mean 1 / MeanHourlyRate. + /// + public double MeanHourlyRate { get; set; } + /// + /// 24 hourly seasonal factors. If shorter than 24, zeros are appended; if longer, the list is truncated. + /// All zeros or null imply no hourly seasonality (treated as 1s after normalization). + /// + public List SeasonalFactors_HoursOfDay { get; } = []; + /// + /// 7 daily factors for Sunday..Saturday. All zeros or null imply no weekly seasonality. + /// + public List SeasonalFactors_DaysOfWeek { get; } = []; + /// + /// 31 factors for 1..31 day-of-month. All zeros or null imply no day-of-month seasonality. + /// Internally adjusted by month length at runtime (see ScheduleToArrive day-of-month check). + /// + public List SeasonalFactors_DaysOfMonth { get; } = []; + /// + /// 12 monthly factors for Jan..Dec. All zeros or null imply no monthly seasonality. + /// + public List SeasonalFactors_MonthsOfYear { get; } = []; + /// + /// Yearly factors. Length is flexible and cycles with year index. All zeros or null imply no yearly seasonality. + /// + public List SeasonalFactors_Years { get; } = []; + + /// + /// Arbitrary custom seasonal cycles. Each item is a tuple of: + /// - Interval: the base interval length for one factor step; the full cycle length is Interval * factors.Count + /// - List of factors: repeats cyclically over time. + /// If empty or null, no custom cycles are applied. + /// + public List<(TimeSpan, List)> CustomizedSeasonalFactors { get; } = []; + + /// + /// Factory method to create a sandbox instance with this static configuration. + /// + public PatternGenerator Sandbox(ILogger logger, int seed = 0) => new(logger, this, nameof(PatternGenerator), seed); + } + + #region Dyanmic Properties + /// + /// The clock time when Start() was called, null before the generator is started. + /// + public TimeSpan? StartTime { get; private set; } + /// + /// Indicates whether the generator is actively producing arrivals. + /// + public bool IsOn { get; private set; } + /// + /// Total number of arrivals generated since the last warmup/reset. + /// + public int Count { get; private set; } + + /// + /// Upper bound of instantaneous rate (per hour) used by thinning. + /// Computed as MeanHourlyRate times the maxima of normalized seasonal factors across all configured dimensions. + /// + private double PeakHourlyRate { get; set; } + + // Pre-normalized, fixed-length seasonal factor lists (each with average 1.0 after Normalize) + private List Adjusted_SeasonalFactors_HoursOfDay { get; } + private List Adjusted_SeasonalFactors_DaysOfWeek { get; } + private List Adjusted_SeasonalFactors_DaysOfMonth { get; } + private List Adjusted_SeasonalFactors_MonthsOfYear { get; } + private List Adjusted_SeasonalFactors_Years { get; } + private List<(TimeSpan Interval, List SeasonalFactors)> Adjusted_CustomizedSeasonalFactors { get; } + + // Max values of each adjusted factor list, used to compute PeakHourlyRate and thinning acceptance ratios + private double AdjMax_SeasonalFactor_HoursOfDay { get; set; } + private double AdjMax_SeasonalFactor_DaysOfWeek { get; set; } + private double AdjMax_SeasonalFactor_DaysOfMonth { get; set; } + private double AdjMax_SeasonalFactor_MonthsOfYear { get; set; } + private double AdjMax_SeasonalFactor_Years { get; set; } + private List AdjMax_CustomizedSeasonalFactors { get; } + + /// + /// For custom seasonal cycles, tracks remaining time offset within the current cycle after jumping by an exponential inter-arrival sample. + /// Maintains phase continuity when stepping through candidate arrival times. + /// + private List CustomizedSeasonalRemainders { get; } + #endregion + + #region Events + /// + /// Start generating arrivals. Schedules the next candidate arrival according to thinning. + /// + public void Start() + { + if (!IsOn) { - public string Id { get { return GetType().Name; } } - /// - /// By default it follow exponential distribution - /// - public double MeanHourlyRate { get; set; } - /// - /// A list of 24 seasonal factors, to be filled with 0s if not full - /// All 0s or null means no seasonal effect - /// - public List SeasonalFactors_HoursOfDay { get; set; } - /// - /// A list of 7 seasonal factors, to be filled with 0s if not full - /// All 0s or null means no seasonal effect - /// - public List SeasonalFactors_DaysOfWeek { get; set; } - /// - /// A list of 31 seasonal factors, to be filled with 0s if not full - /// All 0s or null means no seasonal effect - /// - public List SeasonalFactors_DaysOfMonth { get; set; } - /// - /// A list of 12 seasonal factors, to be filled with 0s if not full - /// All 0s or null means no seasonal effect - /// - public List SeasonalFactors_MonthsOfYear { get; set; } - /// - /// All 0s or null means no seasonal effect - /// - public List SeasonalFactors_Years { get; set; } - public List<(TimeSpan, List)> CustomizedSeasonalFactors { get; set; } - public PatternGenerator Sandbox(int seed = 0) { return new PatternGenerator(this, seed); } + Logger?.LogInformation("Start"); + IsOn = true; + StartTime = ClockTime; + // Note: Do not reset Count here; unit tests expect cumulative count across Start/End cycles. + ScheduleToArrive(); } + } - #region Dyanmic Properties - public DateTime? StartTime { get; private set; } - public bool IsOn { get; private set; } - public int Count { get; private set; } - private double PeakHourlyRate { get; set; } - private List Adjusted_SeasonalFactors_HoursOfDay { get; set; } - private List Adjusted_SeasonalFactors_DaysOfWeek { get; set; } - private List Adjusted_SeasonalFactors_DaysOfMonth { get; set; } - private List Adjusted_SeasonalFactors_MonthsOfYear { get; set; } - private List Adjusted_SeasonalFactors_Years { get; set; } - private List<(TimeSpan Interval, List SeasonalFactors)> Adjusted_CustomizedSeasonalFactors { get; set; } - private double AdjMax_SeasonalFactor_HoursOfDay { get; set; } - private double AdjMax_SeasonalFactor_DaysOfWeek { get; set; } - private double AdjMax_SeasonalFactor_DaysOfMonth { get; set; } - private double AdjMax_SeasonalFactor_MonthsOfYear { get; set; } - private double AdjMax_SeasonalFactor_Years { get; set; } - private List AdjMax_CustomizedSeasonalFactors { get; set; } - private List CustomizedSeasonalRemainders { get; set; } - #endregion - - #region Events - public void Start() + /// + /// Stop generating further arrivals. Pending scheduled candidates will be ignored when fired (due to IsOn check). + /// + public void End() + { + if (IsOn) { - if (!IsOn) - { - Log("Start"); - IsOn = true; - StartTime = ClockTime; - Count = 0; - ScheduleToArrive(); - } + Logger?.LogInformation("End"); + IsOn = false; } + } - public void End() + /// + /// Core thinning loop. Repeatedly samples a candidate inter-arrival time from Exp(PeakHourlyRate) + /// and accepts it with probability equal to the product of current-factor / max-factor across all dimensions. + /// If rejected, a new candidate gap is sampled starting from the last candidate time. + /// When accepted, schedules at the accepted time. + /// + private void ScheduleToArrive() + { + var time = ClockTime; + while (true) { - if (IsOn) + // Candidate inter-arrival gap drawn from exponential with parameter PeakHourlyRate (hours^-1) + var hoursElapsed = Exponential.Sample(DefaultRS, 1 / PeakHourlyRate); + + // For each custom cycle, advance its phase by the elapsed time and figure out which factor index is active + var customizedIndices = new List(); + for (int i = 0; i < Adjusted_CustomizedSeasonalFactors.Count; i++) { - Log("End"); - IsOn = false; + var (interval, factors) = Adjusted_CustomizedSeasonalFactors[i]; + var sum = CustomizedSeasonalRemainders[i].TotalHours + hoursElapsed; + // How many interval steps have elapsed within the cycle during this gap? + var countIntervals = (int)Math.Floor(sum / interval.TotalHours); + // Update remainder within the full cycle span (Interval * factorCount) + CustomizedSeasonalRemainders[i] = TimeSpan.FromHours(sum % (interval.TotalHours * factors.Count)); + // Determine the active factor index after stepping + customizedIndices.Add(countIntervals % factors.Count); } - } - private void ScheduleToArrive() - { - var time = ClockTime; - while (true) + // Candidate arrival absolute time + time = time + TimeSpan.FromHours(hoursElapsed); + + // Convert to a synthetic DateTime to conveniently access Hour/Day/Month fields. + // Using DateTime.MinValue as an anchor as only the components are needed. + var dt = DateTime.MinValue + time; + + // Thinning acceptance checks per dimension. Each check accepts with probability currentFactor / maxFactor. + if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_HoursOfDay[dt.Hour] / AdjMax_SeasonalFactor_HoursOfDay) + continue; + if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_DaysOfWeek[(int)dt.DayOfWeek] / AdjMax_SeasonalFactor_DaysOfWeek) + continue; + + // Day-of-month factor is adjusted by actual month length so that average over a month remains consistent. + // Multiply by 31 and divide by days in the actual month to re-scale the normalized 31-day vector. + if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_DaysOfMonth[dt.Day - 1] * 31 / DateTime.DaysInMonth(dt.Year, dt.Month) / AdjMax_SeasonalFactor_DaysOfMonth) + continue; + + if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_MonthsOfYear[dt.Month - 1] / AdjMax_SeasonalFactor_MonthsOfYear) + continue; + + // Year factors cycle by list length; dt.Year starts from 1 in DateTime.MinValue-based arithmetic + if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_Years[(dt.Year - 1) % Adjusted_SeasonalFactors_Years.Count] / AdjMax_SeasonalFactor_Years) + continue; + + #region For customized seasonality + bool reject = false; + for (int i = 0; i < Adjusted_CustomizedSeasonalFactors.Count; i++) { - var hoursElapsed = Exponential.Sample(DefaultRS, 1 / PeakHourlyRate); - var customizedIndices = new List(); - for (int i = 0; i < Adjusted_CustomizedSeasonalFactors.Count; i++) - { - var (interval, factors) = Adjusted_CustomizedSeasonalFactors[i]; - var sum = CustomizedSeasonalRemainders[i].TotalHours + hoursElapsed; - var countIntervals = (int)Math.Floor(sum / interval.TotalHours); - CustomizedSeasonalRemainders[i] = TimeSpan.FromHours(sum % (interval.TotalHours * factors.Count)); - customizedIndices.Add(countIntervals % factors.Count); - } - time = time.AddHours(hoursElapsed); - if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_HoursOfDay[time.Hour] / AdjMax_SeasonalFactor_HoursOfDay) continue; - if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_DaysOfWeek[(int)time.DayOfWeek] / AdjMax_SeasonalFactor_DaysOfWeek) continue; - if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_DaysOfMonth[time.Day - 1] * 31 / DateTime.DaysInMonth(time.Year, time.Month) / AdjMax_SeasonalFactor_DaysOfMonth) continue; - if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_MonthsOfYear[time.Month - 1] / AdjMax_SeasonalFactor_MonthsOfYear) continue; - if (DefaultRS.NextDouble() > Adjusted_SeasonalFactors_Years[(time.Year - 1) % Adjusted_SeasonalFactors_Years.Count] / AdjMax_SeasonalFactor_Years) continue; - #region For customized seasonality - bool reject = false; - for (int i = 0; i < Adjusted_CustomizedSeasonalFactors.Count; i++) + var idx = customizedIndices[i]; + var factors = Adjusted_CustomizedSeasonalFactors[i].SeasonalFactors; + if (DefaultRS.NextDouble() > factors[idx] / AdjMax_CustomizedSeasonalFactors[i]) { - var idx = customizedIndices[i]; - var factors = Adjusted_CustomizedSeasonalFactors[i].SeasonalFactors; - if (DefaultRS.NextDouble() > factors[idx] / AdjMax_CustomizedSeasonalFactors[i]) - { - reject = true; - break; - } + reject = true; + break; } - if (reject) continue; - #endregion - Schedule(Arrive, time); - break; } + + if (reject) + continue; + #endregion + + // All checks passed: schedule the actual arrival + Schedule(Arrive, time - ClockTime); + break; } + } - private void Arrive() + /// + /// Arrival event handler. Increments , schedules the next candidate arrival, and fires . + /// + private void Arrive() + { + if (IsOn) { - if (IsOn) - { - Log("Arrive"); - Debug.WriteLine("{0}:\t{1}\tArrive", ClockTime, this); + Logger?.LogInformation("Arrive"); + Logger?.LogDebug($"{ClockTime}:\t{this}\tArrive"); - Count++; - ScheduleToArrive(); - OnArrive.Invoke(); - } + Count++; + // Continue the thinning loop to produce subsequent arrivals + ScheduleToArrive(); + OnArrive.Invoke(); } + } - public event Action OnArrive = () => { }; - #endregion - - public PatternGenerator(Statics assets, int seed = 0, string tag = null) - : base(assets, seed, tag) - { - IsOn = false; - Count = 0; + /// + /// Event raised whenever an arrival is accepted and processed. + /// + public event Action OnArrive = () => { }; + #endregion - #region Normalize seasonal factors - List normalize(List factors, int? nIntervals = null) - { - /// return default if undefined - if (factors == null || factors.Sum() == 0) - { - if (nIntervals != null) return Enumerable.Repeat(1d, nIntervals.Value).ToList(); - else return new List { 1 }; - } + /// + /// Convenience constructor without logger. + /// + public PatternGenerator(Statics assets, string id, int seed) + : this(null, assets, id, seed) { } - /// remove the negative part, replace with 0 - factors = factors.Select(f => Math.Max(0, f)).ToList(); + /// + /// Constructs the generator and prepares normalized and max seasonal factors, as well as the peak hourly rate used by thinning. + /// + public PatternGenerator(ILogger? logger, Statics assets, string id, int seed) + : base(logger, assets, id, seed) + { + IsOn = false; + Count = 0; - /// adjust the lenghth - if (nIntervals != null) - { - factors = factors.Take(nIntervals.Value).ToList(); - while (factors.Count < nIntervals.Value) factors.Add(0); - } + // Normalize supplied seasonal lists so that each has average 1.0 and proper length. + Adjusted_SeasonalFactors_HoursOfDay = Normalize(Assets.SeasonalFactors_HoursOfDay, 24); + Adjusted_SeasonalFactors_DaysOfWeek = Normalize(Assets.SeasonalFactors_DaysOfWeek, 7); + Adjusted_SeasonalFactors_DaysOfMonth = Normalize(Assets.SeasonalFactors_DaysOfMonth, 31); + Adjusted_SeasonalFactors_MonthsOfYear = Normalize(Assets.SeasonalFactors_MonthsOfYear, 12); + Adjusted_SeasonalFactors_Years = Normalize(Assets.SeasonalFactors_Years); + Adjusted_CustomizedSeasonalFactors = []; + if (Assets.CustomizedSeasonalFactors != null) + foreach (var (interval, factors) in Assets.CustomizedSeasonalFactors) + Adjusted_CustomizedSeasonalFactors.Add((interval, Normalize(factors))); - /// standardize - var sum = factors.Sum(); - return factors.Select(f => f / sum * factors.Count).ToList(); - } + #region Set max factor and peak hourly rate + // Capture maxima for acceptance probabilities and to compute the thinning upper bound (PeakHourlyRate) + AdjMax_SeasonalFactor_HoursOfDay = Adjusted_SeasonalFactors_HoursOfDay.Max(); + AdjMax_SeasonalFactor_DaysOfWeek = Adjusted_SeasonalFactors_DaysOfWeek.Max(); + AdjMax_SeasonalFactor_DaysOfMonth = Adjusted_SeasonalFactors_DaysOfMonth.Max(); + AdjMax_SeasonalFactor_MonthsOfYear = Adjusted_SeasonalFactors_MonthsOfYear.Max(); + AdjMax_SeasonalFactor_Years = Adjusted_SeasonalFactors_Years.Max(); + AdjMax_CustomizedSeasonalFactors = Adjusted_CustomizedSeasonalFactors.Select(t => t.SeasonalFactors.Max()).ToList(); - Adjusted_SeasonalFactors_HoursOfDay = normalize(Assets.SeasonalFactors_HoursOfDay, 24); - Adjusted_SeasonalFactors_DaysOfWeek = normalize(Assets.SeasonalFactors_DaysOfWeek, 7); - Adjusted_SeasonalFactors_DaysOfMonth = normalize(Assets.SeasonalFactors_DaysOfMonth, 31); - Adjusted_SeasonalFactors_MonthsOfYear = normalize(Assets.SeasonalFactors_MonthsOfYear, 12); - Adjusted_SeasonalFactors_Years = normalize(Assets.SeasonalFactors_Years); - Adjusted_CustomizedSeasonalFactors = new List<(TimeSpan Interval, List SeasonalFactors)>(); - if (Assets.CustomizedSeasonalFactors != null) - foreach (var (interval, factors) in Assets.CustomizedSeasonalFactors) - Adjusted_CustomizedSeasonalFactors.Add((interval, normalize(factors))); - #endregion + // Peak hourly rate = baseline * product of maxima across all dimensions + PeakHourlyRate = Assets.MeanHourlyRate; + PeakHourlyRate *= AdjMax_SeasonalFactor_HoursOfDay; + PeakHourlyRate *= AdjMax_SeasonalFactor_DaysOfWeek; + PeakHourlyRate *= AdjMax_SeasonalFactor_DaysOfMonth; + PeakHourlyRate *= AdjMax_SeasonalFactor_MonthsOfYear; + PeakHourlyRate *= AdjMax_SeasonalFactor_Years; + foreach (var max in AdjMax_CustomizedSeasonalFactors) + PeakHourlyRate *= max; + #endregion - #region Set max factor and peak hourly rate - AdjMax_SeasonalFactor_HoursOfDay = Adjusted_SeasonalFactors_HoursOfDay.Max(); - AdjMax_SeasonalFactor_DaysOfWeek = Adjusted_SeasonalFactors_DaysOfWeek.Max(); - AdjMax_SeasonalFactor_DaysOfMonth = Adjusted_SeasonalFactors_DaysOfMonth.Max(); - AdjMax_SeasonalFactor_MonthsOfYear = Adjusted_SeasonalFactors_MonthsOfYear.Max(); - AdjMax_SeasonalFactor_Years = Adjusted_SeasonalFactors_Years.Max(); - AdjMax_CustomizedSeasonalFactors = Adjusted_CustomizedSeasonalFactors.Select(t => t.SeasonalFactors.Max()).ToList(); - PeakHourlyRate = Assets.MeanHourlyRate; - PeakHourlyRate *= AdjMax_SeasonalFactor_HoursOfDay; - PeakHourlyRate *= AdjMax_SeasonalFactor_DaysOfWeek; - PeakHourlyRate *= AdjMax_SeasonalFactor_DaysOfMonth; - PeakHourlyRate *= AdjMax_SeasonalFactor_MonthsOfYear; - PeakHourlyRate *= AdjMax_SeasonalFactor_Years; - foreach (var max in AdjMax_CustomizedSeasonalFactors) PeakHourlyRate *= max; - #endregion + // Initialize custom cycle phase remainders to zero + CustomizedSeasonalRemainders = Adjusted_CustomizedSeasonalFactors.Select(t => new TimeSpan()).ToList(); + } - CustomizedSeasonalRemainders = Adjusted_CustomizedSeasonalFactors.Select(t => new TimeSpan()).ToList(); + /// + /// Normalize seasonal factor lists: + /// - If null or sums to 0, returns a list of 1s (or [1] if length unspecified). + /// - Negative values are clamped to 0. + /// - If a target interval count is provided, the list is truncated/padded to that length (pad with 0s). + /// - Finally, values are scaled so their average equals 1.0 (sum becomes count). + /// + private static List Normalize(List factors, int? nIntervals = null) + { + // return default if undefined + if (factors == null || factors.Sum() == 0) + { + if (nIntervals != null) + return Enumerable.Repeat(1d, nIntervals.Value).ToList(); + else + return [1]; } - protected override void WarmedUpHandler() + // remove the negative part, replace with 0 + factors = factors.Select(f => Math.Max(0, f)).ToList(); + + // adjust the length + if (nIntervals != null) { - Count = 0; + factors = factors.Take(nIntervals.Value).ToList(); + while (factors.Count < nIntervals.Value) + factors.Add(0); } - public override void Dispose() + // standardize: scale so that average == 1.0 + var sum = factors.Sum(); + return factors.Select(f => f / sum * factors.Count).ToList(); + } + + /// + /// Reset dynamic counters after warm-up. + /// + protected override void WarmedUpHandler() + { + Count = 0; + } + + /// + /// Clean up event subscriptions to avoid leaks and call base.Dispose to release child resources and counters. + /// + public override void Dispose() + { + base.Dispose(); + + var handlers = OnArrive.GetInvocationList(); + foreach (Action i in handlers.Cast()) { - foreach (Action i in OnArrive.GetInvocationList()) OnArrive -= i; + OnArrive -= i; } - } } diff --git a/O2DESNet/Standard/Queue.cs b/O2DESNet/Standard/Queue.cs index fe10c5c..ba9a084 100644 --- a/O2DESNet/Standard/Queue.cs +++ b/O2DESNet/Standard/Queue.cs @@ -1,75 +1,140 @@ -using System; +using Microsoft.Extensions.Logging; + +using O2DESNet.HourCounters; + +using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -namespace O2DESNet.Standard +namespace O2DESNet.Standard; + +/// +/// A bounded FIFO buffer that accepts loads, tracks its occupancy over time, and emits an event +/// when a load is successfully enqueued. +/// +/// Big picture +/// - RqstEnqueue(load) submits a load to the queue. If there is space, it is immediately enqueued; +/// otherwise it waits in a pending list. +/// - When space becomes available (via Dequeue or a prior enqueue), AtmptEnqueue tries to admit the first pending load. +/// - HourCounter HC_Queueing captures time-weighted occupancy statistics (average queue length, utilization, etc.). +/// +public class Queue : Sandbox, IQueue { - public class Queue : Sandbox, IQueue - { - #region Static Properties - public double Capacity { get; private set; } - #endregion + #region Static Properties + /// + /// Maximum number of loads that the queue can hold concurrently. + /// + public double Capacity { get; private set; } + #endregion + + #region Dynamic Properties + /// + /// Loads waiting to be enqueued because the queue was full at request time. + /// + public IReadOnlyList PendingToEnqueue => List_PendingToEnqueue.AsReadOnly(); + /// + /// Loads currently occupying the queue (FIFO by insertion order). + /// + public IReadOnlyList Queueing => List_Queueing.AsReadOnly(); + /// + /// Current number of loads in the queue. + /// + public int Occupancy => List_Queueing.Count; + /// + /// Remaining capacity available for new loads. + /// + public double Vacancy => Capacity - Occupancy; + /// + /// Time-average fraction of capacity occupied, derived from the hour counter. + /// + public double Utilization => AvgNQueueing / Capacity; + /// + /// Time-average number of loads in the queue. + /// + public double AvgNQueueing => HC_Queueing.AverageCount; - #region Dynamic Properties - public IReadOnlyList PendingToEnqueue { get { return List_PendingToEnqueue.AsReadOnly(); } } - public IReadOnlyList Queueing { get { return List_Queueing.AsReadOnly(); } } - public int Occupancy { get { return List_Queueing.Count; } } - public double Vacancy { get { return Capacity - Occupancy; } } - public double Utilization { get { return AvgNQueueing / Capacity; } } - public double AvgNQueueing { get{ return HC_Queueing.AverageCount; } } + private readonly List List_Queueing = []; + private readonly List List_PendingToEnqueue = []; + private HourCounter HC_Queueing { get; set; } + #endregion - private readonly List List_Queueing = new List(); - private readonly List List_PendingToEnqueue = new List(); - private HourCounter HC_Queueing { get; set; } - #endregion + #region Methods / Events + /// + /// Request to enqueue a load. If capacity allows, the load is enqueued immediately; otherwise + /// the load is stored in PendingToEnqueue until space is available. + /// + public void RqstEnqueue(IEntity load) + { + Logger?.LogInformation("RqstEnqueue"); + Logger?.LogDebug($"{ClockTime}:\t{this}\tRqstEnqueue\t{load}"); + List_PendingToEnqueue.Add(load); + AtmptEnqueue(); + } - #region Methods / Events - public void RqstEnqueue(ILoad load) + /// + /// Remove a specific load from the queue if present, and attempt to admit the next pending load. + /// + public void Dequeue(IEntity load) + { + if (List_Queueing.Contains(load)) { - Log("RqstEnqueue"); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tRqstEnqueue\t{2}", ClockTime, this, load); - List_PendingToEnqueue.Add(load); + Logger?.LogInformation("Dequeue", load); + Logger?.LogDebug($"{ClockTime}:\t{this}\tDequeue\t{load}"); + List_Queueing.Remove(load); + HC_Queueing.ObserveChange(-1, ClockTime); AtmptEnqueue(); } - public void Dequeue(ILoad load) - { - if (List_Queueing.Contains(load)) - { - Log("Dequeue", load); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tDequeue\t{2}", ClockTime, this, load); - List_Queueing.Remove(load); - HC_Queueing.ObserveChange(-1, ClockTime); - AtmptEnqueue(); - } - } - private void AtmptEnqueue() + } + /// + /// Try to enqueue the first pending load if capacity is available. On success, updates + /// the time-weighted occupancy and emits OnEnqueued(load). + /// + private void AtmptEnqueue() + { + if (List_PendingToEnqueue.Count > 0 && List_Queueing.Count < Capacity) { - if (List_PendingToEnqueue.Count > 0 && List_Queueing.Count < Capacity) - { - var load = List_PendingToEnqueue.First(); - Log("Enqueue", load); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tEnqueue\t{2}", ClockTime, this, load); - List_Queueing.Add(load); - List_PendingToEnqueue.RemoveAt(0); - HC_Queueing.ObserveChange(1, ClockTime); - OnEnqueued.Invoke(load); - } + var load = List_PendingToEnqueue.First(); + Logger?.LogInformation("Enqueue", load); + Logger?.LogDebug($"{ClockTime}:\t{this}\tEnqueue\t{load}"); + List_Queueing.Add(load); + List_PendingToEnqueue.RemoveAt(0); + HC_Queueing.ObserveChange(1, ClockTime); + OnEnqueued.Invoke(load); } + } - public event Action OnEnqueued = load => { }; - #endregion + /// + /// Event raised when a load has been successfully enqueued. + /// + public event Action OnEnqueued = load => { }; + #endregion - public Queue(double capacity, int seed = 0, string id = null) - : base(seed, id) - { - Capacity = capacity; - HC_Queueing = AddHourCounter(); - } + /// + /// Convenience constructor without logger. + /// + public Queue(double capacity, string id, int seed) + : this(null, capacity, id, seed) { } - public override void Dispose() + /// + /// Initializes the queue with a fixed capacity and a time-weighted occupancy counter. + /// + public Queue(ILogger? logger, double capacity, string id, int seed) + : base(logger, id, seed) + { + Capacity = capacity; + HC_Queueing = AddHourCounter(); + } + + /// + /// Unsubscribe listeners to avoid leaks and dispose base resources. + /// + public override void Dispose() + { + base.Dispose(); + var handlers = OnEnqueued.GetInvocationList().Cast>(); + foreach (Action i in handlers) { - foreach (Action i in OnEnqueued.GetInvocationList()) OnEnqueued -= i; - } + OnEnqueued -= i; + } } } diff --git a/O2DESNet/Standard/Server.cs b/O2DESNet/Standard/Server.cs index a6818e7..8b0e6b3 100644 --- a/O2DESNet/Standard/Server.cs +++ b/O2DESNet/Standard/Server.cs @@ -1,100 +1,200 @@ -using System; +using Microsoft.Extensions.Logging; + +using O2DESNet.HourCounters; + +using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -namespace O2DESNet.Standard +namespace O2DESNet.Standard; + +/// +/// A capacity-constrained service facility that starts, serves, and completes loads. +/// +/// Big picture +/// - Clients call RqstStart(load) to request service. The server admits the load if capacity is available, +/// otherwise the request waits in a pending list. +/// - When a load starts service, the server samples a service time and schedules a completion (ReadyToDepart). +/// - Upon completion, the load moves to a pending-to-depart set until Depart(load) is called by an external coordinator. +/// - The server tracks time-weighted statistics with HourCounter instances for Serving and PendingToDepart states. +/// +public class Server : Sandbox, IServer { - public class Server : Sandbox, IServer + /// + /// Static configuration for the server: capacity and service-time distribution. + /// + public class Statics : IAssets { - public class Statics : IAssets - { - public string Id { get { return GetType().Name; } } - public double Capacity { get; set; } - public Func ServiceTime { get; set; } - } + /// + /// Identifier of the configuration. Defaults to the type name. + /// + public string Id => GetType().Name; + /// + /// Maximum number of loads that can be concurrently in service or pending to depart. + /// + public double Capacity { get; set; } + /// + /// Function to sample a service time given a RNG and the load. Must be set by the user. + /// + public Func? ServiceTime { get; set; } + } - #region Dynamic Properties - public double Capacity { get { return Assets.Capacity; } } - public int Occupancy { get { return HSet_Serving.Count + HSet_PendingToDepart.Count; } } - public double Vacancy { get { return Capacity - Occupancy; } } - public double AvgNServing { get { return HC_Serving.AverageCount; } } - public double AvgNOccupying { get { return HC_Serving.AverageCount + HC_PendingToDepart.AverageCount; } } - public double UtilServing { get { return AvgNServing / Capacity; } } - public double UtilOccupying { get { return AvgNOccupying / Capacity; } } - public IReadOnlyList PendingToStart { get { return List_PendingToStart.AsReadOnly(); } } - public IReadOnlyList Serving { get { return HSet_Serving.ToList().AsReadOnly(); } } - public IReadOnlyList PendingToDepart { get { return HSet_PendingToDepart.ToList().AsReadOnly(); } } - - private HourCounter HC_Serving { get; set; } - private HourCounter HC_PendingToDepart { get; set; } - private readonly List List_PendingToStart = new List(); - private readonly HashSet HSet_Serving = new HashSet(); - private readonly HashSet HSet_PendingToDepart = new HashSet(); - #endregion - - #region Events - public void RqstStart(ILoad load) - { - Log("Request to Start", load); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tRqstStart\t{2}", ClockTime, this, load); - List_PendingToStart.Add(load); - AtmptStart(); - } + #region Dynamic Properties + /// + /// Configured capacity from assets. + /// + public double Capacity => Assets.Capacity; + /// + /// Total number of loads occupying the server (serving + pending to depart). + /// + public int Occupancy => HSet_Serving.Count + HSet_PendingToDepart.Count; + /// + /// Remaining capacity available for starting new loads. + /// + public double Vacancy => Capacity - Occupancy; + /// + /// Time-average number of loads being served (active) over the simulation horizon. + /// + public double AvgNServing => HC_Serving.AverageCount; + /// + /// Time-average number of loads occupying the server (active + passive) over the simulation horizon. + /// + public double AvgNOccupying => HC_Serving.AverageCount + HC_PendingToDepart.AverageCount; + /// + /// Utilization considering only actively served loads. + /// + public double UtilServing => AvgNServing / Capacity; + /// + /// Utilization including both serving and pending-to-depart loads. + /// + public double UtilOccupying => AvgNOccupying / Capacity; + /// + /// Loads waiting to start (FIFO by list order). + /// + public IReadOnlyList PendingToStart => List_PendingToStart.AsReadOnly(); + /// + /// Loads currently in service. + /// + public IReadOnlyList Serving => HSet_Serving.ToList().AsReadOnly(); + /// + /// Loads that completed service and are awaiting explicit departure. + /// + public IReadOnlyList PendingToDepart => HSet_PendingToDepart.ToList().AsReadOnly(); - private void AtmptStart() - { - if (List_PendingToStart.Count > 0 && Vacancy > 0) - { - var load = List_PendingToStart.First(); - Log("Start", load); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tStart\t{2}", ClockTime, this, load); - List_PendingToStart.RemoveAt(0); - HSet_Serving.Add(load); - HC_Serving.ObserveChange(1, ClockTime); - OnStarted.Invoke(load); - Schedule(() => ReadyToDepart(load), Assets.ServiceTime(DefaultRS, load)); - } - } + // Time-weighted counters for statistics + private HourCounter HC_Serving { get; set; } + private HourCounter HC_PendingToDepart { get; set; } + + // Internal state containers + private readonly List List_PendingToStart = []; + private readonly HashSet HSet_Serving = []; + private readonly HashSet HSet_PendingToDepart = []; + #endregion + + #region Events + /// + /// Request to start serving a load. The load is queued to PendingToStart and an attempt is made immediately. + /// + public void RqstStart(IEntity load) + { + Logger?.LogInformation("Request to Start", load); + Logger?.LogDebug($"{ClockTime}:\t{this}\tRqstStart\t{load}"); + List_PendingToStart.Add(load); + AtmptStart(); + } - private void ReadyToDepart(ILoad load) + /// + /// Try to start service if there is a pending load and available capacity. + /// On start: the load moves from pending to serving, serving counter +1, and a completion is scheduled. + /// + private void AtmptStart() + { + if (List_PendingToStart.Count > 0 && Vacancy > 0) { - Log("Ready to Depart", load); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tReadyToDepart\t{2}", ClockTime, this, load); - HSet_Serving.Remove(load); - HSet_PendingToDepart.Add(load); - HC_Serving.ObserveChange(-1, ClockTime); - HC_PendingToDepart.ObserveChange(1, ClockTime); - OnReadyToDepart.Invoke(load); + var load = List_PendingToStart.First(); + Logger?.LogInformation("Start", load); + Logger?.LogDebug($"{ClockTime}:\t{this}\tStart\t{load}"); + List_PendingToStart.RemoveAt(0); + HSet_Serving.Add(load); + HC_Serving.ObserveChange(1, ClockTime); + OnStarted.Invoke(load); + // Schedule completion of service using the provided ServiceTime sampler + Schedule(() => ReadyToDepart(load), Assets.ServiceTime(DefaultRS, load)); } + } - public void Depart(ILoad load) + /// + /// Service completion handler. Moves the load from Serving to PendingToDepart and updates counters. + /// + private void ReadyToDepart(IEntity load) + { + Logger?.LogInformation("Ready to Depart", load); + Logger?.LogDebug($"{ClockTime}:\t{this}\tReadyToDepart\t{load}"); + HSet_Serving.Remove(load); + HSet_PendingToDepart.Add(load); + HC_Serving.ObserveChange(-1, ClockTime); + HC_PendingToDepart.ObserveChange(1, ClockTime); + OnReadyToDepart.Invoke(load); + } + + /// + /// Explicitly depart a completed load (if present). Frees occupancy and triggers a new start attempt. + /// + public void Depart(IEntity load) + { + if (HSet_PendingToDepart.Contains(load)) { - if (HSet_PendingToDepart.Contains(load)) - { - Log("Depart", load); - if (DebugMode) Debug.WriteLine("{0}:\t{1}\tDepart\t{2}", ClockTime, this, load); - HSet_PendingToDepart.Remove(load); - HC_PendingToDepart.ObserveChange(-1, ClockTime); - AtmptStart(); - } + Logger?.LogInformation("Depart", load); + Logger?.LogDebug($"{ClockTime}:\t{this}\tDepart\t{load}"); + HSet_PendingToDepart.Remove(load); + HC_PendingToDepart.ObserveChange(-1, ClockTime); + // New capacity may be available; try to start the next waiting load + AtmptStart(); } + } - public event Action OnStarted = Load => { }; - public event Action OnReadyToDepart = load => { }; - #endregion + /// + /// Event raised when a load has just started service. + /// + public event Action OnStarted = Load => { }; + /// + /// Event raised when a load has completed service and is ready to depart. + /// + public event Action OnReadyToDepart = load => { }; + #endregion - public Server(Statics assets, int seed = 0, string id = null) - : base(assets, seed, id) + /// + /// Convenience constructor without logger. + /// + public Server(Statics assets, string id, int seed) + : this(null, assets, id, seed) { } + + /// + /// Initializes the server and its time-weighted counters. + /// + public Server(ILogger? logger, Statics assets, string id, int seed) + : base(logger, assets, id, seed) + { + HC_Serving = AddHourCounter(); + HC_PendingToDepart = AddHourCounter(); + } + + /// + /// Unsubscribe listeners to avoid leaks. + /// + public override void Dispose() + { + base.Dispose(); + var OnStartedHandlers = OnStarted.GetInvocationList().Cast>(); + foreach (Action i in OnStartedHandlers) { - HC_Serving = AddHourCounter(); - HC_PendingToDepart = AddHourCounter(); + OnStarted -= i; } - public override void Dispose() + var OnReadyToDepartHandlers = OnReadyToDepart.GetInvocationList().Cast>(); + foreach (Action i in OnReadyToDepartHandlers) { - foreach (Action i in OnStarted.GetInvocationList()) OnStarted -= i; - foreach (Action i in OnReadyToDepart.GetInvocationList()) OnReadyToDepart -= i; + OnReadyToDepart -= i; } } } diff --git a/README.md b/README.md index 534dc8f..4d93f20 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,31 @@ # O²DES.NET -O²DES.NET is a framework for object-oriented discrete event simulation. +O²DES.NET is a framework for Object-Oriented Discrete Event Simulation. It hybrids both event-based and state-based formalism, and implement them in an object-oriented programming language. As it is event-based in the kernel, O²DES.NET is able to model the structure and behaviors of a system precisely. On top of it, the state-based formalism enables modularization and hierarchical modeling. Besides, the object-oriented paradigm abstracts the model definitions and makes them seamless to interact with analytical functionalities, regardless of their fidelity levels. -It is developed and used by C#, which facilitates flexible integration with the latest academic research in simulation analytics and enables the connection to a variety of industrial-standard modern developments from the .NET ecosystem, including mobile applications, enterprise software, Mix-Reality, and Artificial Intelligence. +It is developed and used .NET with C# programming language. O2DES.NET library facilitates flexible integration with the latest academic research in simulation analytics and enables the connection to a variety of industrial-standard modern developments within the .NET ecosystem, including .NET Aspire to host as distributed applications, web API applications, mobile applications, enterprise software, Mix-Reality, and for Artificial Intelligence. ## Supported .NET Version -- .NET Standard 2.0 (Recommended) -- .NET Framework 4.7.2 (Minimum Version) +- .NET Standard 2.1 +- .NET 9 (Minimum Version) -## Setup Options for .NET Core 2.2/3.0 (Windows/Linux/MacOS) -- Option 1: Install [.NET Core SDK](https://dotnet.microsoft.com/download). -- Option 2: From Visual Studio Installer, enable Cross-Platform Development. - -## Setup Options for .NET Framework 4.7.2 (Windows Only) -- Option 1: Install [.NET Framework 4.7.2](https://dotnet.microsoft.com/download). -- Option 2: From Visual Studio Installer, go to components and checked option to install .NET Framework 4.7.2. +## Setup Options for .NET 9 (Windows/Linux/MacOS) +- Option 1: Install [.NET 9 SDK](https://dotnet.microsoft.com/download). +- Option 2: From [VS Code](https://code.visualstudio.com/download), then install [.NET Extension Pack](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscode-dotnet-pack). +- Option 3: From [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/), enable the ASP.NET Core or .NET Desktop Development option when installing. # Change Log +## Version 4.0 (Breaking Changes) +### New Features +- All time based types is now using `TimeSpan` instead of `DateTime`. The `ClockTime` is also now using `TimeSpan`. +- Reorganizing constructor parameters for `Sandbox` and `HourCounter` to improve usability. +- Using built-in Microsoft logging framework for logging. The `LogFile` and other debugging methods are removed. +- Updated Unit Tests method names and structure to improve clarity and organization. +- Unit Tests for logging example using `Serilog` added. +- Added `GetStatistics` method to `HourCounter` to retrieve statistics such as mean, standard deviation, min, max, and percentiles. +- The `MathNET.Numerics` library is now included in the solution. + ## Version 3.6 - Improvement of HourCounter to synchronize with simulator ClockTime https://github.com/li-haobin/O2DESNet/issues/1