From 74ab646c5b3595a0758e2abf321f7c3807e31f15 Mon Sep 17 00:00:00 2001 From: DSVarga Date: Fri, 3 Jan 2025 11:20:09 +0100 Subject: [PATCH] Upgrade to v1.0 --- .github/workflows/CI.yml | 2 +- Project.toml | 27 +- ReleaseNotes.md | 4 + docs/Project.toml | 2 + docs/make.jl | 22 +- docs/src/index.md | 27 - docs/src/makeindex.md | 2 +- docs/src/pslyap.md | 41 - docs/src/psric.md | 19 - docs/src/pstools.md | 14 - ext/FourierApproxExt1.jl | 31 + ext/ps_Fourier.jl | 6 + ext/psanalysis_Fourier.jl | 459 ++++ ext/psconversions_Fourier.jl | 91 + ext/pslifting_Fourier.jl | 47 + ext/pstimeresp_Fourier.jl | 274 +++ ext/types/PeriodicStateSpace_Fourier.jl | 64 + src/PeriodicSystems.jl | 19 +- src/ps.jl | 2 +- src/psanalysis.jl | 158 +- src/psclyap.jl | 1317 ---------- src/psconversions.jl | 5 +- src/pscric.jl | 785 ------ src/psdric.jl | 551 ----- src/pslifting.jl | 96 +- src/pslyap.jl | 3006 ----------------------- src/psstab.jl | 106 +- src/pstimeresp.jl | 20 +- src/types/PeriodicStateSpace.jl | 98 +- test/runtests.jl | 4 - test/test_psanalysis.jl | 16 +- test/test_psclyap.jl | 707 ------ test/test_psconversions.jl | 1 + test/test_pscric.jl | 235 -- test/test_psdric.jl | 299 --- test/test_pslifting.jl | 2 +- test/test_pslyap.jl | 1082 -------- test/test_pstimeresp.jl | 5 +- test/test_psutils.jl | 174 -- test/test_stabilization.jl | 14 +- 40 files changed, 1226 insertions(+), 8608 deletions(-) delete mode 100644 docs/src/pslyap.md delete mode 100644 docs/src/psric.md delete mode 100644 docs/src/pstools.md create mode 100644 ext/FourierApproxExt1.jl create mode 100644 ext/ps_Fourier.jl create mode 100644 ext/psanalysis_Fourier.jl create mode 100644 ext/psconversions_Fourier.jl create mode 100644 ext/pslifting_Fourier.jl create mode 100644 ext/pstimeresp_Fourier.jl create mode 100644 ext/types/PeriodicStateSpace_Fourier.jl delete mode 100644 src/psclyap.jl delete mode 100644 src/pscric.jl delete mode 100644 src/psdric.jl delete mode 100644 src/pslyap.jl delete mode 100644 test/test_psclyap.jl delete mode 100644 test/test_pscric.jl delete mode 100644 test/test_psdric.jl delete mode 100644 test/test_pslyap.jl diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9b29075..c90b5e1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -57,7 +57,7 @@ jobs: - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-runtest@latest - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: file: lcov.info env: diff --git a/Project.toml b/Project.toml index f47710d..e767ada 100644 --- a/Project.toml +++ b/Project.toml @@ -1,10 +1,9 @@ name = "PeriodicSystems" uuid = "5decd0d0-8a5f-11ec-2edd-87b25cbcd17e" authors = ["Andreas Varga "] -version = "0.9.1" +version = "1.0.0" [deps] -ApproxFun = "28f2ccd6-bb30-5033-b560-165f7b14dc2f" DescriptorSystems = "a81e2ce2-54d1-11eb-2c75-db236b00f339" FastLapackInterface = "29a986be-02c6-4525-aec4-84b980013641" IRKGaussLegendre = "58bc7355-f626-4c51-96f2-1f8a038f95a2" @@ -16,6 +15,7 @@ MatrixPencils = "48965c70-4690-11ea-1f13-43a2532b2fa8" Optim = "429524aa-4258-5aef-a3af-852621145aeb" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" PeriodicMatrices = "dd9cd634-6083-4ae5-8f2e-b983833de2bb" +PeriodicMatrixEquations = "52570b1b-55d0-4b45-8be2-52db9c79d90e" PeriodicSchurDecompositions = "e5aedecb-f6c0-4c91-b6ff-fbae4296f459" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -23,6 +23,17 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" +[weakdeps] +ApproxFun = "28f2ccd6-bb30-5033-b560-165f7b14dc2f" + +[extensions] +FourierApproxExt1 = "ApproxFun" + +[extras] +ApproxFun = "28f2ccd6-bb30-5033-b560-165f7b14dc2f" +LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + [compat] ApproxFun = "0.13" DescriptorSystems = "1.4" @@ -36,19 +47,15 @@ MatrixEquations = "2.2" MatrixPencils = "1.7" Optim = "1.7" OrdinaryDiffEq = "v5.72.2, 6" -PeriodicMatrices = "0.1.1, 0.1.2" -PeriodicSchurDecompositions = "0.1.1, 0.1.5" +PeriodicMatrices = "0.1.2" +PeriodicMatrixEquations = "0.1" +PeriodicSchurDecompositions = "0.1.5" Reexport = "1.2.2" QuadGK = "2.9" Random = "1" Symbolics = "4, 5" julia = "1.10" - -[extras] -LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - [targets] -test = ["LinearMaps", "Test"] +test = ["ApproxFun", "LinearMaps", "Test"] diff --git a/ReleaseNotes.md b/ReleaseNotes.md index de5a2fc..c29f2cb 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,9 @@ # Release Notes +## Version 1.0.0 + +Breaking release, which concludes the integration of `PeriodicMatrices` and `PeriodicMatrixEquations` packages as separate supporting packages. This version also achieves the splitting of the `ApproxFun` package as an optional package. + ## Version 0.9.1 Fix error in `psmrc2d`. diff --git a/docs/Project.toml b/docs/Project.toml index 1814eb3..73811c6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,7 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" [compat] Documenter = "1" +DocumenterInterLinks = "1" \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 7610617..a720bd6 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,6 +1,15 @@ using Documenter, PeriodicSystems +using DocumenterInterLinks DocMeta.setdocmeta!(PeriodicSystems, :DocTestSetup, :(using PeriodicSystems); recursive=true) +# links = InterLinks( +# "PeriodicMatrixEquations" => ("https://andreasvarga.github.io/PeriodicMatrixEquations.jl/dev/", +# "https://andreasvarga.github.io/PeriodicMatrixEquations.jl/dev/objects.inv"), +# ); +links = InterLinks( + "PeriodicMatrixEquations" => "https://andreasvarga.github.io/PeriodicMatrixEquations.jl/dev/", +); + makedocs(warnonly = true, modules = [PeriodicSystems], sitename = "PeriodicSystems.jl", @@ -16,19 +25,12 @@ makedocs(warnonly = true, "Basic conversions" => ["psconversions.md", "pslifting.md"], - # "order_reduction.md", "psanalysis.md", - "pslyap.md", - "psric.md", - "psstab.md", - # "advanced_operations.md", - # "model_matching.md" + "psstab.md" ], - "Utilities" => [ - "pstools.md" - ], "Index" => "makeindex.md" - ] + ], + plugins=[links] ) deploydocs( diff --git a/docs/src/index.md b/docs/src/index.md index 2fcb0ed..35ac3fe 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -68,33 +68,6 @@ The targeted functionality of this package is described in [1] and will cover bo * **[`pstimeresp`](@ref)** Time response of a periodic system. * **[`psstepresp`](@ref)** Step response of a periodic system. -**Solving periodic Lyapunov equations** - -* **[`pclyap`](@ref)** Solution of periodic Lyapunov differential equations. -* **[`prclyap`](@ref)** Solution of reverse-time periodic Lyapunov differential equations. -* **[`pfclyap`](@ref)** Solution of forward-time periodic Lyapunov differential equations. -* **[`pgclyap`](@ref)** Computation of periodic generators for periodic Lyapunov differential equations. -* **[`pdlyap`](@ref)** Solution of periodic discrete-time Lyapunov equations. -* **[`pdlyap2`](@ref)** Solution of a pair of periodic discrete-time Lyapunov equations. -* **[`prdlyap`](@ref)** Solution of reverse-time periodic discrete-time Lyapunov equations. -* **[`pfdlyap`](@ref)** Solution of forward-time periodic discrete-time Lyapunov equations. -* **[`pcplyap`](@ref)** Solution of positve periodic Lyapunov differential equations. -* **[`prcplyap`](@ref)** Solution of positve reverse-time periodic Lyapunov differential equations. -* **[`pfcplyap`](@ref)** Solution of positve forward-time periodic Lyapunov differential equations. -* **[`pdplyap`](@ref)** Solution of positve periodic discrete-time Lyapunov equations. -* **[`prdplyap`](@ref)** Solution of positve reverse-time periodic discrete-time Lyapunov equations. -* **[`pfdplyap`](@ref)** Solution of positve forward-time periodic discrete-time Lyapunov equations. - -**Solving periodic Riccati equations** - -* **[`pcric`](@ref)** Solution of periodic Riccati differential equations. -* **[`prcric`](@ref)** Solution of control-related reverse-time periodic Riccati differential equation. -* **[`pfcric`](@ref)** Solution of filtering-related forward-time periodic Riccati differential equation. -* **[`pgcric`](@ref)** Computation of periodic generators for periodic Riccati differential equations. -* **[`prdric`](@ref)** Solution of control-related reverse-time periodic Riccati difference equation. -* **[`pfdric`](@ref)** Solution of filtering-related forward-time periodic Riccati difference equation. - - **Simplification of periodic system models** **Periodic state feedback controller and estimator design** diff --git a/docs/src/makeindex.md b/docs/src/makeindex.md index 41b09f0..c0cac47 100644 --- a/docs/src/makeindex.md +++ b/docs/src/makeindex.md @@ -1,7 +1,7 @@ # Index ```@index -Pages = [ "pstypes.md", "ps.md", "psconnect.md", "psconversions.md", "pslifting.md", "psanalysis.md", "pslyap.md", "psstab.md", "psric.md", "pstools.md" ] +Pages = [ "pstypes.md", "ps.md", "psconnect.md", "psconversions.md", "pslifting.md", "psanalysis.md", "psstab.md" ] Modules = [PeriodicSystems] Order = [:type, :function] ``` diff --git a/docs/src/pslyap.md b/docs/src/pslyap.md deleted file mode 100644 index c34c2c3..0000000 --- a/docs/src/pslyap.md +++ /dev/null @@ -1,41 +0,0 @@ -# Periodic Lyapunov equation solvers - -* **[`pclyap`](@ref)** Solution of periodic Lyapunov differential equations. -* **[`prclyap`](@ref)** Solution of reverse-time periodic Lyapunov differential equation equations. -* **[`pfclyap`](@ref)** Solution of forward-time periodic Lyapunov differential equation equations. -* **[`pgclyap`](@ref)** Computation of periodic generators for periodic Lyapunov differential equations. -* **[`pgclyap2`](@ref)** Computation of periodic generators for a pair of periodic Lyapunov differential equations. -* **[`tvclyap_eval`](@ref)** Evaluation of time value of solution from the computed periodic generator. -* **[`pdlyap`](@ref)** Solution of periodic discrete-time Lyapunov equations. -* **[`pdlyap2`](@ref)** Solution of a pair of periodic discrete-time Lyapunov equations. -* **[`prdlyap`](@ref)** Solution of reverse-time periodic discrete-time Lyapunov equations. -* **[`pfdlyap`](@ref)** Solution of forward-time periodic discrete-time Lyapunov equations. -* **[`pcplyap`](@ref)** Solution of positve periodic Lyapunov differential equations. -* **[`prcplyap`](@ref)** Solution of positve reverse-time periodic Lyapunov differential equations. -* **[`pfcplyap`](@ref)** Solution of positve forward-time periodic Lyapunov differential equations. -* **[`pgcplyap`](@ref)** Computation of periodic generators for positive periodic Lyapunov differential equations. -* **[`tvcplyap_eval`](@ref)** Evaluation of time value of the upper triangular factor of solution from the computed periodic generator. -* **[`pdplyap`](@ref)** Solution of positve periodic discrete-time Lyapunov equations. -* **[`prdplyap`](@ref)** Solution of positve reverse-time periodic discrete-time Lyapunov equations. -* **[`pfdplyap`](@ref)** Solution of positve forward-time periodic discrete-time Lyapunov equations. - -```@docs -pclyap -prclyap -pfclyap -pgclyap -pgclyap2 -tvclyap_eval -pdlyap -pdlyap2 -prdlyap -pfdlyap -pcplyap -prcplyap -pfcplyap -pgcplyap -tvcplyap_eval -pdplyap -prdplyap -pfdplyap -``` diff --git a/docs/src/psric.md b/docs/src/psric.md deleted file mode 100644 index 832eeb8..0000000 --- a/docs/src/psric.md +++ /dev/null @@ -1,19 +0,0 @@ -# Periodic Riccati equation solvers - -* **[`pcric`](@ref)** Solution of periodic Riccati differential equations. -* **[`pgcric`](@ref)** Computation of periodic generators for periodic Riccati differential equations. -* **[`tvcric_eval`](@ref)** Evaluation of time value of solution from the computed periodic generator. -* **[`prcric`](@ref)** Solution of control-related reverse-time periodic Riccati differential equation. -* **[`pfcric`](@ref)** Solution of filtering-related forward-time periodic Riccati differential equation. -* **[`prdric`](@ref)** Solution of control-related reverse-time periodic Riccati difference equation. -* **[`pfdric`](@ref)** Solution of filtering-related forward-time periodic Riccati difference equation. - -```@docs -pcric -pgcric -tvcric_eval -prcric -pfcric -prdric -pfdric -``` diff --git a/docs/src/pstools.md b/docs/src/pstools.md deleted file mode 100644 index 31da001..0000000 --- a/docs/src/pstools.md +++ /dev/null @@ -1,14 +0,0 @@ -# Periodic system utilities - -* **[`pslyapd`](@ref)** Solution of periodic discrete-time Lyapunov equations using periodic Schur decomposition. -* **[`pslyapd2`](@ref)** Solution of a pair periodic discrete-time Lyapunov equations using a single periodic Schur decomposition. -* **[`pslyapdkr`](@ref)** Solution of periodic discrete-time Lyapunov equations using Kronecker product expansions. -* **[`psplyapd`](@ref)** Solution of positve periodic discrete-time Lyapunov equations. - - -```@docs -pslyapd -pslyapd2 -pslyapdkr -psplyapd -``` diff --git a/ext/FourierApproxExt1.jl b/ext/FourierApproxExt1.jl new file mode 100644 index 0000000..96c6033 --- /dev/null +++ b/ext/FourierApproxExt1.jl @@ -0,0 +1,31 @@ +module FourierApproxExt1 + +using PeriodicSystems +using Reexport +@reexport using PeriodicMatrices +@reexport using PeriodicMatrixEquations +using ApproxFun +using DescriptorSystems +using FastLapackInterface +using IRKGaussLegendre +using LinearAlgebra +using LineSearches +using MatrixEquations +using MatrixPencils +using Optim +using OrdinaryDiffEq +using PeriodicSchurDecompositions +using QuadGK +using SparseArrays + +import LinearAlgebra: BlasInt, BlasFloat, BlasReal, BlasComplex + + +include("types/PeriodicStateSpace_Fourier.jl") +include("ps_Fourier.jl") +include("psconversions_Fourier.jl") +include("pslifting_Fourier.jl") +include("pstimeresp_Fourier.jl") +include("psanalysis_Fourier.jl") + +end \ No newline at end of file diff --git a/ext/ps_Fourier.jl b/ext/ps_Fourier.jl new file mode 100644 index 0000000..780a030 --- /dev/null +++ b/ext/ps_Fourier.jl @@ -0,0 +1,6 @@ +# function PeriodicSystems.ps(PMT::Type, sys::DST, period::Real; ns::Int = 1) where {PMT <: FourierFunctionMatrix, DST <: DescriptorStateSpace} +# sys.E == I || error("only standard state-spece models supported") +# Ts = sys.Ts +# Ts == 0 || error("only continuous periodic matrix types allowed") +# ps(PMT(sys.A,period), PMT(sys.B,period), PMT(sys.C,period), PMT(sys.D,period)) +# end diff --git a/ext/psanalysis_Fourier.jl b/ext/psanalysis_Fourier.jl new file mode 100644 index 0000000..9bb4b66 --- /dev/null +++ b/ext/psanalysis_Fourier.jl @@ -0,0 +1,459 @@ +PeriodicSystems.pspole(psys::PeriodicStateSpace{<: FourierFunctionMatrix}, N::Int = 10; kwargs...) = psceigfr(psys.A, N; kwargs...) +""" + pszero(psys::PeriodicStateSpece{FourierFunctionMatrix}[, N]; P, atol, rtol, fast) -> val + +Compute the finite and infinite zeros of a continuous-time periodic system `psys = (Af(t), Bf(t), Cf(t), Df(t))` in `val`, +where the periodic system matrices `Af(t)`, `Bf(t)`, `Cf(t)`, and `Df(t)` are in a _Fourier Function Matrix_ representation. +`N` is the number of selected harmonic components in the Fourier series of the system matrices (default: `N = max(20,nh-1)`, +where `nh` is the maximum number of harmonics terms) and the keyword parameter `P` is the number of full periods +to be considered (default: `P = 1`) to build +a frequency-lifted LTI representation based on truncated block Toeplitz matrices. + +The computation of the zeros of the _real_ lifted system is performed by reducing the corresponding system pencil +to an appropriate Kronecker-like form which exhibits the finite and infinite eigenvalues. +The reduction is performed using orthonal similarity transformations and involves rank decisions based on rank revealing QR-decompositions with column pivoting, +if `fast = true`, or, the more reliable, SVD-decompositions, if `fast = false`. For a system `psys` of period `T`, +the finite zeros are determined as those eigenvalues which have imaginary parts in the interval `[-ω/2, ω/2]`, where `ω = 2π/(P*T)`. +To eliminate possible spurious finite eigenvalues, the intersection of two finite eigenvalue sets is computed +for two lifted systems obtained for `N` and `N+2` harmonic components. +The infinite zeros are determined as the infinite zeros of the LTI system `(Af(ti), Bf(ti), Cf(ti), Df(ti))` +resulting for a random time value `ti`. _Warning:_ While this evaluation of the number of infinite zeros mostly +provides the correct result, there is no theoretical assessment of this approach (counterexamples are welcome!). + +The keyword arguments `atol` and `rtol` specify the absolute and relative tolerances for the nonzero +elements of the underlying lifted system pencil, respectively. +The default relative tolerance is `n*ϵ`, where `n` is the size of the smallest dimension of the pencil, and `ϵ` is the +working machine epsilon. +""" +function PeriodicSystems.pszero(psys::PeriodicStateSpace{<: FourierFunctionMatrix}, N::Union{Int,Missing} = missing; P::Int= 1, fast::Bool = true, atol::Real = 0, rtol::Real = 0) + ismissing(N) && (N = max(20, maximum(ncoefficients.(Matrix(psys.A.M))), maximum(ncoefficients.(Matrix(psys.B.M))), + maximum(ncoefficients.(Matrix(psys.C.M))), maximum(ncoefficients.(Matrix(psys.A.M))))) + (N == 0 || islti(psys) ) && (return MatrixPencils.spzeros(dssdata(psaverage(psys))...; fast, atol1 = atol, atol2 = atol, rtol)[1]) + + # employ heuristics to determine fix finite zeros by comparing two sets of computed zeros + z = MatrixPencils.spzeros(dssdata(ps2frls(psys, N; P))...; fast, atol1 = atol, atol2 = atol, rtol)[1] + + period = psys.A.period + ωhp2 = pi/P/period + n = size(psys.A,1) + T = promote_type(Float64, eltype(psys.A)) + zf = z[isfinite.(z)] + ind = sortperm(imag(zf),by=abs); + nf = count(abs.(imag(zf[ind[1:min(4*n,length(ind))]])) .<= ωhp2*(1+sqrt(eps(T)))) + zf = zf[ind[1:nf]] + + z2 = MatrixPencils.spzeros(dssdata(ps2frls(psys, N+2; P))...; fast, atol1 = atol, atol2 = atol, rtol)[1] + zf2 = z2[isfinite.(z2)] + ind = sortperm(imag(zf2),by=abs); + nf2 = count(abs.(imag(zf2[ind[1:min(4*n,length(ind))]])) .<= ωhp2*(1+sqrt(eps(T)))) + zf2 = zf2[ind[1:nf2]] + σf = Complex{T}[] + nf < nf2 || ((zf, zf2) = (zf2, zf)) + atol > 0 || (norms = max(norm(coefficients(psys.A.M),Inf),norm(coefficients(psys.B.M),Inf),norm(coefficients(psys.C.M),Inf),norm(coefficients(psys.D.M),Inf))) + tol = atol > 0 ? atol : (rtol > 0 ? rtol*norms : sqrt(eps(T))*norms) + for i = 1:min(nf,nf2) + minimum(abs.(zf2 .- zf[i])) < tol && push!(σf,zf[i]) + end + isreal(σf) && (σf = real(σf)) + + if any(isinf.(z)) + # Conjecture: The number of infinite zeros is the same as that of the time-evaluated system! + zm = MatrixPencils.spzeros(dssdata(psteval(psys, period*rand()))...; fast, atol1 = atol, atol2 = atol, rtol)[1] + zf = [σf; zm[isinf.(zm)]] + end + nz = length(zf) + nz > n && (@warn "$(nz-n) spurious finite zero(s) present") + return zf +end +""" + psh2norm(psys, K; adj = false, smarg = 1, fast = false, offset = sqrt(ϵ), solver = "", reltol = 1.e-4, abstol = 1.e-7, quad = false) -> nrm + +Compute the H2-norm of a continuous-time periodic system `psys = (A(t),B(t),C(t),D(t))`. +For the computation of the norm, the formulas given in [1] are employed, +in conjunction with the multiple-shooting approach of [2] using `K` discretization points. +For a periodic system of period `T`, for `adj = false` the norm is computed as + + nrm = sqrt(Integral(tr(C(t)P(t)C(t)')))dt/T), + +where `P(t)` is the controllability Gramian satisfying the periodic differential Lyapunov equation + + . + P(t) = A(t)P(t)A(t)' + B(t)B(t)', + +while for `adj = true` the norm is computed as + + nrm = sqrt(Integral(tr(B(t)'Q(t)B(t)))dt/T), + +where Q(t) is the observability Gramian satisfying the periodic differential Lyapunov equation + + . + -Q(t) = A(t)'Q(t)A(t) + C(t)'C(t) . + +If `quad = true`, a simple quadrature formula based on the sum of time values is employed (see [2]). +This option ensures a faster evaluation, but is potentially less accurate then the exact evaluation +employed if `quad = false` (default). + +The norm is set to infinity for an unstable system or for a non-zero `D(t)`. + +To assess the stability, the absolute values of the characteristic multipliers of `A(t)` +must be less than `smarg-β`, where `smarg` is the discrete-time stability margin (default: `smarg = 1`) and +`β` is an offset specified via the keyword parameter `offset = β` to be used to numerically assess the stability +of eigenvalues. The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision. +If `fast = false` (default) then the stability is checked using an approach based on the periodic Schur decomposition of `A(t)`, +while if `fast = true` the stability is checked using a lifting-based approach. + +The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, +together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), +absolute accuracy `abstol` (default: `abstol = 1.e-7`) and +stepsize `dt` (default: `dt = 0`). The value stepsize is relevant only if `solver = "symplectic", in which case +an adaptive stepsize strategy is used if `dt = 0` and a fixed stepsize is used if `dt > 0`. +Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, +which are generally very efficient, but less accurate. If `reltol < 1.e-4`, +higher order solvers are employed able to cope with high accuracy demands. + +The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: + +`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); + +`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); + +`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; + +`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); + +`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). + +_References_ + +[1] P. Colaneri. Continuous-time periodic systems in H2 and H∞: Part I: Theoretical Aspects. + Kybernetika, 36:211-242, 2000. + +[2] A. Varga, On solving periodic differential matrix equations with applications to periodic system norms computation. + Proc. CDC/ECC, Seville, p.6545-6550, 2005. +""" +function PeriodicSystems.psh2norm(psys::PeriodicStateSpace{<: FourierFunctionMatrix}, K::Int = 1; adj::Bool = false, smarg::Real = 1, fast::Bool = false, + offset::Real = sqrt(eps()), solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0, quad = false) + norm(psys.D) == 0 || (return Inf) + !isstable(psys, K; smarg, offset, fast, solver, reltol, abstol) && (return Inf) # unstable system + P = adj ? pgclyap(psys.A, psys.C'*psys.C, K; adj, solver, reltol, abstol) : pgclyap(psys.A, psys.B*psys.B', K; adj, solver, reltol, abstol) + Ts = psys.period/K/P.nperiod + pp = length(P) + if quad + # use simple quadrature formula + nrm = 0 + if adj + for i = K:-1:1 + ip = mod.(i-1,pp).+1 + Bt = tpmeval(psys.B,(i-1)*Ts) + nrm += tr(Bt'*P.values[ip]*Bt) + end + else + for i = 1:K + ip = mod.(i-1,pp).+1 + Ct = tpmeval(psys.C,(i-1)*Ts) + temp = tr(Ct*P.values[ip]*Ct') + # (i == 1 || i == K) && (temp = temp/2) # for some reason the trapezoidal method is less accurate + nrm += temp + end + end + return sqrt(nrm*Ts*P.nperiod/psys.period) + end + μ = Vector{eltype(psys)}(undef,K) + solver == "symplectic" && dt == 0 && (dt = K >= 100 ? Ts : Ts*K/100) + if adj + #Threads.@threads for i = K:-1:1 + for i = K:-1:1 + ip = mod.(i-1,pp).+1 + iw = ip < pp ? ip+1 : 1 + @inbounds μ[i] = tvh2norm(psys.A, psys.B, psys.C, P.values[iw], (i-1)*Ts, i*Ts; adj, solver, reltol, abstol, dt) + end + else + #Threads.@threads for i = K:-1:1 + for i = 1:K + ip = mod.(i-1,pp).+1 + @inbounds μ[i] = tvh2norm(psys.A, psys.B, psys.C, P.values[ip], i*Ts, (i-1)*Ts; adj, solver, reltol, abstol, dt) + end + end + return sqrt(sum(μ)*P.nperiod/psys.period) +end +function PeriodicSystems.tvh2norm(A::PM1, B::PM2, C::PM3, P::AbstractMatrix, tf, t0; adj = false, solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where + {PM1 <: FourierFunctionMatrix, PM2 <: FourierFunctionMatrix, PM3 <: FourierFunctionMatrix} + """ + tvh2norm(A, B, C, P, tf, to; adj, solver, reltol, abstol, dt) -> μ + + Cmputes the H2-norm of the system (A(t),B(t),C(t),0) by integrating tf > t0 and adj = false + jointly the differential matrix Lyapunov equation + + . + W(t) = A(t)*W(t)+W(t)*A'(t)+B(t)B(t)', W(t0) = P + + and + + . + h(t) = trace(C(t)W(t)C'(t)), h(t0) = 0; + + + or for tf < t0 and adj = true + + . + W(t) = -A(t)'*W(t)-W(t)*A(t)-C(t)'C(t), W(t0) = P + + and + + . + h(t) = -trace(B(t)'W(t)B(t)), h(t0) = 0. + + + The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, + together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), + absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = abs(tf-t0)/100`, only used if `solver = "symplectic"`) + """ + n = size(A,1) + n == size(A,2) || error("the periodic matrix A must be square") + n == size(C,2) || error("the periodic matrix C must have same number of columns as A") + T = promote_type(typeof(t0), typeof(tf)) + # using OrdinaryDiffEq + u0 = [MatrixEquations.triu2vec(P);zero(T)] + tspan = (T(t0),T(tf)) + fclyap1!(du,u,p,t) = adj ? muladdcsym1!(du, u, -1, tpmeval(A,t)', tpmeval(C,t)', tpmeval(B,t)') : + muladdcsym1!(du, u, 1, tpmeval(A,t), tpmeval(B,t), tpmeval(C,t)) + prob = ODEProblem(fclyap1!, u0, tspan) + if solver == "stiff" + if reltol > 1.e-4 + # standard stiff + sol = solve(prob, Rodas4(); reltol, abstol, save_everystep = false) + else + # high accuracy stiff + sol = solve(prob, KenCarp58(); reltol, abstol, save_everystep = false) + end + elseif solver == "non-stiff" + if reltol > 1.e-4 + # standard non-stiff + sol = solve(prob, Tsit5(); reltol, abstol, save_everystep = false) + else + # high accuracy non-stiff + sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) + end + elseif solver == "symplectic" + # high accuracy symplectic + if dt == 0 + sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) + #@show sol.retcode + if sol.retcode == :Failure + sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/100) + end + else + sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) + end + else + if reltol > 1.e-4 + # low accuracy automatic selection + sol = solve(prob, AutoTsit5(Rosenbrock23()) ; reltol, abstol, save_everystep = false) + else + # high accuracy automatic selection + sol = solve(prob, AutoVern9(Rodas5(),nonstifftol = 11/10); reltol, abstol, save_everystep = false) + end + end + return sol(tf)[end] +end +""" + pshanorm(psys, K; smarg = 1, offset = sqrt(ϵ), solver = "", reltol = 1.e-4, abstol = 1.e-7) -> nrm + +Compute the Hankel-norm of a stable continuous-time periodic system `psys = (A(t),B(t),C(t),D(t))`. +For the computation of the norm, the approach suggested in [1] is employed, +in conjunction with the multiple-shooting approach using `K` discretization points. +For a periodic system of period `T`, the Hankel-norm is defined as + + nrm = sqrt(max(Λ(P(t)Q(t)))), for t ∈ [0,T] + +where `P(t)` is the controllability Gramian satisfying the periodic differential Lyapunov equation + + . + P(t) = A(t)P(t)A(t)' + B(t)B(t)', + +and `Q(t)` is the observability Gramian satisfying the periodic differential Lyapunov equation + + . + -Q(t) = A(t)'Q(t)A(t) + C(t)'C(t) . + +The norm is evaluated from the `K` time values of `P(t)` and `Q(t)` in the interval `[0,T]` and +the precision is (usually) better for larger values of `K`. + +To assess the stability, the absolute values of the characteristic multipliers of `A(t)` +must be less than `smarg-β`, where `smarg` is the discrete-time stability margin (default: `smarg = 1`) and +`β` is an offset specified via the keyword parameter `offset = β` to be used to numerically assess the stability +of eigenvalues. The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision. + +The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, +together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), +absolute accuracy `abstol` (default: `abstol = 1.e-7`) and +stepsize `dt` (default: `dt = 0`). The value stepsize is relevant only if `solver = "symplectic", in which case +an adaptive stepsize strategy is used if `dt = 0` and a fixed stepsize is used if `dt > 0`. +Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, +which are generally very efficient, but less accurate. If `reltol < 1.e-4`, +higher order solvers are employed able to cope with high accuracy demands. + +The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: + +`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); + +`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); + +`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; + +`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); + +`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). + + +_References_ + +[1] A. Varga, On solving periodic differential matrix equations with applications to periodic system norms computation. + Proc. CDC/ECC, Seville, p.6545-6550, 2005. +""" +function PeriodicSystems.pshanorm(psys::PeriodicStateSpace{<: FourierFunctionMatrix}, K::Int = 1; smarg::Real = 1, + offset::Real = sqrt(eps()), solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) + !isstable(psys, K; smarg, offset, solver, reltol, abstol) && error("The system must be stable") # unstable system + Q = pgclyap(psys.A, psys.C'*psys.C, K; adj = true, solver, reltol, abstol) + P = pgclyap(psys.A, psys.B*psys.B', K; adj = false, solver, reltol, abstol) + return sqrt(maximum(norm.(eigvals(P*Q),Inf))) +end +""" + pslinfnorm(psys, K = 100; hinfnorm = false, rtolinf = 0.001, offset = sqrt(ϵ), reltol, abstol, dt) -> (linfnorm, fpeak) + pshinfnorm(psys, K = 100; rtolinf = 0.001, offset = sqrt(ϵ), reltol, abstol, dt) -> (linfnorm, fpeak) + +Compute for a continuous-time periodic system `psys = (A(t),B(t),C(t),D(t)` the `L∞` norm `linfnorm` with `pslinfnorm` or the +`H∞` norm `hinfnorm` with ` pshinfnorm` as defined in [1]. +If `hinfnorm = true`, the `H∞` norm is computed with ` pslinfnorm`. +The corresponding peak frequency `fpeak`, where the peak gain is achieved, is usually not determined, excepting in some limiting cases. +The `L∞` norm is infinite if `psys` has poles on the imaginary axis. + +To check the lack of poles on the imaginary axis, the characteristic exponents of `A(t)` +must not have real parts in the interval `[-β,β]`, where `β` is the stability domain boundary offset. +The offset `β` to be used can be specified via the keyword parameter `offset = β`. +The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision. + +A bisection based algorith, as described in [2], is employed to approximate the `L∞` norm, and the keyword argument `rtolinf` specifies the relative accuracy for the computed infinity norm. +The default value used for `rtolinf` is `0.001`. + +If `hinfnorm = true`, the `H∞` norm is computed. +In this case, the stability of the system is additionally checked and +the `H∞` norm is infinite for an unstable system. +To check the stability, the characteristic exponents of `A(t)` must have real parts less than `-β`. + +The ODE solver to be employed to compute the characteristic multipliers of the system Hamiltonian can be specified using the keyword argument `solver` (default: `solver = "symplectic"`) +together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), +absolute accuracy `abstol` (default: `abstol = 1.e-7`) and +stepsize `dt` (default: `dt = 0`). The value stepsize is relevant only if `solver = "symplectic", in which case +an adaptive stepsize strategy is used if `dt = 0` and a fixed stepsize is used if `dt > 0`. +Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, +which are generally very efficient, but less accurate. If `reltol < 1.e-4`, +higher order solvers are employed able to cope with high accuracy demands. + +The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: + +`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); + +`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); + +`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; + +`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); + +`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). + + +_References:_ + +[1] P. Colaneri. Continuous-time periodic systems in H2 and H∞: Part I: Theoretical Aspects. + Kybernetika, 36:211-242, 2000. + +[2] A. Varga, On solving periodic differential matrix equations with applications to periodic system norms computation. + Proc. CDC/ECC, Seville, p.6545-6550, 2005. +""" +function PeriodicSystems.pslinfnorm(psys::PeriodicStateSpace{<: FourierFunctionMatrix{:c,T}}, K::Int=100; hinfnorm::Bool = false, rtolinf::Real = float(real(T))(0.001), fast::Bool = true, + offset::Real = sqrt(eps(float(real(T)))), solver = "symplectic", reltol = 1e-6, abstol = 1e-7, dt = 0) where {T} + + islti(psys) && (return glinfnorm(psaverage(psys); hinfnorm, rtolinf)) + + T1 = T <: BlasFloat ? T : (T <: Num ? Float64 : promote_type(Float64,T)) + ZERO = real(T1)(0) + + # detect zero case + # iszero(sys, atol1 = atol1, atol2 = atol2, rtol = rtol) && (return ZERO, ZERO) + + # quick exit for zero dimensions + ny, nu = size(psys) + (nu == 0 || ny == 0) && (return ZERO, ZERO) + + # quick exit in constant case + if PeriodicMatrices.isconstant(psys.D) + gd = opnorm(tpmeval(psys.D,0),2) + else + f = t-> -opnorm(tpmeval(psys.D,t),2) + gd = optimize(f,0,period,Optim.Brent(),rel_tol = eps()).minimum + end + + size(psys.A,1) == 0 && (return gd, ZERO) + + β = abs(offset) + epsm = eps(T1) + toluc1 = 100 * epsm # for simple roots + toluc2 = 10 * sqrt(epsm) # for double root + + # check for poles on the boundary of the stability domain + ft = psceig(psys.A,K) + stable = all(real.(ft) .< -β) + hinfnorm && !stable && (return Inf, NaN) + for i = 1:length(ft) + real(ft[i]) >= -β && real(ft[i]) <= β && (return Inf, T1 <: Complex ? imag(ft[i]) : abs(imag(ft[i]))) + end + + zeroD = gd == 0 + + zeroD && (iszero(psys.B) || iszero(psys.C)) && (return ZERO, ZERO) + + if stable + gh = pshanorm(psys) + gl = max(gd,gh) + gu = gd + 2*gh + else + gl = gd; gu = glinfnorm(ps2fls(psys, 10); rtolinf)[1] + end + solver == "symplectic" && K < 10 && (K = 10; @warn "number of sampling values reset to K = $K") + iter = 1 + while checkham1(psys,gu,K,toluc1, toluc2, solver, reltol, abstol, dt) && iter < 10 + gu *= 2 + iter += 1 + end + # use bisection to determine + g = (gl+gu)/2 + while gu-gl > gu*rtolinf + PeriodicSystems.checkham1(psys,g,K,toluc1, toluc2, solver, reltol, abstol, dt) ? gl = g : gu = g + g = (gl+gu)/2 + end + return g, nothing +end +function PeriodicSystems.checkham1(psys::PeriodicStateSpace{<: FourierFunctionMatrix{:c,T}}, g::Real, K::Int,toluc1, toluc2, solver, reltol, abstol, dt) where {T} + if iszero(psys.D) + Ht = [[psys.A (psys.B*psys.B')/g^2]; [-psys.C'*psys.C -psys.A']] + else + Rti = inv(g^2*I-psys.D'*psys.D) + At = psys.A+psys.B*Rti*psys.D'*psys.C + Gt = psys.B*Rti*psys.B' + Qt = -psys.C'*(I+psys.D*Rti*psys.D')*psys.C + Ht = [[At Gt]; [Qt -At']] + end + heigs = pseig(Ht, K; solver, reltol, abstol, dt) + heigs = heigs[abs.(heigs) .< 1/toluc2] + + # detect unit-circle eigenvalues + mag = abs.(heigs) + uceig = heigs[abs.(1 .- mag) .< toluc2 .+ toluc1*mag] + return length(uceig) > 0 +end +PeriodicSystems.checkham1(psys::PeriodicStateSpace{<: FourierFunctionMatrix{:c,T}}, g::Real, K::Int) where {T} = PeriodicSystems.checkham1(psys,g,K,eps(), sqrt(eps()), "symplectic", 1.e-10,1.e-10,0) +function PeriodicSystems.pshinfnorm(psys::PeriodicStateSpace{<: FourierFunctionMatrix{:c,T}}; + rtolinf::Real = float(real(T))(0.001), offset::Real = sqrt(eps(float(real(T)))), solver = "symplectic", reltol = 1e-6, abstol = 1e-7, dt = 0) where {T} + return pslinfnorm(psys; hinfnorm = true, rtolinf, solver, reltol, abstol, dt) +end diff --git a/ext/psconversions_Fourier.jl b/ext/psconversions_Fourier.jl new file mode 100644 index 0000000..6cc1b6a --- /dev/null +++ b/ext/psconversions_Fourier.jl @@ -0,0 +1,91 @@ +""" + psc2d([PMT,] psysc, Ts; solver, reltol, abstol, dt) -> psys::PeriodicStateSpace{PMT} + +Compute for the continuous-time periodic system `psysc = (A(t),B(t),C(t),D(t))` of period `T` and +for a sampling time `Ts`, the corresponding discretized +periodic system `psys = (Ad,Bd,Cd,Dd)` using a zero-order hold based discretization method. +The resulting discretized system `psys` has the matrices of type `PeriodicArray` by default, or +of type `PMT`, where `PMT` is one of the types `PeriodicMatrix`, `PeriodicArray`, `SwitchingPeriodicMatrix` +or `SwitchingPeriodicArray`. + +The discretization is performed by determining the monodromy matrix as a product of +`K = T/Ts` state transition matrices of the extended state-space matrix `[A(t) B(t); 0 0]` +by integrating numerically the corresponding homogeneous linear ODE. +The ODE solver to be employed can be +specified using the keyword argument `solver`, together with +the required relative accuracy `reltol` (default: `reltol = 1.e-3`), +absolute accuracy `abstol` (default: `abstol = 1.e-7`) and/or +the fixed step length `dt` (default: `dt = Ts/10`). +Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, +which are generally very efficient, but less accurate. If `reltol < 1.e-4`, +higher order solvers are employed able to cope with high accuracy demands. + +The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: + +`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); + +`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); + +`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; + +`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); + +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). + +For large values of `K`, parallel computation of factors can be alternatively performed +by starting Julia with several execution threads. +The number of execution threads is controlled either by using the `-t/--threads` command line argument +or by using the `JULIA_NUM_THREADS` environment variable. +""" +function PeriodicSystems.psc2d(psysc::PeriodicStateSpace{PM}, Ts::Real; solver::String = "auto", reltol = 1e-3, abstol = 1e-7, dt = Ts/10) where {PM <: FourierFunctionMatrix} + Ts > 0 || error("the sampling time Ts must be positive") + period = psysc.period + r = rationalize(period/Ts) + denominator(r) == 1 || error("incommensurate period and sample time") + K = numerator(r) + + T = eltype(psysc) + T1 = T <: BlasFloat ? T : (T <: Num ? Float64 : promote_type(Float64,T)) + n, m = size(psysc.B); p = size(psysc.D,1) + PMT = PeriodicArray + + + # quick exit in constant case + islti(psysc) && (return ps(PMT,c2d(psaverage(psysc),Ts)[1],psysc.period)) + + kk = gcd(K,psysc.A.nperiod) + ka, na = PeriodicMatrices.isconstant(psysc.A) ? (1,K) : (div(K,kk),kk) + kk = gcd(K,psysc.B.nperiod) + (kb, nb) = PeriodicMatrices.isconstant(psysc.B) ? (1,K) : (div(K,kk),kk) + kk = gcd(K,psysc.C.nperiod) + kc, nc = PeriodicMatrices.isconstant(psysc.C) ? (1,K) : (div(K,kk),kk) + kk = gcd(K,psysc.D.nperiod) + kd, nd = PeriodicMatrices.isconstant(psysc.D) ? (1,K) : (div(K,kk),kk) + Ap = Array{T,3}(undef,n,n,ka) + Bp = Array{T,3}(undef,n,m,kb) + Cp = Array{T,3}(undef,p,n,kc) + Dp = Array{T,3}(undef,p,m,kd) + + i1 = 1:n; i2 = n+1:n+m + if PeriodicMatrices.isconstant(psysc.A) && PeriodicMatrices.isconstant(psysc.B) + G = exp([ rmul!(tpmeval(psysc.A,0),Ts) rmul!(tpmeval(psysc.B,0),Ts); zeros(T1,m,n+m)]) + Ap = view(G,i1,i1) + Bp = view(G,i1,i2) + else + kab = max(ka,kb) + Gfun = PeriodicFunctionMatrix(t -> [tpmeval(psysc.A,t) tpmeval(psysc.B,t); zeros(T1,m,n+m)], period; nperiod = div(K,kab)) + #G = monodromy(Gfun, kab; Ts, solver, reltol, abstol, dt) + G = monodromy(Gfun, kab; solver, reltol, abstol, dt) + Ap = view(G.M,i1,i1,1:ka) + Bp = view(G.M,i1,i2,1:kb) + end + [copyto!(view(Cp,:,:,i),tpmeval(psysc.C,(i-1)*Ts)) for i in 1:kc] + [copyto!(view(Dp,:,:,i),tpmeval(psysc.D,(i-1)*Ts)) for i in 1:kd] + return ps(PMT(Ap,period; nperiod = na),PMT(Bp,period; nperiod = nb),PMT(Cp,period; nperiod = nc),PMT(Dp,period; nperiod = nd)) + # end PSC2D +end +function PeriodicSystems.psc2d(PMT::Type, psysc::PeriodicStateSpace{PM}, Ts::Real; kwargs...) where {PM <: FourierFunctionMatrix} + PMT ∈ (PeriodicMatrix, PeriodicArray, SwitchingPeriodicMatrix, SwitchingPeriodicArray) || + error("only discrete periodic matrix types allowed") + convert(PeriodicStateSpace{PMT}, psc2d(psysc, Ts; kwargs...)) +end diff --git a/ext/pslifting_Fourier.jl b/ext/pslifting_Fourier.jl new file mode 100644 index 0000000..74c3503 --- /dev/null +++ b/ext/pslifting_Fourier.jl @@ -0,0 +1,47 @@ +""" + ps2frls(psysc::PeriodicStateSpace, N) -> sys::DescriptorStateSpace + +Build the real frequency-lifted representation of a continuous-time periodic system. + +For a continuos-time periodic system `psysc = (A(t),B(t),C(t),D(t))`, the real +LTI state-space representation `sys = (At-Nt,Bt,Ct,Dt)` is built, where `At`, `Bt`, `Ct` and `Dt` +are truncated block Toeplitz matrices and `Nt` is a block diagonal matrix. +`N` is the number of selected harmonic components in the Fourier series of system matrices. + +_Note:_ This is an experimental implementation based on the operator representation of periodic matrices +in the [ApproxFun.jl](https://github.com/JuliaApproximation/ApproxFun.jl) package. +""" +function PeriodicSystems.ps2frls(psysc::PeriodicStateSpace{PM}, N::Int; P::Int= 1) where {T,PM <: AbstractPeriodicArray{:c,T}} + psyscfr = typeof(psysc) <: PeriodicStateSpace{FourierFunctionMatrix} ? psysc : + convert(PeriodicStateSpace{FourierFunctionMatrix},psysc) + N >= 0 || error("number of selected harmonics must be nonnegative, got $N") + (Af, Bf, Cf, Df) = P == 1 ? (psyscfr.A, psyscfr.B, psyscfr.C, psyscfr.D) : + (FourierFunctionMatrix(Fun(t -> psyscfr.A.M(t),Fourier(0..P*psyscfr.A.period))), + FourierFunctionMatrix(Fun(t -> psyscfr.B.M(t),Fourier(0..P*psyscfr.B.period))), + FourierFunctionMatrix(Fun(t -> psyscfr.C.M(t),Fourier(0..P*psyscfr.C.period))), + FourierFunctionMatrix(Fun(t -> psyscfr.D.M(t),Fourier(0..P*psyscfr.D.period)))) + + n, m = size(Bf); p = size(Cf,1); + D = Derivative(domain(Af.M)) + ND = DiagDerOp(D,n) + Aop = Af.M - ND + Cop = Multiplication(Cf.M,domainspace(ND)) + sdu = domainspace(DiagDerOp(0*D,m)) + Bop = Multiplication(Bf.M,sdu) + Dop = Multiplication(Df.M,sdu) + Ntx = 2*n*(2*N+1) + Ntu = m*(2*N+1) + Nty = p*(2*N+1) + sys = dss(Matrix(Aop[1:Ntx,1:Ntx]), Matrix(Bop[1:Ntx,1:Ntu]), Matrix(Cop[1:Nty,1:Ntx]), Matrix(Dop[1:Nty,1:Ntu])) + return sys +end + +function DiagDerOp(D::Union{ApproxFunBase.DerivativeWrapper,ApproxFunBase.ConstantTimesOperator}, n::Int) + Z = tuple(D,ntuple(n->0I,n-1)...) + for i = 2:n + Z1 = tuple(ntuple(n->0I,i-1)...,D,ntuple(n->0I,n-i)...) + Z = tuple(Z...,Z1...) + end + return hvcat(n,Z...) +end + diff --git a/ext/pstimeresp_Fourier.jl b/ext/pstimeresp_Fourier.jl new file mode 100644 index 0000000..ea13299 --- /dev/null +++ b/ext/pstimeresp_Fourier.jl @@ -0,0 +1,274 @@ +""" + psstepresp(sys[, tfinal]; ustep = ones(psys.nu), x0 = zeros(psys.nx), timesteps = 100, + state_history = false, abstol, reltol) -> (y, tout, x) + +Compute the time response of a periodic system `sys = (A(t),B(t),C(t),D(t))` to step input signals. +The final time `tfinal`, if not specified, is set equal to the period of the periodic system `sys`. +For a discrete-time system, the final time `tfinal` must be commensurate with the system sampling time `Ts` +(otherwise it is adjusted to the nearest smaller commensurate value). +The keyword argument `ustep` is a vector with as many components +as the inputs of `sys` and specifies the desired amplitudes of step inputs (default: all components are set to 1). +The keyword argument `x0` is a vector, which specifies the initial state vector at time `0`, +and is set to zero when omitted. +The keyword argument `timesteps` specifies the number of desired simulation time steps +(default: `timesteps = 100`). + +If `ns` is the total number of simulation values, `n` the number of state components, +`p` the number of system outputs and `m` the number of system inputs, then +the resulting `ns×p×m` array `y` contains the resulting time histories of the outputs of `sys`, such +that `y[:,:,j]` is the time response for the `j`-th input set to `ustep[j]` and the rest of inputs set to zero. +The vector `tout` contains the corresponding values of the time samples. +The `i`-th row `y[i,:,j]` contains the output values at time `tout[i]` of the `j`-th step response. +If the keyword parameter value `state_history = true` is used, then the resulting `ns×n×m` array`x` contains +the resulting time histories of the state vector and +the `i`-th row `x[i,:,j]` contains the state values at time `tout[i]` of the `j`-th step response. +For a discrete-time periodic system with time-varying state dimensions, `n` is the maximum value of the +dimensions of the state vector over one period. The components of `x[i,:,j]` have trailing zero values if the +corresponding state vector dimension is less than `n`. +By default, the state history is not saved and `x = nothing`. + +The total number of simulation values `ns` is set as follows: for a continuous-time system `ns = timesteps+1` +and for a discrete-time system `ns = min(timesteps,tfinal/Ts)+1`. + +For a continuous-time model an equivalent discretized model is determined to be used for simulation, provided +the time step `Δ = tfinal/timesteps` is commensurate with the system period `T` and `tfinal >= T`. +The discretization is performed by determining the monodromy matrix as a product of +state transition matrices of the extended state-space matrix `[A(t) B(t); 0 0]` +by integrating numerically the corresponding homogeneous linear ODE. +If the time step `Δ` is not commensurate with the period `T` or `tfinal < T`, then numerical integrations +of the underlying ODE systems are performed. +The ODE solver to be employed can be +specified using the keyword argument `solver`, together with +the required relative accuracy `reltol` (default: `reltol = 1.e-4`), +absolute accuracy `abstol` (default: `abstol = 1.e-7`) and/or +the fixed step length `dt` (default: `dt = Ts/10`). +Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, +which are generally very efficient, but less accurate. If `reltol < 1.e-4`, +higher order solvers are employed able to cope with high accuracy demands. + +The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: + +`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); + +`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); + +`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; + +`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); + +`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). + +For large numbers of product terms, parallel computation of factors can be alternatively performed +by starting Julia with several execution threads. +The number of execution threads is controlled either by using the `-t/--threads` command line argument +or by using the `JULIA_NUM_THREADS` environment variable. +""" +function PeriodicSystems.psstepresp(psys::PeriodicStateSpace{PM}, tfinal::Real = 0; + x0::AbstractVector{<:Number} = zeros(T,psys.nx[1]), ustep::AbstractVector{<:Number} = ones(T,psys.nu[1]), + state_history::Bool = false, timesteps::Int = 100, + solver::String = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where + {T, PM <: FourierFunctionMatrix{:c,T}} + + T1 = T <: BlasFloat ? T : promote_type(Float64,T) + n, p, m = psys.nx[1], psys.ny[1], psys.nu[1] + + m1 = length(ustep) + m == m1 || error("ustep must have as many components as system inputs") + n == length(x0) || error("x0 must have the same length as the system state vector") + + #disc = Domain == :d + disc = PeriodicMatrices.isdiscrete(psys) + ns = timesteps + ns > 0 || error("Number of time steps must be positive") + tfinal >= 0 || error("Final time must be positive") + if disc + Δ = psys.Ts + tf = tfinal == 0 ? psys.period : (rationalize(tfinal/Δ).den == 1 ? tfinal : (floor(tfinal/Δ)+1)*Δ) + N = Int(round(tf/Δ))+1 # simulation points + K = min(ns+1,N) # number of values + kstep = max(Int(floor((N-1)/ns)),1) + #kstep += (kstep*ns != K) + Δs = Δ*kstep + else + tf = tfinal == 0 ? psys.period : tfinal + Δ = tf/ns + N = ns+1 + Δs = Δ + K = N + end + tout = Vector{Float64}(0:Δs:(K-1)*Δs) + y = similar(Array{T1,3},K,p,m) + xt = copy(x0) + if disc + pa = length(psys.A) + pb = length(psys.B) + pc = length(psys.C) + pd = length(psys.D) + #@show tfinal, tf, Δ, Δs, N, K, ns, kstep, min(timesteps,tfinal/Δ)+1 + if PM <: PeriodicArray + state_history ? x = similar(Array{T1,3},N,n,m) : x = nothing + for j = 1:m + xt = copy(x0) + ut = ustep[j] + k = 0 + for i = 1:N + ia = mod(i-1,pa)+1 + ib = mod(i-1,pb)+1 + ic = mod(i-1,pc)+1 + id = mod(i-1,pd)+1 + if mod(i-1,kstep) == 0 && k < K + k += 1 + y[k,:,j] = psys.C.M[:,:,ic]*xt + psys.D.M[:,:,id][:,j]*ut + state_history && (x[k,:,j] = xt) + end + xt = psys.A.M[:,:,ia]*xt + psys.B.M[:,:,ib][:,j]*ut + end + end + else + nmax = maximum(psys.nx) + state_history ? x = zeros(T1, K, nmax, m) : x = nothing + for j = 1:m + xt = copy(x0) + ut = ustep[j] + k = 0 + for i = 1:N + ia = mod(i-1,pa)+1 + ib = mod(i-1,pb)+1 + ic = mod(i-1,pc)+1 + id = mod(i-1,pd)+1 + if mod(i-1,kstep) == 0 && k < K + k += 1 + y[k,:,j] = psys.C.M[ic]*xt + psys.D.M[id][:,j]*ut + state_history && (copyto!(view(x,k,1:length(xt),j), xt)) + end + xt = psys.A.M[ia]*xt + psys.B.M[ib][:,j]*ut + end + end + end + return y, tout, x + else + # for commensurate time-steps and final time larger than one period use discretized model + if rationalize(psys.period/Δ).den == 1 && (tout[end] ≥ psys.period || tout[end] ≈ psys.period) + state_history ? x = similar(Array{T1,3},N,n,m) : x = nothing + dt == 0 && (dt = Δ/10) + psysd = psc2d(psys, Δ; solver, reltol, abstol, dt) + pa = length(psysd.A) + pb = length(psysd.B) + pc = length(psysd.C) + pd = length(psysd.D) + for j = 1:m + xt = copy(x0) + ut = ustep[j] + for i = 1:N + ia = mod(i-1,pa)+1 + ib = mod(i-1,pb)+1 + ic = mod(i-1,pc)+1 + id = mod(i-1,pd)+1 + # y[i,:,j] = psysd.C.M[ic]*xt + psysd.D.M[id][:,j]*ut + # state_history && (x[i,:,j] = xt) + # xt = psysd.A.M[ia]*xt + psysd.B.M[ib][:,j]*ut + y[i,:,j] = psysd.C.M[:,:,ic]*xt + psysd.D.M[:,:,id][:,j]*ut + state_history && (x[i,:,j] = xt) + xt = psysd.A.M[:,:,ia]*xt + psysd.B.M[:,:,ib][:,j]*ut + end + end + return y, tout, x + else + state_history ? x = similar(Array{T1,3},N,n,m) : x = nothing + xt = copy(x0) + for j = 1:m + xt = copy(x0) + ut = ustep[j] + uf = t-> ut + Bp = psys.B[:,j] + for i = 1:N + t = tout[i] + y[i,:,j] = tpmeval(psys.C,t)*xt + tpmeval(psys.D,t)[:,j]*ut + state_history && (x[i,:,j] = xt) + @inbounds xt = tvtimeresp(psys.A, Bp, i*Δ, (i-1)*Δ, uf, xt; solver, reltol, abstol, dt) + end + end + return y, tout, x + end + end +end +function PeriodicSystems.tvtimeresp(A::PM, B::PM, tf, t0, u, x0::AbstractVector; solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where + {PM <: FourierFunctionMatrix} + """ + tvtimeresp(A, B, tf, t0, u, x0; solver, reltol, abstol) -> x::Vector + + Compute the solution at tf > t0 of the differential equation + . + x(t) = A(t)*x(t)+B(t)*u(t), x(t0) = x0, tf > t0 . + + The ODE solver to be employed can be specified using the keyword argument `solver`, + together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), + absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt' (default: `dt = 0`, only used if `solver = "symplectic"`). + Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, + which are generally very efficient, but less accurate. If `reltol < 1.e-4`, + higher order solvers are employed able to cope with high accuracy demands. + + The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: + + `solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); + + `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); + + `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); + + `solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). + """ + n = size(A,1) + n == size(A,2) || error("the periodic matrix A must be square") + n == size(B,1) || error("the periodic matrix B must have same row dimension as A") + T = promote_type(typeof(t0), typeof(tf)) + # using OrdinaryDiffEq + tspan = (T(t0),T(tf)) + # ftresp(x,p,t) = tpmeval(A,t)*x + tpmeval(B,t)*u(t) + # prob = ODEProblem(ftresp, x0, tspan) + #ftresp!(dx,x,p,t) = mul!(dx,[tpmeval(A,t) tpmeval(B,t)],[x;u(t)]) + function ftresp!(dx,x,p,t) + mul!(dx,tpmeval(A,t),x) + mul!(dx,tpmeval(B,t),u(t),1,1) + end + + prob = ODEProblem(ftresp!, x0, tspan) + + if solver == "stiff" + if reltol > 1.e-4 + # standard stiff + sol = solve(prob, Rodas4(); reltol, abstol, save_everystep = false) + else + # high accuracy stiff + sol = solve(prob, KenCarp58(); reltol, abstol, save_everystep = false) + end + elseif solver == "non-stiff" + if reltol > 1.e-4 + # standard non-stiff + sol = solve(prob, Tsit5(); reltol, abstol, save_everystep = false) + else + # high accuracy non-stiff + sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) + end + elseif solver == "symplectic" + # high accuracy symplectic + if dt == 0 + sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) + #@show sol.retcode + if sol.retcode == :Failure + sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/100) + end + else + sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) + end + else + if reltol > 1.e-4 + # low accuracy automatic selection + sol = solve(prob, AutoTsit5(Rosenbrock23()) ; reltol, abstol, save_everystep = false) + else + # high accuracy automatic selection + sol = solve(prob, AutoVern9(Rodas5(),nonstifftol = 11/10); reltol, abstol, save_everystep = false) + end + end + return sol.u[end] +end diff --git a/ext/types/PeriodicStateSpace_Fourier.jl b/ext/types/PeriodicStateSpace_Fourier.jl new file mode 100644 index 0000000..d4923ab --- /dev/null +++ b/ext/types/PeriodicStateSpace_Fourier.jl @@ -0,0 +1,64 @@ +function PeriodicSystems.PeriodicStateSpace(A::FFM1, B::FFM2, C::FFM3, D::FFM4) where {FFM1 <: FourierFunctionMatrix, FFM2 <: FourierFunctionMatrix, FFM3 <: FourierFunctionMatrix, FFM4 <: FourierFunctionMatrix} + period = ps_validation(A, B, C, D) + T = promote_type(eltype(A),eltype(B),eltype(C),eltype(D)) + PeriodicStateSpace{FourierFunctionMatrix{:c,T,Fun}}((period == A.period && T == eltype(A)) ? A : FourierFunctionMatrix{:c,T}(A,period), + (period == B.period && T == eltype(B)) ? B : FourierFunctionMatrix{:c,T}(B,period), + (period == C.period && T == eltype(C)) ? C : FourierFunctionMatrix{:c,T}(C,period), + (period == D.period && T == eltype(D)) ? D : FourierFunctionMatrix{:c,T}(D,period), + Float64(period)) +end +# function PeriodicStateSpace(A::FFM1, B::FFM2, C::FFM3, D::FFM4) where {FFM1 <: FourierFunctionMatrix, FFM2 <: FourierFunctionMatrix, FFM3 <: FourierFunctionMatrix, FFM4 <: FourierFunctionMatrix} +# period = ps_validation(A, B, C, D) +# T = promote_type(eltype(A),eltype(B),eltype(C),eltype(D)) +# PeriodicStateSpace{FourierFunctionMatrix{:c,T}}((period == A.period && T == eltype(A)) ? A : FourierFunctionMatrix{:c,T}(A,period), +# (period == B.period && T == eltype(B)) ? B : FourierFunctionMatrix{:c,T}(B,period), +# (period == C.period && T == eltype(C)) ? C : FourierFunctionMatrix{:c,T}(C,period), +# (period == D.period && T == eltype(D)) ? D : FourierFunctionMatrix{:c,T}(D,period), +# Float64(period)) +# end + + +function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, sys::PeriodicStateSpace{<:FourierFunctionMatrix}) + summary(io, sys); println(io) + n = size(sys.A,1) + p, m = size(sys.D) + T = eltype(sys) + if n > 0 + nperiod = sys.A.nperiod + println(io, "\nState matrix A::$T($n×$n): subperiod: $(sys.A.period/nperiod) #subperiods: $nperiod ") + PeriodicMatrices.isconstant(sys.A) ? show(io, mime, sys.A.M(0)) : show(io, mime, sys.A.M) + if m > 0 + nperiod = sys.B.nperiod + println(io, "\n\nInput matrix B::$T($n×$m): $(sys.B.period/nperiod) #subperiods: $nperiod ") + PeriodicMatrices.isconstant(sys.B) ? show(io, mime, sys.B.M(0)) : show(io, mime, sys.B.M) + else + println(io, "\n\nEmpty input matrix B.") + end + + if p > 0 + nperiod = sys.C.nperiod + println(io, "\n\nOutput matrix C::$T($p×$n): $(sys.C.period/nperiod) #subperiods: $nperiod ") + PeriodicMatrices.isconstant(sys.C) ? show(io, mime, sys.C.M(0)) : show(io, mime, sys.C.M) + else + println(io, "\n\nEmpty output matrix C.") + end + if m > 0 && p > 0 + nperiod = sys.D.nperiod + println(io, "\n\nFeedthrough matrix D::$T($p×$m): $(sys.D.period/nperiod) #subperiods: $nperiod ") + PeriodicMatrices.isconstant(sys.D) ? show(io, mime, sys.D.M(0)) : show(io, mime, sys.D.M) + else + println(io, "\n\nEmpty feedthrough matrix D.") + end + println(io, "\n\nContinuous-time periodic state-space model.") + elseif m > 0 && p > 0 + nperiod = sys.D.nperiod + println(io, "\nFeedthrough matrix D::$T($p×$m): $(sys.D.period/nperiod) #subperiods: $nperiod ") + PeriodicMatrices.isconstant(sys.D) ? show(io, mime, sys.D.M(0)) : show(io, mime, sys.D.M) + println(io, "\n\nTime-varying gain.") + else + println(io, "\nEmpty state-space model.") + end +end + + + diff --git a/src/PeriodicSystems.jl b/src/PeriodicSystems.jl index 248076b..35a2245 100644 --- a/src/PeriodicSystems.jl +++ b/src/PeriodicSystems.jl @@ -2,7 +2,7 @@ module PeriodicSystems using Reexport @reexport using PeriodicMatrices -using ApproxFun +@reexport using PeriodicMatrixEquations using DescriptorSystems using FastLapackInterface using IRKGaussLegendre @@ -23,22 +23,11 @@ import DescriptorSystems: isstable, horzcat, vertcat, blockdiag, parallel, serie import LinearAlgebra: BlasInt, BlasFloat, BlasReal, BlasComplex import PeriodicMatrices: iscontinuous, isdiscrete -# function ps1(A::T) where {T <: PeriodicSymbolicMatrix} -# return typeof(A) -# end - -# export ps1 - export PeriodicStateSpace export ps, islti, ps_validation export psaverage, psc2d, psmrc2d, psteval, pseval, psparallel, psseries, psappend, pshorzcat, psvertcat, psinv, psfeedback export ps2fls, ps2frls, ps2ls, ps2spls -export pspole, pszero, isstable, psh2norm, pshanorm, pslinfnorm, pstimeresp, psstepresp -export pdlyap, pdlyap2, prdlyap, pfdlyap, pslyapd, pslyapd2, pdlyaps!, pdlyaps1!, pdlyaps2!, pdlyaps3!, dpsylv2, dpsylv2!, pslyapdkr, dpsylv2krsol!, kronset! -export prdplyap, pfdplyap, pdplyap, psplyapd -export pclyap, pfclyap, prclyap, pgclyap, pgclyap2, tvclyap_eval -export pcplyap, pfcplyap, prcplyap, pgcplyap, tvcplyap_eval -export pcric, prcric, pfcric, tvcric, pgcric, prdric, pfdric, tvcric_eval +export pspole, pszero, isstable, psh2norm, pshanorm, pslinfnorm, pstimeresp, psstepresp, tvtimeresp, tvh2norm export psfeedback, pssfeedback, pssofeedback export pcpofstab_sw, pcpofstab_hr, pdpofstab_sw, pdpofstab_hr, pclqr, pclqry, pdlqr, pdlqry, pdkeg, pckeg, pdkegw, pckegw, pdlqofc, pdlqofc_sw, pclqofc_sw, pclqofc_hr @@ -55,10 +44,6 @@ include("pslifting.jl") include("pstimeresp.jl") include("psops.jl") include("psanalysis.jl") -include("pslyap.jl") -include("psclyap.jl") -include("pscric.jl") -include("psdric.jl") include("psstab.jl") end diff --git a/src/ps.jl b/src/ps.jl index 418ee10..157295a 100644 --- a/src/ps.jl +++ b/src/ps.jl @@ -147,7 +147,7 @@ function ps(PMT::Type, sys::DST, period::Real; ns::Int = 1) where {DST <: Descri sys.E == I || error("only standard state-spece models supported") Ts = sys.Ts if Ts == 0 - PMT ∈ (PeriodicFunctionMatrix, HarmonicArray, PeriodicSwitchingMatrix, PeriodicTimeSeriesMatrix, PeriodicSymbolicMatrix, FourierFunctionMatrix) || + PMT ∈ (PeriodicFunctionMatrix, HarmonicArray, PeriodicSwitchingMatrix, PeriodicTimeSeriesMatrix, PeriodicSymbolicMatrix) || error("only continuous periodic matrix types allowed") ps(PMT(sys.A,period), PMT(sys.B,period), PMT(sys.C,period), PMT(sys.D,period)) else diff --git a/src/psanalysis.jl b/src/psanalysis.jl index 0cd753d..1fb0e2d 100644 --- a/src/psanalysis.jl +++ b/src/psanalysis.jl @@ -22,7 +22,7 @@ pspole(psys::PeriodicStateSpace{<: PeriodicSwitchingMatrix}) = psceig(psys.A) pspole(psys::PeriodicStateSpace{<: SwitchingPeriodicMatrix}) = psceig(convert(PeriodicMatrix,psys.A)) pspole(psys::PeriodicStateSpace{<: HarmonicArray}, N::Int = 10; kwargs...) = psceighr(psys.A, N; kwargs...) #pspole(psys::PeriodicStateSpace{<: HarmonicArray}, N::Int = 10; kwargs...) = psceig(psys.A, N; kwargs...) # fallback version -pspole(psys::PeriodicStateSpace{<: FourierFunctionMatrix}, N::Int = 10; kwargs...) = psceigfr(psys.A, N; kwargs...) +#pspole(psys::PeriodicStateSpace{<: FourierFunctionMatrix}, N::Int = 10; kwargs...) = psceigfr(psys.A, N; kwargs...) """ pszero(psys::PeriodicStateSpace{HarmonicArray}[, N]; P, atol, rtol, fast) -> val pszero(psys::PeriodicStateSpace{PeriodicFunctionMatrix}[, N]; P, atol, rtol, fast) -> val @@ -87,72 +87,6 @@ function pszero(psys::PeriodicStateSpace{<: HarmonicArray}, N::Union{Int,Missing return σf end end -""" - pszero(psys::PeriodicStateSpece{FourierFunctionMatrix}[, N]; P, atol, rtol, fast) -> val - -Compute the finite and infinite zeros of a continuous-time periodic system `psys = (Af(t), Bf(t), Cf(t), Df(t))` in `val`, -where the periodic system matrices `Af(t)`, `Bf(t)`, `Cf(t)`, and `Df(t)` are in a _Fourier Function Matrix_ representation. -`N` is the number of selected harmonic components in the Fourier series of the system matrices (default: `N = max(20,nh-1)`, -where `nh` is the maximum number of harmonics terms) and the keyword parameter `P` is the number of full periods -to be considered (default: `P = 1`) to build -a frequency-lifted LTI representation based on truncated block Toeplitz matrices. - -The computation of the zeros of the _real_ lifted system is performed by reducing the corresponding system pencil -to an appropriate Kronecker-like form which exhibits the finite and infinite eigenvalues. -The reduction is performed using orthonal similarity transformations and involves rank decisions based on rank revealing QR-decompositions with column pivoting, -if `fast = true`, or, the more reliable, SVD-decompositions, if `fast = false`. For a system `psys` of period `T`, -the finite zeros are determined as those eigenvalues which have imaginary parts in the interval `[-ω/2, ω/2]`, where `ω = 2π/(P*T)`. -To eliminate possible spurious finite eigenvalues, the intersection of two finite eigenvalue sets is computed -for two lifted systems obtained for `N` and `N+2` harmonic components. -The infinite zeros are determined as the infinite zeros of the LTI system `(Af(ti), Bf(ti), Cf(ti), Df(ti))` -resulting for a random time value `ti`. _Warning:_ While this evaluation of the number of infinite zeros mostly -provides the correct result, there is no theoretical assessment of this approach (counterexamples are welcome!). - -The keyword arguments `atol` and `rtol` specify the absolute and relative tolerances for the nonzero -elements of the underlying lifted system pencil, respectively. -The default relative tolerance is `n*ϵ`, where `n` is the size of the smallest dimension of the pencil, and `ϵ` is the -working machine epsilon. -""" -function pszero(psys::PeriodicStateSpace{<: FourierFunctionMatrix}, N::Union{Int,Missing} = missing; P::Int= 1, fast::Bool = true, atol::Real = 0, rtol::Real = 0) - ismissing(N) && (N = max(20, maximum(ncoefficients.(Matrix(psys.A.M))), maximum(ncoefficients.(Matrix(psys.B.M))), - maximum(ncoefficients.(Matrix(psys.C.M))), maximum(ncoefficients.(Matrix(psys.A.M))))) - (N == 0 || islti(psys) ) && (return MatrixPencils.spzeros(dssdata(psaverage(psys))...; fast, atol1 = atol, atol2 = atol, rtol)[1]) - - # employ heuristics to determine fix finite zeros by comparing two sets of computed zeros - z = MatrixPencils.spzeros(dssdata(ps2frls(psys, N; P))...; fast, atol1 = atol, atol2 = atol, rtol)[1] - - period = psys.A.period - ωhp2 = pi/P/period - n = size(psys.A,1) - T = promote_type(Float64, eltype(psys.A)) - zf = z[isfinite.(z)] - ind = sortperm(imag(zf),by=abs); - nf = count(abs.(imag(zf[ind[1:min(4*n,length(ind))]])) .<= ωhp2*(1+sqrt(eps(T)))) - zf = zf[ind[1:nf]] - - z2 = MatrixPencils.spzeros(dssdata(ps2frls(psys, N+2; P))...; fast, atol1 = atol, atol2 = atol, rtol)[1] - zf2 = z2[isfinite.(z2)] - ind = sortperm(imag(zf2),by=abs); - nf2 = count(abs.(imag(zf2[ind[1:min(4*n,length(ind))]])) .<= ωhp2*(1+sqrt(eps(T)))) - zf2 = zf2[ind[1:nf2]] - σf = Complex{T}[] - nf < nf2 || ((zf, zf2) = (zf2, zf)) - atol > 0 || (norms = max(norm(coefficients(psys.A.M),Inf),norm(coefficients(psys.B.M),Inf),norm(coefficients(psys.C.M),Inf),norm(coefficients(psys.D.M),Inf))) - tol = atol > 0 ? atol : (rtol > 0 ? rtol*norms : sqrt(eps(T))*norms) - for i = 1:min(nf,nf2) - minimum(abs.(zf2 .- zf[i])) < tol && push!(σf,zf[i]) - end - isreal(σf) && (σf = real(σf)) - - if any(isinf.(z)) - # Conjecture: The number of infinite zeros is the same as that of the time-evaluated system! - zm = MatrixPencils.spzeros(dssdata(psteval(psys, period*rand()))...; fast, atol1 = atol, atol2 = atol, rtol)[1] - zf = [σf; zm[isinf.(zm)]] - end - nz = length(zf) - nz > n && (@warn "$(nz-n) spurious finite zero(s) present") - return zf -end """ pszero(psys::PeriodicStateSpace{PeriodicMatrix}[, K]; atol, rtol, fast) -> val pszero(psys::PeriodicStateSpace{PeriodicArray}[, K]; atol, rtol, fast) -> val @@ -339,7 +273,7 @@ function psh2norm(psys::PeriodicStateSpace{<: AbstractPeriodicArray{:d,T}}; adj: end end """ - psh2norm(psys, K; adj = false, smarg = 1, fast = false, offset = sqrt(ϵ), solver = "", reltol = 1.e-4, abstol = 1.e-7, quad = false) -> nrm + psh2norm(psys, K; adj = false, smarg = 1, fast = false, offset = sqrt(ϵ), solver = "auto", reltol = 1.e-4, abstol = 1.e-7, quad = false) -> nrm Compute the H2-norm of a continuous-time periodic system `psys = (A(t),B(t),C(t),D(t))`. For the computation of the norm, the formulas given in [1] are employed, @@ -376,10 +310,8 @@ If `fast = false` (default) then the stability is checked using an approach base while if `fast = true` the stability is checked using a lifting-based approach. The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and -stepsize `dt` (default: `dt = 0`). The value stepsize is relevant only if `solver = "symplectic", in which case -an adaptive stepsize strategy is used if `dt = 0` and a fixed stepsize is used if `dt > 0`. +together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and +absolute accuracy `abstol` (default: `abstol = 1.e-7`). Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, which are generally very efficient, but less accurate. If `reltol < 1.e-4`, higher order solvers are employed able to cope with high accuracy demands. @@ -390,11 +322,7 @@ The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/Ordi `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); -`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). _References_ @@ -404,8 +332,8 @@ _References_ [2] A. Varga, On solving periodic differential matrix equations with applications to periodic system norms computation. Proc. CDC/ECC, Seville, p.6545-6550, 2005. """ -function psh2norm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix, HarmonicArray, FourierFunctionMatrix}}, K::Int = 1; adj::Bool = false, smarg::Real = 1, fast::Bool = false, - offset::Real = sqrt(eps()), solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0, quad = false) +function psh2norm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix, HarmonicArray}}, K::Int = 1; adj::Bool = false, smarg::Real = 1, fast::Bool = false, + offset::Real = sqrt(eps()), solver = "auto", reltol = 1e-4, abstol = 1e-7, quad = false) norm(psys.D) == 0 || (return Inf) !isstable(psys, K; smarg, offset, fast, solver, reltol, abstol) && (return Inf) # unstable system P = adj ? pgclyap(psys.A, psys.C'*psys.C, K; adj, solver, reltol, abstol) : pgclyap(psys.A, psys.B*psys.B', K; adj, solver, reltol, abstol) @@ -432,40 +360,39 @@ function psh2norm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix, Harm return sqrt(nrm*Ts*P.nperiod/psys.period) end μ = Vector{eltype(psys)}(undef,K) - solver == "symplectic" && dt == 0 && (dt = K >= 100 ? Ts : Ts*K/100) if adj #Threads.@threads for i = K:-1:1 for i = K:-1:1 ip = mod.(i-1,pp).+1 iw = ip < pp ? ip+1 : 1 - @inbounds μ[i] = tvh2norm(psys.A, psys.B, psys.C, P.values[iw], (i-1)*Ts, i*Ts; adj, solver, reltol, abstol, dt) + @inbounds μ[i] = tvh2norm(psys.A, psys.B, psys.C, P.values[iw], (i-1)*Ts, i*Ts; adj, solver, reltol, abstol) end else #Threads.@threads for i = K:-1:1 for i = 1:K ip = mod.(i-1,pp).+1 - @inbounds μ[i] = tvh2norm(psys.A, psys.B, psys.C, P.values[ip], i*Ts, (i-1)*Ts; adj, solver, reltol, abstol, dt) + @inbounds μ[i] = tvh2norm(psys.A, psys.B, psys.C, P.values[ip], i*Ts, (i-1)*Ts; adj, solver, reltol, abstol) end end return sqrt(sum(μ)*P.nperiod/psys.period) end function psh2norm(psys::PeriodicStateSpace{<:PeriodicSymbolicMatrix}, K::Int = 1; adj::Bool = false, smarg::Real = 1, fast::Bool = false, - offset::Real = sqrt(eps()), solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0, quad = false) - psh2norm(convert(PeriodicStateSpace{PeriodicFunctionMatrix},psys), K; adj, smarg, fast, offset, solver, reltol, abstol, dt, quad) + offset::Real = sqrt(eps()), solver = "auto", reltol = 1e-4, abstol = 1e-7, quad = false) + psh2norm(convert(PeriodicStateSpace{PeriodicFunctionMatrix},psys), K; adj, smarg, fast, offset, solver, reltol, abstol, quad) end function psh2norm(psys::PeriodicStateSpace{<:PeriodicTimeSeriesMatrix}, K::Int = 1; adj::Bool = false, smarg::Real = 1, fast::Bool = false, - offset::Real = sqrt(eps()), solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0, quad = false) - psh2norm(convert(PeriodicStateSpace{HarmonicArray},psys), K; adj, smarg, fast, offset, solver, reltol, abstol, dt, quad) + offset::Real = sqrt(eps()), solver = "auto", reltol = 1e-4, abstol = 1e-7, quad = false) + psh2norm(convert(PeriodicStateSpace{HarmonicArray},psys), K; adj, smarg, fast, offset, solver, reltol, abstol, quad) end -function tvh2norm(A::PM1, B::PM2, C::PM3, P::AbstractMatrix, tf, t0; adj = false, solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {PM1 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicTimeSeriesMatrix}, - PM2 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicTimeSeriesMatrix}, - PM3 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicTimeSeriesMatrix}} +function tvh2norm(A::PM1, B::PM2, C::PM3, P::AbstractMatrix, tf, t0; adj = false, solver = "auto", reltol = 1e-4, abstol = 1e-7) where + {PM1 <: Union{PeriodicFunctionMatrix,HarmonicArray,PeriodicTimeSeriesMatrix}, + PM2 <: Union{PeriodicFunctionMatrix,HarmonicArray,PeriodicTimeSeriesMatrix}, + PM3 <: Union{PeriodicFunctionMatrix,HarmonicArray,PeriodicTimeSeriesMatrix}} """ - tvh2norm(A, B, C, P, tf, to; adj, solver, reltol, abstol, dt) -> μ + tvh2norm(A, B, C, P, tf, to; adj, solver, reltol, abstol) -> μ - Cmputes the H2-norm of the system (A(t),B(t),C(t),0) by integrating tf > t0 and adj = false + Cmputes the H2-norm of the system (A(t),B(t),C(t),0) by integrating for tf > t0 and adj = false jointly the differential matrix Lyapunov equation . @@ -489,8 +416,8 @@ function tvh2norm(A::PM1, B::PM2, C::PM3, P::AbstractMatrix, tf, t0; adj = false The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, - together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), - absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = abs(tf-t0)/100`, only used if `solver = "symplectic"`) + together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and + absolute accuracy `abstol` (default: `abstol = 1.e-7`). """ n = size(A,1) n == size(A,2) || error("the periodic matrix A must be square") @@ -518,17 +445,6 @@ function tvh2norm(A::PM1, B::PM2, C::PM3, P::AbstractMatrix, tf, t0; adj = false # high accuracy non-stiff sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) end - elseif solver == "symplectic" - # high accuracy symplectic - if dt == 0 - sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) - #@show sol.retcode - if sol.retcode == :Failure - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/100) - end - else - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) - end else if reltol > 1.e-4 # low accuracy automatic selection @@ -631,7 +547,7 @@ function pshanorm(psys::PeriodicStateSpace{<: AbstractPeriodicArray{:d,T}}; smar end end """ - pshanorm(psys, K; smarg = 1, offset = sqrt(ϵ), solver = "", reltol = 1.e-4, abstol = 1.e-7) -> nrm + pshanorm(psys, K; smarg = 1, offset = sqrt(ϵ), solver = "auto", reltol = 1.e-4, abstol = 1.e-7) -> nrm Compute the Hankel-norm of a stable continuous-time periodic system `psys = (A(t),B(t),C(t),D(t))`. For the computation of the norm, the approach suggested in [1] is employed, @@ -659,10 +575,8 @@ must be less than `smarg-β`, where `smarg` is the discrete-time stability margi of eigenvalues. The default value used for `β` is `sqrt(ϵ)`, where `ϵ` is the working machine precision. The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and -stepsize `dt` (default: `dt = 0`). The value stepsize is relevant only if `solver = "symplectic", in which case -an adaptive stepsize strategy is used if `dt = 0` and a fixed stepsize is used if `dt > 0`. +together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and +absolute accuracy `abstol` (default: `abstol = 1.e-7`). Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, which are generally very efficient, but less accurate. If `reltol < 1.e-4`, higher order solvers are employed able to cope with high accuracy demands. @@ -673,11 +587,7 @@ The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/Ordi `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); -`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). _References_ @@ -685,20 +595,20 @@ _References_ [1] A. Varga, On solving periodic differential matrix equations with applications to periodic system norms computation. Proc. CDC/ECC, Seville, p.6545-6550, 2005. """ -function pshanorm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix, HarmonicArray, FourierFunctionMatrix,PeriodicSymbolicMatrix}}, K::Int = 1; smarg::Real = 1, - offset::Real = sqrt(eps()), solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) +function pshanorm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix, HarmonicArray, PeriodicSymbolicMatrix}}, K::Int = 1; smarg::Real = 1, + offset::Real = sqrt(eps()), solver = "auto", reltol = 1e-4, abstol = 1e-7) !isstable(psys, K; smarg, offset, solver, reltol, abstol) && error("The system must be stable") # unstable system Q = pgclyap(psys.A, psys.C'*psys.C, K; adj = true, solver, reltol, abstol) P = pgclyap(psys.A, psys.B*psys.B', K; adj = false, solver, reltol, abstol) return sqrt(maximum(norm.(eigvals(P*Q),Inf))) end # function pshanorm(psys::PeriodicStateSpace{<:PeriodicSymbolicMatrix}, K::Int = 1; smarg::Real = 1, -# offset::Real = sqrt(eps()), solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) +# offset::Real = sqrt(eps()), solver = "auto", reltol = 1e-4, abstol = 1e-7) # pshanorm(convert(PeriodicStateSpace{PeriodicFunctionMatrix},psys), K; smarg, offset, solver, reltol, abstol, dt) # end function pshanorm(psys::PeriodicStateSpace{<:PeriodicTimeSeriesMatrix}, K::Int = 1; smarg::Real = 1, - offset::Real = sqrt(eps()), solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) - pshanorm(convert(PeriodicStateSpace{HarmonicArray},psys), K; smarg, offset, solver, reltol, abstol, dt) + offset::Real = sqrt(eps()), solver = "auto", reltol = 1e-4, abstol = 1e-7) + pshanorm(convert(PeriodicStateSpace{HarmonicArray},psys), K; smarg, offset, solver, reltol, abstol) end """ pslinfnorm(psys, hinfnorm = false, rtolinf = 0.001, fast = true, offset = sqrt(ϵ)) -> (linfnorm, fpeak) @@ -1085,7 +995,7 @@ The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/Ordi `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). _References:_ @@ -1096,7 +1006,7 @@ _References:_ [2] A. Varga, On solving periodic differential matrix equations with applications to periodic system norms computation. Proc. CDC/ECC, Seville, p.6545-6550, 2005. """ -function pslinfnorm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c,T}, HarmonicArray{:c,T}, FourierFunctionMatrix{:c,T},PeriodicSymbolicMatrix{:c,T}}}, K::Int=100; hinfnorm::Bool = false, rtolinf::Real = float(real(T))(0.001), fast::Bool = true, +function pslinfnorm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c,T}, HarmonicArray{:c,T}, PeriodicSymbolicMatrix{:c,T}}}, K::Int=100; hinfnorm::Bool = false, rtolinf::Real = float(real(T))(0.001), fast::Bool = true, offset::Real = sqrt(eps(float(real(T)))), solver = "symplectic", reltol = 1e-6, abstol = 1e-7, dt = 0) where {T} islti(psys) && (return glinfnorm(psaverage(psys); hinfnorm, rtolinf)) @@ -1159,7 +1069,7 @@ function pslinfnorm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c, end return g, nothing end -function checkham1(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c,T}, HarmonicArray{:c,T}, FourierFunctionMatrix{:c,T},PeriodicSymbolicMatrix{:c,T}}}, g::Real, K::Int,toluc1, toluc2, solver, reltol, abstol, dt) where {T} +function checkham1(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c,T}, HarmonicArray{:c,T}, PeriodicSymbolicMatrix{:c,T}}}, g::Real, K::Int,toluc1, toluc2, solver, reltol, abstol, dt) where {T} if iszero(psys.D) Ht = [[psys.A (psys.B*psys.B')/g^2]; [-psys.C'*psys.C -psys.A']] else @@ -1177,7 +1087,7 @@ function checkham1(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c,T uceig = heigs[abs.(1 .- mag) .< toluc2 .+ toluc1*mag] return length(uceig) > 0 end -checkham1(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c,T}, HarmonicArray{:c,T}, FourierFunctionMatrix{:c,T},PeriodicSymbolicMatrix{:c,T}}}, g::Real, K::Int) where {T} = checkham1(psys,g,K,eps(), sqrt(eps()), "symplectic", 1.e-10,1.e-10,0) +checkham1(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c,T}, HarmonicArray{:c,T}, PeriodicSymbolicMatrix{:c,T}}}, g::Real, K::Int) where {T} = checkham1(psys,g,K,eps(), sqrt(eps()), "symplectic", 1.e-10,1.e-10,0) function pshinfnorm(psys::PeriodicStateSpace{<: Union{PeriodicFunctionMatrix{:c,T}, HarmonicArray{:c,T}, FourierFunctionMatrix{:c,T},PeriodicSymbolicMatrix{:c,T}}}; rtolinf::Real = float(real(T))(0.001), offset::Real = sqrt(eps(float(real(T)))), solver = "symplectic", reltol = 1e-6, abstol = 1e-7, dt = 0) where {T} return pslinfnorm(psys; hinfnorm = true, rtolinf, solver, reltol, abstol, dt) diff --git a/src/psclyap.jl b/src/psclyap.jl deleted file mode 100644 index f0386ae..0000000 --- a/src/psclyap.jl +++ /dev/null @@ -1,1317 +0,0 @@ -""" - pclyap(A, C; K = 10, adj = false, solver, reltol, abstol, intpol, intpolmeth) -> X - pclyap(A, C; K = 10, adj = false, solver, reltol, abstol) -> X - -Solve the periodic Lyapunov differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + C(t) , if adj = false, - -or - - . - -X(t) = A(t)'X(t) + X(t)A(t) + C(t) , if adj = true. - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods. -Additionally `C` must be symmetric. -The resulting symmetric periodic solution `X` has the type `PeriodicFunctionMatrix` and -`X(t)` can be used to evaluate the value of `X` at time `t`. -`X` has the period set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -The multiple-shooting method of [1] is employed to convert the (continuous-time) periodic differential Lyapunov equation -into a discrete-time periodic Lyapunov equation satisfied by a multiple point generator of the solution. -The keyword argument `K` specifies the number of grid points to be used -for the discretization of the continuous-time problem (default: `K = 10`). -If `A` and `C` are of types `PeriodicTimeSeriesMatrix` or `PeriodicSwitchingMatrix`, then `K` specifies the number of grid points used between two consecutive switching time values (default: `K = 1`). -The multiple point periodic generator is computed by solving the appropriate discrete-time periodic Lyapunov -equation using the periodic Schur method of [2]. -The resulting periodic generator is finally converted into a periodic function matrix which determines for a given `t` -the function value `X(t)` by integrating the appropriate ODE from the nearest grid point value. - -To speedup function evaluations, interpolation based function evaluations can be used -by setting the keyword argument `intpol = true` (default: `intpol = false`). -In this case the interpolation method to be used can be specified via the keyword argument -`intpolmeth = meth`. The allowable values for `meth` are: `"constant"`, `"linear"`, `"quadratic"` and `"cubic"` (default). -Interpolation is not possible if `A` and `C` are of type `PeriodicSwitchingMatrix`. - -The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, together with -the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and -absolute accuracy `abstol` (default: `abstol = 1.e-7`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -Parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. - -""" -function pclyap(A::PeriodicFunctionMatrix, C::PeriodicFunctionMatrix; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7, intpol = false, intpolmeth = "cubic", stability_check = false) - if intpol - return convert(PeriodicFunctionMatrix,pgclyap(A, C, K; adj, solver, reltol, abstol, stability_check), method = intpolmeth) - else - W0 = pgclyap(A, C, K; adj, solver, reltol, abstol, stability_check) - return PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, A, C; solver, adj, reltol, abstol),A.period) - end -end -pclyap(A::PeriodicFunctionMatrix, C::AbstractMatrix; kwargs...) = pclyap(A, PeriodicFunctionMatrix(C, A.period; nperiod = A.nperiod); kwargs...) -function pclyap(A::PeriodicSymbolicMatrix, C::PeriodicSymbolicMatrix; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7, intpol = false, intpolmeth = "cubic", stability_check = false) - At = convert(PeriodicFunctionMatrix,A) - Ct = convert(PeriodicFunctionMatrix,C) - if intpol - return convert(PeriodicFunctionMatrix,pgclyap(At, Ct, K; adj, solver, reltol, abstol, stability_check), method = intpolmeth) - else - W0 = pgclyap(At, Ct, K; adj, solver, reltol, abstol, stability_check) - PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, At, Ct; solver, adj, reltol, abstol), W0.period; nperiod = W0.nperiod) - end - #convert(PeriodicSymbolicMatrix, pgclyap(convert(PeriodicFunctionMatrix,A), convert(PeriodicFunctionMatrix,C), K; adj, solver, reltol, abstol)) -end -function pclyap(A::HarmonicArray, C::HarmonicArray; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7, intpol = false, intpolmeth = "cubic", stability_check = false) - if intpol - return convert(PeriodicFunctionMatrix,pgclyap(A, C, K; adj, solver, reltol, abstol, stability_check), method = intpolmeth) - else - W0 = pgclyap(A, C, K; adj, solver, reltol, abstol, stability_check) - PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, A, C; solver, adj, reltol, abstol), W0.period; nperiod = W0.nperiod) - end - #convert(HarmonicArray, pgclyap(A, C, K; adj, solver, reltol, abstol)) -end -function pclyap(A::FourierFunctionMatrix, C::FourierFunctionMatrix; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7, intpol = false, intpolmeth = "cubic", stability_check = false) - if intpol - return convert(PeriodicFunctionMatrix,pgclyap(A, C, K; adj, solver, reltol, abstol, stability_check), method = intpolmeth) - else - W0 = pgclyap(A, C, K; adj, solver, reltol, abstol, stability_check) - PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, A, C; solver, adj, reltol, abstol), W0.period; nperiod = W0.nperiod) - end - #convert(FourierFunctionMatrix, pgclyap(A, C, K; adj, solver, reltol, abstol)) -end -function pclyap(A::PeriodicTimeSeriesMatrix, C::PeriodicTimeSeriesMatrix; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7, intpol = false, intpolmeth = "cubic", stability_check = false) - if intpol - return convert(PeriodicFunctionMatrix,pgclyap(A, C, K; adj, solver, reltol, abstol, stability_check), method = intpolmeth) - #return convert(PeriodicFunctionMatrix,pgclyap(convert(HarmonicArray,A), convert(HarmonicArray,C), K; adj, solver, reltol, abstol, stability_check), method = intpolmeth) - else - #W0 = pgclyap(A, C, K; adj, solver, reltol, abstol, stability_check) - W0 = pgclyap(convert(HarmonicArray,A), convert(HarmonicArray,C), K; adj, solver, reltol, abstol, stability_check) - PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, A, C; solver, adj, reltol, abstol), W0.period; nperiod = W0.nperiod) - end - # pgclyap(convert(HarmonicArray,A), convert(HarmonicArray,C), K; adj, solver, reltol, abstol) -end -function pclyap(A::PeriodicSwitchingMatrix, C::PeriodicSwitchingMatrix; K::Int = 1, adj = false, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7, stability_check = false) - At = convert(PeriodicFunctionMatrix,A) - Ct = convert(PeriodicFunctionMatrix,C) - W0 = pgclyap(At, Ct, K; adj, solver, reltol, abstol, stability_check) - PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, At, Ct; solver, adj, reltol, abstol), W0.period; nperiod = W0.nperiod) -end - - -for PM in (:PeriodicFunctionMatrix, :PeriodicSymbolicMatrix, :HarmonicArray, :FourierFunctionMatrix, :PeriodicTimeSeriesMatrix) - @eval begin - function prclyap(A::$PM, C::$PM; K::Int = 10, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7, intpol = false, intpolmeth = "cubic") - pclyap(A, C; K, adj = true, solver, reltol, abstol, intpol, intpolmeth) - end - function prclyap(A::$PM, C::AbstractMatrix; kwargs...) - prclyap(A, $PM(C, A.period; nperiod = A.nperiod); kwargs...) - end - function pfclyap(A::$PM, C::$PM; K::Int = 10, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7, intpol = false, intpolmeth = "cubic") - pclyap(A, C; K, adj = false, solver, reltol, abstol, intpol, intpolmeth) - end - function pfclyap(A::$PM, C::AbstractMatrix; kwargs...) - pfclyap(A, $PM(C, A.period; nperiod = A.nperiod); kwargs...) - end - end -end -function prclyap(A::PM, C::PM; K::Int = 1, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7) where {PM <: PeriodicSwitchingMatrix} - pclyap(A, C; K, adj = true, solver, reltol, abstol) -end -function prclyap(A::PM, C::AbstractMatrix; kwargs...) where {PM <: PeriodicSwitchingMatrix} - prclyap(A, PM(C, A.period; nperiod = A.nperiod); kwargs...) -end -function pfclyap(A::PM, C::PM; K::Int = 1, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7) where {PM <: PeriodicSwitchingMatrix} - pclyap(A, C; K, adj = false, solver, reltol, abstol) -end -function pfclyap(A::PM, C::AbstractMatrix; kwargs...) where {PM <: PeriodicSwitchingMatrix} - pfclyap(A, PM(C, A.period; nperiod = A.nperiod); kwargs...) -end - -""" - pfclyap(A, C; K = 10, solver, reltol, abstol, intpol, intpolmeth) -> X - pfclyap(A, C; K = 10, solver, reltol, abstol) -> X - -Solve the periodic forward-time Lyapunov differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + C(t) . - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods, -and additionally `C` must be symmetric. The resulting symmetric periodic solution `X` has the period -set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -This function is merely an interface to [`pclyap`](@ref) (see this function for the description of keyword parameters). -""" -pfclyap(A::PeriodicFunctionMatrix, C::PeriodicFunctionMatrix; K::Int = 10, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7) -""" - prclyap(A, C; K = 10, solver, reltol, abstol, intpol, intpolmeth) -> X - prclyap(A, C; K = 10, solver, reltol, abstol) -> X - -Solve the periodic reverse-time Lyapunov differential equation - - . - -X(t) = A(t)'X(t) + X(t)A(t) + C(t). - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods, -and additionally `C` must be symmetric. The resulting symmetric periodic solution `X` has the period -set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -This function is merely an interface to [`pclyap`](@ref) (see this function for the description of keyword parameters). -""" -prclyap(A::PeriodicFunctionMatrix, C::PeriodicFunctionMatrix; K::Int = 10, solver = "non-stiff", reltol = 1.e-4, abstol = 1.e-7) - -""" - pgclyap(A, C[, K = 1]; adj = false, solver, reltol, abstol, dt) -> X - -Compute periodic generators for the periodic Lyapunov differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + C(t) , if adj = false, - -or - - . - -X(t) = A(t)'X(t) + X(t)A(t) + C(t) , if adj = true. - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods, -and additionally `C` must be symmetric. -If `A` and `C` have the types `PeriodicFunctionMatrix`, `HarmonicArray`, `FourierFunctionMatrix` or `PeriodicTimeSeriesMatrix`, -then the resulting `X` is a collection of periodic generator matrices determined -as a periodic time-series matrix with `N` components, where `N = 1` if `A` and `C` are constant matrices -and `N = K` otherwise. -If `A` and `C` have the type `PeriodicSwitchingMatrix`, then `X` is a collection of periodic generator matrices -determined as a periodic switching matrix, -whose switching times are the unique switching times contained in the union of the switching times of `A` and `C`. -If `K > 1`, a refined grid of `K` equidistant values is used for each two consecutive -switching times in the union. -The period of `X` is set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. Any component matrix of `X` is a valid initial value to be used to generate the -solution over a full period by integrating the appropriate differential equation. -The multiple-shooting method of [1] is employed, first, to convert the continuous-time periodic Lyapunov differential equation -into a discrete-time periodic Lyapunov equation satisfied by -the generator solution in the grid points and then to compute the solution by solving an appropriate discrete-time periodic Lyapunov -equation using the periodic Schur method of [2]. - -The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = 0`, only used if `solver = "symplectic"`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - - -Parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. - -""" -function pgclyap(A::PM1, C::PM2, K::Int = 1; adj = false, solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0, stability_check = false) where - {PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}, PM2 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}} - K > 0 || throw(ArgumentError("number of grid ponts K must be greater than 0, got K = $K")) - period = promote_period(A, C) - na = Int(round(period/A.period)) - nc = Int(round(period/C.period)) - nperiod = gcd(na*A.nperiod, nc*C.nperiod) - n = size(A,1) - Ts = period/K/nperiod - solver == "symplectic" && dt == 0 && (dt = K >= 100 ? Ts : Ts*K/100/nperiod) - - T = promote_type(eltype(A),eltype(C),Float64) - T == Num && (T = Float64) - if PeriodicMatrices.isconstant(A) && PeriodicMatrices.isconstant(C) - if stability_check - ev = eigvals(tpmeval(A,0)) - maximum(real.(ev)) >= - sqrt(eps(T)) && error("system stability check failed") - end - X = adj ? lyapc(tpmeval(A,0)', tpmeval(C,0)) : lyapc(tpmeval(A,0), tpmeval(C,0)) - else - Ka = PeriodicMatrices.isconstant(A) ? 1 : max(1,Int(round(A.period/A.nperiod/Ts))) - Ad = Array{T,3}(undef, n, n, Ka) - Cd = Array{T,3}(undef, n, n, K) - Threads.@threads for i = 1:Ka - @inbounds Ad[:,:,i] = tvstm(A, i*Ts, (i-1)*Ts; solver, reltol, abstol) - end - if stability_check - ev = pseig3(Ad) - maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - end - if adj - Threads.@threads for i = K:-1:1 - @inbounds Cd[:,:,i] = tvclyap(A, C, (i-1)*Ts, i*Ts; adj, solver, reltol, abstol, dt) - end - X = pslyapd(Ad, Cd; adj) - else - Threads.@threads for i = 1:K - @inbounds Cd[:,:,i] = tvclyap(A, C, i*Ts, (i-1)*Ts; adj, solver, reltol, abstol, dt) - end - X = pslyapd(Ad, Cd; adj) - end - end - return PeriodicTimeSeriesMatrix([X[:,:,i] for i in 1:size(X,3)], period; nperiod) -end -""" - pgclyap2(A, C, E, [, K = 1]; solver, reltol, abstol, dt) -> (X,Y) - -Compute the solutions of the periodic differential Lyapunov equations - - - - X(t) = A(t)*X(t) + X(t)*A'(t) + C(t) - -and - - . - -Y(t) = A(t)'Y(t) + Y(t)A(t) + E(t). - -The periodic matrices `A`, `C` and `E` must have the same dimensions, the same type and -commensurate periods. Additionally `C` and `E` must be symmetric. -If `A`, `C` and `E` have the types `PeriodicFunctionMatrix`, `HarmonicArray`, `FourierFunctionMatrix` or `PeriodicTimeSeriesMatrix`, -then the resulting `X` and `Y` are collections of periodic generator matrices determined -as periodic time-series matrices with `N` components, where `N = 1` if `A`, `C` and `E` are constant matrices -and `N = K` otherwise. -The period `T` of `X` and `Y` is set to the least common commensurate period of `A`, `C` and `E` and the number of subperiods -is adjusted accordingly. Any component matrix of `X` or `Y` is a valid initial value to be used to generate the -solution over a full period by integrating the appropriate differential equation. -The multiple-shooting method of [1] is employed, first, to convert the continuous-time periodic Lyapunov equations -into discrete-time periodic Lyapunov equations satisfied by -the generator solutions in the grid points and then to compute the solutions by solving appropriate discrete-time periodic Lyapunov -equations using the periodic Schur method of [2]. - -The ODE solver to be employed to convert the continuous-time problems into discrete-time problems can be specified using the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = 0`, only used if `solver = "symplectic"`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -Parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. -""" -function pgclyap2(A::PM1, C::PM2, E::PM3, K::Int = 1; solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0, stability_check = false) where - {PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}, PM2 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}, PM3 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}} - K > 0 || throw(ArgumentError("number of grid ponts K must be greater than 0, got K = $K")) - period = promote_period(A, C, E) - na = Int(round(period/A.period)) - nc = Int(round(period/C.period)) - ne = Int(round(period/E.period)) - nperiod = gcd(na*A.nperiod, nc*C.nperiod, ne*E.nperiod) - n = size(A,1) - Ts = period/K/nperiod - solver == "symplectic" && dt == 0 && (dt = K >= 100 ? Ts : Ts*K/100/nperiod) - - if PeriodicMatrices.isconstant(A) && PeriodicMatrices.isconstant(C) && PeriodicMatrices.isconstant(E) - if stability_check - ev = eigvals(tpmeval(A,0)) - maximum(real.(ev)) >= - sqrt(eps(T)) && error("system stability check failed") - end - X, Y = lyapc(tpmeval(A,0), tpmeval(C,0)), lyapc(tpmeval(A,0)', tpmeval(E,0)) - #X, Y = MatrixEquations.lyapc2(tpmeval(A,0), tpmeval(C,0), tpmeval(E,0)) - else - T = promote_type(eltype(A),eltype(C),eltype(E),Float64) - T == Num && (T = Float64) - if stability_check - ev = K < 100 ? PeriodicMatrices.pseig(A,100) : PeriodicMatrices.pseig(A,K) - maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - end - Ka = PeriodicMatrices.isconstant(A) ? 1 : max(1,Int(round(A.period/A.nperiod/Ts))) - Ad = Array{T,3}(undef, n, n, Ka) - Cd = Array{T,3}(undef, n, n, K) - Ed = Array{T,3}(undef, n, n, K) - Threads.@threads for i = 1:Ka - @inbounds Ad[:,:,i] = tvstm(A, i*Ts, (i-1)*Ts; solver, reltol, abstol) - end - Threads.@threads for i = K:-1:1 - @inbounds Cd[:,:,i] = tvclyap(A, C, (i-1)*Ts, i*Ts; adj = false, solver, reltol, abstol, dt) - end - Threads.@threads for i = 1:K - @inbounds Ed[:,:,i] = tvclyap(A, E, i*Ts, (i-1)*Ts; adj = true, solver, reltol, abstol, dt) - end - X, Y = pslyapd2(Ad, Cd, Ed) - end - return PeriodicTimeSeriesMatrix([X[:,:,i] for i in 1:size(X,3)], period; nperiod), PeriodicTimeSeriesMatrix([Y[:,:,i] for i in 1:size(Y,3)], period; nperiod) -end -""" - pgclyap2(A, C, E, [, K = 1]; solver, reltol, abstol, dt) -> (X,Y) - -Compute the solution of the discrete-time periodic Lyapunov equation - - X(i+1) = Φ(i)*X(i)*Φ'(i) + W(i), i = 1, ..., K, X(K+1) := X(1) - -and a periodic generator for the periodic Lyapunov differential equations - - . - -Y(t) = A(t)'Y(t) + Y(t)A(t) + E(t). - -The periodic matrices `A` and `E` and the constant matrix `C` must have the same dimensions, and `A` and `E` -must have the same type and commensurate periods. Additionally `C` and `E` must be symmetric. -`Φ(i)` denotes the transition matrix on the time interval `[Δ*(i-1), Δ*i]` corresponding to `A`, -where `Δ = T/K` with `T` the common period of `A` and `E`. `W(i) = 0` for `i = 1, ..., K-1` and `W(K) = C`. -If `A` and `E` have the types `PeriodicFunctionMatrix`, `HarmonicArray`, `FourierFunctionMatrix` or `PeriodicTimeSeriesMatrix`, -then the resulting `Y` is a collection of periodic generator matrices determined -as a periodic time-series matrix with `N` components, where `N = 1` if `A` and `E` are constant matrices -and `N = K` otherwise. -The period `T` of `Y` is set to the least common commensurate period of `A` and `E` and the number of subperiods -is adjusted accordingly. Any component matrix of `Y` is a valid initial value to be used to generate the -solution over a full period by integrating the appropriate differential equation. -The multiple-shooting method of [1] is employed, first, to convert the continuous-time periodic Lyapunov into a discrete-time periodic Lyapunov equation satisfied by -the generator solution in the grid points and then to compute the solution by solving an appropriate discrete-time periodic Lyapunov -equation using the periodic Schur method of [2]. - -The ODE solver to be employed to convert the continuous-time problems into discrete-time problems can be specified using the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = 0`, only used if `solver = "symplectic"`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -Parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. -""" -function pgclyap2(A::PM1, C::AbstractMatrix, E::PM3, K::Int = 1; solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0, stability_check = false) where - {PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}, PM3 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}} - K > 0 || throw(ArgumentError("number of grid ponts K must be greater than 0, got K = $K")) - period = promote_period(A, E) - na = Int(round(period/A.period)) - ne = Int(round(period/E.period)) - nperiod = gcd(na*A.nperiod, ne*E.nperiod) - n = size(A,1) - Ts = period/K/nperiod - solver == "symplectic" && dt == 0 && (dt = K >= 100 ? Ts : Ts*K/100/nperiod) - - if PeriodicMatrices.isconstant(A) && PeriodicMatrices.isconstant(E) - A0 = tpmeval(A,0) - if stability_check - ev = eigvals(A0) - maximum(real.(ev)) >= - sqrt(eps(eltype(A))) && error("system stability check failed") - end - Ad = exp(A0*period) - X = lyapd(Ad,C) - Y = lyapc(A0', tpmeval(E,0)) - else - T = promote_type(eltype(A),eltype(C),eltype(E),Float64) - T == Num && (T = Float64) - if stability_check - ev = K < 100 ? PeriodicMatrices.pseig(A,100) : PeriodicMatrices.pseig(A,K) - maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - end - Ka = PeriodicMatrices.isconstant(A) ? 1 : max(1,Int(round(A.period/A.nperiod/Ts))) - Ad = Array{T,3}(undef, n, n, Ka) - Cd = zeros(T, n, n, K) - Ed = Array{T,3}(undef, n, n, K) - Threads.@threads for i = 1:Ka - @inbounds Ad[:,:,i] = tvstm(A, i*Ts, (i-1)*Ts; solver, reltol, abstol) - end - copyto!(view(Cd,:,:,K),C) - Threads.@threads for i = K:-1:1 - @inbounds Ed[:,:,i] = tvclyap(A, E, (i-1)*Ts, i*Ts; adj = true, solver, reltol, abstol, dt) - end - X, Y = pslyapd2(Ad, Cd, Ed) - end - return PeriodicTimeSeriesMatrix([X[:,:,i] for i in 1:size(X,3)], period; nperiod), PeriodicTimeSeriesMatrix([Y[:,:,i] for i in 1:size(Y,3)], period; nperiod) -end - -function pgclyap(A::PM1, C::PM2, K::Int = 1; adj = false, solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0, stability_check = false) where - {PM1 <: PeriodicSwitchingMatrix, PM2 <: PeriodicSwitchingMatrix} - K > 0 || throw(ArgumentError("number of grid ponts K must be greater than 0, got K = $K")) - period = promote_period(A, C) - na = round(Int,period/A.period) - nc = round(Int,period/C.period) - nperiod = gcd(na*A.nperiod, nc*C.nperiod) - n = size(A,1) - tsub = period/nperiod - #solver == "symplectic" && dt == 0 && (dt = K >= 100 ? Ts : Ts*K/100/nperiod) - - ts = unique(sort([A.ts;C.ts])) - Kc = length(ts) - - if PeriodicMatrices.isconstant(A) && PeriodicMatrices.isconstant(C) - if stability_check - ev = eigvals(tpmeval(A,0)) - maximum(real.(ev)) >= - sqrt(eps(T)) && error("system stability check failed") - end - X = adj ? lyapc(tpmeval(A,0)', tpmeval(C,0)) : lyapc(tpmeval(A,0), tpmeval(C,0)) - return PeriodicSwitchingMatrix([X], ts, period; nperiod) - else - T = promote_type(eltype(A),eltype(C),Float64) - Ka = PeriodicMatrices.isconstant(A) ? 1 : Kc - Kc1 = Kc*K - Ad = Array{T,3}(undef, n, n, Ka*K) - Cd = Array{T,3}(undef, n, n, Kc1) - Threads.@threads for i = 1:Ka - tf = i == Ka ? tsub : ts[i+1] - k = (i-1)*K+1 - @inbounds Ad[:,:,k] = exp(tpmeval(A,ts[i])*(tf-ts[i])/K) - K == 1 || [Ad[:,:,j] = Ad[:,:,k] for j in k+1:k+K-1] - end - if stability_check - ev = pseig3(Ad) - maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - end - if adj - Threads.@threads for i = Kc:-1:1 - t0 = i == Kc ? tsub : ts[i+1] - k = (i-1)*K+1 - @inbounds Cd[:,:,k] = tvclyap(A, C, ts[i], ts[i]+(t0-ts[i])/K; adj, solver, reltol, abstol, dt) - K == 1 || [Cd[:,:,j] = Cd[:,:,k] for j in k+1:k+K-1] - end - X = pslyapd(Ad, Cd; adj) - else - k = 1 - Threads.@threads for i = 1:Kc - tf = i == Kc ? tsub : ts[i+1] - k = (i-1)*K+1 - @inbounds Cd[:,:,k] = tvclyap(A, C, ts[i]+(tf-ts[i])/K, ts[i]; adj, solver, reltol, abstol, dt) - K == 1 || [Cd[:,:,j] = Cd[:,:,k] for j in k+1:k+K-1] - end - X = pslyapd(Ad, Cd; adj) - # A1 = PeriodicArray(Ad,A.period; nperiod = A.nperiod) - # C1 = PeriodicArray(Cd,C.period; nperiod = C.nperiod) - # X1 = PeriodicArray(X,period; nperiod) - # @show norm(A1*X1*A1'+C1-pmshift(X1)) - end - end - if K == 1 - return PeriodicSwitchingMatrix([X[:,:,i] for i in 1:size(X,3)], ts, period; nperiod) - else - tt = T[] - for i = 1:Kc - tf = i == Kc ? tsub : ts[i+1] - Δ = (tf-ts[i])/K - push!(tt,(ts[i] .+ collect(T,0:K-1)*Δ)...) - end - return PeriodicSwitchingMatrix([X[:,:,i] for i in 1:size(X,3)], tt, period; nperiod) - end -end -function pgclyap(A::PM1, C::PM2, K::Int = 1; adj = false, solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0, stability_check = false) where - {PM1 <: PeriodicTimeSeriesMatrix, PM2 <: PeriodicTimeSeriesMatrix} - K > 0 || throw(ArgumentError("number of grid ponts K must be greater than 0, got K = $K")) - period = promote_period(A, C) - na = round(Int,period/A.period) - nc = round(Int,period/C.period) - nperiod = gcd(na*A.nperiod, nc*C.nperiod) - n = size(A,1) - tsub = period/nperiod - #solver == "symplectic" && dt == 0 && (dt = K >= 100 ? Ts : Ts*K/100/nperiod) - - if A.period == C.period - nperiod = gcd(A.nperiod,C.nperiod) - ns = div(lcm(A.nperiod*length(A),C.nperiod*length(C)),nperiod) - Δ = A.period/nperiod/ns - δ = Δ/2 - else - Tsub = A.period/A.nperiod - Tsub ≈ C.period/C.nperiod || error("periods or subperiods must be equal for addition") - nperiod = lcm(A.nperiod,C.nperiod) - period = Tsub*nperiod - ns = lcm(length(A),length(C)) - Δ = Tsub/ns - δ = Δ/2 - end - Kc = ns - Tsd = Δ/K - Ts = Δ - δ = Tsd/2 - if PeriodicMatrices.isconstant(A) && PeriodicMatrices.isconstant(C) - if stability_check - ev = eigvals(tpmeval(A,0)) - maximum(real.(ev)) >= - sqrt(eps(T)) && error("system stability check failed") - end - X = adj ? lyapc(tpmeval(A,0)', tpmeval(C,0)) : lyapc(tpmeval(A,0), tpmeval(C,0)) - return PeriodicTimeSeriesMatrix([X], period; nperiod) - else - T = promote_type(eltype(A),eltype(C),Float64) - Ka = PeriodicMatrices.isconstant(A) ? 1 : Kc - Kc1 = Kc*K - Ad = Array{T,3}(undef, n, n, Ka*K) - Cd = Array{T,3}(undef, n, n, Kc1) - Threads.@threads for i = 1:Ka - k = (i-1)*K+1 - @inbounds Ad[:,:,k] = exp(tpmeval(A,(i-1)*Ts+δ)*Tsd) - K == 1 || [Ad[:,:,j] = Ad[:,:,k] for j in k+1:k+K-1] - end - if stability_check - ev = pseig3(Ad) - maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - end - if adj - k = Kc1 - Threads.@threads for i = Kc:-1:1 - tf = (i-1)*Ts - k = (i-1)*K+1 - @inbounds Cd[:,:,k] = tvclyap(A, C, tf, tf+Tsd; adj, solver, reltol, abstol, dt) - #K == 1 || [Cd[:,:,j] = Cd[:,:,k] for j in k-K+1:k-1] - K == 1 || [Cd[:,:,j] = Cd[:,:,k] for j in k+1:k+K-1] - end - X = pslyapd(Ad, Cd; adj) - else - Threads.@threads for i = 1:Kc - t0 = (i-1)*Ts - k = (i-1)*K+1 - @inbounds Cd[:,:,k] = tvclyap(A, C, t0+Tsd, t0; adj, solver, reltol, abstol, dt) - K == 1 || [Cd[:,:,j] = Cd[:,:,k] for j in k+1:k+K-1] - end - X = pslyapd(Ad, Cd; adj) - # A1 = PeriodicArray(Ad,A.period; nperiod = A.nperiod) - # C1 = PeriodicArray(Cd,C.period; nperiod = C.nperiod) - # X1 = PeriodicArray(X,period; nperiod) - # @show norm(A1*X1*A1'+C1-pmshift(X1)) - end - end - return PeriodicTimeSeriesMatrix([X[:,:,i] for i in 1:size(X,3)], period; nperiod) -end -""" - tvclyap_eval(t, W, A, C; adj = false, solver, reltol, abstol, dt) -> Xval - -Compute the time value `Xval := X(t)` of the solution of the periodic Lyapunov differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + C(t) , X(t0) = W(t0), t > t0, if adj = false - -or - - . - -X(t) = A(t)'X(t) + X(t)A(t) + C(t) , X(t0) = W(t0), t < t0, if adj = true, - -using the periodic generator `W` determined with the function [`pgclyap`](@ref) for the same periodic matrices `A` and `C` -and the same value of the keyword argument `adj`. -The initial time `t0` is the nearest time grid value to `t`, from below, if `adj = false`, or from above, if `adj = true`. - -The above ODE is solved by employing the integration method specified via the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = 0`, only used if `solver = "symplectic"`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -""" -function tvclyap_eval(t::Real,X::PeriodicTimeSeriesMatrix,A::PM1, C::PM2; adj = false, solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicTimeSeriesMatrix}, PM2 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicTimeSeriesMatrix}} - tsub = X.period/X.nperiod - ns = length(X.values) - Δ = tsub/ns - tf = mod(t,tsub) - tf == 0 && (return X.values[1]) - if adj - ind = round(Int,tf/Δ) - if ind == ns - t0 = ind*Δ; ind = 1 - else - t0 = (ind+1)*Δ; ind = ind+2; - ind > ns && (ind = 1) - end - else - ind = round(Int,tf/Δ) - ind == 0 && (ind = 1) - t0 = (ind-1)*Δ - end - return tvclyap(A, C, tf, t0, X.values[ind]; adj, solver, reltol, abstol, dt) -end -function tvclyap_eval(t::Real,X::PeriodicTimeSeriesMatrix,A::PM1, X0::AbstractMatrix; adj = false, solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicTimeSeriesMatrix}} - tsub = X.period/X.nperiod - ns = length(X.values) - Δ = tsub/ns - tf = mod(t,tsub) - tf == 0 && (return X.values[1]) - if adj - ind = round(Int,tf/Δ) - if ind == ns - t0 = ind*Δ; ind = 1 - else - t0 = (ind+1)*Δ; ind = ind+2; - ind > ns && (ind = 1) - end - else - ind = round(Int,tf/Δ) - ind == 0 && (ind = 1) - t0 = (ind-1)*Δ - end - #@show tf, t0 - return tvclyap(A, PM1(zeros(eltype(X0),size(X0)...),A.period), tf, t0, X.values[ind]; adj, solver, reltol, abstol, dt) -end -function tvclyap_eval(t::Real,X::PeriodicSwitchingMatrix,A::PM1, C::PM2; adj = false, solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {PM1 <: PeriodicSwitchingMatrix, PM2 <: PeriodicSwitchingMatrix} - tsub = X.period/X.nperiod - tf = mod(t,tsub) - tf == 0 && (return X.values[1]) - if adj - ind = findfirst(X.ts .> tf*(1+10*eps())) - isnothing(ind) ? (ind = 1; t0 = tsub) : t0 = X.ts[ind]; - else - ind = findfirst(X.ts .> tf*(1+10*eps())) - isnothing(ind) ? ind = length(X) : ind -= 1 - t0 = X.ts[ind] - end - return tvclyap(A, C, tf, t0, X.values[ind]; adj, solver, reltol, abstol, dt) -end - -function tvclyap(A::PM1, C::PM2, tf, t0, W0::Union{AbstractMatrix,Missing} = missing; adj = false, solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {T1, T2, PM1 <: AbstractPeriodicArray{:c,T1}, PM2 <: AbstractPeriodicArray{:c,T2}} - #{PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicSwitchingMatrix}, PM2 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicSwitchingMatrix}} - """ - tvclyap(A, C, tf, t0; adj, solver, reltol, abstol) -> W::Matrix - - Compute the solution at tf > t0 of the differential matrix Lyapunov equation - . - W(t) = A(t)*W(t)+W(t)*A'(t)+C(t), W(t0) = 0, tf > t0, if adj = false - - or - . - W(t) = -A(t)'*W(t)-W(t)*A(t)-C(t), W(t0) = 0, tf < t0, if adj = true. - - The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, - together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), - absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = abs(tf-t0)/100`, only used if `solver = "symplectic"`). - """ - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - (n,n) == size(C) || error("the periodic matrix C must have same dimensions as A") - T = promote_type(typeof(t0), typeof(tf)) - # using OrdinaryDiffEq - ismissing(W0) ? u0 = zeros(T,div(n*(n+1),2)) : u0 = MatrixEquations.triu2vec(W0) - tspan = (T(t0),T(tf)) - fclyap!(du,u,p,t) = adj ? muladdcsym!(du, u, -1, tpmeval(A,t)', tpmeval(C,t)) : muladdcsym!(du, u, 1, tpmeval(A,t), tpmeval(C,t)) - prob = ODEProblem(fclyap!, u0, tspan) - if solver == "stiff" - if reltol > 1.e-4 - # standard stiff - sol = solve(prob, Rodas4(); reltol, abstol, save_everystep = false) - else - # high accuracy stiff - sol = solve(prob, KenCarp58(); reltol, abstol, save_everystep = false) - end - elseif solver == "non-stiff" - if reltol > 1.e-4 - # standard non-stiff - sol = solve(prob, Tsit5(); reltol, abstol, save_everystep = false) - else - # high accuracy non-stiff - sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) - end - elseif solver == "symplectic" - # high accuracy symplectic - if dt == 0 - sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) - #@show sol.retcode - if sol.retcode == :Failure - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/100) - end - else - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) - end - else - if reltol > 1.e-4 - # low accuracy automatic selection - sol = solve(prob, AutoTsit5(Rosenbrock23()) ; reltol, abstol, save_everystep = false) - else - # high accuracy automatic selection - sol = solve(prob, AutoVern9(Rodas5(),nonstifftol = 11/10); reltol, abstol, save_everystep = false) - end - end - return MatrixEquations.vec2triu(sol.u[end], her=true) -end -function tvclyap(A::PM1, C::PM2, ts::AbstractVector, W0::AbstractMatrix; adj = false, solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {T1, T2, PM1 <: AbstractPeriodicArray{:c,T1}, PM2 <: AbstractPeriodicArray{:c,T2}} - #{PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicSwitchingMatrix}, PM2 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicSwitchingMatrix}} - """ - tvclyap(A, C, ts, W0; adj, solver, reltol, abstol) -> W::Matrix - - Compute the solution at the time values ts of the differential matrix Lyapunov equation - . - W(t) = A(t)*W(t)+W(t)*A'(t)+C(t), W(ts[1]) = W0, if adj = false - - or - . - W(t) = -A(t)'*W(t)-W(t)*A(t)-C(t), W(ts[end]) = W0, tf < t0, if adj = true. - - The ODE solver to be employed can be specified using the keyword argument `solver`, - together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), - absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = abs(tf-t0)/100`, only used if `solver = "symplectic"`). - - """ - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - (n,n) == size(C) || error("the periodic matrix C must have same dimensions as A") - T = eltype(ts) - # using OrdinaryDiffEq - u0 = MatrixEquations.triu2vec(W0) - tspan = adj ? (ts[end], ts[1]) : (ts[1], ts[end]) - fclyap!(du,u,p,t) = adj ? muladdcsym!(du, u, -1, tpmeval(A,t)', tpmeval(C,t)) : muladdcsym!(du, u, 1, tpmeval(A,t), tpmeval(C,t)) - prob = ODEProblem(fclyap!, u0, tspan) - if solver == "stiff" - if reltol > 1.e-4 - # standard stiff - sol = solve(prob, Rodas4(); reltol, abstol, save_everystep = false) - else - # high accuracy stiff - sol = solve(prob, KenCarp58(); reltol, abstol, save_everystep = false) - end - elseif solver == "non-stiff" - if reltol > 1.e-4 - # standard non-stiff - sol = solve(prob, Tsit5(); reltol, abstol, save_everystep = false) - else - # high accuracy non-stiff - sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) - end - elseif solver == "symplectic" - # high accuracy symplectic - if dt == 0 - sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) - #@show sol.retcode - if sol.retcode == :Failure - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/100) - end - else - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) - end - else - if reltol > 1.e-4 - # low accuracy automatic selection - sol = solve(prob, AutoTsit5(Rosenbrock23()) ; reltol, abstol, save_everystep = false) - else - # high accuracy automatic selection - sol = solve(prob, AutoVern9(Rodas5(),nonstifftol = 11/10); reltol, abstol, save_everystep = false) - end - end - return MatrixEquations.vec2triu.(sol(ts).u, her=true) -end -function tvclyap(A::PM1, C::PM2, W0::AbstractMatrix; adj = false, solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {T1, T2, PM1 <: AbstractPeriodicArray{:c,T1}, PM2 <: AbstractPeriodicArray{:c,T2}} - #{PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicSwitchingMatrix}, PM2 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicSwitchingMatrix}} - """ - tvclyap(A, C, ts, W0; adj, solver, reltol, abstol) -> W::Matrix - - Compute the solution at the time values ts of the differential matrix Lyapunov equation - . - W(t) = A(t)*W(t)+W(t)*A'(t)+C(t), W(ts[1]) = W0, if adj = false - - or - . - W(t) = -A(t)'*W(t)-W(t)*A(t)-C(t), W(ts[end]) = W0, tf < t0, if adj = true. - - The ODE solver to be employed can be specified using the keyword argument `solver`, - together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), - absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = abs(tf-t0)/100`, only used if `solver = "symplectic"`). - Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, - which are generally very efficient, but less accurate. If `reltol < 1.e-4`, - higher order solvers are employed able to cope with high accuracy demands. - - The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - - `solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - - `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - - `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - - `solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - """ - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - (n,n) == size(C) || error("the periodic matrix C must have same dimensions as A") - period = A.period - T = eltype(period) - # using OrdinaryDiffEq - u0 = MatrixEquations.triu2vec(W0) - tspan = adj ? (period, zero(T)) : (zero(T), period) - fclyap!(du,u,p,t) = adj ? muladdcsym!(du, u, -1, tpmeval(A,t)', tpmeval(C,t)) : muladdcsym!(du, u, 1, tpmeval(A,t), tpmeval(C,t)) - prob = ODEProblem(fclyap!, u0, tspan) - if solver == "stiff" - if reltol > 1.e-4 - # standard stiff - sol = solve(prob, Rodas4(); reltol, abstol, save_everystep = false) - else - # high accuracy stiff - sol = solve(prob, KenCarp58(); reltol, abstol, save_everystep = false) - end - elseif solver == "non-stiff" - if reltol > 1.e-4 - # standard non-stiff - sol = solve(prob, Tsit5(); reltol, abstol, save_everystep = false) - else - # high accuracy non-stiff - sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) - end - elseif solver == "symplectic" - # high accuracy symplectic - if dt == 0 - sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) - #@show sol.retcode - if sol.retcode == :Failure - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/100) - end - else - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) - end - else - if reltol > 1.e-4 - # low accuracy automatic selection - sol = solve(prob, AutoTsit5(Rosenbrock23()) ; reltol, abstol, save_everystep = false) - else - # high accuracy automatic selection - sol = solve(prob, AutoVern9(Rodas5(),nonstifftol = 11/10); reltol, abstol, save_everystep = false) - end - end - return PeriodicFunctionMatrix(t-> MatrixEquations.vec2triu(sol(t), her=true),period) -end -function muladdcsym!(y::AbstractVector, x::AbstractVector, isig, A::AbstractMatrix, C::AbstractMatrix) - # A*X + X*A' + C - n = size(A, 1) - T1 = promote_type(eltype(A), eltype(x)) - # TO DO: eliminate building of X by using directly x - X = MatrixEquations.vec2triu(convert(AbstractVector{T1}, x), her=true) - @inbounds begin - k = 1 - for j = 1:n - for i = 1:j - temp = C[i,j] - for l = 1:n - temp += A[i,l] * X[l,j] + X[i,l] * A[j,l] - end - y[k] = isig*temp - k += 1 - end - end - end - return y -end -""" - pcplyap(A, C; K = 10, adj = false, solver, reltol, abstol) -> U - -Compute the upper triangular periodic factor `U(t)` of the solution `X(t) = U(t)U(t)'` of the -periodic Lyapunov differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + C(t)C(t)' , if adj = false, - -or of the solution `X(t) = U(t)'U(t)` of the periodic Lyapunov differential equation - - . - -X(t) = A(t)'X(t) + X(t)A(t) + C(t)'C(t) , if adj = true. - -The periodic matrices `A` and `C` must have the same type, commensurate periods and `A` must be stable. -The resulting upper triangular periodic factor `U` has the type `PeriodicFunctionMatrix` and -`U(t)` can be used to evaluate the value of `U` at time `t`. -`U` has the period set to the least common commensurate period of `A` and `C` and -the number of subperiods is adjusted accordingly. - -An extension of the multiple-shooting method of [1] is employed to convert the (continuous-time) periodic differential Lyapunov equation -into a discrete-time periodic Lyapunov equation satisfied by a multiple point generator of the solution. -The keyword argument `K` specifies the number of grid points to be used -for the discretization of the continuous-time problem (default: `K = 10`). -If `A` and `C` are of types `PeriodicTimeSeriesMatrix` or `PeriodicSwitchingMatrix`, -then `K` specifies the number of grid points used between two consecutive switching time values (default: `K = 1`). -The upper triangular factor of the multiple point generator is computed by solving the appropriate discrete-time periodic Lyapunov -equation using the iterative method (Algorithm 5) of [2]. -The resulting periodic generator is finally converted into -a periodic function matrix which determines for a given `t` -the function value `U(t)` by integrating the appropriate ODE from the nearest grid point value. - -The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and -absolute accuracy `abstol` (default: `abstol = 1.e-7`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -To speedup function evaluations, interpolation based function evaluations can be used -by setting the keyword argument `intpol = true` (default: `intpol = false`). -In this case the interpolation method to be used can be specified via the keyword argument -`intpolmeth = meth`. The allowable values for `meth` are: `"constant"`, `"linear"`, `"quadratic"` and `"cubic"` (default). -Interpolation is not possible if `A` and `C` are of type `PeriodicSwitchingMatrix`. - -Parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. - -""" -function pcplyap(A::PeriodicFunctionMatrix, C::PeriodicFunctionMatrix; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-7, abstol = 1.e-7, intpol = false, intpolmeth = "cubic") - if intpol - return convert(PeriodicFunctionMatrix,pgcplyap(A, C, K; adj, solver, reltol, abstol), method = intpolmeth) - else - U0 = pgcplyap(A, C, K; adj, solver, reltol, abstol) - return PeriodicFunctionMatrix(t->PeriodicSystems.tvcplyap_eval(t, U0, A, C; adj, solver, reltol, abstol),A.period) - end -end -pcplyap(A::PeriodicFunctionMatrix, C::AbstractMatrix; kwargs...) = pcplyap(A, PeriodicFunctionMatrix(C, A.period; nperiod = A.nperiod); kwargs...) -function pcplyap(A::PeriodicSymbolicMatrix, C::PeriodicSymbolicMatrix; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-7, abstol = 1.e-7) - convert(PeriodicSymbolicMatrix, pgcplyap(convert(PeriodicFunctionMatrix,A), convert(PeriodicFunctionMatrix,C), K; adj, solver, reltol, abstol)) -end -function pcplyap(A::HarmonicArray, C::HarmonicArray; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-7, abstol = 1.e-7) - convert(HarmonicArray, pgcplyap(A, C, K; adj, solver, reltol, abstol)) -end -function pcplyap(A::FourierFunctionMatrix, C::FourierFunctionMatrix; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-7, abstol = 1.e-7) - convert(FourierFunctionMatrix, pgcplyap(A, C, K; adj, solver, reltol, abstol)) -end -function pcplyap(A::PeriodicTimeSeriesMatrix, C::PeriodicTimeSeriesMatrix; K::Int = 10, adj = false, solver = "non-stiff", reltol = 1.e-7, abstol = 1.e-7) - pgcplyap(convert(HarmonicArray,A), convert(HarmonicArray,C), K; adj, solver, reltol, abstol) -end -for PM in (:PeriodicFunctionMatrix, :PeriodicSymbolicMatrix, :HarmonicArray, :FourierFunctionMatrix, :PeriodicTimeSeriesMatrix) - @eval begin - function prcplyap(A::$PM, C::$PM; K::Int = 10, solver = "non-stiff", reltol = 1.e-7, abstol = 1.e-7) - pcplyap(A, C; K, adj = true, solver, reltol, abstol) - end - function prcplyap(A::$PM, C::AbstractMatrix; kwargs...) - prcplyap(A, $PM(C, A.period; nperiod = A.nperiod); kwargs...) - end - function pfcplyap(A::$PM, C::$PM; K::Int = 10, solver = "non-stiff", reltol = 1.e-7, abstol = 1.e-7) - pcplyap(A, C; K, adj = false, solver, reltol, abstol) - end - function pfcplyap(A::$PM, C::AbstractMatrix; kwargs...) - pfcpyap(A, $PM(C, A.period; nperiod = A.nperiod); kwargs...) - end - end -end -""" - pfcplyap(A, B; K = 10, solver, reltol, abstol) -> U - -Compute the upper triangular periodic factor `U(t)` of the solution `X(t) = U(t)U(t)'` - - . - X(t) = A(t)X(t) + X(t)A(t)' + B(t)B(t)' . - -The periodic matrices `A` and `B` must have the same type, the same row dimensions and commensurate periods. -The resulting periodic factor `U` has the period -set to the least common commensurate period of `A` and `B` and the number of subperiods -is adjusted accordingly. - -This function is merely an interface to [`pcplyap`](@ref) (see this function for the description of keyword parameters). -""" -pfcplyap(A::PeriodicFunctionMatrix, C::PeriodicFunctionMatrix; K::Int = 10, reltol = 1.e-4, abstol = 1.e-7) -""" - prcplyap(A, C; K = 10, solver, reltol, abstol) -> U - -Compute the upper triangular periodic factor `U(t)` of the solution `X(t) = U(t)'U(t)` - - . - -X(t) = A(t)'X(t) + X(t)A(t) + C(t)'C(t). - -The periodic matrices `A` and `C` must have the same type, the same column dimensions and commensurate periods. -The resulting periodic factor `U` has the period -set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -This function is merely an interface to [`pcplyap`](@ref) (see this function for the description of keyword parameters). -""" -prcplyap(A::PeriodicFunctionMatrix, C::PeriodicFunctionMatrix; K::Int = 10, reltol = 1.e-4, abstol = 1.e-7) - -""" - pgcplyap(A, C[, K = 1]; adj = false, solver, reltol, abstol, dt) -> U - -Compute upper triangular periodic generators `U(t)` of the solution `X(t) = U(t)U(t)'` of the -periodic Lyapunov differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + C(t)C(t)' , if adj = false, - -or of the solution `X(t) = U(t)'U(t)` of the periodic Lyapunov differential equation - - . - -X(t) = A(t)'X(t) + X(t)A(t) + C(t)'C(t) , if adj = true. - -The periodic matrices `A` and `C` must have the same type, commensurate periods and `A` must be stable. -The resulting `U` is a collection of periodic generator matrices determined -as a periodic time-series matrix with `N` components, where `N = 1` if `A` and `C` are constant matrices -and `N = K` otherwise. -The period of `U` is set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. Any component matrix of `U` is a valid initial value to be used to generate the -solution over a full period by integrating the appropriate differential equation. -An extension of the multiple-shooting method of [1] is employed, first, to convert the continuous-time periodic Lyapunov -into a discrete-time periodic Lyapunov equation satisfied by -the generator solution in `K` time grid points and then to compute the solution by solving an appropriate discrete-time periodic Lyapunov -equation using the iterative method (Algorithm 5) of [2]. - -The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = 0`, only used if `solver = "symplectic"`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -For large values of `K`, parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. - -""" -function pgcplyap(A::PM1, C::PM2, K::Int = 1; adj = false, solver = "", reltol = 1e-7, abstol = 1e-7, dt = 0) where - {PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}, PM2 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix}} - period = promote_period(A, C) - na = Int(round(period/A.period)) - nc = Int(round(period/C.period)) - nperiod = gcd(na*A.nperiod, nc*C.nperiod) - n = size(A,1) - Ts = period/K/nperiod - - if PeriodicMatrices.isconstant(A) && PeriodicMatrices.isconstant(C) - U = adj ? plyapc(tpmeval(A,0)', tpmeval(C,0)') : plyapc(tpmeval(A,0), tpmeval(C,0)) - else - T = promote_type(eltype(A),eltype(C),Float64) - T == Num && (T = Float64) - Ka = PeriodicMatrices.isconstant(A) ? 1 : max(1,Int(round(A.period/A.nperiod/Ts))) - Ad = Array{T,3}(undef, n, n, Ka) - Cd = Array{T,3}(undef, n, n, K) - Threads.@threads for i = 1:Ka - @inbounds Ad[:,:,i] = tvstm(A, i*Ts, (i-1)*Ts; solver, reltol, abstol) - end - if adj - #Threads.@threads for i = K:-1:1 - for i = K:-1:1 - Xd = tvclyap(A, C'*C, (i-1)*Ts, i*Ts; adj, solver, reltol, abstol, dt) - Fd = cholesky(Xd, RowMaximum(), check = false) - Cd[:,:,i] = [Fd.U[1:Fd.rank, invperm(Fd.p)]; zeros(T,n-Fd.rank,n)] - end - U = psplyapd(Ad, Cd; adj) - else - #Threads.@threads for i = 1:K - for i = 1:K - Xd = tvclyap(A, C*C', i*Ts, (i-1)*Ts; adj, reltol, abstol, dt) - Fd = cholesky(Xd, RowMaximum(), check = false) - Cd[:,:,i] = [Fd.L[invperm(Fd.p), 1:Fd.rank] zeros(T,n,n-Fd.rank)] - end - U = psplyapd(Ad, Cd; adj) - end - end - return PeriodicTimeSeriesMatrix([U[:,:,i] for i in 1:size(U,3)], period; nperiod) -end -""" - tvcplyap_eval(t, U, A, C; adj = false, solver, reltol, abstol, dt) -> Uval - - -Compute the time value `Uval := U(t)` of the upper triangular periodic generators -`U(t)` of the solution `X(t) = U(t)U(t)'` of the periodic Lyapunov differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + C(t)C(t)' , X(t0) = U(t0)U(t0)', t > t0, if adj = false, - -or of the solution `X(t) = U(t)'U(t)` of the periodic Lyapunov differential equation - - . - -X(t) = A(t)'X(t) + X(t)A(t) + C(t)'C(t) , X(t0) = U(t0)'U(t0), t < t0, if adj = true, - -using the periodic generator `U` determined with the function [`pgcplyap`](@ref) for the same periodic matrices `A` and `C` -and the same value of the keyword argument `adj`. -The initial time `t0` is the nearest time grid value to `t`, from below, if `adj = false`, or from above, if `adj = true`. - -The above ODE is solved by employing the integration method specified via the keyword argument `solver`, -together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt` (default: `dt = 0`, only used if `solver = "symplectic"`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). -""" -function tvcplyap_eval(t::Real,U::PeriodicTimeSeriesMatrix,A::PM1, C::PM2; adj = false, solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {PM1 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicTimeSeriesMatrix}, PM2 <: Union{PeriodicFunctionMatrix,PeriodicSymbolicMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicTimeSeriesMatrix}} - tsub = U.period/U.nperiod - ns = length(U.values) - Δ = tsub/ns - tf = mod(t,tsub) - tf == 0 && (return U.values[1]) - if adj - ind = round(Int,tf/Δ) - if ind == ns - t0 = ind*Δ; ind = 1 - else - t0 = (ind+1)*Δ; ind = ind+2; - ind > ns && (ind = 1) - end - else - ind = round(Int,tf/Δ) - ind == 0 && (ind = 1) - t0 = (ind-1)*Δ - end - n = size(A,1) - T = eltype(U) - # use fallback method - Q = adj ? C'*C : C*C' - X0 = adj ? U.values[ind]'*U.values[ind] : U.values[ind]*U.values[ind]' - Xd = tvclyap(A, Q, tf, t0, X0; adj, solver, reltol, abstol, dt) - if adj - Fd = cholesky(Xd, RowMaximum(), check = false) - return makesp!([qr(Fd.U[1:Fd.rank, invperm(Fd.p)]).R; zeros(T,n-Fd.rank,n)];adj) - else - Fd = cholesky(Xd, RowMaximum(), check = false) - return makesp!(triu(LAPACK.gerqf!([Fd.L[invperm(Fd.p), 1:Fd.rank] zeros(T,n,n-Fd.rank)], similar(Xd,n))[1]);adj) - end -end -function makesp!(r; adj = true) - # make upper triangular matrix with positive diagonal elements - if adj - for i = 1:size(r,1) - r[i,i] < 0 && (r[i,i:end] = -r[i,i:end]) - end - else - for i = 1:size(r,1) - r[i,i] < 0 && (r[1:i,i] = -r[1:i,i]) - end - end - return r -end -function pseig3(A::Array{T,3}; rev::Bool = true, fast::Bool = false) where T - n = size(A,1) - n == size(A,2) || error("A must have equal first and second dimensions") - p = size(A,3) - if fast - if rev - ev = eigvals(psreduc_reg(A)...) - else - imap = p:-1:1 - ev = eigvals(psreduc_reg(view(A,imap))...) - end - isreal(ev) && (ev = real(ev)) - sorteigvals!(ev) - return sort!(ev,by=abs,rev=true) - else - T1 = promote_type(Float64,T) - ev = PeriodicMatrices.pschur(T1.(A); rev, withZ = false)[3] - isreal(ev) && (ev = real(ev)) - return ev - end -end - - \ No newline at end of file diff --git a/src/psconversions.jl b/src/psconversions.jl index 4fdc242..3835256 100644 --- a/src/psconversions.jl +++ b/src/psconversions.jl @@ -60,7 +60,6 @@ function psmrc2d(sys::DescriptorStateSpace{T}, Ts::Real; ki::Vector{Int} = ones( Δ = sys.Ts T1 = T <: BlasFloat ? T : promote_type(Float64,T) n = sys.nx - @show Δ if Δ == 0 i1 = 1:n; i2 = n+1:n+m G = exp([ rmul!(A,Ts) rmul!(B,Ts); zeros(T1,m,n+m)]) @@ -127,7 +126,7 @@ by starting Julia with several execution threads. The number of execution threads is controlled either by using the `-t/--threads` command line argument or by using the `JULIA_NUM_THREADS` environment variable. """ -function psc2d(psysc::PeriodicStateSpace{PM}, Ts::Real; solver::String = "", reltol = 1e-3, abstol = 1e-7, dt = Ts/10) where {PM <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}} +function psc2d(psysc::PeriodicStateSpace{PM}, Ts::Real; solver::String = "", reltol = 1e-3, abstol = 1e-7, dt = Ts/10) where {PM <: Union{PeriodicFunctionMatrix,HarmonicArray}} Ts > 0 || error("the sampling time Ts must be positive") period = psysc.period r = rationalize(period/Ts) @@ -174,7 +173,7 @@ function psc2d(psysc::PeriodicStateSpace{PM}, Ts::Real; solver::String = "", re return ps(PMT(Ap,period; nperiod = na),PMT(Bp,period; nperiod = nb),PMT(Cp,period; nperiod = nc),PMT(Dp,period; nperiod = nd)) # end PSC2D end -function psc2d(PMT::Type, psysc::PeriodicStateSpace{PM}, Ts::Real; kwargs...) where {PM <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}} +function psc2d(PMT::Type, psysc::PeriodicStateSpace{PM}, Ts::Real; kwargs...) where {PM <: Union{PeriodicFunctionMatrix,HarmonicArray}} PMT ∈ (PeriodicMatrix, PeriodicArray, SwitchingPeriodicMatrix, SwitchingPeriodicArray) || error("only discrete periodic matrix types allowed") convert(PeriodicStateSpace{PMT}, psc2d(psysc, Ts; kwargs...)) diff --git a/src/pscric.jl b/src/pscric.jl deleted file mode 100644 index 8c680b9..0000000 --- a/src/pscric.jl +++ /dev/null @@ -1,785 +0,0 @@ -""" - pcric(A, R, Q; K = 10, adj = false, solver, reltol, abstol, fast, intpol, intpolmeth) -> (X, EVALS) - -Solve the periodic Riccati differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + Q(t) - X(t)R(t)X(t) , if adj = false, - -or - - . - -X(t) = A(t)'X(t) + X(t)A(t) + Q(t) - X(t)R(t)X(t) , if adj = true - -and compute the stable closed-loop characteristic multipliers in `EVALS` (see [`pgcric`](@ref) for details). - -The periodic matrices `A`, `R` and `Q` must have the same type, the same dimensions and commensurate periods, -and additionally `R` and `Q` must be symmetric. The resulting symmetric periodic solution `X` has the type `PeriodicFunctionMatrix` and -`X(t)` can be used to evaluate the value of `X` at time `t`. -`X` has the period set to the least common commensurate period of `A`, `R` and `Q` and -the number of subperiods is adjusted accordingly. -_Note:_ Presently the `PeriodicSwitchingMatrix` type is not supported. - -If `fast = true` (default) the multiple-shooting method is used in conjunction with fast pencil reduction techniques, as proposed in [1], -to determine the periodic solution in `t = 0` and a multiple point generator of the appropriate periodic differential Riccati equation -is determined (see [2] for details). -If `fast = false`, the multiple-shooting method is used in -conjunction with the periodic Schur decomposition to determine multiple point generators directly from the stable periodic invariant subspace of -an appropriate symplectic transition matrix (see also [2] for more details). - -The keyword argument `K` specifies the number of grid points to be used -for the resulting multiple point periodic generator (default: `K = 10`). -The obtained periodic generator is finally converted into a periodic function matrix which determines for a given `t` -the function value `X(t)` by integrating the appropriate ODE from the nearest grid point value. - -To speedup function evaluations, interpolation based function evaluations can be used -by setting the keyword argument `intpol = true` (default: `intpol = true` if `solver = "symplectic"`, otherwise `intpol = false`). -In this case the interpolation method to be used can be specified via the keyword argument -`intpolmeth = meth`. The allowable values for `meth` are: `"constant"`, `"linear"`, `"quadratic"` and `"cubic"` (default). - -The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, together with -the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and -absolute accuracy `abstol` (default: `abstol = 1.e-7`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -For large values of `K`, parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. On solving periodic Riccati equations. - Numerical Linear Algebra with Applications, 15:809-835, 2008. -""" -function pcric(A::PeriodicFunctionMatrix, R::PeriodicFunctionMatrix, Q::PeriodicFunctionMatrix; K::Int = 10, adj = false, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, - fast = true, intpol = solver == "symplectic" ? true : false, intpolmeth = "cubic") - X, EVALS = pgcric(A, R, Q, K; adj, solver, reltol, abstol, dt, fast, PSD_SLICOT) - if intpol - return convert(PeriodicFunctionMatrix, X, method = intpolmeth), EVALS - else - return PeriodicFunctionMatrix(t->PeriodicSystems.tvcric_eval(t, X, A, R, Q; solver, adj, reltol, abstol),A.period), EVALS - end -end -for PM in (:PeriodicSymbolicMatrix, :HarmonicArray, :FourierFunctionMatrix) - @eval begin - function pcric(A::$PM, R::$PM, Q::$PM; K::Int = 10, adj = false, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, - fast = true, intpol = solver == "symplectic" ? true : false, intpolmeth = "cubic") - At = convert(PeriodicFunctionMatrix,A) - Rt = convert(PeriodicFunctionMatrix,R) - Qt = convert(PeriodicFunctionMatrix,Q) - X, EVALS = pgcric(At, Rt, Qt, K; adj, solver, reltol, abstol, dt, fast, PSD_SLICOT) - if intpol - return convert(PeriodicFunctionMatrix, X, method = intpolmeth), EVALS - else - return PeriodicFunctionMatrix(t->PeriodicSystems.tvcric_eval(t, X, At, Rt, Qt; solver, adj, reltol, abstol),A.period), EVALS - end - end - end -end -# function pcric(A::HarmonicArray, R::HarmonicArray, Q::HarmonicArray; K::Int = 10, adj = false, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, fast = true) -# X, EVALS = pgcric(A, R, Q, K; adj, solver, reltol, abstol, dt, fast, PSD_SLICOT) -# return convert(HarmonicArray, X), EVALS -# end -# function pcric(A::FourierFunctionMatrix, R::FourierFunctionMatrix, Q::FourierFunctionMatrix; K::Int = 10, adj = false, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, fast = true) -# X, EVALS = pgcric(A, R, Q, K; adj, solver, reltol, abstol, dt, fast, PSD_SLICOT) -# return convert(FourierFunctionMatrix, X), EVALS -# end -# function pcric(A::PeriodicTimeSeriesMatrix, R::PeriodicTimeSeriesMatrix, Q::PeriodicTimeSeriesMatrix; K::Int = 10, adj = false, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, fast = true) -# pgcric(convert(HarmonicArray,A), convert(HarmonicArray,R), convert(HarmonicArray,Q), K; adj, solver, reltol, abstol, dt, fast, PSD_SLICOT) -# end -function pcric(A::PeriodicTimeSeriesMatrix, R::PeriodicTimeSeriesMatrix, Q::PeriodicTimeSeriesMatrix; kwargs...) - pcric(convert(HarmonicArray,A), convert(HarmonicArray,R), convert(HarmonicArray,Q); kwargs...) -end - -for PM in (:PeriodicFunctionMatrix, :PeriodicSymbolicMatrix, :HarmonicArray, :FourierFunctionMatrix, :PeriodicTimeSeriesMatrix) - @eval begin - function prcric(A::$PM, B::$PM, R::$PM, Q::$PM; K::Int = 10, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, - fast = true, intpol = solver == "symplectic" ? true : false, intpolmeth = "cubic") - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - n == size(B,1) || error("the periodic matrix B must have the same number of rows as A") - m = size(B,2) - (m,m) == size(R) || error("the periodic matrix R must have the same dimensions as the column dimension of B") - (n,n) == size(Q) || error("the periodic matrix Q must have the same dimensions as A") - issymmetric(R) || error("the periodic matrix R must be symmetric") - issymmetric(Q) || error("the periodic matrix Q must be symmetric") - Rt = pmmultrsym(B*inv(R), B) - X, EVALS = pcric(A, Rt, Q; K, adj = true, solver, reltol, abstol, dt, fast, PSD_SLICOT, intpol, intpolmeth) - return X, EVALS, inv(R)*transpose(B)*X - end - function prcric(A::$PM, B::$PM, R::AbstractMatrix, Q::AbstractMatrix; K::Int = 10, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, - fast = true, intpol = solver == "symplectic" ? true : false, intpolmeth = "cubic") - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - n == size(B,1) || error("the periodic matrix B must have the same number of rows as A") - m = size(B,2) - (m,m) == size(R) || error("the periodic matrix R must have the same dimensions as the column dimension of B") - (n,n) == size(Q) || error("the periodic matrix Q must have the same dimensions as A") - issymmetric(R) || error("the matrix R must be symmetric") - issymmetric(Q) || error("the matrix Q must be symmetric") - Rt = pmmultrsym(B*$PM(inv(R),B.period), B) - X, EVALS = pcric(A, Rt, $PM(Q, A.period); K, adj = true, solver, reltol, abstol, dt, fast, PSD_SLICOT, intpol, intpolmeth) - return X, EVALS, inv(R)*B'*X - end - function pfcric(A::$PM, C::$PM, R::$PM, Q::$PM; K::Int = 10, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, - fast = true, intpol = solver == "symplectic" ? true : false, intpolmeth = "cubic") - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - n == size(C,2) || error("the periodic matrix C must have the same number of columns as A") - m = size(C,1) - (m,m) == size(R) || error("the periodic matrix R must have same dimensions as the row dimension of C") - (n,n) == size(Q) || error("the periodic matrix Q must have the same dimensions as A") - issymmetric(R) || error("the periodic matrix R must be symmetric") - issymmetric(Q) || error("the periodic matrix Q must be symmetric") - Rt = pmtrmulsym(C,inv(R)*C) - X, EVALS = pcric(A, Rt, Q; K, adj = false, solver, reltol, abstol, dt, fast, PSD_SLICOT, intpol, intpolmeth) - return X, EVALS, (X*C')*inv(R) - end - function pfcric(A::$PM, C::$PM, R::AbstractMatrix, Q::AbstractMatrix; K::Int = 10, PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1.e-4, abstol = 1.e-7, dt = 0.0, - fast = true, intpol = solver == "symplectic" ? true : false, intpolmeth = "cubic") - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - n == size(C,2) || error("the periodic matrix C must have the same number of columns as A") - m = size(C,1) - (m,m) == size(R) || error("the periodic matrix R must have same dimensions as the row dimension of C") - (n,n) == size(Q) || error("the periodic matrix Q must have the same dimensions as A") - issymmetric(R) || error("the matrix R must be symmetric") - issymmetric(Q) || error("the matrix Q must be symmetric") - Rt = pmtrmulsym(C,$PM(inv(R), C.period)*C) - X, EVALS = pcric(A, Rt, $PM(Q, A.period); K, adj = false, solver, reltol, abstol, dt, fast, PSD_SLICOT, intpol, intpolmeth) - return X, EVALS, (X*C')*inv(R) - end - end -end -""" - pfcric(A, C, R, Q; K = 10, solver, intpol, intpolmeth, reltol, abstol, fast) -> (X, EVALS, F) - -Compute the symmetric stabilizing solution `X(t)` of the periodic filtering related Riccati differential equation - - . -1 - X(t) = A(t)X(t) + X(t)A(t)' + Q(t) - X(t)C(t)'R(t) C(t)X(t) , - -the periodic stabilizing Kalman gain - - -1 - F(t) = X(t)C(t)'R(t) - -and the corresponding stable characteristic multipliers `EVALS` of `A(t)-F(t)C(t)`. - -The periodic matrices `A`, `C`, `R` and `Q` must have the same type and commensurate periods, -and additionally `R` must be symmetric positive definite and `Q` must be symmetric positive semidefinite. -The resulting symmetric periodic solution `X` has the period -set to the least common commensurate period of `A`, `C`, `R` and `Q` and the number of subperiods -is adjusted accordingly. - -If `fast = true` (default) the multiple-shooting method is used in conjunction with fast pencil reduction techniques, as proposed in [1], -to determine the periodic solution in `t = 0` and a multiple point generator of the appropriate periodic differential Riccati equation -is determined (see [2] for details). -If `fast = false`, the multiple-shooting method is used in -conjunction with the periodic Schur decomposition to determine multiple point generators directly from the stable periodic invariant subspace of -an appropriate symplectic transition matrix (see also [2] for more details). - -The keyword argument `K` specifies the number of grid points to be used -for the resulting multiple point periodic generator (default: `K = 10`). -The obtained periodic generator is finally converted into a periodic function matrix which determines for a given `t` -the function value `X(t)` by integrating the appropriate ODE from the nearest grid point value. - -To speedup function evaluations, interpolation based function evaluations can be used -by setting the keyword argument `intpol = true` (default: `intpol = true` if `solver = "symplectic"`, otherwise `intpol = false`). -In this case the interpolation method to be used can be specified via the keyword argument -`intpolmeth = meth`. The allowable values for `meth` are: `"constant"`, `"linear"`, `"quadratic"` and `"cubic"` (default). - -The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, together with -the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and -absolute accuracy `abstol` (default: `abstol = 1.e-7`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -For large values of `K`, parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. On solving periodic Riccati equations. - Numerical Linear Algebra with Applications, 15:809-835, 2008. -""" -pfcric(A::PeriodicFunctionMatrix, C::PeriodicFunctionMatrix, R::PeriodicFunctionMatrix, Q::PeriodicFunctionMatrix) -""" - prcric(A, B, R, Q; K = 10, solver, intpol, intpolmeth, reltol, abstol, fast) -> (X, EVALS, F) - -Compute the symmetric stabilizing solution `X(t)` of the periodic control related Riccati differential equation - - . -1 - -X(t) = A(t)'X(t) + X(t)A(t) + Q(t) - X(t)B(t)R(t) B(t)'X(t) , - -the periodic stabilizing state-feedback gain - - -1 - F(t) = R(t) B(t)'X(t) - -and the corresponding stable characteristic multipliers `EVALS` of `A(t)-B(t)F(t)`. - -The periodic matrices `A`, `B`, `R` and `Q` must have the same type and commensurate periods, -and additionally `R` must be symmetric positive definite and `Q` must be symmetric positive semidefinite. -The resulting symmetric periodic solution `X` has the period -set to the least common commensurate period of `A`, `B`, `R` and `Q` and the number of subperiods -is adjusted accordingly. - -If `fast = true` (default) the multiple-shooting method is used in conjunction with fast pencil reduction techniques, as proposed in [1], -to determine the periodic solution in `t = 0` and a multiple point generator of the appropriate periodic differential Riccati equation -is determined (see [2] for details). -If `fast = false`, the multiple-shooting method is used in -conjunction with the periodic Schur decomposition to determine multiple point generators directly from the stable periodic invariant subspace of -an appropriate symplectic transition matrix (see also [2] for more details). - -The keyword argument `K` specifies the number of grid points to be used -for the resulting multiple point periodic generator (default: `K = 10`). -The obtained periodic generator is finally converted into a periodic function matrix which determines for a given `t` -the function value `X(t)` by integrating the appropriate ODE from the nearest grid point value. - -To speedup function evaluations, interpolation based function evaluations can be used -by setting the keyword argument `intpol = true` (default: `intpol = true` if `solver = "symplectic"`, otherwise `intpol = false`). -In this case the interpolation method to be used can be specified via the keyword argument -`intpolmeth = meth`. The allowable values for `meth` are: `"constant"`, `"linear"`, `"quadratic"` and `"cubic"` (default). - -The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, together with -the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and -absolute accuracy `abstol` (default: `abstol = 1.e-7`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -For large values of `K`, parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. On solving periodic Riccati equations. - Numerical Linear Algebra with Applications, 15:809-835, 2008. -""" -prcric(A::PeriodicFunctionMatrix, B::PeriodicFunctionMatrix, R::PeriodicFunctionMatrix, Q::PeriodicFunctionMatrix) -""" - pgcric(A, R, Q[, K = 1]; adj = false, solver, reltol, abstol, fast, PSD_SLICOT) -> (X, EVALS) - -Compute periodic generators for the periodic Riccati differential equation in the _filtering_ form - - . - X(t) = A(t)X(t) + X(t)A(t)' + Q(t) - X(t)R(t)X(t), if adj = false, - -or in the _control_ form - - . - -X(t) = A(t)'X(t) + X(t)A(t) + Q(t) - X(t)R(t)X(t) , if adj = true, - -where `A(t)`, `R(t)` and `Q(t)` are periodic matrices of commensurate periods, -with `A(t)` square, `R(t)` symmetric and positive definite, -and `Q(t)` symmetric and positive semidefinite. -The resulting `X` is a collection of periodic generator matrices determined -as a periodic time-series matrix with `N` components, where `N = 1` if `A(t)`, `R(t)` and `Q(t)` -are constant matrices and `N = K` otherwise. -`EVALS` contains the stable characteristic multipliers of the monodromy matrix of -the corresponding Hamiltonian matrix (also called closed-loop characteristic multipliers). -The period of `X` is set to the least common commensurate period of `A(t)`, `R(t)` and `Q(t)` -and the number of subperiods is adjusted accordingly. -Any component matrix of `X` is a valid initial value to be used to generate the -solution over a full period by integrating the appropriate differential equation. - -If `fast = true` (default) the multiple-shooting method is used in conjunction with fast pencil reduction techniques, as proposed in [1], -to determine the periodic solution in `t = 0` and a multiple point generator of the appropriate periodic differential Riccati equation -is determined (see [2] for details). -If `fast = false`, the multiple-shooting method is used in -conjunction with the periodic Schur decomposition to determine multiple point generators directly from the stable periodic invariant subspace of -an appropriate symplectic transition matrix (see also [2] for more details). - -The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, together with -the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and -absolute accuracy `abstol` (default: `abstol = 1.e-7`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - -For large values of `K`, parallel computation of the matrices of the discrete-time problem can be alternatively performed -by starting Julia with several execution threads. -The number of execution threads is controlled either by using the `-t/--threads` command line argument -or by using the `JULIA_NUM_THREADS` environment variable. - -_References_ - -[1] A. Varga. On solving periodic differential matrix equations with applications to periodic system norms computation. - Proc. IEEE CDC/ECC, Seville, 2005. - -[2] A. Varga. On solving periodic Riccati equations. - Numerical Linear Algebra with Applications, 15:809-835, 2008. -""" -function pgcric(A::PM1, R::PM3, Q::PM4, K::Int = 1; scaling = true, adj = false, rtol::Real = size(A,1)*eps(real(float(one(eltype(A))))), - PSD_SLICOT::Bool = true, solver = "symplectic", reltol = 1e-4, abstol = 1e-7, dt = 0.0, fast = false) where - {PM1 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}, - PM3 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}, - PM4 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}} - - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - (n,n) == size(R) || error("the periodic matrix R must have same dimensions as A") - (n,n) == size(Q) || error("the periodic matrix Q must have same dimensions as A") - - period = promote_period(A, Q, R) - na = Int(round(period/A.period)) - nq = Int(round(period/Q.period)) - nr = Int(round(period/R.period)) - nperiod = gcd(na*A.nperiod, nq*Q.nperiod, nr*R.nperiod) - Ts = period/K/nperiod - if PeriodicMatrices.isconstant(A) && PeriodicMatrices.isconstant(R) && PeriodicMatrices.isconstant(Q) - if adj - X, EVALS, = arec(tpmeval(A,0),tpmeval(R,0), tpmeval(Q,0); scaling = 'S', rtol) - else - X, EVALS, = arec(tpmeval(A,0)', tpmeval(R,0), tpmeval(Q,0); scaling = 'S', rtol) - end - return PeriodicTimeSeriesMatrix([X], period; nperiod), EVALS - end - - T = promote_type(eltype(A),eltype(Q),eltype(R),Float64) - n2 = n+n - # use block scaling if appropriate - if scaling - qs = sqrt(norm(Q,1)) - rs = sqrt(norm(R,1)) - scaling = (qs > rs) & (rs > 0) - end - if scaling - scal = qs/rs - Qt = Q/scal - Rt = R*scal - else - Qt = Q; Rt = R - end - - #hpd = Array{T,3}(undef, n2, n2, K) - i1 = 1:n; i2 = n+1:n2 - if K == 1 - hpd = tvcric(A, Rt, Qt, Ts, 0; adj, solver, reltol, abstol) - SF = schur(hpd) - select = abs.(SF.values) .< 1 - n == count(select .== true) || error("The symplectic pencil is not dichotomic") - ordschur!(SF, select) - x = SF.Z[i2,i1]/SF.Z[i1,i1]; x = (x+x')/2 - EVALS = SF.values[i1] - X = PeriodicTimeSeriesMatrix([scaling ? scal*x : x], period; nperiod) - ce = log.(complex(EVALS))/period - return X, isreal(ce) ? real(ce) : ce - end - if fast - hpd = Vector{Matrix{T}}(undef, K) - Threads.@threads for i = 1:K - @inbounds hpd[i] = tvcric(A, Rt, Qt, i*Ts, (i-1)*Ts; adj, solver, reltol, abstol, dt) - end - a, e = psreduc_reg(hpd) - # Compute the ordered QZ decomposition with large eigenvalues in the - # leading positions. Only Z is used. - # Note: eigenvalues of this pencil have a tendency - # to deflate out in the ``desired'' order (large values first) - SF = schur!(e, a) # use (e,a) instead (a,e) - # this code may produce inaccurate small characteristic multipliers for large values of K - # the following test try to detect the presence of infinite values - all(isfinite.(SF.values)) || @warn "possible accuracy loss" - select = adj ? abs.(SF.values) .> 1 : abs.(SF.values) .< 1 - n == count(select .== true) || error("The symplectic pencil is not dichotomic") - ordschur!(SF, select) - EVALS = adj ? SF.values[i2] : SF.values[i1] - # compute the periodic generator in t = 0 - x = SF.Z[i2,i1]/SF.Z[i1,i1]; - x = (x+x')/2 - X = similar(Vector{Matrix{T}},K) - xn = x; xlast = zeros(eltype(x),n,n); kit = 0; - tol = 10*eps()*norm(xn,1) - while norm(xn-xlast,1) > tol && kit <= 3 - kit += 1 - if adj - for i in K:-1:1 - x = (x*hpd[i][i1,i2]-hpd[i][i2,i2])\(hpd[i][i2,i1]-x*hpd[i][i1,i1]) - x = (x+x')/2; - X[i] = x; - end - else - for i in 1:K - x = (hpd[i][i2,i1]+hpd[i][i2,i2]*x)/(hpd[i][i1,i1]+hpd[i][i1,i2]*x) - x = (x+x')/2 - i < K ? X[i+1] = x : X[1] = x - end - end - xlast = xn - xn = X[1] - end - - ce = log.(complex(EVALS))/period - return PeriodicTimeSeriesMatrix(scaling ? scal*X : X, period; nperiod), isreal(ce) ? real(ce) : ce - end - if PSD_SLICOT - hpd = Array{T,3}(undef, n2, n2, K) - Threads.@threads for i = 1:K - @inbounds hpd[:,:,i] = tvcric(A, Rt, Qt, i*Ts, (i-1)*Ts; adj, solver, reltol, abstol, dt) - end - # this code is based on SLICOT tools - S, Z, ev, sind, = PeriodicMatrices.pschur(hpd) - select = adj ? abs.(ev) .< 1 : abs.(ev) .> 1 - psordschur!(S, Z, select; schurindex = sind) - EVALS = adj ? ev[select] : ev[.!select] - X = similar(Vector{Matrix{T}},K) - for i = 1:K - #x = Z1[i][i2,i1]/Z1[i][i1,i1]; - x = Z[i2,i1,i]/Z[i1,i1,i]; - x = (x+x')/2 - X[i] = x - end - else - # this experimental code is based on tools provided in the PeriodicSchurDecompositions package - hpd = Vector{Matrix{T}}(undef, K) - Threads.@threads for i = 1:K - @inbounds hpd[i] = tvcric(A, Rt, Qt, i*Ts, (i-1)*Ts; adj, solver, reltol, abstol, dt) - end - PSF = PeriodicSchurDecompositions.pschur(hpd,:L) - select = adj ? abs.(PSF.values) .< 1 : abs.(PSF.values) .> 1 - ordschur!(PSF, select) - EVALS = adj ? PSF.values[i1] : PSF.values[i2] - X = similar(Vector{Matrix{T}},K) - for i = 1:K - x = PSF.Z[i][i2,i1]/PSF.Z[i][i1,i1]; - x = (x+x')/2 - X[i] = x - end - end - ce = log.(complex(EVALS))/period - return PeriodicTimeSeriesMatrix(scaling ? scal*X : X, period; nperiod), isreal(ce) ? real(ce) : ce -end -function tvcric(A::PM1, R::PM3, Q::PM4, tf, t0; adj = false, solver = "symplectic", reltol = 1e-4, abstol = 1e-7, dt = 0.0) where - {PM1 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}, - PM3 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}, - PM4 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}} - """ - tvcric(A, R, Q, tf, t0; adj, solver, reltol, abstol, dt) -> Φ::Matrix - - Compute the state transition matrix for a linear Hamiltonian ODE with periodic time-varying coefficients. - For the given periodic matrices `A(t)`, `R(t)`, `Q(t)`, with `A(t)` square, `R(t)` and `Q(t)` symmetric, - and the initial time `t0` and final time `tf`, the state transition matrix `Φ(tf,t0)` - is computed by integrating numerically the homogeneous linear ODE - - dΦ(t,t0)/dt = H(t)Φ(t,t0), Φ(t0,t0) = I - - on the time interval `[t0,tf]`. `H(t)` is a periodic Hamiltonian matrix defined as - - - H(t) = [ -A'(t) R(t) ], if adj = false - [ Q(t) A(t) ] - - - or - - H(t) = [ A(t) -R(t) ], if adj = true. - [ -Q(t) -A'(t) ] - - The ODE solver to be employed can be specified using the keyword argument `solver`, - (default: `solver = "symplectic"`) together with - the required relative accuracy `reltol` (default: `reltol = 1.e-4`), - absolute accuracy `abstol` (default: `abstol = 1.e-7`) and/or - the fixed step length `dt` (default: `dt = min(A.period/A.nperiod/100,tf-t0)`). - Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, - which are generally very efficient, but less accurate. If `reltol < 1.e-4`, - higher order solvers are employed able to cope with high accuracy demands. - - The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - - `solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - - `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - - `solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - - `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - - `solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - """ - n = size(A,1) - T = promote_type(typeof(t0), typeof(tf)) - period = promote_period(A, R, Q) - - # using OrdinaryDiffEq - n2 = n+n - u0 = Matrix{T}(I,n2,n2) - tspan = (T(t0),T(tf)) - H = adj ? PeriodicFunctionMatrix(t -> [ tpmeval(A,t) -tpmeval(R,t); -tpmeval(Q,t) -tpmeval(A,t)'], period) : - PeriodicFunctionMatrix(t -> [-tpmeval(A,t)' tpmeval(R,t); tpmeval(Q,t) tpmeval(A,t)], period) - - if solver != "linear" - #f!(du,u,p,t) = mul!(du,A.f(t),u) - #fcric!(du,u,p,t) = mul!(du,tpmeval(H,t),u) - #prob = ODEProblem(fcric!, u0, tspan) - prob = ODEProblem(HamODE!, u0, tspan, (adj,A,R,Q) ) - end - - if solver == "stiff" - if reltol > 1.e-4 - # standard stiff - sol = solve(prob, Rodas4(); reltol, abstol, save_everystep = false) - else - # high accuracy stiff - sol = solve(prob, KenCarp58(); reltol, abstol, save_everystep = false) - end - elseif solver == "non-stiff" - if reltol > 1.e-4 - # standard non-stiff - sol = solve(prob, Tsit5(); reltol, abstol, save_everystep = false) - else - # high accuracy non-stiff - sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) - end - elseif solver == "linear" - iszero(dt) && (dt = min(A.period/A.nperiod/100,tf-t0)) - function update_func!(A,u,p,t) - A .= p(t) - end - DEop = DiffEqArrayOperator(ones(T,n2,n2),update_func=update_func!) - #prob = ODEProblem(DEop, u0, tspan, A.f) - prob = ODEProblem(DEop, u0, tspan, t-> tpmeval(H,t)) - sol = solve(prob,MagnusGL6(), dt = dt, save_everystep = false) - elseif solver == "symplectic" - # high accuracy symplectic - if dt == 0 - sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) - #@show sol.retcode - if sol.retcode == :Failure - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/100) - end - else - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) - end - else - if reltol > 1.e-4 - # low accuracy automatic selection - sol = solve(prob, AutoTsit5(Rosenbrock23()) ; reltol, abstol, save_everystep = false) - else - # high accuracy automatic selection - sol = solve(prob, AutoVern9(Rodas5(),nonstifftol = 11/10); reltol, abstol, save_everystep = false) - end - end - return sol(tf) -end -function HamODE!(du, u, pars, t) - (adj, A, R, Q) = pars - adj ? mul!(du,[ tpmeval(A,t) -tpmeval(R,t); -tpmeval(Q,t) -tpmeval(A,t)'],u) : - mul!(du,[-tpmeval(A,t)' tpmeval(R,t); tpmeval(Q,t) tpmeval(A,t)],u) -end -""" - tvcric_eval(t, W, A, R, Q; adj, solver, reltol, abstol, dt) -> Xval - -Compute the time value `Xval := X(t)` of the solution of the periodic Riccati differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + Q(t) - X(t)R(t)X(t) , X(t0) = W(t0), t0 < t, if adj = false (default), - -or - - . - -X(t) = A(t)'X(t) + X(t)A(t) + Q(t) - X(t)R(t)X(t) , X(t0) = W(t0), t0 > t, if adj = true, - -using the periodic generator `W` determined with the function [`pgcric`](@ref) for the same periodic matrices `A`, `R` and `Q` -and the same value of the keyword argument `adj`. -The initial time `t0` is the nearest time grid value to `t`, from below, if `adj = false`, or from above, if `adj = true`. -The resulting `Xval` is a symmetric matrix. - -The ODE solver to be employed can be specified using the keyword argument `solver`, -(default: `solver = "symplectic"`) together with -the required relative accuracy `reltol` (default: `reltol = 1.e-4`), -absolute accuracy `abstol` (default: `abstol = 1.e-7`) and/or -the fixed step length `dt` (default: `dt = min(A.period/A.nperiod/100,tf-t0)`). -Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, -which are generally very efficient, but less accurate. If `reltol < 1.e-4`, -higher order solvers are employed able to cope with high accuracy demands. - -The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - -`solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - -`solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - -`solver = "linear"` - use a special solver for linear ODEs (`MagnusGL6()`) with fixed time step `dt`; - -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). -""" -function tvcric_eval(t::Real,X::PeriodicTimeSeriesMatrix,A::PM1, R::PM3, Q::PM4; adj = false, solver = "non-stiff", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {PM1 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}, - PM3 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}, - PM4 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}} - tsub = X.period/X.nperiod - tf = mod(t,tsub) - tf == 0 && (return X.values[1]) - ind = findfirst(X.ts .≈ tf) - isnothing(ind) || (return X.values[ind]) - if adj - ind = findfirst(X.ts .> tf*(1+10*eps())) - isnothing(ind) ? (ind = 1; t0 = tsub) : t0 = X.ts[ind]; - else - ind = findfirst(X.ts .> tf*(1+10*eps())) - isnothing(ind) ? ind = length(X) : ind -= 1 - t0 = X.ts[ind] - end - return tvcric_sol(A, R, Q, tf, t0, X.values[ind]; adj, solver, reltol, abstol, dt) -end -function tvcric_sol(A::PM1, R::PM3, Q::PM4, tf, t0, X0::AbstractMatrix; adj = false, solver = "symplectic", reltol = 1e-4, abstol = 1e-7, dt = 0.0) where - {PM1 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}, - PM3 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}, - PM4 <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix}} - """ - tvcric_sol(A, R, Q, tf, t0, X0; adj, solver, reltol, abstol, dt) -> Xval - - Compute the time value `Xval := X(tf)` of the solution of the periodic Riccati differential equation - - . - X(t) = A(t)X(t) + X(t)A(t)' + Q(t) - X(t)R(t)X(t) , X(t0) = X0, t0 < tf, if adj = false, - - or - - . - -X(t) = A(t)'X(t) + X(t)A(t) + Q(t) - X(t)R(t)X(t) , X(t0) = X0, t0 > tf, if adj = true, - - using the initial value `X0`. The periodic matrices `A`, `R` and `Q` must have the same type, the same dimensions and commensurate periods, - and additionally `X0`, `R` and `Q` must be symmetric. The resulting `Xval` is a symmetric matrix `Xval`. - - The ODE solver to be employed can be specified using the keyword argument `solver`, - (default: `solver = "symplectic"`) together with - the required relative accuracy `reltol` (default: `reltol = 1.e-4`), - absolute accuracy `abstol` (default: `abstol = 1.e-7`) and/or - the fixed step length `dt` (default: `dt = min(A.period/A.nperiod/100,tf-t0)`). - Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, - which are generally very efficient, but less accurate. If `reltol < 1.e-4`, - higher order solvers are employed able to cope with high accuracy demands. - - The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package can be selected: - - `solver = "non-stiff"` - use a solver for non-stiff problems (`Tsit5()` or `Vern9()`); - - `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - - `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - - `solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - """ - T = promote_type(typeof(t0), typeof(tf)) - - # using OrdinaryDiffEq - u0 = triu2vec(X0) - tspan = (T(t0),T(tf)) - prob = ODEProblem(RicODE!, u0, tspan, (adj,A,R,Q) ) - - if solver == "stiff" - if reltol > 1.e-4 - # standard stiff - sol = solve(prob, Rodas4(); reltol, abstol, save_everystep = false) - else - # high accuracy stiff - sol = solve(prob, KenCarp58(); reltol, abstol, save_everystep = false) - end - elseif solver == "non-stiff" - if reltol > 1.e-4 - # standard non-stiff - sol = solve(prob, Tsit5(); reltol, abstol, save_everystep = false) - else - # high accuracy non-stiff - sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) - end - elseif solver == "symplectic" - # high accuracy symplectic - if dt == 0 - sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) - if sol.retcode == SciMLBase.ReturnCode.Failure - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/1000) - end - else - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) - end - else - if reltol > 1.e-4 - # low accuracy automatic selection - sol = solve(prob, AutoTsit5(Rosenbrock23()) ; reltol, abstol, save_everystep = false) - else - # high accuracy automatic selection - sol = solve(prob, AutoVern9(Rodas5(),nonstifftol = 11/10); reltol, abstol, save_everystep = false) - end - end - return vec2triu(sol(tf),her=true) -end -function RicODE!(du, u, pars, t) - (adj, A, R, Q) = pars - At = A(t) - Xt = vec2triu(u,her=true) - if adj - du[:] = -triu2vec(At'*Xt + Xt*At + Q(t) - Xt*R(t)*Xt) - else - du[:] = triu2vec(At*Xt + Xt*At' + Q(t) - Xt*R(t)*Xt) - end -end diff --git a/src/psdric.jl b/src/psdric.jl deleted file mode 100644 index 2107019..0000000 --- a/src/psdric.jl +++ /dev/null @@ -1,551 +0,0 @@ -""" - prdric(A, B, R, Q[, S]; itmax = 0, nodeflate = false, fast, rtol) -> (X, EVALS, F) - -Solve the periodic Riccati difference equation - - X(i) = Q(i) + A(i)'X(i+1)A(i) - (A(i)'X(i+1)B(i) + S(i))* - -1 - (B(i)'X(i+1)B(i) + R(i)) (A(i)'X(i+1)B(i) + S(i))' - -and compute the stabilizing periodic state feedback - - -1 - F(i) = -(B(i)'X(i+1)B(i) + R(i)) (B(i)'X(i+1)A(i) + S(i)') - -and the corresponding stable closed-loop characteristic multipliers of `A(i)-B(i)F(i)` in `EVALS`. - -The `n×n` and `n×m` periodic matrices `A(i)` and `B(i)` are contained in the -`PeriodicArray` objects `A` and `B`, and must have the same sampling time. -`R(i)`, `Q(i)` and `S(i)` are `m×m`, `n×n` and `n×m` periodic matrices of same sampling times -as `A` and `B`, and such that `R(i)` and `Q(i)` are symmetric. `R(i)`, `Q(i)` and `S(i)` are contained in the -`PeriodicArray` objects `R`, `Q` and `S`. -`R`, `Q` and `S` can be alternatively provided as constant real matrices. -The resulting symmetric periodic solution `X` and periodic state feedback gain `F` have the period -set to the least common commensurate period of `A`, `B`, `R` and `Q` and the number of subperiods -is adjusted accordingly. - -If `fast = true`, the fast structure exploiting pencil reduction based method of [1] is used -to determine a periodic generator in `X(1)`, which allows to generate iteratively the solution -over the whole period. -If `fast = false` (default), the periodic Schur decomposition based approach of [1] is employed, applied to a -symplectic pair of periodic matrices. If `nodeflate = false` (default), the underlying periodic pencil -is preprocessed to eliminate (deflate) the infinite characteristic multipliers originating -from the problem structure. If `nodeflate = true`, no preliminary deflation is performed. - -An iterative refining of the accuracy of the computed solution -can be performed by using `itmax = k`, with `k > 0` (default: `k = 0`). - -To detect singularities of involved matrices, the keyword parameter `rtol = tol` can be used to -specify the lower bound for the 1-norm reciprocal condition number. -The default value of `tol` is `n*ϵ`, where `ϵ` is the working _machine epsilon_. - -_References_ - -[1] A. Varga. On solving periodic Riccati equations. - Numerical Linear Algebra with Applications, 15:809-835, 2008. - -""" -function prdric(A::PA1, B::PA2, R::PA3, Q::PA4, S::Union{PA5,Missing} = missing; itmax::Int = 0, nodeflate::Bool = false, PSD_SLICOT::Bool = true, fast = false, rtol::Real = size(A,1)*eps()) where - {PA1 <: PeriodicArray, PA2 <: PeriodicArray, PA3 <: PeriodicArray, PA4 <: PeriodicArray, PA5 <: PeriodicArray} - n = size(A,1) - n == size(A,2) || error("the periodic matrix A must be square") - n == size(B,1) || error("the periodic matrix B must have the same number of rows as A") - m = size(B,2) - (m,m) == size(R) || error("the periodic matrix R must have the same dimensions as the column dimension of B") - (n,n) == size(Q) || error("the periodic matrix Q must have the same dimensions as A") - issymmetric(R) || error("the periodic matrix R must be symmetric") - issymmetric(Q) || error("the periodic matrix Q must be symmetric") - if ismissing(S) - S = PeriodicArray(zeros(n,m),A.period,nperiod = max(A.dperiod,B.dperiod,Q.dperiod,R.dperiod)) - end - A.Ts ≈ B.Ts ≈ R.Ts ≈ Q.Ts || error("A, B, R and Q must have the same sampling time") - - pa = size(A.M,3) - pb = size(B.M,3) - pq = size(Q.M,3) - pr = size(R.M,3) - ps = size(S.M,3) - p = lcm(pa,pb,pq,pr,ps) - period = promote_period(A, B, R, Q, S) - - epsm = eps() - - # [ A(i) 0 B(i) ] [ I 0 0 ] - # Work on M(i) = [ -Q(i) I -S(i) ] and L(i) = [ 0 A(i)' 0 ] - # [ S(i)' 0 R(i) ] [ 0 -B(i)' 0 ] - - n2 = 2*n - ZERA = zeros(n,n) - ZERBT = zeros(m,n) - EYE = eye(Float64,n) - if fast - # use fast structure exploiting reduction of the symplectic pencil - ZERA2 = zeros(n2,n2) - a = [A.M[:,:,1] ZERA; -Q.M[:,:,1] EYE; S.M[:,:,1]' ZERBT] - e = [ EYE ZERA; ZERA A.M[:,:,1]'; ZERBT -B.M[:,:,1]'] - QR = qr([B.M[:,:,1]; -S.M[:,:,1]; R.M[:,:,1]]) - i1 = m+1:n2+m; - i2 = n2+1:n2+n2 - si = QR.Q[:,i1]'*a - ti = QR.Q[:,i1]'*e - for i = 2:p - (ia,ib,iq,ir,is) = mod.(i-1,(pa,pb,pq,pr,ps)).+1 - a = [A.M[:,:,ia] ZERA; -Q.M[:,:,iq] EYE; S.M[:,:,is]' ZERBT] - e = [ EYE ZERA; ZERA A.M[:,:,ia]'; ZERBT -B.M[:,:,ib]'] - QR = qr([B.M[:,:,ib]; -S.M[:,:,is]; R.M[:,:,ir]]) - F = qr([ -ti ; QR.Q[:,i1]'*a ]) - si = (F.Q'*[si; ZERA2])[i2,:]; - ti = (F.Q'*[ZERA2; QR.Q[:,i1]'*e])[i2,:]; - end - SF = schur!(ti, si) # use (e,a) instead (a,e) to favor natural appearance of large eigenvalues - select = abs.(SF.values) .> 1 - n == count(select .== true) || error("The symplectic pencil is not dichotomic") - n == count(view(select,1:n)) || ordschur!(SF, select) - i1 = 1:n; i2 = n+1:n2 - EVALS = SF.values[i2] - # compute the periodic generator in i = 1 - x = SF.Z[i2,i1]/SF.Z[i1,i1]; x = (x+x')/2 - - NormRes = 1; it = 0 - X = Array{Float64,3}(undef, n, n, p) - F = Array{Float64,3}(undef, m, n, p) - tol = sqrt(epsm)/100000 - while NormRes > tol && it <= itmax - for i = p:-1:1 - (ia,ib,iq,ir,is) = mod.(i-1,(pa,pb,pq,pr,ps)).+1 - F[:,:,i] = (B.M[:,:,ib]'*x*B.M[:,:,ib]+R.M[:,:,ir])\(B.M[:,:,ib]'*x*A.M[:,:,ia]+S.M[:,:,is]') - x = A.M[:,:,ia]'*x*A.M[:,:,ia] - (A.M[:,:,ia]'*x*B.M[:,:,ib] + S.M[:,:,is])*F[:,:,i] + Q.M[:,:,iq] - X[:,:,i] = (x+x')/2 - end - (ia,ib,iq,ir,is) = mod.(p-1,(pa,pb,pq,pr,ps)).+1 - G = (B.M[:,:,ib]'*x*B.M[:,:,ib]+R.M[:,:,ir])\(B.M[:,:,ib]'*x*A.M[:,:,ia]+S.M[:,:,is]') - Res = A.M[:,:,ia]'*x*A.M[:,:,ia] - (A.M[:,:,ia]'*x*B.M[:,:,ib] + S.M[:,:,is])*G + Q.M[:,:,iq]- X[:,:,p] - NormRes = norm(Res) / max(1,norm(X[:,:,p])); - #@show it, NormRes - it += 1 - end - return PeriodicArray(X,period), EVALS, PeriodicArray(F,period) - end - p2 = p+p - St = Array{Float64,3}(undef, n2+m, n2+m, p2) - i1 = 1:n - i2 = n+1:n2 - iric = 1:n2 - if nodeflate - # compute eigenvalues of the quotient product - X = Array{Float64,3}(undef, n, n, p) - F = Array{Float64,3}(undef, m, n, p) - k = 1 - for i = 1:p - (ia,ib,iq,ir,is) = mod.(i-1,(pa,pb,pq,pr,ps)).+1 - St[:,:,k] = [ A.M[:,:,ia] ZERA B.M[:,:,ib]; -Q.M[:,:,iq] EYE -S.M[:,:,is]; S.M[:,:,is]' ZERBT R.M[:,:,ir] ] - St[:,:,k+1] = [ EYE zeros(n,n+m); ZERA A.M[:,:,ia]' zeros(n,m); ZERBT -B.M[:,:,ib]' zeros(m,m)] - k += 2 - end - s = trues(p2); [s[2*i] = false for i in 1:p] - _, Zt, ev, sind, = pgschur!(St, s) - select = abs.(ev) .< 1 - n == count(select .== true) || error("The symplectic pencil is not dichotomic") - pgordschur!(St, s, Zt, select; schurindex = sind) - EVALS = ev[select] - for i = 1:p - xi = view(Zt,n+1:n2+m,i1,2*i-1) - FU = MatrixEquations._LUwithRicTest(view(Zt,i1,i1,2*i-1),rtol) - rdiv!(xi,FU) - F[:,:,i] = -xi[n+1:end,:] - x = view(xi,i1,:) - X[:,:,i] = (x+x')/2 - end - else - k = 1 - for i = 1:p - (ia,ib,iq,ir,is) = mod.(i-1,(pa,pb,pq,pr,ps)).+1 - H = qr([A.M[:,:,ia]'; -B.M[:,:,ib]']) - L2 = H.Q'*[-Q.M[:,:,iq] EYE -S.M[:,:,is]; S.M[:,:,is]' ZERBT R.M[:,:,ir]] - St[:,:,k] = [ A.M[:,:,ia] ZERA B.M[:,:,ib]; L2] - St[:,:,k+1] = [ EYE zeros(n,n+m); ZERA H.R zeros(n,m); zeros(m,n2+m)] - k += 2 - end - k = 1 - z = Array{Float64,3}(undef, n2+m, n2, p) - for i = 1:p - G = qr(St[n2+1:end,:,k]') - cond(G.R) * epsm < 1 || error("The extended symplectic pencil is not regular") - zi = ((G.Q*I)[:,[m+1:m+n2; 1:m]])[:,iric] - St[:,iric,k] = St[:,:,k]*zi - km1 = k == 1 ? p2 : k-1 - St[:,iric, km1] = St[:,:, km1]*zi - z[:,:,i] = zi - k += 2 - end - s = trues(p2); [s[2*i] = false for i in 1:p] - # compute eigenvalues for the inverse product to exploit the natural order of - # computed eigenvalues (i.e., large eigenvalues first) - St, Zt, ev, sind, = pgschur(view(St,iric,iric,p2:-1:1), s) - select = abs.(ev) .> 1 - n == count(select .== true) || error("The symplectic pencil is not dichotomic") - n == count(view(select,1:n)) || pgordschur!(St, s, Zt, select; schurindex = sind) - EVALS = ev[.!select] - X = Array{Float64,3}(undef, n, n, p) - F = Array{Float64,3}(undef, m, n, p) - for i = 1:p - #zi = z[:,:,i]*Zt[:,i1,2*i-1] - zi = z[:,:,i]*Zt[:,i1,mod(p2-2*i+2,p2)+1] - #xi = view(Zt,n+1:n2+m,i1,2*i-1) - FU = MatrixEquations._LUwithRicTest(view(zi,i1,i1),rtol) - # rdiv!(xi,FU) - # F[:,:,i] = -xi[n+1:end,:] - # X[:,:,i] = xi[i1,:] - xi = zi[n+1:end,i1]/FU - F[:,:,i] = -xi[n+1:end,:] - x = view(xi,i1,:) - X[:,:,i] = (x+x')/2 - end - end - if itmax > 0 - NormRes = 1; it = 0 - tol = sqrt(epsm)/100000 - x = X[:,:,1] - while NormRes > tol && it < itmax - for i = p:-1:1 - (ia,ib,iq,ir,is) = mod.(i-1,(pa,pb,pq,pr,ps)).+1 - F[:,:,i] = (B.M[:,:,ib]'*x*B.M[:,:,ib]+R.M[:,:,ir])\(B.M[:,:,ib]'*x*A.M[:,:,ia]+S.M[:,:,is]') - x = A.M[:,:,ia]'*x*A.M[:,:,ia] - (A.M[:,:,ia]'*x*B.M[:,:,ib] + S.M[:,:,is])*F[:,:,i] + Q.M[:,:,iq] - X[:,:,i] = (x+x')/2 - end - (ia,ib,iq,ir,is) = mod.(p-1,(pa,pb,pq,pr,ps)).+1 - G = (B.M[:,:,ib]'*x*B.M[:,:,ib]+R.M[:,:,ir])\(B.M[:,:,ib]'*x*A.M[:,:,ia]+S.M[:,:,is]') - Res = A.M[:,:,ia]'*x*A.M[:,:,ia] - (A.M[:,:,ia]'*x*B.M[:,:,ib] + S.M[:,:,is])*G + Q.M[:,:,iq]- X[:,:,p] - NormRes = norm(Res) / max(1,norm(X[:,:,p])); - #@show it, NormRes - it += 1 - end - end - return PeriodicArray(X,period), EVALS, PeriodicArray(F,period) -end -function prdric(A::PM1, B::PM2, R::PM3, Q::PM4, S::AbstractMatrix; kwargs...) where - {PM1 <: PeriodicArray, PM2 <: PeriodicArray, PM3 <: PeriodicArray, PM4 <: PeriodicArray} - nperiod = rationalize(A.period/A.Ts).num - prdric(A, B, R, Q, PeriodicArray(S, A.period; nperiod); kwargs...) -end -function prdric(A::PM1, B::PM2, R::AbstractMatrix, Q::PM4, S::Union{PM5,AbstractMatrix,Missing} = missing; kwargs...) where - {PM1 <: PeriodicArray, PM2 <: PeriodicArray, PM4 <: PeriodicArray, PM5 <: PeriodicArray} - nperiod = rationalize(A.period/A.Ts).num - prdric(A, B, PeriodicArray(R, A.period; nperiod), Q, ismissing(S) ? S : isa(S,AbstractMatrix) ? PeriodicArray(S, A.period; nperiod) : S; kwargs...) -end -function prdric(A::PM1, B::PM2, R::Union{PM4,AbstractMatrix}, Q::AbstractMatrix, S::Union{PM5,AbstractMatrix,Missing} = missing; kwargs...) where - {PM1 <: PeriodicArray, PM2 <: PeriodicArray, PM4 <: PeriodicArray, PM5 <: PeriodicArray} - nperiod = rationalize(A.period/A.Ts).num - prdric(A, B, isa(R,AbstractMatrix) ? PeriodicArray(R, A.period; nperiod) : R, PeriodicArray(Q, A.period; nperiod), ismissing(S) ? S : isa(S,AbstractMatrix) ? PeriodicArray(S, A.period; nperiod) : S; kwargs...) -end -""" - prdric(A, B, R, Q[, S]; itmax = 0, nodeflate = false, fast, rtol) -> (X, EVALS, F) - -Solve the periodic Riccati difference equation - - X(i) = Q(i) + A(i)'X(i+1)A(i) - (A(i)'X(i+1)B(i) + S(i))* - -1 - (B(i)'X(i+1)B(i) + R(i)) (A(i)'X(i+1)B(i) + S(i))' - -and compute the stabilizing periodic state feedback - - -1 - F(i) = -(B(i)'X(i+1)B(i) + R(i)) (B(i)'X(i+1)A(i) + S(i)') - -and the corresponding stable closed-loop _core_ characteristic multipliers of `A(i)-B(i)F(i)` in `EVALS`. - -The `n(i+1)×n(i)` and `n(i+1)×m` periodic matrices `A(i)` and `B(i)` are contained in the -`PeriodicMatrix` objects `A` and `B`, and must have the same sampling time. -`R(i)`, `Q(i)` and `S(i)` are `m×m`, `n(i)×n(i)` and `n(i)×m` periodic matrices of same sampling times -as `A` and `B`, and such that `R(i)` and `Q(i)` are symmetric. `R(i)`, `Q(i)` and `S(i)` are contained in the -`PeriodicMatrix` objects `R`, `Q` and `S`. -`R`, `Q` and `S` can be alternatively provided as constant real matrices. -The resulting `n(i)×n(i)` symmetric periodic solution `X(i)` and `m×n(i)` periodic state feedback gain `F(i)` have the period -set to the least common commensurate period of `A`, `B`, `R` and `Q` and the number of subperiods -is adjusted accordingly. - -If `fast = true`, the fast structure exploiting pencil reduction based method of [1] is used -to determine a periodic generator in `X(j)`, which allows to generate iteratively the solution -over the whole period. The value of `j` corresponds to the least dimension `nc` of `n(i)` -(which is also the number of core characteristic multipliers). -If `fast = false` (default), the periodic Schur decomposition based approach of [1] is employed, applied to a -symplectic pair of periodic matrices. If `nodeflate = false` (default), the underlying periodic pencil -is preprocessed to eliminate (deflate) the infinite characteristic multipliers originating -from the problem structure. If `nodeflate = true`, no preliminary deflation is performed. - -An iterative refining of the accuracy of the computed solution -can be performed by using `itmax = k`, with `k > 0` (default: `k = 0`). - -To detect singularities of involved matrices, the keyword parameter `rtol = tol` can be used to -specify the lower bound for the 1-norm reciprocal condition number. -The default value of `tol` is `n*ϵ`, where `ϵ` is the working _machine epsilon_ and `n` is the maximum of `n(i)`. - -_References_ - -[1] A. Varga. On solving periodic Riccati equations. - Numerical Linear Algebra with Applications, 15:809-835, 2008. -""" -function prdric(A::PM1, B::PM2, R::PM3, Q::PM4, S::Union{PM5,Missing} = missing; itmax::Int = 0, nodeflate::Bool = false, PSD_SLICOT::Bool = true, fast = false, rtol::Real = size(A.M[1],1)*eps()) where - {PM1 <: PeriodicMatrix, PM2 <: PeriodicMatrix, PM3 <: PeriodicMatrix, PM4 <: PeriodicMatrix, PM5 <: PeriodicMatrix} - ma, na = size(A) - mb, nb = size(B) - nq = size(Q,2) - missingS = ismissing(S) - missingS || (ms = size(S,1)) - m = maximum(nb) - m == minimum(nb) || throw(DimensionMismatch("only constant number of columns of B supported")) - pr = length(R) - pa = length(A) - pb = length(B) - pq = length(Q) - ps = missingS ? 1 : length(S) - p = lcm(pa,pb,pq,pr,ps) - - # perform checks applicable to both constant and time-varying dimensions - all(view(ma,mod.(1:p-1,pa).+1) .== view(mb,mod.(1:p-1,pb).+1)) || - error("the number of rows of A[i] must be equal to the number of rows of B[i]") - - all([LinearAlgebra.checksquare(R.M[i]) == m for i in 1:pr]) || - throw(DimensionMismatch("incompatible dimensions between B and R")) - all([issymmetric(R.M[i]) for i in 1:pr]) || error("all R[i] must be symmetric matrices") - all([issymmetric(Q.M[i]) for i in 1:pq]) || error("all Q[i] must be symmetric matrices") - n = maximum(na) - ma1 = maximum(ma) - constdim = (n == minimum(na) && ma1 == minimum(ma)) - if constdim - # constant dimensions - n == ma1 || error("the periodic matrix A must be square") - n == minimum(nq) == maximum(nq) || throw(DimensionMismatch("incompatible dimensions between A and Q")) - missingS && - (S = PeriodicMatrix(zeros(n,m),A.period,nperiod = rationalize(A.period/A.Ts).num)) - ms = size(S,1) - else - # time-varying dimensions - missingS && (ps = p; S = PeriodicMatrix([zeros(na[i],m) for i in 1:p], A.period, nperiod = 1)) - pa == pb == pq == ps || throw(DimensionMismatch("A, B, Q and S must have the same length")) - (pr == 1 || pr == pa) || throw(DimensionMismatch("R must have the length equal to or length of A")) - all(ma .== view(na,mod.(1:pa,pa).+1)) || - error("the number of columns of A[i+1] must be equal to the number of rows of A[i]") - all([LinearAlgebra.checksquare(Q.M[i]) == na[i] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and Q")) - end - A.Ts ≈ B.Ts ≈ R.Ts ≈ Q.Ts ≈ S.Ts || error("A, B, R, Q and S must have the same sampling time") - - period = promote_period(A, B, R, Q, S) - - epsm = eps() - - # [ A(i) 0 B(i) ] [ I 0 0 ] - # Work on M(i) = [ -Q(i) I -S(i) ] and L(i) = [ 0 A(i)' 0 ] - # [ S(i)' 0 R(i) ] [ 0 -B(i)' 0 ] - - n2 = 2*n - ZERA = zeros(n,n) - ZERBT = zeros(m,n) - EYE = eye(Float64,n) - if fast - # use fast structure exploiting reduction of the symplectic periodic pair (M(i),L(i)) - ZERA2 = zeros(n2,n2) - k = argmin(na) - ni = na[k] - nip1 = na[mod(k,pa)+1] - (ia,ib,iq,ir,is) = mod.(k-1,(pa,pb,pq,pr,ps)).+1 - a = [A.M[ia] view(ZERA,1:nip1,1:ni); -Q.M[iq] view(EYE,1:ni,1:ni); S.M[is]' view(ZERBT,1:m,1:ni)] - e = [ view(EYE,1:nip1,1:nip1) view(ZERA,1:nip1,1:nip1); view(ZERA,1:ni,1:nip1) A.M[ia]'; view(ZERBT,1:m,1:nip1) -B.M[ib]'] - QR = qr([B.M[ib]; -S.M[is]; R.M[ir]]) - i1 = m+1:nip1+ni+m; - si = QR.Q[:,i1]'*a - ti = QR.Q[:,i1]'*e - n1 = size(si,2) - nc = na[k] - for i = k+1:p+k-1 - (ia,ib,iq,ir,is) = mod.(i-1,(pa,pb,pq,pr,ps)).+1 - ni = na[ia] - nip1 = na[mod(ia,pa)+1] - a = [A.M[ia] view(ZERA,1:nip1,1:ni); -Q.M[iq] view(EYE,1:ni,1:ni); S.M[is]' view(ZERBT,1:m,1:ni)] - e = [ view(EYE,1:nip1,1:nip1) view(ZERA,1:nip1,1:nip1); view(ZERA,1:ni,1:nip1) A.M[ia]'; view(ZERBT,1:m,1:nip1) -B.M[ib]'] - QR = qr([B.M[ib]; -S.M[is]; R.M[ir]]) - i1 = m+1:nip1+ni+m; - F = qr([ -ti ; QR.Q[:,i1]'*a]) - ni2 = 2*na[ia] - si = (F.Q'*[si; view(ZERA2,1:ma[ia]+na[ia],1:n1)])[ni2+1:end,:] - ip1 = ia+1; ip1 > pa && (ip1 = 1) - ti = (F.Q'*[view(ZERA2,1:size(ti,1),1:2*na[ip1]); QR.Q[:,i1]'*e])[ni2+1:end,:] - end - - SF = schur!(ti, si) # use (e,a) instead (a,e) to favor natural appearance of large eigenvalues - select = abs.(SF.values) .> 1 - nc == count(select .== true) || error("The symplectic pencil is not dichotomic") - nc == count(view(select,1:nc)) || ordschur!(SF, select) - i1 = 1:nc; i2 = nc+1:2*nc - EVALS = SF.values[i2] - # compute the periodic generator in i = 1 - x = SF.Z[i2,i1]/SF.Z[i1,i1]; x = (x+x')/2 - - NormRes = 1; it = 0 - X = Vector{Array{Float64}}(undef, p) - F = Vector{Array{Float64}}(undef, p) - tol = sqrt(epsm)/100000 - while NormRes > tol && it <= itmax - for i = p:-1:1 - ix = mod(i+k-2,p)+1 - (ia,ib,iq,ir,is) = mod.(i+k-2,(pa,pb,pq,pr,ps)).+1 - F[ix] = (B.M[ib]'*x*B.M[ib]+R.M[ir])\(B.M[ib]'*x*A.M[ia]+S.M[is]') - x = A.M[ia]'*x*A.M[ia] - (A.M[ia]'*x*B.M[ib] + S.M[is])*F[ix] + Q.M[iq] - X[ix] = (x+x')/2 - end - (ia,ib,iq,ir,is) = mod.(p+k-1-1,(pa,pb,pq,pr,ps)).+1 - G = (B.M[ib]'*x*B.M[ib]+R.M[ir])\(B.M[ib]'*x*A.M[ia]+S.M[is]') - kp = mod(p+k-2,p)+1 - Res = A.M[ia]'*x*A.M[ia] - (A.M[ia]'*x*B.M[ib] + S.M[is])*G + Q.M[iq]- X[kp] - NormRes = norm(Res) / max(1,norm(X[kp])); - #@show it, NormRes - it += 1 - end - return PeriodicMatrix(X,period), EVALS, PeriodicMatrix(F,period) - else - At = zeros(n,n,pa); [copyto!(view(At,1:ma[i],1:na[i],i),A.M[i]) for i in 1:pa] - Bt = zeros(n,m,pb); [copyto!(view(Bt,1:mb[i],1:m,i),B.M[i]) for i in 1:pb] - Rt = zeros(m,m,pr); [copyto!(view(Rt,1:m,1:m,i),R.M[i]) for i in 1:pr] - Qt = zeros(n,n,pq); [copyto!(view(Qt,1:nq[i],1:nq[i],i),Q.M[i]) for i in 1:pq] - St = zeros(n,m,ps); missingS || [copyto!(view(St,1:ms[i],1:m,i),S.M[i]) for i in 1:ps] - Xt, EVALS, Ft = prdric(PeriodicArray(At,A.period;nperiod = A.nperiod),PeriodicArray(Bt,B.period;nperiod = B.nperiod), - PeriodicArray(Rt,R.period;nperiod = R.nperiod),PeriodicArray(Qt,Q.period;nperiod = Q.nperiod), - missingS ? missing : PeriodicArray(St,S.period;nperiod = S.nperiod); itmax, nodeflate, PSD_SLICOT, fast, rtol) - - return PeriodicMatrix([Xt.M[1:na[mod(i-1,pa)+1],1:na[mod(i-1,pa)+1],i] for i in 1:p],period), EVALS, PeriodicMatrix([Ft.M[1:m,1:na[mod(i-1,pa)+1],i] for i in 1:p],period) - end - -end -function prdric(A::PM1, B::PM2, R::PM3, Q::PM4, S::AbstractMatrix; kwargs...) where - {PM1 <: PeriodicMatrix, PM2 <: PeriodicMatrix, PM3 <: PeriodicMatrix, PM4 <: PeriodicMatrix} - nperiod = rationalize(A.period/A.Ts).num - prdric(A, B, R, Q, PeriodicMatrix(S, A.period; nperiod); kwargs...) -end -function prdric(A::PM1, B::PM2, R::AbstractMatrix, Q::PM4, S::Union{PM5,AbstractMatrix,Missing} = missing; kwargs...) where - {PM1 <: PeriodicMatrix, PM2 <: PeriodicMatrix, PM4 <: PeriodicMatrix, PM5 <: PeriodicMatrix} - nperiod = rationalize(A.period/A.Ts).num - prdric(A, B, PeriodicMatrix(R, A.period; nperiod), Q, ismissing(S) ? S : isa(S,AbstractMatrix) ? PeriodicMatrix(S, A.period; nperiod) : S; kwargs...) -end -function prdric(A::PM1, B::PM2, R::Union{PM4,AbstractMatrix}, Q::AbstractMatrix, S::Union{PM5,AbstractMatrix,Missing} = missing; kwargs...) where - {PM1 <: PeriodicMatrix, PM2 <: PeriodicMatrix, PM4 <: PeriodicMatrix, PM5 <: PeriodicMatrix} - ma, na = size(A) - if maximum(na) == minimum(na) && maximum(ma) == minimum(ma) - nperiod = rationalize(A.period/A.Ts).num - prdric(A, B, isa(R,AbstractMatrix) ? PeriodicMatrix(R, A.period; nperiod) : R, PeriodicMatrix(Q, A.period; nperiod), ismissing(S) ? S : isa(S,AbstractMatrix) ? PeriodicMatrix(S, A.period; nperiod) : S; kwargs...) - else - error("Constant Q and S are not possible for time-varying dimensions") - end -end -""" - pfdric(A, C, R, Q[, S]; itmax = 0, nodeflate = false, fast, rtol) -> (X, EVALS, F) - -Solve the periodic Riccati difference equation - - X(i+1) = Q(i) + A(i)X(i)A(i)' - (A(i)X(i)C(i)' + S(i))* - -1 - (C(i)X(i)C(i)' + R(i)) (A(i)X(i)C(i)' + S(i))' - -and compute the stabilizing periodic Kalman gain - - -1 - F(i) = -(C(i)X(i)A(i)' + S(i)')(C(i)X(i)C(i)' + R(i)) - -and the corresponding stable Kalman filter characteristic multipliers of `A(i)-F(i)C(i)` in `EVALS`. - -The `n×n` and `m×n` periodic matrices `A(i)` and `C(i)` are contained in the -`PeriodicArray` objects `A` and `C`, and must have the same sampling time. -`R(i)`, `Q(i)` and `S(i)` are `m×m`, `n×n` and `m×n` periodic matrices of same sampling times -as `A` and `C`, and such that `R(i)` and `Q(i)` are symmetric. -`R`, `Q` and `S` can be alternatively provided as constant real matrices. -The resulting symmetric periodic solution `X` and Kalman filter gain `F` have the period -set to the least common commensurate period of `A`, `C`, `R` and `Q` and the number of subperiods -is adjusted accordingly. - -The dual method of [1] is employed (see [`prdric`](@ref) for the description of keyword parameters). - -_References_ - -[1] A. Varga. On solving periodic Riccati equations. - Numerical Linear Algebra with Applications, 15:809-835, 2008. - -""" -function pfdric(A::PA1, C::PA2, R::PA3, Q::PA4, S::Union{PA5,Missing} = missing; kwargs...) where - {PA1 <: PeriodicArray, PA2 <: PeriodicArray, PA3 <: PeriodicArray, PA4 <: PeriodicArray, PA5 <: PeriodicArray} - Xt, EVALS, Ft = prdric(reverse(A)', reverse(C)', reverse(R), reverse(Q), ismissing(S) ? S : reverse(S); kwargs...) - return reverse(pmshift(Xt)), EVALS, reverse(Ft)' -end -function pfdric(A::PM1, C::PM2, R::PM3, Q::PM4, S::AbstractMatrix; kwargs...) where - {PM1 <: PeriodicArray, PM2 <: PeriodicArray, PM3 <: PeriodicArray, PM4 <: PeriodicArray} - nperiod = rationalize(A.period/A.Ts).num - pfdric(A, C, R, Q, PeriodicArray(S, A.period; nperiod); kwargs...) -end -function pfdric(A::PM1, C::PM2, R::AbstractMatrix, Q::PM4, S::Union{PM5,AbstractMatrix,Missing} = missing; kwargs...) where - {PM1 <: PeriodicArray, PM2 <: PeriodicArray, PM4 <: PeriodicArray, PM5 <: PeriodicArray} - nperiod = rationalize(A.period/A.Ts).num - pfdric(A, C, PeriodicArray(R, A.period; nperiod), Q, ismissing(S) ? S : isa(S,AbstractMatrix) ? PeriodicArray(S, A.period; nperiod) : S; kwargs...) -end -function pfdric(A::PM1, C::PM2, R::Union{PM4,AbstractMatrix}, Q::AbstractMatrix, S::Union{PM5,AbstractMatrix,Missing} = missing; kwargs...) where - {PM1 <: PeriodicArray, PM2 <: PeriodicArray, PM4 <: PeriodicArray, PM5 <: PeriodicArray} - nperiod = rationalize(A.period/A.Ts).num - pfdric(A, C, isa(R,AbstractMatrix) ? PeriodicArray(R, A.period; nperiod) : R, PeriodicArray(Q, A.period; nperiod), ismissing(S) ? S : isa(S,AbstractMatrix) ? PeriodicArray(S, A.period; nperiod) : S; kwargs...) -end -""" - pfdric(A, C, R, Q[, S]; itmax = 0, nodeflate = false, fast, rtol) -> (X, EVALS, F) - -Solve the periodic Riccati difference equation - - X(i+1) = Q(i) + A(i)X(i)A(i)' - (A(i)X(i)C(i)' + S(i))* - -1 - (C(i)X(i)C(i)' + R(i)) (A(i)X(i)C(i)' + S(i))' - -and compute the stabilizing periodic Kalman gain - - -1 - F(i) = -(C(i)X(i)A(i)' + S(i)')(C(i)X(i)C(i)' + R(i)) - -and the corresponding stable Kalman filter _core_ characteristic multipliers of `A(i)-F(i)C(i)` in `EVALS`. - -The `n(i+1)×n(i)` and `m×n(i)` periodic matrices `A(i)` and `C(i)` are contained in the -`PeriodicMatrix` objects `A` and `C`, and must have the same sampling time. -`R(i)`, `Q(i)` and `S(i)` are `m×m`, `n(i)×n(i)` and `m×n(i)` periodic matrices of same sampling times -as `A` and `C`, and such that `R(i)` and `Q(i)` are symmetric. -`R`, `Q` and `S` can be alternatively provided as constant real matrices. -The resulting symmetric periodic solution `X` and Kalman filter gain `F` have the period -set to the least common commensurate period of `A`, `C`, `R` and `Q` and the number of subperiods -is adjusted accordingly. - -The dual method of [1] is employed (see [`prdric`](@ref) for the description of keyword parameters). - -_References_ - -[1] A. Varga. On solving periodic Riccati equations. - Numerical Linear Algebra with Applications, 15:809-835, 2008. -""" -function pfdric(A::PM1, C::PM2, R::PM3, Q::PM4, S::Union{PM5,Missing} = missing; kwargs...) where - {PM1 <: PeriodicMatrix, PM2 <: PeriodicMatrix, PM3 <: PeriodicMatrix, PM4 <: PeriodicMatrix, PM5 <: PeriodicMatrix} - Xt, EVALS, Ft = prdric(reverse(A)', reverse(C)', reverse(R), reverse(Q), ismissing(S) ? S : reverse(S); kwargs...) - return reverse(pmshift(Xt)), EVALS, reverse(Ft)' -end -function pfdric(A::PM1, C::PM2, R::PM3, Q::PM4, S::AbstractMatrix; kwargs...) where - {PM1 <: PeriodicMatrix, PM2 <: PeriodicMatrix, PM3 <: PeriodicMatrix, PM4 <: PeriodicMatrix, PM5 <: PeriodicMatrix} - Xt, EVALS, Ft = prdric(reverse(A)', reverse(C)', reverse(R), reverse(Q), S; kwargs...) - return reverse(pmshift(Xt)), EVALS, reverse(Ft)' - end - function pfdric(A::PM1, C::PM2, R::AbstractMatrix, Q::PM4, S::Union{PM5,AbstractMatrix,Missing} = missing; kwargs...) where - {PM1 <: PeriodicMatrix, PM2 <: PeriodicMatrix, PM4 <: PeriodicMatrix, PM5 <: PeriodicMatrix} - nperiod = rationalize(A.period/A.Ts).num - pfdric(A, C, PeriodicMatrix(R, A.period; nperiod), Q, ismissing(S) ? S : isa(S,AbstractMatrix) ? PeriodicMatrix(S, A.period; nperiod) : S; kwargs...) -end -function pfdric(A::PM1, C::PM2, R::Union{PM4,AbstractMatrix}, Q::AbstractMatrix, S::Union{PM5,AbstractMatrix,Missing} = missing; kwargs...) where - {PM1 <: PeriodicMatrix, PM2 <: PeriodicMatrix, PM4 <: PeriodicMatrix, PM5 <: PeriodicMatrix} - ma, na = size(A) - if maximum(na) == minimum(na) && maximum(ma) == minimum(ma) - nperiod = rationalize(A.period/A.Ts).num - pfdric(A, C, isa(R,AbstractMatrix) ? PeriodicMatrix(R, A.period; nperiod) : R, PeriodicMatrix(Q, A.period; nperiod), ismissing(S) ? S : isa(S,AbstractMatrix) ? PeriodicMatrix(S, A.period; nperiod) : S; kwargs...) - else - error("Constant Q and S are not possible for time-varying dimensions") - end -end - - diff --git a/src/pslifting.jl b/src/pslifting.jl index a5b23f6..4e5963d 100644 --- a/src/pslifting.jl +++ b/src/pslifting.jl @@ -1,3 +1,17 @@ +""" + ps2frls(psysc::PeriodicStateSpace, N) -> sys::DescriptorStateSpace + +Build the real frequency-lifted representation of a continuous-time periodic system. + +For a continuos-time periodic system `psysc = (A(t),B(t),C(t),D(t))`, the real +LTI state-space representation `sys = (At-Nt,Bt,Ct,Dt)` is built, where `At`, `Bt`, `Ct` and `Dt` +are truncated block Toeplitz matrices and `Nt` is a block diagonal matrix. +`N` is the number of selected harmonic components in the Fourier series of system matrices. + +_Note:_ This is an experimental implementation based on the operator representation of periodic matrices +in the [ApproxFun.jl](https://github.com/JuliaApproximation/ApproxFun.jl) package. +""" +function ps2frls end """ ps2ls(psys::PeriodicStateSpace[, kstart]; ss = false, cyclic = false) -> sys::DescriptorStateSpace @@ -386,43 +400,43 @@ end ps2ls1(psys::PeriodicStateSpace{PeriodicArray{:d,T}}, k::Int = 1; kwargs...) where {T} = ps2ls1(convert(PeriodicStateSpace{PeriodicMatrix},psys), k; kwargs...) -""" - ps2frls(psysc::PeriodicStateSpace, N) -> sys::DescriptorStateSpace +# """ +# ps2frls(psysc::PeriodicStateSpace, N) -> sys::DescriptorStateSpace -Build the real frequency-lifted representation of a continuous-time periodic system. +# Build the real frequency-lifted representation of a continuous-time periodic system. -For a continuos-time periodic system `psysc = (A(t),B(t),C(t),D(t))`, the real -LTI state-space representation `sys = (At-Nt,Bt,Ct,Dt)` is built, where `At`, `Bt`, `Ct` and `Dt` -are truncated block Toeplitz matrices and `Nt` is a block diagonal matrix. -`N` is the number of selected harmonic components in the Fourier series of system matrices. +# For a continuos-time periodic system `psysc = (A(t),B(t),C(t),D(t))`, the real +# LTI state-space representation `sys = (At-Nt,Bt,Ct,Dt)` is built, where `At`, `Bt`, `Ct` and `Dt` +# are truncated block Toeplitz matrices and `Nt` is a block diagonal matrix. +# `N` is the number of selected harmonic components in the Fourier series of system matrices. -_Note:_ This is an experimental implementation based on the operator representation of periodic matrices -in the [ApproxFun.jl](https://github.com/JuliaApproximation/ApproxFun.jl) package. -""" -function ps2frls(psysc::PeriodicStateSpace{PM}, N::Int; P::Int= 1) where {T,PM <: AbstractPeriodicArray{:c,T}} - psyscfr = typeof(psysc) <: PeriodicStateSpace{FourierFunctionMatrix} ? psysc : - convert(PeriodicStateSpace{FourierFunctionMatrix},psysc) - N >= 0 || error("number of selected harmonics must be nonnegative, got $N") - (Af, Bf, Cf, Df) = P == 1 ? (psyscfr.A, psyscfr.B, psyscfr.C, psyscfr.D) : - (FourierFunctionMatrix(Fun(t -> psyscfr.A.M(t),Fourier(0..P*psyscfr.A.period))), - FourierFunctionMatrix(Fun(t -> psyscfr.B.M(t),Fourier(0..P*psyscfr.B.period))), - FourierFunctionMatrix(Fun(t -> psyscfr.C.M(t),Fourier(0..P*psyscfr.C.period))), - FourierFunctionMatrix(Fun(t -> psyscfr.D.M(t),Fourier(0..P*psyscfr.D.period)))) +# _Note:_ This is an experimental implementation based on the operator representation of periodic matrices +# in the [ApproxFun.jl](https://github.com/JuliaApproximation/ApproxFun.jl) package. +# """ +# function ps2frls(psysc::PeriodicStateSpace{PM}, N::Int; P::Int= 1) where {T,PM <: AbstractPeriodicArray{:c,T}} +# psyscfr = typeof(psysc) <: PeriodicStateSpace{FourierFunctionMatrix} ? psysc : +# convert(PeriodicStateSpace{FourierFunctionMatrix},psysc) +# N >= 0 || error("number of selected harmonics must be nonnegative, got $N") +# (Af, Bf, Cf, Df) = P == 1 ? (psyscfr.A, psyscfr.B, psyscfr.C, psyscfr.D) : +# (FourierFunctionMatrix(Fun(t -> psyscfr.A.M(t),Fourier(0..P*psyscfr.A.period))), +# FourierFunctionMatrix(Fun(t -> psyscfr.B.M(t),Fourier(0..P*psyscfr.B.period))), +# FourierFunctionMatrix(Fun(t -> psyscfr.C.M(t),Fourier(0..P*psyscfr.C.period))), +# FourierFunctionMatrix(Fun(t -> psyscfr.D.M(t),Fourier(0..P*psyscfr.D.period)))) - n, m = size(Bf); p = size(Cf,1); - D = Derivative(domain(Af.M)) - ND = DiagDerOp(D,n) - Aop = Af.M - ND - Cop = Multiplication(Cf.M,domainspace(ND)) - sdu = domainspace(DiagDerOp(0*D,m)) - Bop = Multiplication(Bf.M,sdu) - Dop = Multiplication(Df.M,sdu) - Ntx = 2*n*(2*N+1) - Ntu = m*(2*N+1) - Nty = p*(2*N+1) - sys = dss(Matrix(Aop[1:Ntx,1:Ntx]), Matrix(Bop[1:Ntx,1:Ntu]), Matrix(Cop[1:Nty,1:Ntx]), Matrix(Dop[1:Nty,1:Ntu])) - return sys -end +# n, m = size(Bf); p = size(Cf,1); +# D = Derivative(domain(Af.M)) +# ND = DiagDerOp(D,n) +# Aop = Af.M - ND +# Cop = Multiplication(Cf.M,domainspace(ND)) +# sdu = domainspace(DiagDerOp(0*D,m)) +# Bop = Multiplication(Bf.M,sdu) +# Dop = Multiplication(Df.M,sdu) +# Ntx = 2*n*(2*N+1) +# Ntu = m*(2*N+1) +# Nty = p*(2*N+1) +# sys = dss(Matrix(Aop[1:Ntx,1:Ntx]), Matrix(Bop[1:Ntx,1:Ntu]), Matrix(Cop[1:Nty,1:Ntx]), Matrix(Dop[1:Nty,1:Ntu])) +# return sys +# end """ ps2fls(psysc::PeriodicStateSpace, N; P) -> sys::DescriptorStateSpace @@ -669,12 +683,12 @@ end # return BT # end -function DiagDerOp(D::Union{ApproxFunBase.DerivativeWrapper,ApproxFunBase.ConstantTimesOperator}, n::Int) - Z = tuple(D,ntuple(n->0I,n-1)...) - for i = 2:n - Z1 = tuple(ntuple(n->0I,i-1)...,D,ntuple(n->0I,n-i)...) - Z = tuple(Z...,Z1...) - end - return hvcat(n,Z...) -end +# function DiagDerOp(D::Union{ApproxFunBase.DerivativeWrapper,ApproxFunBase.ConstantTimesOperator}, n::Int) +# Z = tuple(D,ntuple(n->0I,n-1)...) +# for i = 2:n +# Z1 = tuple(ntuple(n->0I,i-1)...,D,ntuple(n->0I,n-i)...) +# Z = tuple(Z...,Z1...) +# end +# return hvcat(n,Z...) +# end diff --git a/src/pslyap.jl b/src/pslyap.jl deleted file mode 100644 index 5fc2acb..0000000 --- a/src/pslyap.jl +++ /dev/null @@ -1,3006 +0,0 @@ -for PM in (:PeriodicArray, :PeriodicMatrix) - @eval begin - function pdlyap(A::$PM, C::$PM; adj::Bool = true, stability_check = false) - A.Ts ≈ C.Ts || error("A and C must have the same sampling time") - period = promote_period(A, C) - na = rationalize(period/A.period).num - K = na*A.nperiod*A.dperiod - X = pslyapd(A.M, C.M; adj, stability_check) - p = lcm(length(A),length(C)) - return $PM(X, period; nperiod = div(K,p)) - end - function pdlyap2(A::$PM, C::$PM, E::$PM; stability_check = false) - A.Ts ≈ C.Ts ≈ E.Ts || error("A, C and E must have the same sampling time") - period = promote_period(A, C, E) - na = rationalize(period/A.period).num - K = na*A.nperiod*A.dperiod - X, Y = pslyapd2(A.M, C.M, E.M; stability_check) - p = lcm(length(A),length(C),length(E)) - return $PM(X, period; nperiod = div(K,p)), $PM(Y, period; nperiod = div(K,p)) - end - end -end -function pdlyap(A::PM, C::PM; kwargs...) where {PM <: SwitchingPeriodicMatrix} - X = pdlyap(convert(PeriodicMatrix,A),convert(PeriodicMatrix,C); kwargs...) - return convert(SwitchingPeriodicMatrix,X) -end -function pdlyap2(A::PM, C::PM, E::PM; kwargs...) where {PM <: SwitchingPeriodicMatrix} - X, Y = pdlyap2(convert(PeriodicMatrix,A),convert(PeriodicMatrix,C),convert(PeriodicMatrix,E); kwargs...) - return convert(SwitchingPeriodicMatrix,X), convert(SwitchingPeriodicMatrix,Y) -end -function pdlyap(A::PM, C::PM; kwargs...) where {PM <: SwitchingPeriodicArray} - X = pdlyap(convert(PeriodicArray,A),convert(PeriodicArray,C); kwargs...) - return convert(SwitchingPeriodicArray,X) -end -function pdlyap2(A::PM, C::PM, E::PM; kwargs...) where {PM <: SwitchingPeriodicArray} - X, Y = pdlyap2(convert(PeriodicArray,A),convert(PeriodicArray,C),convert(PeriodicArray,E); kwargs...) - return convert(SwitchingPeriodicArray,X), convert(SwitchingPeriodicArray,Y) -end - - -""" - pdlyap(A, C; adj = true, stability_check = false) -> X - -Solve the periodic discrete-time Lyapunov equation - - A'σXA + C = X for adj = true - -or - - AXA' + C = σX for adj = false, - -where `σ` is the forward shift operator `σX(i) = X(i+1)`. - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods, -and additionally `C` must be symmetric. The resulting symmetric periodic solution `X` has the period -set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -If `stability_check = true`, the stability of characteristic multipliers of `A` is checked and an error is issued -if any characteristic multiplier has modulus equal to or larger than one. - -The periodic discrete analog of the Bartels-Stewart method based on the periodic Schur form -of the periodic matrix `A` is employed [1]. - -_Reference:_ - -[1] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. -""" -pdlyap(A::PeriodicArray, C::PeriodicArray; adj::Bool = true) -""" - pdlyap2(A, C, E; stability_check = false) -> (X, Y) - -Solve the pair of periodic discrete-time Lyapunov equations - - AXA' + C = σX, - A'σYA + E = Y, - -where `σ` is the forward shift operator `σX(i) = X(i+1)` and `σY(i) = Y(i+1)`. - -The periodic matrices `A`, `C` and `E` must have the same type, the same dimensions and commensurate periods, -and additionally `C` and `E` must be symmetric. The resulting symmetric periodic solutions `X` and `Y` have the period -set to the least common commensurate period of `A`, `C` and `E` and the number of subperiods -is adjusted accordingly. - -If `stability_check = true`, the stability of characteristic multipliers of `A` is checked and an error is issued -if any characteristic multiplier has modulus equal to or larger than one. - -The periodic discrete analog of the Bartels-Stewart method based on the periodic Schur form -of the periodic matrix `A` is employed [1]. - -_Reference:_ - -[1] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. -""" -pdlyap2(A::PeriodicArray, C::PeriodicArray, E::PeriodicArray; stability_check = false) - -for PM in (:PeriodicArray, :PeriodicMatrix, :SwitchingPeriodicMatrix, :SwitchingPeriodicArray) - @eval begin - function prdlyap(A::$PM, C::$PM; stability_check = false) - pdlyap(A, C; adj = true, stability_check) - end - function prdlyap(A::$PM, C::AbstractArray; stability_check = false) - pdlyap(A, $PM(C, A.Ts; nperiod = 1); adj = true, stability_check) - end - function pfdlyap(A::$PM, C::$PM; stability_check = false) - pdlyap(A, C; adj = false, stability_check) - end - function pfdlyap(A::$PM, C::AbstractArray; stability_check = false) - pdlyap(A, $PM(C, A.Ts; nperiod = 1); adj = false, stability_check) - end - end -end -""" - prdlyap(A, C; stability_check = false) -> X - -Solve the reverse-time periodic discrete-time Lyapunov equation - - A'σXA + C = X - -where `σ` is the forward shift operator `σX(i) = X(i+1)`. - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods, -and additionally `C` must be symmetric. The resulting symmetric periodic solution `X` has the period -set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -If `stability_check = true`, the stability of characteristic multipliers of `A` is checked and an error is issued -if any characteristic multiplier has modulus equal to or larger than one. -""" -prdlyap(A::PeriodicArray, C::PeriodicArray) -""" - pfdlyap(A, C; stability_check = false) -> X - -Solve the forward-time periodic discrete-time Lyapunov equation - - AXA' + C = σX - -where `σ` is the forward shift operator `σX(i) = X(i+1)`. - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods, -and additionally `C` must be symmetric. The resulting symmetric periodic solution `X` has the period -set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -If `stability_check = true`, the stability of characteristic multipliers of `A` is checked and an error is issued -if any characteristic multiplier has modulus equal to or larger than one. -""" -pfdlyap(A::PeriodicArray, C::PeriodicArray) -""" - pslyapd(A, C; adj = true, stability_check = false) -> X - -Solve the periodic discrete-time Lyapunov equation. - -For the square `n`-th order periodic matrices `A(i)`, `i = 1, ..., pa` and -`C(i)`, `i = 1, ..., pc` of periods `pa` and `pc`, respectively, -the periodic solution `X(i)`, `i = 1, ..., p` of period `p = lcm(pa,pc)` of the -periodic Lyapunov equation is computed: - - A(i)'*X(i+1)*A(i) + C(i) = X(i), i = 1, ..., p for `adj = true`; - - A(i)*X(i)*A(i)' + C(i) = X(i+1), i = 1, ..., p for `adj = false`. - -The periodic matrices `A` and `C` are stored in the `n×n×pa` and `n×n×pc` 3-dimensional -arrays `A` and `C`, respectively, and `X` results as a `n×n×p` 3-dimensional array. - -Alternatively, the periodic matrices `A` and `C` can be stored in the `pa`- and `pc`-dimensional -vectors of matrices `A` and `C`, respectively, and `X` results as a `p`-dimensional vector of matrices. - -If `stability_check = true`, the stability of characteristic multipliers of `A` is checked and an error is issued -if any characteristic multiplier has modulus equal to or larger than one. - -The periodic discrete analog of the Bartels-Stewart method based on the periodic Schur form -of the periodic matrix `A` is employed [1]. - -_Reference:_ - -[1] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. -""" -function pslyapd(A::AbstractArray{T1, 3}, C::AbstractArray{T2, 3}; adj::Bool = true, stability_check = false) where {T1, T2} - n = LinearAlgebra.checksquare(A[:,:,1]) - pa = size(A,3) - pc = size(C,3) - (LinearAlgebra.checksquare(C[:,:,1]) == n && all([issymmetric(C[:,:,i]) for i in 1:pc])) || - throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - p = lcm(pa,pc) - - T = promote_type(T1, T2) - T <: BlasFloat || (T = promote_type(Float64,T)) - A1 = T1 == T ? A : A1 = convert(Array{T,3},A) - C1 = T2 == T ? C : C1 = convert(Array{T,3},C) - - # Reduce A to Schur form and transform C - AS, Q, ev, KSCHUR = PeriodicMatrices.pschur(A1) - stability_check && maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - - #X = Q'*C*Q - X = Array{T,3}(undef, n, n, p) - - for i = 1:p - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - ia1 = mod(i,pa)+1 - - X[:,:,i] = adj ? utqu(view(C1,:,:,ic),view(Q,:,:,ia)) : - utqu(view(C1,:,:,ic),view(Q,:,:,ia1)) - end - - # solve A'σXA + C = X if adj = true or AXA' + C = σX if adj = false - pdlyaps!(KSCHUR, AS, X; adj) - - #X <- Q*X*Q' - for i = 1:p - ia = mod(i-1,pa)+1 - utqu!(view(X,:,:,i),view(Q,:,:,ia)') - end - return X -end -function pslyapd(A::AbstractVector{Matrix{T1}}, C::AbstractVector{Matrix{T2}}; adj::Bool = true, stability_check = false) where {T1, T2} - pa = length(A) - pc = length(C) - ma, na = size.(A,1), size.(A,2) - mc, nc = size.(C,1), size.(C,2) - p = lcm(pa,pc) - all(ma .== view(na,mod.(1:pa,pa).+1)) || - error("the number of columns of A[i+1] must be equal to the number of rows of A[i]") - if adj - all([LinearAlgebra.checksquare(C[mod(i-1,pc)+1]) == na[mod(i-1,pa)+1] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and C")) - else - all([LinearAlgebra.checksquare(C[mod(i-1,pc)+1]) == ma[mod(i-1,pa)+1] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and C")) - end - - all([issymmetric(C[i]) for i in 1:pc]) || error("all C[i] must be symmetric matrices") - - n = maximum(na) - - T = promote_type(T1, T2) - T <: BlasFloat || (T = promote_type(Float64,T)) - A1 = zeros(T, n, n, pa) - C1 = zeros(T, n, n, pc) - [copyto!(view(A1,1:ma[i],1:na[i],i), T.(A[i])) for i in 1:pa] - adj ? [copyto!(view(C1,1:nc[i],1:nc[i],i), T.(C[i])) for i in 1:pc] : - [copyto!(view(C1,1:mc[i],1:mc[i],i), T.(C[i])) for i in 1:pc] - - # Reduce A to Schur form and transform C - AS, Q, ev, KSCHUR = PeriodicMatrices.pschur(A1) - stability_check && maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - - # if adj = true: X = Q'*C*Q; if adj = false: X = σQ'*C*σQ - X = Array{T,3}(undef, n, n, p) - - for i = 1:p - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - ia1 = mod(i,pa)+1 - - X[:,:,i] = adj ? utqu(view(C1,:,:,ic),view(Q,:,:,ia)) : - utqu(view(C1,:,:,ic),view(Q,:,:,ia1)) - end - # solve A'σXA + C = X if adj = true or AXA' + C = σX if adj = false - pdlyaps!(KSCHUR, AS, X; adj) - - #X <- Q*X*Q' - for i = 1:p - ia = mod(i-1,pa)+1 - utqu!(view(X,:,:,i),view(Q,:,:,ia)') - end - return adj ? [X[1:na[mod(i-1,pa)+1],1:na[mod(i-1,pa)+1],i] for i in 1:p] : - [X[1:na[mod(i-1,pa)+1],1:na[mod(i-1,pa)+1],i] for i in 1:p] -end -""" - pslyapd2(A, C, E; stability_check = false) -> (X, Y) - -Solve a pair of periodic discrete-time Lyapunov equations. - -For the square `n`-th order periodic matrices `A(i)`, `i = 1, ..., pa`, -`C(i)`, `i = 1, ..., pc`, and `E(i)`, `i = 1, ..., pe` of periods `pa`, `pc` and `pe`, respectively, -the periodic solutions `X(i)`, `i = 1, ..., p` and `Y(i)`, `i = 1, ..., p` -of period `p = lcm(pa,pc,pe)` of the periodic Lyapunov equations are computed: - - A(i)*X(i)*A(i)' + C(i) = X(i+1), i = 1, ..., p , - - A(i)'*Y(i+1)*A(i) + E(i) = Y(i), i = 1, ..., p . - -The periodic matrices `A`, `C` and `E` are stored in the `n×n×pa`, `n×n×pc` and `n×n×pe` 3-dimensional -arrays `A`, `C` and `E`, respectively, and `X` and `Y` result as `n×n×p` 3-dimensional arrays. - -Alternatively, the periodic matrices `A`, `C` and `E` can be stored in the `pa`-, `pc`- and `pe`-dimensional -vectors of matrices `A`, `C` and `E`, respectively, and `X` and `Y` result as `p`-dimensional vectors of matrices. - -If `stability_check = true`, the stability of characteristic multipliers of `A` is checked and an error is issued -if any characteristic multiplier has modulus equal to or larger than one. - -The periodic discrete analog of the Bartels-Stewart method based on the periodic Schur form -of the periodic matrix `A` is employed [1]. - -_Reference:_ - -[1] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. -""" -function pslyapd2(A::AbstractVector{Matrix{T1}}, C::AbstractVector{Matrix{T2}}, E::AbstractVector{Matrix{T3}}; stability_check = false) where {T1, T2, T3} - pa = length(A) - pc = length(C) - pe = length(E) - ma, na = size.(A,1), size.(A,2) - mc, nc = size.(C,1), size.(C,2) - me, ne = size.(E,1), size.(E,2) - p = lcm(pa,pc,pe) - all(ma .== view(na,mod.(1:pa,pa).+1)) || - error("the number of columns of A[i+1] must be equal to the number of rows of A[i]") - all([LinearAlgebra.checksquare(C[mod(i-1,pc)+1]) == ma[mod(i-1,pa)+1] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and C")) - all([LinearAlgebra.checksquare(E[mod(i-1,pe)+1]) == na[mod(i-1,pa)+1] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and E")) - - - all([issymmetric(C[i]) for i in 1:pc]) || error("all C[i] must be symmetric matrices") - all([issymmetric(E[i]) for i in 1:pe]) || error("all E[i] must be symmetric matrices") - - n = maximum(na) - - T = promote_type(T1, T2, T2) - T <: BlasFloat || (T = promote_type(Float64,T)) - A1 = zeros(T, n, n, pa) - C1 = zeros(T, n, n, pc) - E1 = zeros(T, n, n, pe) - [copyto!(view(A1,1:ma[i],1:na[i],i), T.(A[i])) for i in 1:pa] - [copyto!(view(E1,1:ne[i],1:ne[i],i), T.(E[i])) for i in 1:pe] - [copyto!(view(C1,1:mc[i],1:mc[i],i), T.(C[i])) for i in 1:pc] - - # Reduce A to Schur form and transform C - AS, Q, ev, KSCHUR = PeriodicMatrices.pschur(A1) - stability_check && maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - - # Y = Q'*E*Q; X = σQ'*C*σQ - X = Array{T,3}(undef, n, n, p) - Y = Array{T,3}(undef, n, n, p) - - for i = 1:p - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - ie = mod(i-1,pe)+1 - ia1 = mod(i,pa)+1 - - X[:,:,i] = utqu(view(C1,:,:,ic),view(Q,:,:,ia1)) - Y[:,:,i] = utqu(view(E1,:,:,ie),view(Q,:,:,ia)) - end - # solve A'σXA + C = X - pdlyaps!(KSCHUR, AS, X; adj = false) - # solve AXA' + E = σX - pdlyaps!(KSCHUR, AS, Y; adj = true) - - #X <- Q*X*Q', Y <- = Q*Y*Q' - for i = 1:p - ia = mod(i-1,pa)+1 - utqu!(view(X,:,:,i),view(Q,:,:,ia)') - utqu!(view(Y,:,:,i),view(Q,:,:,ia)') - end - return [X[1:na[mod(i-1,pa)+1],1:na[mod(i-1,pa)+1],i] for i in 1:p], [Y[1:na[mod(i-1,pa)+1],1:na[mod(i-1,pa)+1],i] for i in 1:p] -end -function pslyapd2(A::AbstractArray{T1, 3}, C::AbstractArray{T2, 3}, E::AbstractArray{T3, 3}; stability_check = false) where {T1, T2, T3} - n = LinearAlgebra.checksquare(A[:,:,1]) - pa = size(A,3) - pc = size(C,3) - pe = size(E,3) - (LinearAlgebra.checksquare(C[:,:,1]) == n && all([issymmetric(C[:,:,i]) for i in 1:pc])) || - throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - (LinearAlgebra.checksquare(E[:,:,1]) == n && all([issymmetric(E[:,:,i]) for i in 1:pe])) || - throw(DimensionMismatch("all E[:,:,i] must be $n x $n symmetric matrices")) - p = lcm(pa,pc,pe) - - T = promote_type(T1, T2, T2) - T <: BlasFloat || (T = promote_type(Float64,T)) - A1 = T1 == T ? A : A1 = convert(Array{T,3},A) - C1 = T2 == T ? C : C1 = convert(Array{T,3},C) - E1 = T2 == T ? E : E1 = convert(Array{T,3},E) - - # Reduce A to Schur form and transform C and E - AS, Q, ev, KSCHUR = PeriodicMatrices.pschur(A1) - stability_check && maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - - # Y = Q'*E*Q; X = σQ'*C*σQ - X = Array{T,3}(undef, n, n, p) - Y = Array{T,3}(undef, n, n, p) - for i = 1:p - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - ie = mod(i-1,pe)+1 - ia1 = mod(i,pa)+1 - X[:,:,i] = utqu(view(C1,:,:,ic),view(Q,:,:,ia1)) - Y[:,:,i] = utqu(view(E1,:,:,ie),view(Q,:,:,ia)) - end - - # solve A'σXA + C = X - pdlyaps!(KSCHUR, AS, X; adj = false) - # solve AXA' + E = σX - pdlyaps!(KSCHUR, AS, Y; adj = true) - - #X <- Q*X*Q', Y <- = Q*Y*Q' - for i = 1:p - ia = mod(i-1,pa)+1 - utqu!(view(X,:,:,i),view(Q,:,:,ia)') - utqu!(view(Y,:,:,i),view(Q,:,:,ia)') - end - return X, Y -end -function pslyapd!(X::AbstractArray{T, 3}, A::AbstractArray{T, 3}, C::AbstractArray{T, 3}, Xt::AbstractMatrix{T}, Q::AbstractArray{T, 3}; adj::Bool = true, stability_check = false) where {T} - n = LinearAlgebra.checksquare(A[:,:,1]) - pa = size(A,3) - pc = size(C,3) - (LinearAlgebra.checksquare(C[:,:,1]) == n && all([issymmetric(C[:,:,i]) for i in 1:pc])) || - throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - p = lcm(pa,pc) - - # Reduce A to Schur form and transform C - ev, KSCHUR = PeriodicMatrices.pschur!(A,Q) - stability_check && maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - - for i = 1:p - ic = mod(i-1,pc)+1 - if adj - # X[:,:,i] = utqu(view(C,:,:,ic),view(Q,:,:,ia)) - ia = mod(i-1,pa)+1 - mul!(Xt,view(Q,:,:,ia)',view(C,:,:,ic)) - muladdsym!(view(X,:,:,i),Xt,view(Q,:,:,ia),(0,1)) - else - # X[:,:,i] = utqu(view(C,:,:,ic),view(Q,:,:,ia1)) - ia1 = mod(i,pa)+1 - mul!(Xt,view(Q,:,:,ia1)',view(C,:,:,ic)) - muladdsym!(view(X,:,:,i),Xt,view(Q,:,:,ia1),(0,1)) - end - end - - # solve A'σXA + C = X for adj = true or AXA' + C = σX for adj = false - pdlyaps!(KSCHUR, A, X; adj) - - #X <- Q*X*Q' - for i = 1:p - ia = mod(i-1,pa)+1 - # utqu!(view(X,:,:,i),view(Q,:,:,ia)') - mul!(Xt,view(X,:,:,i),view(Q,:,:,ia)') - muladdsym!(view(X,:,:,i),view(Q,:,:,ia),Xt,(0,1)) - end - return X -end -function pslyapd2!(X::AbstractArray{T, 3}, Y::AbstractArray{T, 3}, A::AbstractArray{T, 3}, C::AbstractArray{T, 3}, E::AbstractArray{T, 3}, Xt::AbstractMatrix{T}, Q::AbstractArray{T, 3}, WORK, pschur_ws; stability_check = false) where {T} - n = LinearAlgebra.checksquare(view(A,:,:,1)) - n == LinearAlgebra.checksquare(view(C,:,:,1)) || - throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - n == LinearAlgebra.checksquare(view(E,:,:,1)) || - throw(DimensionMismatch("all E[:,:,i] must be $n x $n symmetric matrices")) - n == LinearAlgebra.checksquare(view(X,:,:,1)) || - throw(DimensionMismatch("all X[:,:,i] must be $n x $n matrices")) - n == LinearAlgebra.checksquare(view(Y,:,:,1)) || - throw(DimensionMismatch("all Y[:,:,i] must be $n x $n matrices")) - pa = size(A,3) - pc = size(C,3) - pe = size(E,3) - # (LinearAlgebra.checksquare(C[:,:,1]) == n && all([issymmetric(C[:,:,i]) for i in 1:pc])) || - # throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - # (LinearAlgebra.checksquare(E[:,:,1]) == n && all([issymmetric(E[:,:,i]) for i in 1:pe])) || - # throw(DimensionMismatch("all E[:,:,i] must be $n x $n symmetric matrices")) - p = lcm(pa,pc,pe) - p == size(X,3) == size(Y,3) || throw(DimensionMismatch("incompatible third dimensions of X and Y with A, C, and E")) - - # Reduce A to Schur form and transform C and E - ev, KSCHUR = PeriodicMatrices.pschur!(pschur_ws,A,Q) - stability_check && maximum(abs.(ev)) >= one(T) - sqrt(eps(T)) && error("system stability check failed") - - for i = 1:p - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - ie = mod(i-1,pe)+1 - ia1 = mod(i,pa)+1 - # X[:,:,i] = utqu(view(C,:,:,ic),view(Q,:,:,ia1)) - # Y[:,:,i] = utqu(view(E,:,:,ie),view(Q,:,:,ia)) - mul!(Xt,view(Q,:,:,ia1)',view(C,:,:,ic)) - muladdsym!(view(X,:,:,i),Xt,view(Q,:,:,ia1),(0,1)) - mul!(Xt,view(Q,:,:,ia)',view(E,:,:,ie)) - muladdsym!(view(Y,:,:,i),Xt,view(Q,:,:,ia),(0,1)) - end - - # solve A'σXA + C = X and AYA' + E = σY - pdlyaps2!(KSCHUR, A, X, Y, WORK) - - #X <- Q*X*Q', Y <- = Q*Y*Q' - for i = 1:p - ia = mod(i-1,pa)+1 - # utqu!(view(X,:,:,i),view(Q,:,:,ia)') - # utqu!(view(Y,:,:,i),view(Q,:,:,ia)') - mul!(Xt,view(X,:,:,i),view(Q,:,:,ia)') - muladdsym!(view(X,:,:,i),view(Q,:,:,ia),Xt,(0,1)) - mul!(Xt,view(Y,:,:,i),view(Q,:,:,ia)') - muladdsym!(view(Y,:,:,i),view(Q,:,:,ia),Xt,(0,1)) - end - return nothing -end - -""" - pslyapdkr(A, C; adj = true) -> X - -Solve the periodic discrete-time Lyapunov matrix equation - - A'σXA + C = X, if adj = true, - -or - - A*X*A' + C = σX, if adj = false, - -where `σ` is the forward shift operator `σX(i) = X(i+1)`. -The periodic matrix `A` must not have characteristic multipliers on the unit circle. -The periodic matrices `A` and `C` are either stored as 3-dimensional arrays or as -as vectors of matrices. - -The Kronecker product expansion of equations is employed and therefore -this function is not recommended for large order matrices or large periods. -""" -function pslyapdkr(A::AbstractArray{T1, 3}, C::AbstractArray{T2, 3}; adj = true) where {T1, T2} - m, n, pc = size(C) - n == LinearAlgebra.checksquare(A[:,:,1]) - m == LinearAlgebra.checksquare(C[:,:,1]) - m == n || throw(DimensionMismatch("A and C have incompatible dimensions")) - pa = size(A,3) - n2 = n*n - p = lcm(pa,pc) - N = p*n2 - R = zeros(promote_type(T1,T2), N, N) - if adj - copyto!(view(R,1:n2,N-n2+1:N),kron(A[:,:,pa]',A[:,:,pa]')) - i1 = n2+1; j1 = 1 - for i = p-1:-1:1 - ia = mod(i-1,pa)+1 - i2 = i1+n2-1 - j2 = j1+n2-1 - copyto!(view(R,i1:i2,j1:j2),kron(A[:,:,ia]',A[:,:,ia]')) - i1 = i2+1 - j1 = j2+1 - end - indc = mod.(p-1:-1:0,pc).+1 - return reshape((I-R) \ (C[:,:,indc][:]), n, n, p)[:,:,indc] - else - copyto!(view(R,1:n2,N-n2+1:N),kron(A[:,:,pa],A[:,:,pa])) - (i2, j2) = (n2+n2, n2) - for i = 1:p-1 - i1 = i2-n2+1 - j1 = j2-n2+1 - ia = mod(i-1,pa)+1 - copyto!(view(R,i1:i2,j1:j2),kron(A[:,:,ia],A[:,:,ia])) - i2 += n2 - j2 += n2 - end - indc = mod.(-1:p-2,pc).+1 - return reshape((I-R) \ (C[:,:,indc][:]), n, n, p) - end -end -function pslyapdkr(A::AbstractVector{Matrix{T1}}, C::AbstractVector{Matrix{T2}}; adj = true) where {T1, T2} - pa = length(A) - pc = length(C) - ma, na = size.(A,1), size.(A,2) - p = lcm(pa,pc) - all(ma .== view(na,mod.(1:pa,pa).+1)) || - error("the number of columns of A[i+1] must be equal to the number of rows of A[i]") - if adj - all([LinearAlgebra.checksquare(C[mod(i-1,pc)+1]) == na[mod(i-1,pa)+1] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and C")) - else - all([LinearAlgebra.checksquare(C[mod(i-1,pc)+1]) == ma[mod(i-1,pa)+1] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and C")) - end - - all([issymmetric(C[i]) for i in 1:pc]) || error("all C[i] must be symmetric matrices") - - T = promote_type(T1,T2) - m2 = ma.^2 - n2 = na.^2 - mn = ma.*na - p = lcm(pa,pc) - N = adj ? sum(m2) : sum(n2) - R = zeros(T, N, N) - Y = zeros(T, N) - - if adj - copyto!(view(R,1:n2[pa],N-m2[pa]+1:N),kron(A[pa]',A[pa]')) - copyto!(view(Y,1:n2[pa]),C[pa][:]) - i1 = n2[pa]+1; j1 = 1 - for i = p-1:-1:1 - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - i2 = i1+n2[ia]-1 - j2 = j1+m2[ia]-1 - copyto!(view(R,i1:i2,j1:j2),kron(A[ia]',A[ia]')) - copyto!(view(Y,i1:i2),C[ic][:]) - i1 = i2+1 - j1 = j2+1 - end - ldiv!(qr!(I-R),Y) - z = Vector{Matrix{T}}(undef,0) - i2 = N - for i = 1:p - ia = mod(i-1,pa)+1 - ia1 = mod(i-2,pa)+1 - i1 = i2-m2[ia1]+1 - push!(z,reshape(view(Y,i1:i2),ma[ia1],ma[ia1])) - i2 = i1-1 - end - return z - else - copyto!(view(R,1:m2[pa],N-n2[pa]+1:N),kron(A[pa],A[pa])) - copyto!(view(Y,1:m2[pa]),C[pa][:]) - i1 = m2[pa]+1; j1 = 1 - for i = 1:p-1 - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - i2 = i1+m2[ia]-1 - j2 = j1+n2[ia]-1 - copyto!(view(R,i1:i2,j1:j2),kron(A[ia],A[ia])) - copyto!(view(Y,i1:i2),C[ic][:]) - i1 = i2+1 - j1 = j2+1 - end - ldiv!(qr!(I-R),Y) - z = Vector{Matrix{T}}(undef,0) - i1 = 1 - for i = 1:p - ia = mod(i-1,pa)+1 - i2 = i1+n2[ia]-1 - push!(z,reshape(view(Y,i1:i2),na[ia],na[ia])) - i1 = i2+1 - end - return z - end -end - -function pdlyaps!(KSCHUR::Int, A::AbstractArray{T1,3}, C::AbstractArray{T1,3}; adj = true) where {T1<:BlasReal} - # Standard solver for A in a periodic Schur form, with structure exploiting solution of - # the underlying 2x2 periodic Sylvester equations. - n = LinearAlgebra.checksquare(A[:,:,1]) - pa = size(A,3) - pc = size(C,3) - (LinearAlgebra.checksquare(C[:,:,1]) == n && all([issymmetric(C[:,:,i]) for i in 1:pc])) || - throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - rem(pc,pa) == 0 || error("the period of C must be an integer multiple of A") - (KSCHUR <= 0 || KSCHUR > pa ) && - error("KSCHUR has a value $KSCHUR, which is inconsistent with A ") - - if pa == 1 && pc == 1 - lyapds!(view(A,:,:,1), view(C,:,:,1); adj) - return #C[:,:,:] - end - ONE = one(T1) - - # allocate cache for 2x2 periodic Sylvester solver - G = Array{T1,3}(undef,2,2,pc) - WUSD = Array{Float64,3}(undef,4,4,pc) - WUD = Array{Float64,3}(undef,4,4,pc) - WUL = Matrix{Float64}(undef,4*pc,4) - WY = Vector{Float64}(undef,4*pc) - W = Matrix{Float64}(undef,8,8) - qr_ws = QRWs(zeros(8), zeros(4)) - ormqr_ws = QROrmWs(zeros(4), qr_ws.τ) - - # determine the dimensions of the diagonal blocks of real Schur form - ba, p = MatrixEquations.sfstruct(A[:,:,KSCHUR]) - if adj - # - # Solve A(j)'*X(j+1)*A(j) + C(j) = X(j) . - # - # The (K,L)th blocks of X(j), j = 1, ..., p are determined - # starting from upper-left corner column by column by - # - # A(j)(K,K)'*X(j+1)(K,L)*A(j)(L,L) - X(j)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # where - # K L-1 - # R(j)(K,L) = SUM {A(j)(I,K)'*SUM [X(j+1)(I,J)*A(j)(J,L)]} - # I=1 J=1 - # - # K-1 - # + {SUM [A(j)(I,K)'*X(j+1)(I,L)]}*A(j)(L,L) - # I=1 - i = 1 - @inbounds for kk = 1:p - dk = ba[kk] - k = i:i+dk-1 - j = 1 - ir = 1:i-1 - for ll = 1:kk - dl = ba[ll] - j1 = j+dl-1 - l = j:j1 - Ckl = view(C,k,l,1:pc) - y = view(G,1:dk,1:dl,1:pc) - copyto!(y,Ckl) - if kk > 1 - # C(j+1)[l,k] = C(j+1)[l,ir]*A(j)[ir,k] - ic = 1:j1 - for ii = 1:pc - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pc)+1 - mul!(view(C,l,k,ii1),view(C,l,ir,ii1),view(A,ir,k,ia)) - #y += C(j+1)[ic,k]'*A(j)[ic,l] - mul!(view(y,:,:,ii),transpose(view(C,ic,k,ii1)),view(A,ic,l,ia),ONE,ONE) - end - end - dpsylv2krsol!(adj, dk, dl, KSCHUR, view(A,k,k,1:pa), view(A,l,l,1:pa), y, WUD, WUSD, WUL, WY, W, qr_ws, ormqr_ws) - copyto!(Ckl,y) - if ll == kk && dl == 2 - for ii = 1:pc - temp = 0.5*(Ckl[1,2,ii]+Ckl[2,1,ii]) - Ckl[1,2,ii] = temp; Ckl[2,1,ii] = temp - end - end - j += dl - if ll < kk - # C(j+1)[l,k] += C(j+1)[k,l]'*A(j)[k,k] - for ii = 1:pc - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pc)+1 - mul!(view(C,l,k,ii1),transpose(view(C,k,l,ii1)),view(A,k,k,ia),ONE,ONE) - end - end - end - if kk > 1 - # C(j)[ir,k] = C(j)[k,ir]' - for ii = 1:pc - transpose!(view(C,ir,k,ii),view(C,k,ir,ii)) - end - end - i += dk - end - else - # - # Solve A(j)*X(j)*A(j)' + C(j) = X(j+1) . - # - # The (K,L)th block of X(j) is determined starting from - # bottom-right corner column by column by - # - # A(j)(K,K)*X(j)(K,L)*A(j)(L,L)' - X(j+1)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # Where - # - # N N - # R(j)(K,L) = SUM {A(j)(K,I)* SUM [X(j)(I,J)*A(j)(L,J)']} + - # I=K J=L+1 - # - # N - # { SUM [A(j)(K,J)*X(j)(J,L)]}*A(j)(L,L)' - # J=K+1 - j = n - for ll = p:-1:1 - dl = ba[ll] - l = j-dl+1:j - i = n - ir = j+1:n - for kk = p:-1:ll - dk = ba[kk] - i1 = i-dk+1 - k = i1:i - Clk = view(C,l,k,1:pc) - y = view(G,1:dl,1:dk,1:pc) - copyto!(y,Clk) - if ll < p - ic = i1:n - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] = C(j)[k,ir]*A(j)[l,ir]' - mul!(view(C,k,l,ii),view(C,k,ir,ii),transpose(view(A,l,ir,ia))) - # y += (A(j)[k,ic]*C(j)[ic,l])' - mul!(view(y,:,:,ii),transpose(view(C,ic,l,ii)),transpose(view(A,k,ic,ia)),ONE,ONE) - end - end - dpsylv2krsol!(adj, dl, dk, KSCHUR, view(A,l,l,1:pa), view(A,k,k,1:pa), y, WUD, WUSD, WUL, WY, W, qr_ws, ormqr_ws) - #dpsylv2!(adj, dl, dk, KSCHUR, view(A,l,l,1:pa), view(A,k,k,1:pa), y, WZ, WY) - copyto!(Clk,y) - if ll == kk && dl == 2 - for ii = 1:pc - temp = 0.5*(Clk[1,2,ii]+Clk[2,1,ii]) - Clk[1,2,ii] = temp; Clk[2,1,ii] = temp - end - end - i -= dk - if i >= j - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] += (A(j)[l,l]*C(j)[l,k])' - mul!(view(C,k,l,ii),transpose(view(C,l,k,ii)),transpose(view(A,l,l,ia)),ONE,ONE) - end - else - break - end - end - if ll < p - ir = i+2:n - for ii = 1:pc - # C(j)[ir,l] = C(j)[l,ir]' - transpose!(view(C,ir,l,ii),view(C,l,ir,ii)) - end - end - j -= dl - end - end - return #C[:,:,:] -end -function pdlyaps2!(KSCHUR::Int, A::AbstractArray{T1,3}, C::AbstractArray{T1,3}, E::AbstractArray{T1,3}, WORK) where {T1<:BlasReal} - # Standard solver for A in a periodic Schur form, with structure exploiting solution of - # the underlying 2x2 periodic Sylvester equations. - n = LinearAlgebra.checksquare(view(A,:,:,1)) - pa = size(A,3) - pc = size(C,3) - pe = size(E,3) - LinearAlgebra.checksquare(view(C,:,:,1)) == n || throw(DimensionMismatch("C[:,:,i] must be $n x $n symmetric matrices")) - LinearAlgebra.checksquare(view(E,:,:,1)) == n || throw(DimensionMismatch("E[:,:,i] must be $n x $n symmetric matrices")) - # (LinearAlgebra.checksquare(view(C,:,:,1)) == n && all([issymmetric(view(C,:,:,i)) for i in 1:pc])) || - # throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - rem(pc,pa) == 0 || error("the period of C must be an integer multiple of A") - rem(pe,pa) == 0 || error("the period of E must be an integer multiple of A") - (KSCHUR <= 0 || KSCHUR > pa ) && - error("KSCHUR has a value $KSCHUR, which is inconsistent with A ") - - if pa == 1 && pc == 1 && pe == 1 - lyapds!(view(A,:,:,1), view(C,:,:,1); adj = false) - lyapds!(view(A,:,:,1), view(E,:,:,1); adj = true) - return #C[:,:,:] - end - ONE = one(T1) - - # use preallocated cache for 2x2 periodic Sylvester solver - # G(2×2×pc), WUSD(4×4×pc), WUD(4×4×pc), WUL(4pc×4), WY(4pc), W(8×8), - # qr_ws = QRWs(zeros(8), zeros(4)), ormqr_ws = QROrmWs(zeros(4), qr_ws.τ) - (G, WUSD, WUD, WUL, WY, W, qr_ws, ormqr_ws) = WORK - - # determine the dimensions of the diagonal blocks of real Schur form - ba, p = MatrixEquations.sfstruct(view(A,:,:,KSCHUR)) - - - # Solve A(j)*X(j)*A(j)' + C(j) = X(j+1) . - # - # The (K,L)th block of X(j) is determined starting from - # bottom-right corner column by column by - # - # A(j)(K,K)*X(j)(K,L)*A(j)(L,L)' - X(j+1)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # Where - # - # N N - # R(j)(K,L) = SUM {A(j)(K,I)* SUM [X(j)(I,J)*A(j)(L,J)']} + - # I=K J=L+1 - # - # N - # { SUM [A(j)(K,J)*X(j)(J,L)]}*A(j)(L,L)' - # J=K+1 - j = n - for ll = p:-1:1 - dl = ba[ll] - l = j-dl+1:j - i = n - ir = j+1:n - for kk = p:-1:ll - dk = ba[kk] - i1 = i-dk+1 - k = i1:i - Clk = view(C,l,k,1:pc) - y = view(G,1:dl,1:dk,1:pc) - copyto!(y,Clk) - if ll < p - ic = i1:n - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] = C(j)[k,ir]*A(j)[l,ir]' - mul!(view(C,k,l,ii),view(C,k,ir,ii),transpose(view(A,l,ir,ia))) - # y += (A(j)[k,ic]*C(j)[ic,l])' - mul!(view(y,:,:,ii),transpose(view(C,ic,l,ii)),transpose(view(A,k,ic,ia)),ONE,ONE) - end - end - dpsylv2krsol!(false, dl, dk, KSCHUR, view(A,l,l,1:pa), view(A,k,k,1:pa), y, WUD, WUSD, WUL, WY, W, qr_ws, ormqr_ws) - #dpsylv2!(adj, dl, dk, KSCHUR, view(A,l,l,1:pa), view(A,k,k,1:pa), y, WZ, WY) - copyto!(Clk,y) - if ll == kk && dl == 2 - for ii = 1:pc - temp = 0.5*(Clk[1,2,ii]+Clk[2,1,ii]) - Clk[1,2,ii] = temp; Clk[2,1,ii] = temp - end - end - i -= dk - if i >= j - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] += (A(j)[l,l]*C(j)[l,k])' - mul!(view(C,k,l,ii),transpose(view(C,l,k,ii)),transpose(view(A,l,l,ia)),ONE,ONE) - end - else - break - end - end - if ll < p - ir = i+2:n - for ii = 1:pc - # C(j)[ir,l] = C(j)[l,ir]' - transpose!(view(C,ir,l,ii),view(C,l,ir,ii)) - end - end - j -= dl - end - # Solve A(j)'*X(j+1)*A(j) + E(j) = X(j) . - # - # The (K,L)th blocks of X(j), j = 1, ..., p are determined - # starting from upper-left corner column by column by - # - # A(j)(K,K)'*X(j+1)(K,L)*A(j)(L,L) - X(j)(K,L) = -E(j)(K,L) - R(j)(K,L) - # - # where - # K L-1 - # R(j)(K,L) = SUM {A(j)(I,K)'*SUM [X(j+1)(I,J)*A(j)(J,L)]} - # I=1 J=1 - # - # K-1 - # + {SUM [A(j)(I,K)'*X(j+1)(I,L)]}*A(j)(L,L) - # I=1 - i = 1 - @inbounds for kk = 1:p - dk = ba[kk] - k = i:i+dk-1 - j = 1 - ir = 1:i-1 - for ll = 1:kk - dl = ba[ll] - j1 = j+dl-1 - l = j:j1 - Ekl = view(E,k,l,1:pe) - y = view(G,1:dk,1:dl,1:pe) - copyto!(y,Ekl) - if kk > 1 - # E(j+1)[l,k] = E(j+1)[l,ir]*A(j)[ir,k] - ic = 1:j1 - for ii = 1:pe - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pe)+1 - mul!(view(E,l,k,ii1),view(E,l,ir,ii1),view(A,ir,k,ia)) - #y += E(j+1)[ic,k]'*A(j)[ic,l] - mul!(view(y,:,:,ii),transpose(view(E,ic,k,ii1)),view(A,ic,l,ia),ONE,ONE) - end - end - dpsylv2krsol!(true, dk, dl, KSCHUR, view(A,k,k,1:pa), view(A,l,l,1:pa), y, WUD, WUSD, WUL, WY, W, qr_ws, ormqr_ws) - #dpsylv2!(adj, dk, dl, KSCHUR, view(A,k,k,1:pa), view(A,l,l,1:pa), y, WZ, WY) - copyto!(Ekl,y) - if ll == kk && dl == 2 - for ii = 1:pe - temp = 0.5*(Ekl[1,2,ii]+Ekl[2,1,ii]) - Ekl[1,2,ii] = temp; Ekl[2,1,ii] = temp - end - end - j += dl - if ll < kk - # E(j+1)[l,k] += E(j+1)[k,l]'*A(j)[k,k] - for ii = 1:pe - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pe)+1 - mul!(view(E,l,k,ii1),transpose(view(E,k,l,ii1)),view(A,k,k,ia),ONE,ONE) - end - end - end - if kk > 1 - # E(j)[ir,k] = E(j)[k,ir]' - for ii = 1:pe - transpose!(view(E,ir,k,ii),view(E,k,ir,ii)) - end - end - i += dk - end - return nothing -end - -function pdlyaps3!(KSCHUR::Int, A::AbstractArray{T1,3}, C::AbstractArray{T1,3}; adj = true) where {T1<:BlasReal} - # Alternative solver for A in a periodic Schur form, with Kronecker product expansion based solution of - # the underlying 2x2 periodic Sylvester equations, using fine structure exploitation of small matrices. - n = LinearAlgebra.checksquare(A[:,:,1]) - pa = size(A,3) - pc = size(C,3) - (LinearAlgebra.checksquare(C[:,:,1]) == n && all([issymmetric(C[:,:,i]) for i in 1:pc])) || - throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - rem(pc,pa) == 0 || error("the period of C must be an integer multiple of A") - (KSCHUR <= 0 || KSCHUR > pa ) && - error("KSCHUR has a value $KSCHUR, which is inconsistent with A ") - - if pa == 1 && pc == 1 - lyapds!(view(A,:,:,1), view(C,:,:,1); adj) - return #C[:,:,:] - end - ONE = one(T1) - - # determine the dimensions of the diagonal blocks of real Schur form - - G = Array{T1,3}(undef,2,2,pc) - WZ = Matrix{Float64}(undef,4*pc,max(4*pc,5)) - WY = Vector{Float64}(undef,4*pc) - ba, p = MatrixEquations.sfstruct(A[:,:,KSCHUR]) - if adj - # - # Solve A(j)'*X(j+1)*A(j) + C(j) = X(j) . - # - # The (K,L)th blocks of X(j), j = 1, ..., p are determined - # starting from upper-left corner column by column by - # - # A(j)(K,K)'*X(j+1)(K,L)*A(j)(L,L) - X(j)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # where - # K L-1 - # R(j)(K,L) = SUM {A(j)(I,K)'*SUM [X(j+1)(I,J)*A(j)(J,L)]} - # I=1 J=1 - # - # K-1 - # + {SUM [A(j)(I,K)'*X(j+1)(I,L)]}*A(j)(L,L) - # I=1 - i = 1 - @inbounds for kk = 1:p - dk = ba[kk] - k = i:i+dk-1 - j = 1 - ir = 1:i-1 - for ll = 1:kk - dl = ba[ll] - j1 = j+dl-1 - l = j:j1 - Ckl = view(C,k,l,1:pc) - y = view(G,1:dk,1:dl,1:pc) - copyto!(y,Ckl) - if kk > 1 - # C(j+1)[l,k] = C(j+1)[l,ir]*A(j)[ir,k] - ic = 1:j1 - for ii = 1:pc - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pc)+1 - mul!(view(C,l,k,ii1),view(C,l,ir,ii1),view(A,ir,k,ia)) - #y += C(j+1)[ic,k]'*A(j)[ic,l] - mul!(view(y,:,:,ii),transpose(view(C,ic,k,ii1)),view(A,ic,l,ia),ONE,ONE) - end - end - dpsylv2!(adj, dk, dl, KSCHUR, view(A,k,k,1:pa), view(A,l,l,1:pa), y, WZ, WY) - copyto!(Ckl,y) - if ll == kk && dl == 2 - for ii = 1:pc - temp = 0.5*(Ckl[1,2,ii]+Ckl[2,1,ii]) - Ckl[1,2,ii] = temp; Ckl[2,1,ii] = temp - end - end - j += dl - if ll < kk - # C(j+1)[l,k] += C(j+1)[k,l]'*A(j)[k,k] - for ii = 1:pc - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pc)+1 - mul!(view(C,l,k,ii1),transpose(view(C,k,l,ii1)),view(A,k,k,ia),ONE,ONE) - end - end - end - if kk > 1 - # C(j)[ir,k] = C(j)[k,ir]' - for ii = 1:pc - transpose!(view(C,ir,k,ii),view(C,k,ir,ii)) - end - end - i += dk - end - else - # - # Solve A(j)*X(j)*A(j)' + C(j) = X(j+1) . - # - # The (K,L)th block of X(j) is determined starting from - # bottom-right corner column by column by - # - # A(j)(K,K)*X(j)(K,L)*A(j)(L,L)' - X(j+1)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # Where - # - # N N - # R(j)(K,L) = SUM {A(j)(K,I)* SUM [X(j)(I,J)*A(j)(L,J)']} + - # I=K J=L+1 - # - # N - # { SUM [A(j)(K,J)*X(j)(J,L)]}*A(j)(L,L)' - # J=K+1 - j = n - for ll = p:-1:1 - dl = ba[ll] - l = j-dl+1:j - i = n - ir = j+1:n - for kk = p:-1:ll - dk = ba[kk] - i1 = i-dk+1 - k = i1:i - Clk = view(C,l,k,1:pc) - y = view(G,1:dl,1:dk,1:pc) - copyto!(y,Clk) - if ll < p - ic = i1:n - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] = C(j)[k,ir]*A(j)[l,ir]' - mul!(view(C,k,l,ii),view(C,k,ir,ii),transpose(view(A,l,ir,ia))) - # y += (A(j)[k,ic]*C(j)[ic,l])' - mul!(view(y,:,:,ii),transpose(view(C,ic,l,ii)),transpose(view(A,k,ic,ia)),ONE,ONE) - end - end - dpsylv2!(adj, dl, dk, KSCHUR, view(A,l,l,1:pa), view(A,k,k,1:pa), y, WZ, WY) - copyto!(Clk,y) - if ll == kk && dl == 2 - for ii = 1:pc - temp = 0.5*(Clk[1,2,ii]+Clk[2,1,ii]) - Clk[1,2,ii] = temp; Clk[2,1,ii] = temp - end - end - i -= dk - if i >= j - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] += (A(j)[l,l]*C(j)[l,k])' - mul!(view(C,k,l,ii),transpose(view(C,l,k,ii)),transpose(view(A,l,l,ia)),ONE,ONE) - end - else - break - end - end - if ll < p - ir = i+2:n - for ii = 1:pc - # C(j)[ir,l] = C(j)[l,ir]' - transpose!(view(C,ir,l,ii),view(C,l,ir,ii)) - end - end - j -= dl - end - end - return #C[:,:,:] -end -function pdlyaps2!(KSCHUR::Int, A::AbstractArray{T1,3}, C::AbstractArray{T1,3}; adj = true) where {T1<:BlasReal} - # Alternative solver for A in a periodic Schur form, with Kronecker product expansion based solution of - # the underlying 2x2 periodic Sylvester equations. No fine structure exploitation is implemented. - n = LinearAlgebra.checksquare(A[:,:,1]) - pa = size(A,3) - pc = size(C,3) - (LinearAlgebra.checksquare(C[:,:,1]) == n && all([issymmetric(C[:,:,i]) for i in 1:pc])) || - throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - rem(pc,pa) == 0 || error("the period of C must be an integer multiple of A") - (KSCHUR <= 0 || KSCHUR > pa ) && - error("KSCHUR has a value $KSCHUR, which is inconsistent with A ") - - if pa == 1 && pc == 1 - lyapds!(view(A,:,:,1), view(C,:,:,1); adj) - return #C[:,:,:] - end - ONE = one(T1) - - # determine the dimensions of the diagonal blocks of real Schur form - - G = Array{T1,3}(undef,2,2,pc) - WZ = Matrix{Float64}(undef,4*pc,max(4*pc,5)) - WY = Vector{Float64}(undef,4*pc) - ba, p = MatrixEquations.sfstruct(A[:,:,KSCHUR]) - if adj - # - # Solve A(j)'*X(j+1)*A(j) + C(j) = X(j) . - # - # The (K,L)th blocks of X(j), j = 1, ..., p are determined - # starting from upper-left corner column by column by - # - # A(j)(K,K)'*X(j+1)(K,L)*A(j)(L,L) - X(j)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # where - # K L-1 - # R(j)(K,L) = SUM {A(j)(I,K)'*SUM [X(j+1)(I,J)*A(j)(J,L)]} - # I=1 J=1 - # - # K-1 - # + {SUM [A(j)(I,K)'*X(j+1)(I,L)]}*A(j)(L,L) - # I=1 - i = 1 - @inbounds for kk = 1:p - dk = ba[kk] - k = i:i+dk-1 - j = 1 - ir = 1:i-1 - for ll = 1:kk - dl = ba[ll] - j1 = j+dl-1 - l = j:j1 - Ckl = view(C,k,l,1:pc) - y = view(G,1:dk,1:dl,1:pc) - copyto!(y,Ckl) - if kk > 1 - # C(j+1)[l,k] = C(j+1)[l,ir]*A(j)[ir,k] - ic = 1:j1 - for ii = 1:pc - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pc)+1 - mul!(view(C,l,k,ii1),view(C,l,ir,ii1),view(A,ir,k,ia)) - #y += C(j+1)[ic,k]'*A(j)[ic,l] - mul!(view(y,:,:,ii),transpose(view(C,ic,k,ii1)),view(A,ic,l,ia),ONE,ONE) - end - end - _dpsylv2!(adj, dk, dl, view(A,k,k,1:pa), view(A,l,l,1:pa), y, WZ, WY) - copyto!(Ckl,y) - if ll == kk && dl == 2 - for ii = 1:pc - temp = 0.5*(Ckl[1,2,ii]+Ckl[2,1,ii]) - Ckl[1,2,ii] = temp; Ckl[2,1,ii] = temp - end - end - j += dl - if ll < kk - # C(j+1)[l,k] += C(j+1)[k,l]'*A(j)[k,k] - for ii = 1:pc - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pc)+1 - mul!(view(C,l,k,ii1),transpose(view(C,k,l,ii1)),view(A,k,k,ia),ONE,ONE) - end - end - end - if kk > 1 - # C(j)[ir,k] = C(j)[k,ir]' - for ii = 1:pc - transpose!(view(C,ir,k,ii),view(C,k,ir,ii)) - end - end - i += dk - end - else - # - # Solve A(j)*X(j)*A(j)' + C(j) = X(j+1) . - # - # The (K,L)th block of X(j) is determined starting from - # bottom-right corner column by column by - # - # A(j)(K,K)*X(j)(K,L)*A(j)(L,L)' - X(j+1)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # Where - # - # N N - # R(j)(K,L) = SUM {A(j)(K,I)* SUM [X(j)(I,J)*A(j)(L,J)']} + - # I=K J=L+1 - # - # N - # { SUM [A(j)(K,J)*X(j)(J,L)]}*A(j)(L,L)' - # J=K+1 - j = n - for ll = p:-1:1 - dl = ba[ll] - l = j-dl+1:j - i = n - ir = j+1:n - for kk = p:-1:ll - dk = ba[kk] - i1 = i-dk+1 - k = i1:i - Clk = view(C,l,k,1:pc) - y = view(G,1:dl,1:dk,1:pc) - copyto!(y,Clk) - if ll < p - ic = i1:n - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] = C(j)[k,ir]*A(j)[l,ir]' - mul!(view(C,k,l,ii),view(C,k,ir,ii),transpose(view(A,l,ir,ia))) - # y += (A(j)[k,ic]*C(j)[ic,l])' - mul!(view(y,:,:,ii),transpose(view(C,ic,l,ii)),transpose(view(A,k,ic,ia)),ONE,ONE) - end - end - _dpsylv2!(adj, dl, dk, view(A,l,l,1:pa), view(A,k,k,1:pa), y, WZ, WY) - copyto!(Clk,y) - i -= dk - if i >= j - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] += (A(j)[l,l]*C(j)[l,k])' - mul!(view(C,k,l,ii),transpose(view(C,l,k,ii)),transpose(view(A,l,l,ia)),ONE,ONE) - end - else - break - end - end - if ll < p - ir = i+2:n - for ii = 1:pc - # C(j)[ir,l] = C(j)[l,ir]' - transpose!(view(C,ir,l,ii),view(C,l,ir,ii)) - end - end - j -= dl - end - end - return #C[:,:,:] -end -function pdlyaps1!(KSCHUR::Int, A::StridedArray{T1,3}, C::StridedArray{T1,3}; adj = true) where {T1<:BlasReal} - # Alternative solver for A in a periodic Schur form, with fast iterative solution of - # the underlying 2x2 periodic Sylvester equations. This version is usually faster - # than the numerically more robust implementations employing Kronecker expansion based - # linear equations solvers. - n = LinearAlgebra.checksquare(A[:,:,1]) - pa = size(A,3) - pc = size(C,3) - (LinearAlgebra.checksquare(C[:,:,1]) == n && all([issymmetric(C[:,:,i]) for i in 1:pc])) || - throw(DimensionMismatch("all C[:,:,i] must be $n x $n symmetric matrices")) - rem(pc,pa) == 0 || error("the period of C must be an integer multiple of A") - (KSCHUR <= 0 || KSCHUR > pa ) && - error("KSCHUR has a value $KSCHUR, which is inconsistent with A ") - - if pa == 1 && pc == 1 - lyapds!(view(A,:,:,1), view(C,:,:,1); adj) - return C[:,:,:] - end - ONE = one(T1) - - # determine the dimensions of the diagonal blocks of real Schur form - - G = Array{T1,3}(undef,2,2,pc) - W = Matrix{Float64}(undef,2,14) - WX = Matrix{Float64}(undef,4,5) - ba, p = MatrixEquations.sfstruct(A[:,:,KSCHUR]) - if adj - # - # Solve A(j)'*X(j+1)*A(j) + C(j) = X(j) . - # - # The (K,L)th blocks of X(j), j = 1, ..., p are determined - # starting from upper-left corner column by column by - # - # A(j)(K,K)'*X(j+1)(K,L)*A(j)(L,L) - X(j)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # where - # K L-1 - # R(j)(K,L) = SUM {A(j)(I,K)'*SUM [X(j+1)(I,J)*A(j)(J,L)]} - # I=1 J=1 - # - # K-1 - # + {SUM [A(j)(I,K)'*X(j+1)(I,L)]}*A(j)(L,L) - # I=1 - i = 1 - @inbounds for kk = 1:p - dk = ba[kk] - k = i:i+dk-1 - j = 1 - ir = 1:i-1 - for ll = 1:kk - dl = ba[ll] - j1 = j+dl-1 - l = j:j1 - Ckl = view(C,k,l,1:pc) - y = view(G,1:dk,1:dl,1:pc) - copyto!(y,Ckl) - if kk > 1 - # C(j+1)[l,k] = C(j+1)[l,ir]*A(j)[ir,k] - ic = 1:j1 - for ii = 1:pc - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pc)+1 - mul!(view(C,l,k,ii1),view(C,l,ir,ii1),view(A,ir,k,ia)) - #y += C(j+1)[ic,k]'*A(j)[ic,l] - mul!(view(y,:,:,ii),transpose(view(C,ic,k,ii1)),view(A,ic,l,ia),ONE,ONE) - end - end - Ckl[:,:,:] .= dpsylv2(adj, dk, dl, KSCHUR, view(A,k,k,1:pa), view(A,l,l,1:pa), y, W, WX) - if ll == kk && dl == 2 - for ii = 1:pc - temp = 0.5*(Ckl[1,2,ii]+Ckl[2,1,ii]) - Ckl[1,2,ii] = temp; Ckl[2,1,ii] = temp - end - end - j += dl - if ll < kk - # C(j+1)[l,k] += C(j+1)[k,l]'*A(j)[k,k] - for ii = 1:pc - ia = mod(ii-1,pa)+1 - ii1 = mod(ii,pc)+1 - mul!(view(C,l,k,ii1),transpose(view(C,k,l,ii1)),view(A,k,k,ia),ONE,ONE) - end - end - end - if kk > 1 - # C(j)[ir,k] = C(j)[k,ir]' - for ii = 1:pc - transpose!(view(C,ir,k,ii),view(C,k,ir,ii)) - end - end - i += dk - end - else - # - # Solve A(j)*X(j)*A(j)' + C(j) = X(j+1) . - # - # The (K,L)th block of X(j) is determined starting from - # bottom-right corner column by column by - # - # A(j)(K,K)*X(j)(K,L)*A(j)(L,L)' - X(j+1)(K,L) = -C(j)(K,L) - R(j)(K,L) - # - # Where - # - # N N - # R(j)(K,L) = SUM {A(j)(K,I)* SUM [X(j)(I,J)*A(j)(L,J)']} + - # I=K J=L+1 - # - # N - # { SUM [A(j)(K,J)*X(j)(J,L)]}*A(j)(L,L)' - # J=K+1 - j = n - for ll = p:-1:1 - dl = ba[ll] - l = j-dl+1:j - i = n - ir = j+1:n - for kk = p:-1:ll - dk = ba[kk] - i1 = i-dk+1 - k = i1:i - Clk = view(C,l,k,1:pc) - y = view(G,1:dl,1:dk,1:pc) - copyto!(y,Clk) - if ll < p - ic = i1:n - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] = C(j)[k,ir]*A(j)[l,ir]' - mul!(view(C,k,l,ii),view(C,k,ir,ii),transpose(view(A,l,ir,ia))) - # y += (A(j)[k,ic]*C(j)[ic,l])' - mul!(view(y,:,:,ii),transpose(view(C,ic,l,ii)),transpose(view(A,k,ic,ia)),ONE,ONE) - end - end - Clk[:,:,:] .= dpsylv2(adj, dl, dk, KSCHUR, view(A,l,l,1:pa), view(A,k,k,1:pa), y, W, WX) - i -= dk - if i >= j - for ii = 1:pc - ia = mod(ii-1,pa)+1 - # C(j)[k,l] += (A(j)[l,l]*C(j)[l,k])' - mul!(view(C,k,l,ii),transpose(view(C,l,k,ii)),transpose(view(A,l,l,ia)),ONE,ONE) - end - else - break - end - end - if ll < p - ir = i+2:n - for ii = 1:pc - # C(j)[ir,l] = C(j)[l,ir]' - transpose!(view(C,ir,l,ii),view(C,l,ir,ii)) - end - end - j -= dl - end - end - return C[:,:,:] -end - -function dpsylv2!(adj::Bool, n1::Int, n2::Int, KSCHUR::Int, AL::StridedArray{T,3}, AR::StridedArray{T,3}, - C::StridedArray{T,3}, WZ::AbstractMatrix{T}, WY::AbstractVector{T}) where {T} -# To solve for the n1-by-n2 matrices X_j, j = 1, ..., p, -# 1 <= n1,n2 <= 2, in the p simultaneous equations: - -# if adj = true - -# AL_j'*X_(j+1)*AR_j - X_j = C_j, X_(p+1) = X_1 (1) - -# or if adj = false - -# AL_j*X_j*AR_j' - X_(j+1) = C_j, X_(p+1) = X_1 (2) - -# where AL_j is n1-by-n1, AR_j is n2-by-n2, C_j is n1-by-n2. - -# NOTE: This routine is primarily intended to be used in conjuntion -# with solvers for periodic Lyapunov equations. Thus, both -# AL and AR are formed from the diagonal blocks of the same -# matrix in periodic real Schur form. -# The solution X overwrites the right-hand side C. -# WZ and WY are 4p-by-4p and 4p-by-1 working matrices, respectively, -# allocated only once in the caller routine. -# AL and AR are assumed to have the same period, but -# C may have different period than AL and AR. The period -# of C must be an integer multiple of that of AL and AR. -# In the interests of speed, this routine does not -# check the inputs for errors. - -# METHOD - -# The solution is computed by explicitly forming and solving the underlying linear equation -# Z*vec(X) = vec(C), where Z is built using Kronecker products of component matrices [1]. -# - -# REFERENCES - -# [1] A. Varga. -# Periodic Lyapunov equations: some applications and new algorithms. -# Int. J. Control, vol, 67, pp, 69-87, 1997. - pa = size(AL,3) - p = size(C,3) - ii1 = 1:n1; ii2 = 1:n2; - # Quick return if possible. - if p == 1 - MatrixEquations.lyapdsylv2!(adj, view(C, ii1, ii2, 1), n1, n2, view(AL, ii1, ii1, 1), view(AR, ii2, ii2, 1), - view(WZ,1:4,1:4), view(WZ,1:4,5)) - return - end - n12 = n1*n2 - N = p*n12 - R = view(WZ,1:N,1:N); copyto!(R,-I) - #RT = zeros(T, N, N); copyto!(RT,-I) - Y = view(WY,1:N) - if adj - if n12 == 1 - ia = mod(KSCHUR+pa-1,pa)+1 - ic = mod(KSCHUR+p-1,p)+1 - R[1,N] = AR[1,1,ia]*AL[1,1,ia] - Y[1] = -C[1,1,ic] - i1 = 2 - for i = p+KSCHUR-1:-1:KSCHUR+1 - ia = mod(i-1,pa)+1 - ic = mod(i-1,p)+1 - R[i1,i1-1] = AR[1,1,ia]*AL[1,1,ia] - Y[i1] = -C[1,1,ic] - i1 += 1 - end - elseif n1 == 1 && n2 == 2 - ias = mod(KSCHUR+p-1,pa)+1 - # [ al11*ar11 al11*ar21 ] - # [ al11*ar12 al11*ar22 ] - R[1,N-1] = AL[1,1,ias]*AR[1,1,ias]; R[1,N] = AL[1,1,ias]*AR[2,1,ias] - R[2,N-1] = AL[1,1,ias]*AR[1,2,ias]; R[2,N] = AL[1,1,ias]*AR[2,2,ias] - ic = mod(KSCHUR+p-1,p)+1 - Y[1] = -C[1,1,ic] - Y[2] = -C[1,2,ic] - i1 = n12+1; j1 = 1 - for i = p+KSCHUR-1:-1:KSCHUR+1 - ia = mod(i-1,pa)+1 - ic = mod(i-1,p)+1 - i2 = i1+n12-1 - j2 = j1+n12-1 - # [ al11*ar11 * ] - # [ al11*ar12 al11*ar22 ] - R[i1,j1] = AL[1,1,ia]*AR[1,1,ia] - ia == ias && (R[i1,j1+1] = AL[1,1,ias]*AR[2,1,ias]) - R[i1+1,j1] = AL[1,1,ia]*AR[1,2,ia]; R[i1+1,j1+1] = AL[1,1,ia]*AR[2,2,ia] - Y[i1] = -C[1,1,ic] - Y[i1+1] = -C[1,2,ic] - i1 = i2+1 - j1 = j2+1 - end - elseif n1 == 2 && n2 == 1 - ias = mod(KSCHUR+p-1,pa)+1 - # [ al11*ar11 al21*ar11 ] - # [ al12*ar11 al22*ar11 ] - R[1,N-1] = AL[1,1,ias]*AR[1,1,ias]; R[1,N] = AL[2,1,ias]*AR[1,1,ias] - R[2,N-1] = AL[1,2,ias]*AR[1,1,ias]; R[2,N] = AL[2,2,ias]*AR[1,1,ias] - ic = mod(KSCHUR+p-1,p)+1 - Y[1] = -C[1,1,ic] - Y[2] = -C[2,1,ic] - i1 = n12+1; j1 = 1 - for i = p+KSCHUR-1:-1:KSCHUR+1 - ia = mod(i-1,pa)+1 - ic = mod(i-1,p)+1 - i2 = i1+n12-1 - j2 = j1+n12-1 - # [ al11*ar11 * ] - # [ al12*ar11 al22*ar11 ] - R[i1,j1] = AL[1,1,ia]*AR[1,1,ia] - ia == ias && (R[i1,j1+1] = AL[2,1,ias]*AR[1,1,ias]) - R[i1+1,j1] = AL[1,2,ia]*AR[1,1,ia]; R[i1+1,j1+1] = AL[2,2,ia]*AR[1,1,ia] - Y[i1] = -C[1,1,ic] - Y[i1+1] = -C[2,1,ic] - i1 = i2+1 - j1 = j2+1 - end - else - ias = mod(KSCHUR+p-1,pa)+1 - transpose!(view(R,1:n12,N-n12+1:N),kron(view(AR,ii2,ii2,ias),view(AL,ii1,ii1,ias))) - ic = mod(KSCHUR+p-1,p)+1 - Y[1] = -C[1,1,ic] - Y[2] = -C[2,1,ic] - Y[3] = -C[1,2,ic] - Y[4] = -C[2,2,ic] - i1 = n12+1; j1 = 1 - for i = p+KSCHUR-1:-1:KSCHUR+1 - ia = mod(i-1,pa)+1 - ic = mod(i-1,p)+1 - i2 = i1+n12-1 - j2 = j1+n12-1 - if ia == ias - transpose!(view(R,i1:i2,j1:j2),kron(view(AR,ii2,ii2,ia),view(AL,ii1,ii1,ia))) - else - # al11*ar11 0 0 0 - # al12*ar11 al22*ar11 0 0 - # al11*ar12 0 al11*ar22 0 - # al12*ar12 al22*ar12 al12*ar22 al22*ar22 - R[i1,j1] = AL[1,1,ia]*AR[1,1,ia] - R[i1+1,j1] = AL[1,2,ia]*AR[1,1,ia]; R[i1+1,j1+1] = AL[2,2,ia]*AR[1,1,ia] - R[i1+2,j1] = AL[1,1,ia]*AR[1,2,ia]; R[i1+2,j1+2] = AL[1,1,ia]*AR[2,2,ia]; - R[i1+3,j1] = AL[1,2,ia]*AR[1,2,ia]; R[i1+3,j1+1] = AL[2,2,ia]*AR[1,2,ia]; - R[i1+3,j1+2] = AL[1,2,ia]*AR[2,2,ia]; R[i1+3,j1+3] = AL[2,2,ia]*AR[2,2,ia]; - end - Y[i1] = -C[1,1,ic] - Y[i1+1] = -C[2,1,ic] - Y[i1+2] = -C[1,2,ic] - Y[i1+3] = -C[2,2,ic] - i1 = i2+1 - j1 = j2+1 - end - end - ldiv!(qr!(R), Y ) - any(!isfinite, Y) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - i1 = 1 - for i = p+KSCHUR:-1:KSCHUR+1 - ic = mod(i-1,p)+1 - i2 = i1+n12-1 - copyto!(view(C,ii1,ii2,ic), view(Y,i1:i2)) - i1 = i2+1 - end - else - if n12 == 1 - ia = mod(KSCHUR+p-1,pa)+1 - ic = mod(KSCHUR+p-1,p)+1 - R[1,N] = AR[1,1,ia]*AL[1,1,ia] - Y[1] = -C[1,1,ic] - i1 = 2 - for i = 1:p-1 - ia = mod(i+KSCHUR-1,pa)+1 - ic = mod(i+KSCHUR-1,p)+1 - R[i1,i1-1] = AR[1,1,ia]*AL[1,1,ia] - Y[i1] = -C[1,1,ic] - i1 += 1 - end - elseif n1 == 1 && n2 == 2 - ias = mod(KSCHUR+p-1,pa)+1 - # [ al11*ar11 al11*ar12 ] - # [ al11*ar21 al11*ar22 ] - R[1,N-1] = AL[1,1,ias]*AR[1,1,ias]; R[1,N] = AL[1,1,ias]*AR[1,2,ias] - R[2,N-1] = AL[1,1,ias]*AR[2,1,ias]; R[2,N] = AL[1,1,ias]*AR[2,2,ias] - ic = mod(KSCHUR+p-1,p)+1 - Y[1] = -C[1,1,ic] - Y[2] = -C[1,2,ic] - i1 = n12+1; j1 = 1 - for i = 1:p-1 - ia = mod(i+KSCHUR-1,pa)+1 - ic = mod(i+KSCHUR-1,p)+1 - i2 = i1+n12-1 - j2 = j1+n12-1 - # [ al11*ar11 al11*ar12 ] - # [ * al11*ar22 ] - R[i1,j1] = AL[1,1,ia]*AR[1,1,ia]; R[i1,j1+1] = AL[1,1,ia]*AR[1,2,ia] - ia == ias && (R[i1+1,j1] = AL[1,1,ia]*AR[2,1,ia]) - R[i1+1,j1+1] = AL[1,1,ia]*AR[2,2,ia] - Y[i1] = -C[1,1,ic] - Y[i1+1] = -C[1,2,ic] - i1 = i2+1 - j1 = j2+1 - end - elseif n1 == 2 && n2 == 1 - ias = mod(KSCHUR+p-1,pa)+1 - # [ al11*ar11 al12*ar11 ] - # [ al21*ar21 al22*ar11 ] - R[1,N-1] = AL[1,1,ias]*AR[1,1,ias]; R[1,N] = AL[1,2,ias]*AR[1,1,ias] - R[2,N-1] = AL[2,1,ias]*AR[1,1,ias]; R[2,N] = AL[2,2,ias]*AR[1,1,ias] - ic = mod(KSCHUR+p-1,p)+1 - Y[1] = -C[1,1,ic] - Y[2] = -C[2,1,ic] - i1 = n12+1; j1 = 1 - for i = 1:p-1 - ia = mod(i+KSCHUR-1,pa)+1 - ic = mod(i+KSCHUR-1,p)+1 - i2 = i1+n12-1 - j2 = j1+n12-1 - # [ al11*ar11 al12*ar11 ] - # [ * al22*ar11 ] - R[i1,j1] = AL[1,1,ia]*AR[1,1,ia]; R[i1,j1+1] = AL[1,2,ia]*AR[1,1,ia] - ia == ias && (R[i1+1,j1] = AL[2,1,ia]*AR[1,1,ia]) - R[i1+1,j1+1] = AL[2,2,ia]*AR[1,1,ia] - Y[i1] = -C[1,1,ic] - Y[i1+1] = -C[2,1,ic] - i1 = i2+1 - j1 = j2+1 - end - else - ias = mod(KSCHUR+p-1,pa)+1 - ic = mod(KSCHUR+p-1,p)+1 - copyto!(view(R,1:n12,N-n12+1:N),kron(view(AR,ii2,ii2,ias),view(AL,ii1,ii1,ias))) - copyto!(view(Y,1:n12), view(C,ii1,ii2,ic)) - Y[1] = -C[1,1,ic] - Y[2] = -C[2,1,ic] - Y[3] = -C[1,2,ic] - Y[4] = -C[2,2,ic] - i1 = n12+1; j1 = 1 - for i = 1:p-1 - ia = mod(i+KSCHUR-1,pa)+1 - ic = mod(i+KSCHUR-1,p)+1 - i2 = i1+n12-1 - j2 = j1+n12-1 - #R[i1:i2,j1:j2] = kron(view(AR,ii2,ii2,ia),view(AL,ii1,ii1,ia)) - if ia == ias - copyto!(view(R,i1:i2,j1:j2),kron(view(AR,ii2,ii2,ia),view(AL,ii1,ii1,ia))) - else - # al11*ar11 al12*ar11 al11*ar12 al12*ar12 - # 0 al22*ar11 0 al22*ar12 - # 0 0 al11*ar22 al12*ar22 - # 0 0 0 al22*ar22 - R[i1,j1] = AL[1,1,ia]*AR[1,1,ia]; R[i1,j1+1] = AL[1,2,ia]*AR[1,1,ia]; - R[i1,j1+2] = AL[1,1,ia]*AR[1,2,ia]; R[i1,j1+3] = AL[1,2,ia]*AR[1,2,ia]; - R[i1+1,j1+1] = AL[2,2,ia]*AR[1,1,ia]; R[i1+1,j1+3] = AL[2,2,ia]*AR[1,2,ia] - R[i1+2,j1+2] = AL[1,1,ia]*AR[2,2,ia]; R[i1+2,j1+3] = AL[1,2,ia]*AR[2,2,ia]; - R[i1+3,j1+3] = AL[2,2,ia]*AR[2,2,ia]; - end - Y[i1] = -C[1,1,ic] - Y[i1+1] = -C[2,1,ic] - Y[i1+2] = -C[1,2,ic] - Y[i1+3] = -C[2,2,ic] - i1 = i2+1 - j1 = j2+1 - end - end - ldiv!(qr!(R), Y ) - any(!isfinite, Y) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - i1 = 1 - for i = 1:p - ic = mod(i+KSCHUR-1,p)+1 - i2 = i1+n12-1 - copyto!(view(C,ii1,ii2,ic), view(Y,i1:i2)) - i1 = i2+1 - end - end -end - -function kronset!(R::AbstractMatrix{T}, adj::Bool, n1::Int, n2::Int, SCHUR::Bool, AL::StridedMatrix{T}, AR::StridedMatrix{T}) where {T} - n12 = n1*n2 - if n12 == 1 - R[1,1] = AR[1,1]*AL[1,1] - return - end - if adj - if n1 == 1 && n2 == 2 - # [ al11*ar11 al11*ar21 ] - # [ al11*ar12 al11*ar22 ] - R[1,1] = AL[1,1]*AR[1,1]; - R[1,2] = SCHUR ? AL[1,1]*AR[2,1] : zero(T) - R[2,1] = AL[1,1]*AR[1,2]; R[2,2] = AL[1,1]*AR[2,2] - elseif n1 == 2 && n2 == 1 - # [ al11*ar11 al21*ar11 ] - # [ al12*ar11 al22*ar11 ] - R[1,1] = AL[1,1]*AR[1,1]; - R[1,2] = SCHUR ? AL[2,1]*AR[1,1] : zero(T) - R[2,1] = AL[1,2]*AR[1,1]; R[2,2] = AL[2,2]*AR[1,1] - else - i12 = 1:n12 - if SCHUR - ii1 = 1:n1; ii2 = 1:n2 - kron!(view(R,i12,i12),view(AR,ii2,ii2),view(AL,ii1,ii1)) - _transpose!(view(R,i12,i12)) - else - # al11*ar11 0 0 0 - # al12*ar11 al22*ar11 0 0 - # al11*ar12 0 al11*ar22 0 - # al12*ar12 al22*ar12 al12*ar22 al22*ar22 - R[1,1] = AL[1,1]*AR[1,1] - R[2,1] = AL[1,2]*AR[1,1]; R[2,2] = AL[2,2]*AR[1,1] - R[3,1] = AL[1,1]*AR[1,2]; R[3,2] = zero(T); R[3,3] = AL[1,1]*AR[2,2]; - R[4,1] = AL[1,2]*AR[1,2]; R[4,2] = AL[2,2]*AR[1,2]; - R[4,3] = AL[1,2]*AR[2,2]; R[4,4] = AL[2,2]*AR[2,2]; - tril!(view(R,i12,i12)) - end - end - else - if n1 == 1 && n2 == 2 - # [ al11*ar11 al11*ar12 ] - # [ al11*ar21 al11*ar22 ] - R[1,1] = AL[1,1]*AR[1,1]; R[1,2] = AL[1,1]*AR[1,2] - R[2,1] = SCHUR ? AL[1,1]*AR[2,1] : zero(T) - R[2,2] = AL[1,1]*AR[2,2] - elseif n1 == 2 && n2 == 1 - # [ al11*ar11 al12*ar11 ] - # [ al21*ar21 al22*ar11 ] - R[1,1] = AL[1,1]*AR[1,1]; R[1,2] = AL[1,2]*AR[1,1] - R[2,1] = SCHUR ? AL[2,1]*AR[1,1] : zero(T) - R[2,2] = AL[2,2]*AR[1,1] - else - i12 = 1:n12 - if SCHUR - ii1 = 1:n1; ii2 = 1:n2 - kron!(view(R,i12,i12),view(AR,ii2,ii2),view(AL,ii1,ii1)) - else - # al11*ar11 al12*ar11 al11*ar12 al12*ar12 - # 0 al22*ar11 0 al22*ar12 - # 0 0 al11*ar22 al12*ar22 - # 0 0 0 al22*ar22 - R[1,1] = AL[1,1]*AR[1,1]; R[1,2] = AL[1,2]*AR[1,1]; - R[1,3] = AL[1,1]*AR[1,2]; R[1,4] = AL[1,2]*AR[1,2]; - R[2,2] = AL[2,2]*AR[1,1]; R[2,3] = zero(T); R[2,4] = AL[2,2]*AR[1,2] - R[3,3] = AL[1,1]*AR[2,2]; R[3,4] = AL[1,2]*AR[2,2]; - R[4,4] = AL[2,2]*AR[2,2]; - triu!(view(R,i12,i12)) - end - end - end - return nothing -end -function _transpose!(A::AbstractMatrix) - n = LinearAlgebra.checksquare(A) - for j = 1:n - for i = 1:j-1 - temp = A[i,j] - A[i,j] = A[j,i] - A[j,i] = temp - end - end -end - -function dpsylv2krsol!(adj::Bool, n1::Int, n2::Int, KSCHUR::Int, AL::StridedArray{T,3}, AR::StridedArray{T,3}, - C::StridedArray{T,3}, WUD::AbstractArray{T,3}, WUSD::AbstractArray{T,3}, WUL::AbstractMatrix{T}, WY::AbstractVector{T}, W::AbstractMatrix{T}, qr_ws, ormqr_ws) where {T} -# To solve for the n1-by-n2 matrices X_j, j = 1, ..., p, -# 1 <= n1,n2 <= 2, in the p simultaneous equations: - -# if adj = true - -# AL_j'*X_(j+1)*AR_j - X_j = C_j, X_(p+1) = X_1 (1) - -# or if adj = false - -# AL_j*X_j*AR_j' - X_(j+1) = C_j, X_(p+1) = X_1 (2) - -# where AL_j is n1 by n1, AR_j is n2 by n2, C_j is n1 by n2. - -# NOTE: This routine is primarily intended to be used in conjuntion -# with solvers for periodic Lyapunov equations. Thus, both -# AL and AR are formed from the diagonal blocks of the same -# matrix in periodic real Schur form. -# The solution X overwrites the right-hand side C. -# WUD and WUSD are 4x4xp 3-dimensional arrays, WUL and W are 4px4 and 8x4 matrices, -# WY is a 4p-dimensional vector. -# AL and AR are assumed to have the same period, but -# C may have different period than AL and AR. The period -# of C must be an integer multiple of that of AL and AR. -# In the interests of speed, this routine does not -# check the inputs for errors. - -# METHOD - -# The solution is computed by explicitly forming and solving the underlying linear equation -# Z*vec(X) = vec(C), using Kronecker products of component matrices [1]. -# Only the diagonal, supra-diagonal and last column blocks of Z are explicitly built. -# A structure exploiting QR-factorization based solution method is employed, using -# Algorithm 3 of [1], with the LU factorizations replaced by QR-factorizations. -# -# REFERENCES -# [1] A. Varga. -# Periodic Lyapunov equations: some applications and new algorithms. -# Int. J. Control, vol, 67, pp, 69-87, 1997. - pa = size(AL,3) - p = size(C,3) - ii1 = 1:n1; ii2 = 1:n2; - # Quick return if possible. - if p == 1 - MatrixEquations.lyapdsylv2!(adj, view(C, ii1, ii2, 1), n1, n2, view(AL, ii1, ii1, 1), view(AR, ii2, ii2, 1), - view(WUL,1:4,1:4), view(WY,1:4)) - return - end - n12 = n1*n2 - n22 = 2*n12 - N = p*n12 - i1 = 1:n12; i2 = n12+1:n22 - USD = view(WUSD,i1,i1,1:p-1) - UD = view(WUD,i1,i1,1:p) - UK = view(WUL,1:N,i1) - YK = view(WY,1:N) - zmi = view(W,1:n22,i1) - uu = view(W,1:n22,i2) - length(qr_ws.τ) == n12 || resize!(qr_ws.τ,n12) - - - ias = mod(KSCHUR+p-1,pa)+1 - ic = mod(KSCHUR+p-1,p)+1 - copyto!(view(YK,i1),view(C,ii1,ii2,ic)) - kronset!(view(UK,i1,i1), adj, n1, n2, true, view(AL,ii1,ii1,ias), view(AR,ii2,ii2,ias)) - fill!(view(UK,n12+1:N-n12,i1),zero(T)) - copyto!(view(UK,N-n12+1:N,i1),-I) - copyto!(view(YK,i1),view(C,ii1,ii2,ic)) - - - # Build the blocks of the bordered almost block diagonal (BABD) system and the right-hand side - if adj - j = 1; j1 = 1; - for i = p+KSCHUR-1:-1:KSCHUR+1 - j1 += n12 - ia = mod(i-1,pa)+1 - ic = mod(i-1,p)+1 - kronset!(view(USD,:,:,j), adj, n1, n2, ia == ias, view(AL,ii1,ii1,ia), view(AR,ii2,ii2,ia)) - copyto!(view(YK,j1:j1+n12-1),view(C,ii1,ii2,ic)) - j += 1 - end - else - j = 1; j1 = 1; - for i = 1:p-1 - j1 += n12 - ia = mod(i+KSCHUR-1,pa)+1 - ic = mod(i+KSCHUR-1,p)+1 - kronset!(view(USD,:,:,i), adj, n1, n2, ia == ias, view(AL,ii1,ii1,ia), view(AR,ii2,ii2,ia)) - copyto!(view(YK,j1:j1+n12-1),view(C,ii1,ii2,ic)) - end - end - for i = 1:N - YK[i] = -YK[i] - end - - # Solve the BABD system H*y = g using Algorithm 3 of [1] with LU factorizations replaced by QR decompositions - # First compute the QR factorization H = Q*R and update g <- Q'*g - j1 = 1; - il = N-n12+1:N - for j = 1:p-1 - if j == 1 - copyto!(view(uu,i1,i1), -I) - else - copyto!(view(uu,i1,i1),view(UD,:,:,j)) - end - copyto!(view(uu,i2,i1),view(USD,:,:,j)) - #F = qr!(uu) - FastLapackInterface.LAPACK.geqrf!(qr_ws, uu; resize = false) - #copy!(view(UD,:,:,j), F.R) - set_R!(view(UD,:,:,j), uu) - # lmul!(F.Q',view(UK,j1:j1+n22-1,i1)) - LAPACK.ormqr!(ormqr_ws, 'L', 'T', uu, view(UK,j1:j1+n22-1,i1)) - #lmul!(F.Q',view(YK,j1:j1+n22-1)) - LAPACK.ormqr!(ormqr_ws, 'L', 'T', uu, view(YK,j1:j1+n22-1)) - fill!(view(zmi,i1,i1), zero(T)) - copyto!(view(zmi,i2,i1),-I) - #lmul!(F.Q',zmi) - LAPACK.ormqr!(ormqr_ws, 'L', 'T', uu, zmi) - copyto!(view(USD,:,:,j), view(zmi,i1,:)) - copyto!(view(UD,:,:,j+1), view(zmi,i2,:)) - j1 += n12 - end - # F = qr!(view(UK,il,i1)) - # copyto!(view(UD,:,:,p), F.R) - # lmul!(F.Q',view(YK,il,1:1)) - LAPACK.geqrf!(qr_ws, view(UK,il,i1)) - set_R!(view(UD,:,:,p), view(UK,il,i1)) - LAPACK.ormqr!(ormqr_ws, 'L', 'T',view(UK,il,i1), view(YK,il)) - - - # Solve R*y = g by overwritting g - ldiv!(UpperTriangular(view(UD,:,:,p)),view(YK,il)) - il1 = il .- n12 - mul!(view(YK,il1),view(UK,il1,i1),view(YK,il),-1,1) - ldiv!(UpperTriangular(view(UD,:,:,p-1)),view(YK,il1)) - for i = p-2:-1:1 - il2 = il1 .- n12 - mul!(view(YK,il2),view(UK,il2,i1),view(YK,il),-1,1) - mul!(view(YK,il2),view(USD,:,:,i),view(YK,il1),-1,1) - ldiv!(UpperTriangular(view(UD,:,:,i)),view(YK,il2)) - il1 = il1 .- n12 - end - - any(!isfinite, YK) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - - # Reorder solution blocks - if adj - i1 = 1 - for i = p+KSCHUR:-1:KSCHUR+1 - ic = mod(i-1,p)+1 - i2 = i1+n12-1 - copyto!(view(C,ii1,ii2,ic), view(YK,i1:i2)) - i1 = i2+1 - end - else - i1 = 1 - for i = 1:p - ic = mod(i+KSCHUR-1,p)+1 - i2 = i1+n12-1 - copyto!(view(C,ii1,ii2,ic), view(YK,i1:i2)) - i1 = i2+1 - end - end - return nothing -end -function set_R!(R::AbstractMatrix, U::AbstractMatrix) - m, n = size(U) - if m >= n - p = LinearAlgebra.checksquare(R) - @assert p == n - pm = n - else - p = m - @assert (m, n) == size(R) - pm = n - end - ZERO = zero(eltype(R)) - for j in 1:p - R[j,j] = U[j,j] - for i in 1:j-1 - R[i,j] = U[i,j] - R[j,i] = ZERO - end - end - pm > p && copyto!(view(R,:,p+1:n),view(U,:,p+1:n)) - return nothing -end - -function _dpsylv2!(adj::Bool, n1::Int, n2::Int, AL::StridedArray{T,3}, AR::StridedArray{T,3}, - C::StridedArray{T,3}, WZ::AbstractMatrix{T}, WY::AbstractVector{T}) where {T} -# To solve for the n1-by-n2 matrices X_j, j = 1, ..., p, -# 1 <= n1,n2 <= 2, in the p simultaneous equations: - -# if adj = true - -# AL_j'*X_(j+1)*AR_j - X_j = C_j, X_(p+1) = X_1 (1) - -# or if adj = false - -# AL_j*X_j*AR_j' - X_(j+1) = C_j, X_(p+1) = X_1 (2) - -# where AL_j is n1 by n1, AR_j is n2 by n2, C_j is n1 by n2. - -# NOTE: This routine is primarily intended to be used in conjuntion -# with solvers for periodic Lyapunov equations. Thus, both -# AL and AR are formed from the diagonal blocks of the same -# matrix in periodic real Schur form. -# The solution X overwrites the right-hand side C. -# WZ and WY are 4p-by-4p and 4p-by-1 working matrices, respectively, -# allocated only once in the caller routine. -# AL and AR are assumed to have the same period, but -# C may have different period than AL and AR. The period -# of C must be an integer multiple of that of AL and AR. -# In the interests of speed, this routine does not -# check the inputs for errors. - -# METHOD - -# The solution is computed by explicitly forming and solving the underlying linear equation -# Z*vec(X) = vec(C), where Z is built using Kronecker products of component matrices [1]. -# - -# REFERENCES - -# [1] A. Varga. -# Periodic Lyapunov equations: some applications and new algorithms. -# Int. J. Control, vol, 67, pp, 69-87, 1997. - pa = size(AL,3) - p = size(C,3) - ii1 = 1:n1; ii2 = 1:n2; - # Quick return if possible. - if p == 1 - MatrixEquations.lyapdsylv2!(adj, view(C, ii1, ii2, 1), n1, n2, view(AL, ii1, ii1, 1), view(AR, ii2, ii2, 1), - view(WZ,1:4,1:4), view(WZ,1:4,5)) - return - end - n12 = n1*n2 - N = p*n12 - R = view(WZ,1:N,1:N) - R = zeros(T, N, N) - Y = view(WY,1:N) - if adj - if n12 == 1 - R[1,N] = AR[1,1,pa]*AL[1,1,pa] - R[1,1] = -1 - i1 = 2 - for i = p-1:-1:1 - ia = mod(i-1,pa)+1 - R[i1,i1-1] = AR[1,1,ia]*AL[1,1,ia] - R[i1,i1] = -1 - i1 += 1 - end - else - R[1:n12,N-n12+1:N] = transpose(kron(view(AR,ii2,ii2,pa),view(AL,ii1,ii1,pa))) - copyto!(view(R,1:n12,1:n12),-I) - i1 = n12+1; j1 = 1 - for i = p-1:-1:1 - ia = mod(i-1,pa)+1 - i2 = i1+n12-1 - j2 = j1+n12-1 - R[i1:i2,j1:j2] = transpose(kron(view(AR,ii2,ii2,ia),view(AL,ii1,ii1,ia))) - copyto!(view(R,i1:i2,i1:i2),-I) - i1 = i2+1 - j1 = j2+1 - end - end - reverse!(C,dims = 3) - copyto!(Y, view(C,ii1,ii2,1:p)) - ldiv!(qr!(R), Y ) - any(!isfinite, Y) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - copyto!(view(C,ii1,ii2,1:p), lmul!(-1,Y)) - reverse!(C,dims = 3) - else - if n12 == 1 - R[1,N] = AR[1,1,pa]*AL[1,1,pa] - R[1,1] = -1 - Y[1] = C[1,1,p] - i1 = 2 - for i = 1:p-1 - ia = mod(i-1,pa)+1 - R[i1,i1-1] = AR[1,1,ia]*AL[1,1,ia] - R[i1,i1] = -1 - Y[i1] = C[1,1,i] - i1 += 1 - end - else - R[1:n12,N-n12+1:N] = kron(view(AR,ii2,ii2,pa),view(AL,ii1,ii1,pa)) - copyto!(view(R,1:n12,1:n12),-I) - copyto!(view(Y,1:n12), view(C,ii1,ii2,p)) - i1 = n12+1; j1 = 1 - for i = 1:p-1 - ia = mod(i-1,pa)+1 - i2 = i1+n12-1 - j2 = j1+n12-1 - R[i1:i2,j1:j2] = kron(view(AR,ii2,ii2,ia),view(AL,ii1,ii1,ia)) - copyto!(view(R,i1:i2,i1:i2),-I) - copyto!(view(Y,i1:i2), view(C,ii1,ii2,i)) - i1 = i2+1 - j1 = j2+1 - end - end - ldiv!(qr!(R), Y ) - any(!isfinite, Y) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - copyto!(view(C,ii1,ii2,1:p), lmul!(-1,Y)) - end -end - -function dpsylv2(REV::Bool, N1::Int, N2::Int, KSCHUR::Int, TL::StridedArray{T,3}, TR::StridedArray{T,3}, - B::StridedArray{T,3}, W::AbstractMatrix{T}, WX::AbstractMatrix{T}) where {T} -# To solve for the N1-by-N2 matrices X_j, j = 1, ..., P, -# 1 <= N1,N2 <= 2, in the P simultaneous equations: - -# if REV = true - -# TL_j'*X_(j+1)*TR_j - X_j = B_j, X_(P+1) = X_1 (1) - -# or if REV = false - -# TL_j*X_j*TR_j' - X_(j+1) = B_j, X_(P+1) = X_1 (2) - -# where TL_j is N1 by N1, TR_j is N2 by N2, B_j is N1 by N2. - -# NOTE: This routine is primarily intended to be used in conjuntion -# with solvers for periodic Lyapunov equations. Thus, both -# TL and TR are formed from the diagonal blocks of the same -# matrix in periodic real Schur form. W and WX are working matrices -# allocated only once in the caller routine. -# TL and TR are assumed to have the same period, but -# B may have different period than TL and TR. The period -# of B must be an integer multiple of that of TL and TR. -# In the interests of speed, this routine does not -# check the inputs for errors. - -# METHOD - -# An initial approximation X_j, j=1, ..., P, is computed by reducing -# the system (1) or (2) to an equivalent single Lyapunov equation. -# Then, the accuracy of the solution is iteratively improved by -# performing forward or backward sweeps depending on the stability -# of eigenvalues. A maximum of 30 sweeps are performed. - -# REFERENCES - -# [1] A. Varga. -# Periodic Lyapunov equations: some applications and new algorithms. -# Int. J. Control, vol, 67, pp, 69-87, 1997. - - IND(J,P) = mod( J-1, P ) + 1 - P = size(TL,3) - PB = size(B,3) - i1 = 1:N1; i2 = 1:N2; - Xw = view(WX,:,1:4) - Yw = view(WX,:,5) - Z = view(W,:,7:8) - X = Array{T,3}(undef, N1, N2, PB) - - # Quick return if possible. - (N1 == 0 || N2 == 0) && (return X) - if P == 1 && PB == 1 - copyto!(view(X,i1,i2,1), view(B,i1,i2,1)) - MatrixEquations.lyapdsylv2!(REV, view(X, i1, i2, 1), N1, N2, view(TL, i1, i1, 1), view(TR, i2, i2, 1), Xw, Yw) - return X[:,:,:] - end - # partition working space - AL = view(W,:,1:2) - AR = view(W,:,3:4) - Q = view(W,:,5:6) - ZOLD = view(W,:,9:10) - ATMP = view(W,:,11:12) - BTMP = view(W,:,13:14) - - EPSM = 2*eps(T) - -# Define - -# AL(i,j) := TL_{j+i-1}*...*TL_{j+1}*TL_{j}, AL(j,j) := I; -# AR(i,j) := TR_{j+i-1}*...*TR_{j+1}*TR_{j}, AR(j,j) := I; - -# If REV = true, compute an initial approximation for -# X_{KSCHUR+1} = Z by solving - -# AL(KSCHUR+P+1,KSCHUR+1)'*Z*AR(KSCHUR+P+1,KSCHUR+1) - Z = Qr(KSCHUR+1) - -# where - -# Qr(j) = B_{j} + TL_{j}'*Qr(j+1)*TR_{j}, Qr(KSCHUR) = B(KSCHUR), -# for j = KSCHUR+P-1, ..., KSCHUR+1 - -# If REV = false, compute an initial approximation for -# X_{KSCHUR} = Z by solving - -# AL(KSCHUR+P,KSCHUR)*Z*AR(KSCHUR+P,KSCHUR)' - Z = Qf(KSCHUR) - -# where - -# Qf(j) = B_{j} + TL_{j}*Qf(j-1)*TR_{j}', Qf(KSCHUR) = B(KSCHUR), -# for j = KSCHUR+1, ..., KSCHUR+P-1 - x = 1 - if REV - L1 = KSCHUR+PB-1 - L2 = KSCHUR+1 - LSTEP = -1 - else - L1 = KSCHUR+1 - L2 = KSCHUR+PB-1 - LSTEP = 1 - end - AL[ i1, i1 ] = TL[ i1, i1, KSCHUR ] - AR[ i2, i2 ] = TR[ i2, i2, KSCHUR ] - Q[i1,i2] = B[i1,i2,KSCHUR] - - if N1 == 1 && N2 == 1 - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - X11 = TL[ 1, 1, J ] - Y11 = TR[ 1, 1, J ] - AL[ 1, 1 ] = AL[ 1, 1] * X11 - AR[ 1, 1 ] = AR[ 1, 1] * Y11 - Q[ 1, 1 ] = B[ 1, 1, JB] + X11 * Q[ 1, 1 ] * Y11 - end - elseif N1 == 1 && N2 == 2 - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - X11 = TL[ 1, 1, J ] - AL[ 1, 1 ] = AL[ 1, 1] * X11 - Q[1,1] = X11*Q[1,1] - Q[1,2] = X11*Q[1,2] - - Y11 = TR[ 1, 1, J ] - Y22 = TR[ 2, 2, J ] - Y12 = TR[ 1, 2, J ] - if REV - AR[1,2] = AR[1,1]*Y12 + AR[1,2]*Y22 - AR[1,1] = AR[1,1]*Y11 - AR[2,2] = AR[2,1]*Y12 + AR[2,2]*Y22 - AR[2,1] = AR[2,1]*Y11 - Q[1,2] = Q[1,1]*Y12 + Q[1,2]*Y22 + B[ 1, 2, JB ] - Q[1,1] = Q[1,1]*Y11 + B[ 1, 1, JB ] - else - AR[1,1] = Y11*AR[1,1] + Y12*AR[2,1] - AR[2,1] = Y22*AR[2,1] - AR[1,2] = Y11*AR[1,2] + Y12*AR[2,2] - AR[2,2] = Y22*AR[2,2] - Q[1,1] = Q[1,1]*Y11 + Q[1,2]*Y12 + B[ 1, 1, JB ] - Q[1,2] = Q[1,2]*Y22 + B[ 1, 2, JB ] - end - end - elseif N1 == 2 && N2 == 1 - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - X11 = TL[ 1, 1, J ] - X12 = TL[ 1, 2, J ] - X22 = TL[ 2, 2, J ] - if REV - AL[1,2] = AL[1,1]*X12 + AL[1,2]*X22 - AL[1,1] = AL[1,1]*X11 - AL[2,2] = AL[2,1]*X12 + AL[2,2]*X22 - AL[2,1] = AL[2,1]*X11 - Q[2,1] = X12*Q[1,1] + X22*Q[2,1] - Q[1,1] = X11*Q[1,1] - else - AL[1,1] = X11*AL[1,1] + X12*AL[2,1] - AL[2,1] = X22*AL[2,1] - AL[1,2] = X11*AL[1,2] + X12*AL[2,2] - AL[2,2] = X22*AL[2,2] - Q[1,1] = X11*Q[1,1] + X12*Q[2,1] - Q[2,1] = X22*Q[2,1] - end - - Y11 = TR[ 1, 1, J ] - AR[1,1] = AR[1,1]*Y11 - Q[1,1] = Q[1,1]*Y11 + B[ 1, 1, JB ] - Q[2,1] = Q[2,1]*Y11 + B[ 2, 1, JB ] - end - elseif N1 == 2 && N2 == 2 - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - X11 = TL[ 1, 1, J ] - X12 = TL[ 1, 2, J ] - X22 = TL[ 2, 2, J ] - if REV - AL[1,2] = AL[1,1]*X12 + AL[1,2]*X22 - AL[1,1] = AL[1,1]*X11 - AL[2,2] = AL[2,1]*X12 + AL[2,2]*X22 - AL[2,1] = AL[2,1]*X11 - Q[2,1] = X12*Q[1,1] + X22*Q[2,1] - Q[1,1] = X11*Q[1,1] - Q[2,2] = X12*Q[1,2] + X22*Q[2,2] - Q[1,2] = X11*Q[1,2] - else - AL[1,1] = X11*AL[1,1] + X12*AL[2,1] - AL[2,1] = X22*AL[2,1] - AL[1,2] = X11*AL[1,2] + X12*AL[2,2] - AL[2,2] = X22*AL[2,2] - Q[1,1] = X11*Q[1,1] + X12*Q[2,1] - Q[2,1] = X22*Q[2,1] - Q[1,2] = X11*Q[1,2] + X12*Q[2,2] - Q[2,2] = X22*Q[2,2] - end - - Y11 = TR[ 1, 1, J ] - Y12 = TR[ 1, 2, J ] - Y22 = TR[ 2, 2, J ] - if REV - AR[1,2] = AR[1,1]*Y12 + AR[1,2]*Y22 - AR[1,1] = AR[1,1]*Y11 - AR[2,2] = AR[2,1]*Y12 + AR[2,2]*Y22 - AR[2,1] = AR[2,1]*Y11 - Q[1,2] = Q[1,1]*Y12 + Q[1,2]*Y22 + B[ 1, 2, JB ] - Q[1,1] = Q[1,1]*Y11 + B[ 1, 1, JB ] - Q[2,2] = Q[2,1]*Y12 + Q[2,2]*Y22 + B[ 2, 2, JB ] - Q[2,1] = Q[2,1]*Y11 + B[ 2, 1, JB ] - else - AR[1,1] = Y11*AR[1,1] + Y12*AR[2,1] - AR[2,1] = Y22*AR[2,1] - AR[1,2] = Y11*AR[1,2] + Y12*AR[2,2] - AR[2,2] = Y22*AR[2,2] - Q[1,1] = Q[1,1]*Y11 + Q[1,2]*Y12 + B[ 1, 1, JB ] - Q[1,2] = Q[1,2]*Y22 + B[ 1, 2, JB ] - Q[2,1] = Q[2,1]*Y11 + Q[2,2]*Y12 + B[ 2, 1, JB ] - Q[2,2] = Q[2,2]*Y22 + B[ 2, 2, JB ] - end - end - end - Z[i1,i2] = -Q[i1,i2] -# Compute X_[KSCHUR+1] (if REV=.TRUE.) or X_KSCHUR (if REV=.FALSE.). - MatrixEquations.lyapdsylv2!(REV, view(Z,i1,i2), N1, N2, view(AL,i1,i1), view(AR,i2,i2), Xw, Yw) - XNORM = norm(view(Z,i1,i2), Inf) - XNORM == 0 && (XNORM = one(T)) - -# Determine the type of iteration to use - - if N1 == 1 - EL = abs( AL[1,1] ) - else - EL = sqrt( abs( AL[1,1]*AL[2,2] - AL[1,2]*AL[2,1] ) ) - end - if N2 == 1 - ER = abs( AR[1,1] ) - else - ER = sqrt( abs( AR[1,1]*AR[2,2] - AR[1,2]*AR[2,1] ) ) - end - STAB = EL*ER < one(T) - -# Save initial X_[KSCHUR+1]. - - copyto!(view(ZOLD,i1,i2), view(Z,i1,i2)) - -# Set iteration indices. - - if REV - if STAB - L1 = KSCHUR + PB - L2 = KSCHUR + 1 - LSTEP = -1 - ITIND = -1 - else - L1 = KSCHUR + 1 - L2 = KSCHUR + PB - LSTEP = 1 - ITIND = 0 - end - else - if STAB - L1 = KSCHUR - L2 = KSCHUR + PB - 1 - LSTEP = 1 - ITIND = 0 - else - L1 = KSCHUR + PB - 1 - L2 = KSCHUR - LSTEP = -1 - ITIND = -1 - end - end - for ITER = 1:30 - if N1 == 1 && N2 == 1 - if STAB - -# Use direct recursion. - - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - J1 = IND( JJ+ITIND+1, PB ) - Z[1,1] = TL[ 1, 1, J ] * Z[1,1] * TR[ 1, 1, J ] - B[ 1, 1, JB ] - X[ 1, 1, J1 ] = Z[ 1, 1 ] - end - else - -# Use inverse recursion. - - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - J1 = IND( JJ+ITIND+1, PB ) - Z[1,1] = (Z[1,1] + B[ 1, 1, JB ] ) / TL[ 1, 1, J ] / TR[ 1, 1, J ] - X[ 1, 1, J1 ] = Z[ 1, 1 ] - end - end - elseif N1 == 1 && N2 == 2 - if STAB - -# Use direct recursion. - - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - J1 = IND( JJ+ITIND+1, PB ) - X11 = TL[ 1, 1, J ] - Y11 = TR[ 1, 1, J ] - Y22 = TR[ 2, 2, J ] - if REV - Y12 = TR[ 1, 2, J ] - Y21 = TR[ 2, 1, J ] - else - Y12 = TR[ 2, 1, J ] - Y21 = TR[ 1, 2, J ] - end - - Z[1,1] = X11*Z[1,1] - Z[1,2] = X11*Z[1,2] - TEMP = Z[1,1]*Y11 + Z[1,2]*Y21 - Z[1,2] = Z[1,1]*Y12 + Z[1,2]*Y22 - B[ 1, 2, JB ] - Z[1,1] = TEMP - B[ 1, 1, JB ] - X[ 1, 1, J1 ] = Z[ 1, 1 ] - X[ 1, 2, J1 ] = Z[ 1, 2 ] - end - else - -# Use inverse recursion. - - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - J1 = IND( JJ+ITIND+1, PB ) - BTMP[ 1, 1 ] = ( Z[1,1] + B[1,1,JB] ) / TL[ 1, 1, J ] - BTMP[ 2, 1 ] = ( Z[1,2] + B[1,2,JB] ) / TL[ 1, 1, J ] - ATMP[ 1, 1 ] = TR[ 1, 1, J ] - ATMP[ 2, 2 ] = TR[ 2, 2, J ] - if REV - ATMP[ 1, 2 ] = TR[ 2, 1, J ] - ATMP[ 2, 1 ] = TR[ 1, 2, J ] - else - ATMP[ 1, 2 ] = TR[ 1, 2, J ] - ATMP[ 2, 1 ] = TR[ 2, 1, J ] - end - - #CALL DGESV( 2, 1, ATMP, 2, JPIV, BTMP, 2, INFO ) - luslv!(ATMP,view(BTMP,1:2,1:1)) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - #BTMP = ATMP\BTMP - Z[ 1, 1 ] = BTMP[ 1, 1 ] - Z[ 1, 2 ] = BTMP[ 2, 1 ] - X[ 1, 1, J1 ] = Z[ 1, 1 ] - X[ 1, 2, J1 ] = Z[ 1, 2 ] - end - end - elseif N1 == 2 && N2 == 1 - if STAB - -# Use direct recursion. - - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - J1 = IND( JJ+ITIND+1, PB ) - X11 = TL[ 1, 1, J ] - X22 = TL[ 2, 2, J ] - if REV - X12 = TL[ 2, 1, J ] - X21 = TL[ 1, 2, J ] - else - X12 = TL[ 1, 2, J ] - X21 = TL[ 2, 1, J ] - end - Y11 = TR[ 1, 1, J ] - - TEMP = X11*Z[1,1] + X12*Z[2,1] - Z[2,1] = X21*Z[1,1] + X22*Z[2,1] - Z[1,1] = TEMP - Z[1,1] = Z[1,1]*Y11 - B[ 1, 1, JB ] - Z[2,1] = Z[2,1]*Y11 - B[ 2, 1, JB ] - X[ 1, 1, J1 ] = Z[ 1, 1 ] - X[ 2, 1, J1 ] = Z[ 2, 1 ] - end - else - -# Use inverse recursion. - - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - J1 = IND( JJ+ITIND+1, PB ) - Z[ 1, 1 ] = ( Z[1,1] + B[1,1,JB] ) / TR[ 1, 1, J ] - Z[ 2, 1 ] = ( Z[2,1] + B[2,1,JB] ) / TR[ 1, 1, J ] - ATMP[ 1, 1 ] = TL[ 1, 1, J ] - ATMP[ 2, 2 ] = TL[ 2, 2, J ] - if REV - ATMP[ 1, 2 ] = TL[ 2, 1, J ] - ATMP[ 2, 1 ] = TL[ 1, 2, J ] - else - ATMP[ 1, 2 ] = TL[ 1, 2, J ] - ATMP[ 2, 1 ] = TL[ 2, 1, J ] - end - - #CALL DGESV( 2, 1, ATMP, 2, JPIV, Z, 2, INFO ) - luslv!(ATMP,view(Z,1:2,1:1)) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - #Z = ATMP\Z - X[ 1, 1, J1 ] = Z[ 1, 1 ] - X[ 2, 1, J1 ] = Z[ 2, 1 ] - end - end - elseif N1 == 2 && N2 == 2 - if STAB - -# Use direct recursion. - - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - J1 = IND( JJ+ITIND+1, PB ) - X11 = TL[ 1, 1, J ] - X22 = TL[ 2, 2, J ] - Y11 = TR[ 1, 1, J ] - Y22 = TR[ 2, 2, J ] - if REV - X12 = TL[ 2, 1, J ] - X21 = TL[ 1, 2, J ] - Y12 = TR[ 1, 2, J ] - Y21 = TR[ 2, 1, J ] - else - X12 = TL[ 1, 2, J ] - X21 = TL[ 2, 1, J ] - Y12 = TR[ 2, 1, J ] - Y21 = TR[ 1, 2, J ] - end - - TEMP = X11*Z[1,1] + X12*Z[2,1] - Z[2,1] = X21*Z[1,1] + X22*Z[2,1] - Z[1,1] = TEMP - TEMP = X11*Z[1,2] + X12*Z[2,2] - Z[2,2] = X21*Z[1,2] + X22*Z[2,2] - Z[1,2] = TEMP - TEMP = Z[1,1]*Y11 + Z[1,2]*Y21 - Z[1,2] = Z[1,1]*Y12 + Z[1,2]*Y22 - B[ 1, 2, JB ] - Z[1,1] = TEMP - B[ 1, 1, JB ] - TEMP = Z[2,1]*Y11 + Z[2,2]*Y21 - Z[2,2] = Z[2,1]*Y12 + Z[2,2]*Y22 - B[ 2, 2, JB ] - Z[2,1] = TEMP - B[ 2, 1, JB ] - X[ 1, 1, J1 ] = Z[ 1, 1 ] - X[ 2, 1, J1 ] = Z[ 2, 1 ] - X[ 1, 2, J1 ] = Z[ 1, 2 ] - X[ 2, 2, J1 ] = Z[ 2, 2 ] - end - else - -# Use inverse recursion. - - for JJ = L1:LSTEP:L2 - J = IND( JJ, P ) - JB = IND( JJ, PB ) - J1 = IND( JJ+ITIND+1, PB ) - BTMP[ 1, 1 ] = Z[ 1, 1 ] + B[ 1, 1, JB ] - BTMP[ 2, 1 ] = Z[ 1, 2 ] + B[ 1, 2, JB ] - BTMP[ 1, 2 ] = Z[ 2, 1 ] + B[ 2, 1, JB ] - BTMP[ 2, 2 ] = Z[ 2, 2 ] + B[ 2, 2, JB ] - ATMP[ 1, 1 ] = TR[ 1, 1, J ] - ATMP[ 2, 2 ] = TR[ 2, 2, J ] - if REV - ATMP[ 1, 2 ] = TR[ 2, 1, J ] - ATMP[ 2, 1 ] = TR[ 1, 2, J ] - else - ATMP[ 1, 2 ] = TR[ 1, 2, J ] - ATMP[ 2, 1 ] = TR[ 2, 1, J ] - end - - #CALL DGESV( 2, 2, ATMP, 2, JPIV, BTMP, 2, INFO ) - luslv!(ATMP,view(BTMP,1:2,1:2)) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - #BTMP = ATMP\BTMP - Z[ 1, 1 ] = BTMP[ 1, 1 ] - Z[ 1, 2 ] = BTMP[ 2, 1 ] - Z[ 2, 1 ] = BTMP[ 1, 2 ] - Z[ 2, 2 ] = BTMP[ 2, 2 ] - - ATMP[ 1, 1 ] = TL[ 1, 1, J ] - ATMP[ 2, 2 ] = TL[ 2, 2, J ] - if REV - ATMP[ 1, 2 ] = TL[ 2, 1, J ] - ATMP[ 2, 1 ] = TL[ 1, 2, J ] - else - ATMP[ 1, 2 ] = TL[ 1, 2, J ] - ATMP[ 2, 1 ] = TL[ 2, 1, J ] - end - - #CALL DGESV( 2, 2, ATMP, 2, JPIV, Z, 2, INFO ) - luslv!(ATMP,view(Z,1:2,1:2)) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - #Z = ATMP\Z - X[ 1, 1, J1 ] = Z[ 1, 1 ] - X[ 1, 2, J1 ] = Z[ 1, 2 ] - X[ 2, 1, J1 ] = Z[ 2, 1 ] - X[ 2, 2, J1 ] = Z[ 2, 2 ] - end - end - end - any(!isfinite,X) && throw("PS:SingularException: A has characteristic multipliers α and β such that αβ ≈ 1") - DNORM = zero(T) - for J = 1:N2 - for I = 1:N1 - DNORM = max( DNORM, abs( ZOLD[I,J] - Z[I,J] ) ) - ZOLD[ I, J ] = Z[ I, J ] - end - end - # println("XNORM = $XNORM DNORM = $DNORM") - DNORM <= EPSM*XNORM && (return -X) - end - @warn "iterative process not converged in 30 iterations: solution may be inaccurate" - return -X -# END of DPSYLV2 -end -@inline function luslv!(A::AbstractMatrix{T}, B::AbstractMatrix{T}) where T - # - # fail = luslv!(A,B) - # - # This function is a speed-oriented implementation of a Gaussion-elimination based - # solver of small order linear equations of the form A*X = B. The computed solution X - # overwrites the vector B, while the resulting A contains in its upper triangular part, - # the upper triangular factor U of its LU decomposition. - # The diagnostic output parameter fail, of type Bool, is set to false in the case - # of normal return or is set to true if the exact singularity of A is detected - # or if the resulting B has non-finite components. - # - n, m = size(B) - @inbounds begin - for k = 1:n - # find index max - kp = k - if k < n - amax = abs(A[k, k]) - for i = k+1:n - absi = abs(A[i,k]) - if absi > amax - kp = i - amax = absi - end - end - end - iszero(A[kp,k]) && return true - if k != kp - # Interchange - for i = 1:n - tmp = A[k,i] - A[k,i] = A[kp,i] - A[kp,i] = tmp - end - for j = 1:m - tmp = B[k,j] - B[k,j] = B[kp,j] - B[kp,j] = tmp - end - end - # Scale first column - Akkinv = inv(A[k,k]) - i1 = k+1:n - Ak = view(A,i1,k) - rmul!(Ak,Akkinv) - # Update the rest of A and B - for j = k+1:n - axpy!(-A[k,j],Ak,view(A,i1,j)) - end - for j = 1:m - axpy!(-B[k,j],Ak,view(B,i1,j)) - end - end - ldiv!(UpperTriangular(A), view(B,:,:)) - return any(!isfinite, B) - end -end -for PM in (:PeriodicArray, :PeriodicMatrix) - @eval begin - function pdplyap(A::$PM, C::$PM; adj::Bool = true, rtol = eps(float(promote_type(eltype(A), eltype(C))))^0.75) - A.Ts ≈ C.Ts || error("A and C must have the same sampling time") - period = promote_period(A, C) - na = rationalize(period/A.period).num - K = na*A.nperiod*A.dperiod - U = psplyapd(A.M, C.M; adj, rtol) - p = lcm(length(A),length(C)) - return $PM(U, period; nperiod = div(K,p)) - end - end -end -""" - pdplyap(A, C; adj = true) -> U - -Compute the upper triangular factor `U` of the solution `X = U'U` of the -periodic discrete-time Lyapunov matrix equation - - A'σXA + C'C = X, if adj = true, - -or of the solution `X = UU'` of the periodic discrete-time Lyapunov matrix equation - - AXA' + CC' = σX, if adj = false, - -where `σ` is the forward shift operator `σX(i) = X(i+1)`. -The periodic matrix `A` must be stable, i.e., have all characteristic multipliers -with moduli less than one. - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods. -The resulting upper triangular periodic matrix `U` has the period -set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -The iterative method (Algorithm 5) of [1] and its dual version are employed. - -_Reference:_ - -[1] A. Varga. Periodic Lyapunov equations: some applications and new algorithms. - Int. J. Control, vol, 67, pp, 69-87, 1997. -""" -pdplyap(A::PeriodicArray, C::PeriodicArray; adj::Bool = true) - -""" - psplyapd(A, C; adj = true, rtol = ϵ^(3/4)) -> U - -Compute the upper triangular factor `U` of the solution `X = U'U` of the -periodic discrete-time Lyapunov matrix equation - - A'σXA + C'C = X, if adj = true, - -or of the solution `X = UU'` of the periodic discrete-time Lyapunov matrix equation - - AXA' + CC' = σX, if adj = false, - -where `σ` is the forward shift operator `σX(i) = X(i+1)`. -The periodic matrix `A` must be stable, i.e., have all characteristic multipliers -with moduli less than one. - -The periodic matrices `A` and `C` are either stored as 3-dimensional arrays or as -as vectors of matrices. - -The iterative method (Algorithm 5) of [1] and its dual version are employed. - -_Reference:_ - -[1] A. Varga, "Periodic Lyapunov equations: some applications and new algorithms", - Int. J. Control, vol. 67, pp. 69-87, 1997. -""" -function psplyapd(A::AbstractVector{Matrix{T1}}, C::AbstractVector{Matrix{T2}}; adj::Bool = true, rtol = eps(float(promote_type(T1, T2)))^0.75) where {T1, T2} - pa = length(A) - pc = length(C) - ma, na = size.(A,1), size.(A,2) - mc, nc = size.(C,1), size.(C,2) - p = lcm(pa,pc) - all(ma .== view(na,mod.(1:pa,pa).+1)) || - error("the number of columns of A[i+1] must be equal to the number of rows of A[i]") - if adj - all([nc[mod(i-1,pc)+1] == na[mod(i-1,pa)+1] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and C")) - else - all([mc[mod(i-1,pc)+1] == ma[mod(i-1,pa)+1] for i in 1:p]) || - throw(DimensionMismatch("incompatible dimensions between A and C")) - end - rev(t) = reverse(reverse(t,dims=1),dims=2) - - T = promote_type(T1, T2) - T <: BlasFloat || (T = promote_type(Float64,T)) - - ii = argmin(na) - - nmin = na[ii] - x = zeros(T,nmin,nmin) - - if adj - U = Vector{Matrix}(undef,p) - # compute an initializing periodic factor U[ii] - V = zeros(T,nmin,nmin) - Φ = Matrix{T}(I(nmin)) - for i = ii:ii+p-1 - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - V = qr([V; C[ic]*Φ]).R - Φ = A[ia]*Φ - end - U[ii] = plyapd(Φ', V') - for iter = 1:100 - if iter > 1 - y = U[ii]'*U[ii] - if norm(y-x,1) <= rtol*norm(y) - break - end - x = copy(y) - end - for i = ii+p:-1:ii+1 - j = mod(i-1,p)+1 - jm1 = mod(i-2,p)+1 - iam1 = mod(i-2,pa)+1 - icm1 = mod(i-2,pc)+1 - U[jm1] = qr([U[j]*A[iam1]; C[icm1]]).R - end - end - else - U = Vector{Matrix}(undef,p) - # compute an initializing periodic factor U[ii] - V = zeros(T,nmin,nmin) - Φ = Matrix{T}(I(nmin)) - for i = ii+p-1:-1:ii - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - V = rev(qr(rev([V Φ*C[ic]]')).R') - Φ = Φ*A[ia] - end - U[ii] = plyapd(Φ, V) - for iter = 1:100 - if iter > 1 - y = U[ii]*U[ii]' - if norm(y-x,1) <= rtol*norm(y) - break - end - x = copy(y) - end - for i = ii+1:ii+p - j = mod(i-1,p)+1 - jm1 = mod(i-2,p)+1 - iam1 = mod(i-2,pa)+1 - icm1 = mod(i-2,pc)+1 - U[j] = rev(qr(rev([A[iam1]*U[jm1] C[icm1]]')).R') - end - end - end - - for i = 1:p - U[i] = makesp(U[i];adj) - end - return U -end - -function makesp(r; adj = true) - # make upper trapezoidal matrix square and with positive diagonal elements - if adj - n, nc = size(r) - rp = [r; zeros(nc-n,nc)] - for i = 1:n - rp[i,i] < 0 && (rp[i,i:end] = -rp[i,i:end]) - end - else - n, nc = size(r) - rp = [zeros(n,n-nc) r ] - for i = n-nc+1:n - rp[i,i] < 0 && (rp[1:i,i] = -rp[1:i,i]) - end - end - return rp -end -function psplyapd(A::AbstractArray{T1,3}, C::AbstractArray{T2,3}; adj::Bool = true, rtol = eps(float(promote_type(T1, T2)))^0.75) where {T1, T2} - mc, nc, pc = size(C) - n = LinearAlgebra.checksquare(A[:,:,1]) - pa = size(A,3) - p = lcm(pa,pc) - if adj - nc == n || throw(DimensionMismatch("incompatible dimensions between A and C")) - else - mc == n || throw(DimensionMismatch("incompatible dimensions between A and C")) - end - rev(t) = reverse(reverse(t,dims=1),dims=2) - - T = promote_type(T1, T2) - T <: BlasFloat || (T = promote_type(Float64,T)) - - x = zeros(T,n,n) - U = Array{T,3}(undef,n,n,p) - - if adj - # compute an initializing periodic factor U[ii] - V = zeros(T,n,n) - Φ = Matrix{T}(I(n)) - for i = 1:p - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - V = qr([V; C[:,:,ic]*Φ]).R - Φ = A[:,:,ia]*Φ - end - U[:,:,1] = plyapd(Φ', V') - for iter = 1:100 - if iter > 1 - y = U[:,:,1]'*U[:,:,1] - if norm(y-x,1) <= rtol*norm(y) - break - end - x = copy(y) - end - for i = p+1:-1:2 - j = mod(i-1,p)+1 - jm1 = mod(i-2,p)+1 - iam1 = mod(i-2,pa)+1 - icm1 = mod(i-2,pc)+1 - U[:,:,jm1] = qr([U[:,:,j]*A[:,:,iam1]; C[:,:,icm1]]).R - end - end - else - # compute an initializing periodic factor U[ii] - V = zeros(T,n,n) - Φ = Matrix{T}(I(n)) - for i = p:-1:1 - ia = mod(i-1,pa)+1 - ic = mod(i-1,pc)+1 - V = rev(qr(rev([V Φ*C[:,:,ic]]')).R') - Φ = Φ*A[:,:,ia] - end - U[:,:,1] = plyapd(Φ, V) - for iter = 1:100 - if iter > 1 - y = U[:,:,1]*U[:,:,1]' - if norm(y-x,1) <= rtol*norm(y) - break - end - x = copy(y) - end - for i = 2:p+1 - j = mod(i-1,p)+1 - jm1 = mod(i-2,p)+1 - iam1 = mod(i-2,pa)+1 - icm1 = mod(i-2,pc)+1 - U[:,:,j] = rev(qr(rev([A[:,:,iam1]*U[:,:,jm1] C[:,:,icm1]]')).R') - end - end - end - - for i = 1:p - U[:,:,i] = makesp(U[:,:,i];adj) - end - return U -end -for PM in (:PeriodicArray, :PeriodicMatrix) - @eval begin - function prdplyap(A::$PM, C::$PM) - pdplyap(A, C; adj = true) - end - function prdplyap(A::$PM, C::AbstractArray) - pdplyap(A, $PM(C, A.Ts; nperiod = 1); adj = true) - end - function pfdplyap(A::$PM, C::$PM) - pdplyap(A, C; adj = false) - end - function pfdplyap(A::$PM, C::AbstractArray) - pdplyap(A, $PM(C, A.Ts; nperiod = 1); adj = false) - end - end -end -""" - prdplyap(A, C) -> U - -Compute the upper triangular factor `U` of the solution `X = U'*U` of the -reverse time periodic discrete-time Lyapunov matrix equation - - A'σXA + C'C = X - -where `σ` is the forward shift operator `σX(i) = X(i+1)`. -The periodic matrix `A` must be stable, i.e., have all characteristic multipliers -with moduli less than one. - -The periodic matrices `A` and `C` must have the same type, the same dimensions and commensurate periods. -The resulting upper triangular periodic matrix `U` has the period -set to the least common commensurate period of `A` and `C` and the number of subperiods -is adjusted accordingly. - -Note: `X` is the _observability Gramian_ of the periodic pair `(A,C)`. -""" -prdplyap(A::PeriodicArray, C::PeriodicArray) -""" - pfdplyap(A, B) -> U - -Compute the upper triangular factor `U` of the solution `X = U*U'` of the -forward-time periodic discrete-time Lyapunov equation - - AXA' + BB' = σX - -where `σ` is the forward shift operator `σX(i) = X(i+1)`. -The periodic matrix `A` must be stable, i.e., have all characteristic multipliers -with moduli less than one. - -The periodic matrices `A` and `B` must have the same type, the same dimensions and commensurate periods. -The resulting upper triangular periodic matrix `U` has the period -set to the least common commensurate period of `A` and `B` and the number of subperiods -is adjusted accordingly. - -Note: `X` is the _reachability Gramian_ of the periodic pair `(A,B)`. -""" -pfdplyap(A::PeriodicArray, B::PeriodicArray) - - diff --git a/src/psstab.jl b/src/psstab.jl index 8d899cd..27788f0 100644 --- a/src/psstab.jl +++ b/src/psstab.jl @@ -21,7 +21,8 @@ The matrix `S` is set to zero when omitted. The dimension of `u(t)` is deduced f Also returned are the closed-loop characteristic exponents `EVALS` of `A(t)+B(t)F(t)`. -The keyword arguments contained in `kwargs` are those used for the function [`prdric`](@ref). +The keyword arguments contained in `kwargs` are those used for the function +[`PeriodicMatrixEquations.prdric`](@extref). Note: The pair `(A(t),B(t))` must be stabilizable and `[Q S;S' R]` must be nonnegative definite. """ @@ -61,7 +62,7 @@ If `intpol = false`, the gain `F(t)` is computed from a multi-point solution of by the integration of the corresponding ODE using the nearest point value as initial condition. This option is not recommended to be used jointly with symplectic integrators, which are used by default. -The keyword arguments contained in `kwargs` are those used for the function [`prcric`](@ref) (excepting intpol). +The keyword arguments contained in `kwargs` are those used for the function [`PeriodicMatrixEquations.prcric`](@extref) (excepting intpol). Note: The pair `(A(t),B(t))` must be stabilizable, `R` must be positive definite and `[Q S;S' R]` must be nonnegative definite . """ @@ -118,7 +119,7 @@ The matrix `S` is set to zero when omitted. Also returned are the closed-loop characteristic exponents `EVALS` of `A(t)+B(t)F(t)`. -The keyword arguments contained in `kwargs` are those used for the function [`prdric`](@ref). +The keyword arguments contained in `kwargs` are those used for the function [`PeriodicMatrixEquations.prdric`](@extref). Note: The pair `(A(t),B(t))` must be stabilizable and `[Q S;S' R]` must be nonnegative definite. """ @@ -172,7 +173,7 @@ If `intpol = false`, the gain `F(t)` is computed from a multi-point solution of by the integration of the corresponding ODE using the nearest point value as initial condition. This option is not recommended to be used jointly with symplectic integrators, which are used by default. -The keyword arguments contained in `kwargs` are those used for the function [`prcric`](@ref) (excepting intpol). +The keyword arguments contained in `kwargs` are those used for the function [`PeriodicMatrixEquations.prcric`](@extref) (excepting intpol). Note: The pair `(A(t),B(t))` must be stabilizable and `[Q S;S' R]` must be nonnegative definite. """ @@ -223,7 +224,7 @@ The matrix `Sn` is set to zero when omitted. Also returned are the closed-loop characteristic exponents `EVALS` of `A(t)-L(t)C(t)`. -The keyword arguments contained in `kwargs` are those used for the function [`pfdric`](@ref). +The keyword arguments contained in `kwargs` are those used for the function [`PeriodicMatrixEquations.pfdric`](@extref). Note: The pair `(A(t),C(t))` must be detectable and `[Qw Sn;Sn' Rv]` must be nonnegative definite. """ @@ -260,7 +261,7 @@ If `intpol = false`, the gain `L(t)` is computed from a multi-point solution of by the integration of the corresponding ODE using the nearest point value as initial condition. This option is not recommended to be used jointly with symplectic integrators, which are used by default. -The keyword arguments contained in `kwargs` are those used for the function [`pfcric`](@ref) (excepting intpol). +The keyword arguments contained in `kwargs` are those used for the function [`PeriodicMatrixEquations.pfcric`](@extref) (excepting intpol). Note: The pair `(A(t),C(t))` must be detectable and `[Qw Sn;Sn' Rv]` must be nonnegative definite. """ @@ -311,7 +312,7 @@ The matrix `Sn` is set to zero when omitted. The number of disturbance inputs `m Also returned are the closed-loop characteristic exponents `EVALS` of `A(t)-L(t)C(t)`. -The keyword arguments contained in `kwargs` are those used for the function [`pfdric`](@ref). +The keyword arguments contained in `kwargs` are those used for the function [`PeriodicMatrixEquations.pfdric`](@extref). Note: The pair `(A(t),C(t))` must be detectable, `Qw` must be non-negative definite, `Rv` must be positive definite and `[Qw Sn; Sn' Rv]` must be nonnegative definite. @@ -369,7 +370,7 @@ If `intpol = false`, the gain `L(t)` is computed from a multi-point solution of by the integration of the corresponding ODE using the nearest point value as initial condition. This option is not recommended to be used jointly with symplectic integrators, which are used by default. -The keyword arguments contained in `kwargs` are those used for the function [`pfcric`](@ref) (excepting intpol). +The keyword arguments contained in `kwargs` are those used for the function [`PeriodicMatrixEquations.pfcric`](@extref) (excepting intpol). Note: The pair `(A(t),C(t))` must be detectable, `Qw` must be non-negative definite, `Rv` must be positive definite and `[Qw Sn; Sn' Rv]` must be nonnegative definite. @@ -1084,8 +1085,8 @@ function Kbuild_sw(x::Array{T,3}, D::PM, ns::Vector{Int}) where {T, PM <: Period end """ pclqofc_sw(psys, Q, R, ts = missing; K = 1, sdeg = 0, G = I, vinit, optimizer, stabilizer, - maxiter, vtol, Jtol, gtol, show_trace, solver, reltol, abstol, dt, - N = 128, intpolmeth = "cubic", quad = false ) -> (Fopt,info) + maxiter, vtol, Jtol, gtol, show_trace, solver, reltol, abstol, + N = 128, quad = false ) -> (Fopt,info) Compute the optimal periodic stabilizing gain matrix `Fopt(t)`, such that for a continuous-time periodic state-space model `psys` of the form @@ -1182,9 +1183,7 @@ If `K = 1` (default), the single shooting method is employed to compute periodic If `K > 1`, the multiple-shooting method of [2] is employed, first, to convert the continuous-time periodic Lyapunov differential equations into discrete-time periodic Lyapunov equations satisfied by the generator solution in `K` grid points and then to compute the solution by solving an appropriate discrete-time periodic Lyapunov equation using the periodic Schur method of [3]. If quad = true, a quadrature-based evaluation of gradients is used, as proposed in [1], in conjunction with -interpolation techniques. The interpolation method to be used can be specified using the keyword parameter `intpolmeth` (default: `intpolmeth = "cubic"`). -Other interpolation options are `"intpolmeth = quadratic"`, `intpolmeth = "linear"` and `intpolmeth = "constant"`. -The number of sample values to be used for interpolation can be specified with the keyword parameter `N` (deafult: `N = 128`). +interpolation techniques. The number of sample values to be used for interpolation can be specified with the keyword parameter `N` (deafult: `N = 128`). The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), @@ -1199,9 +1198,7 @@ The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/Ordi `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). Parallel computation of the matrices of the discrete-time problem can be alternatively performed by starting Julia with several execution threads. @@ -1221,7 +1218,7 @@ function pclqofc_sw(psys::PeriodicStateSpace{PM}, Q::Union{AbstractMatrix,PM1}, ts = missing; K::Int = 1, sdeg::Real = 0, G = I, lub = missing, vinit::Union{AbstractArray{<:Real,3},Missing} = missing, optimizer = LBFGS(;alphaguess = LineSearches.InitialStatic(;scaled=true)), stabilizer = LBFGS(;alphaguess = LineSearches.InitialStatic(;scaled=true)), maxiter = 1000, vtol = 0., Jtol = 0., gtol = 1e-5, show_trace = false, - solver = "", reltol = 1.e-5, abstol = 1.e-7, dt = 0, N = 128, intpolmeth = "cubic", quad = false) where + solver = "auto", reltol = 1.e-5, abstol = 1.e-7, N = 128, intpolmeth = "cubic", quad = false) where {PM <: PeriodicFunctionMatrix, PM1 <: PeriodicFunctionMatrix, PM2 <: PeriodicFunctionMatrix} period = psys.period #optimizer = Optim.NelderMead() @@ -1289,7 +1286,7 @@ function pclqofc_sw(psys::PeriodicStateSpace{PM}, Q::Union{AbstractMatrix,PM1}, evs = sd if sd >= stlim shift = min(-sd*1.01,-0.001) - options = (solver = solver, reltol = reltol, abstol = abstol, N = 128, dt = dt, intpolmeth = "cubic", quad = true) + options = (solver = solver, reltol = reltol, abstol = abstol, N = 128, intpolmeth = intpolmeth, quad = true) nit = 10 it = 1 evs = sd @@ -1312,7 +1309,7 @@ function pclqofc_sw(psys::PeriodicStateSpace{PM}, Q::Union{AbstractMatrix,PM1}, xopt = x sd = evs sd = sdeg < 0 ? maximum(real(psceig(psys.A+Bu*convert(PeriodicFunctionMatrix,KK)*C,100))) : evs - options = (solver = solver, reltol = reltol, abstol = abstol, N = N, dt = dt, intpolmeth = intpolmeth, quad = quad) + options = (solver = solver, reltol = reltol, abstol = abstol, N = N, intpolmeth = intpolmeth, quad = quad) par = (K, A, Bu, C, R, Q, ts, X0, options) fopt = pclqofcswfungrad!(true, nothing, x, par) result = nothing @@ -1320,7 +1317,7 @@ function pclqofc_sw(psys::PeriodicStateSpace{PM}, Q::Union{AbstractMatrix,PM1}, else maxit = maxiter - options = (solver = solver, reltol = reltol, abstol = abstol, N = N, dt = dt, intpolmeth = intpolmeth, quad = quad) + options = (solver = solver, reltol = reltol, abstol = abstol, N = N, intpolmeth = intpolmeth, quad = quad) par = (K, A, Bu, C, R, Q, ts, X0, options) result = optimize(Optim.only_fg!((F,G,x) -> pclqofcswfungrad!(F, G, x, par)), x, optimizer, Optim.Options(x_tol = vtol, f_tol = Jtol, g_tol = gtol, iterations = maxit, show_trace=show_trace)) @@ -1364,7 +1361,7 @@ function fungradsw!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, ts, X0, options) #K = 100 if isnothing(Grad) try - P = pgclyap(Ar, QR, K; adj = true, solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt, stability_check = true) + P = pgclyap(Ar, QR, K; adj = true, solver = options.solver, reltol = options.reltol, abstol = options.abstol, stability_check = true) return tr(P(0)*X0) catch te isnothing(findfirst("stability",te.msg)) && (@show te; error("unknown error")) @@ -1372,7 +1369,7 @@ function fungradsw!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, ts, X0, options) end end Z, Y = try - pgclyap2(Ar, X0, QR, K; solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt, stability_check = true) + pgclyap2(Ar, X0, QR, K; solver = options.solver, reltol = options.reltol, abstol = options.abstol, stability_check = true) catch te isnothing(findfirst("stability",te.msg)) && (@show te; error("unknown error")) return 1.e20 @@ -1390,10 +1387,11 @@ function fungradsw!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, ts, X0, options) end else N = max(options.N,32) - Yt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, Y, Ar, QR; adj = true, solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt),A.period) + Yt = PeriodicFunctionMatrix(t->PeriodicMatrixEquations.tvclyap_eval(t, Y, Ar, QR; adj = true, solver = options.solver, reltol = options.reltol, abstol = options.abstol),A.period) P = convert(PeriodicFunctionMatrix,convert(PeriodicTimeSeriesMatrix,Yt;ns=N), method = options.intpolmeth) - if quad - Xt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, Z, Ar, Z(0); adj = false, solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt),A.period) + if quad + #Xt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, Z, Ar, Z(0); adj = false, solver = options.solver, reltol = options.reltol, abstol = options.abstol),A.period) + Xt = PeriodicFunctionMatrix(t->PeriodicMatrixEquations.tvclyap_eval(t, Z, Ar; solver = options.solver, reltol = options.reltol, abstol = options.abstol),A.period) Xts = Xt.f.((0:N-1)*period/N) Xts = [Xts; [Xts[1]-X0]] #X = convert(PeriodicFunctionMatrix,convert(PeriodicTimeSeriesMatrix,Xt;ns=N), method = options.intpolmeth) @@ -1407,7 +1405,7 @@ function fungradsw!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, ts, X0, options) if quad Grad[:,:,i] .= QuadGK.quadgk(t-> 2*(B(t)'*P(t)+RFC(t))*X(t)*C(t)', t0, tf, rtol=1e-5)[1] else - Grad[:,:,i] = tvgrad!(V, Ar, B, C, RFC, P, tf, t0; solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt) + Grad[:,:,i] = tvgrad!(V, Ar, B, C, RFC, P, tf, t0; solver = options.solver, reltol = options.reltol, abstol = options.abstol) end end end @@ -1421,10 +1419,10 @@ function Kbuild_sw(x::AbstractArray{T,3}, D::PM, ts) where {T, PM <: PeriodicFun return iszero(D) ? K : inv(I+K*convert(PeriodicSwitchingMatrix,D[:,1:mu]))*K end -function tvgrad!(V, A::PM, B::PM, C::PM, RFC::PM, P::PFM, tf, t0; solver = options.solver, reltol = options.reltol, abstol = options.atol, dt = options.dt) where +function tvgrad!(V, A::PM, B::PM, C::PM, RFC::PM, P::PFM, tf, t0; solver = options.solver, reltol = options.reltol, abstol = options.atol) where {PM <: Union{PeriodicFunctionMatrix,HarmonicArray}, PFM <: PeriodicFunctionMatrix} """ - tvgrad!(A, B, C, R, F, P, V, tf, to; solver, reltol, abstol, dt) -> G + tvgrad!(A, B, C, R, F, P, V, tf, to; solver, reltol, abstol) -> G Compute the gradient of the objective function for a linear-quadratic problem for a closed-loop system (A(t),B(t),C(t),0) with output feedback. by integrating for tf > t0, jointly the differential matrix Lyapunov equation @@ -1439,8 +1437,8 @@ function tvgrad!(V, A::PM, B::PM, C::PM, RFC::PM, P::PFM, tf, t0; solver = optio The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, - together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), - absolute accuracy `abstol` (default: `abstol = 1.e-7`) and stepsize `dt' (default: `dt = abs(tf-t0)/100`, only used if `solver = "symplectic"`). + together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`) and + absolute accuracy `abstol` (default: `abstol = 1.e-7`). Depending on the desired relative accuracy `reltol`, lower order solvers are employed for `reltol >= 1.e-4`, which are generally very efficient, but less accurate. If `reltol < 1.e-4`, @@ -1452,10 +1450,7 @@ function tvgrad!(V, A::PM, B::PM, C::PM, RFC::PM, P::PFM, tf, t0; solver = optio `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); - `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - - `solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). - + `solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). """ n = size(A,1) n == size(A,2) || error("the periodic matrix A must be square") @@ -1485,17 +1480,6 @@ function tvgrad!(V, A::PM, B::PM, C::PM, RFC::PM, P::PFM, tf, t0; solver = optio # high accuracy non-stiff sol = solve(prob, Vern9(); reltol, abstol, save_everystep = false) end - elseif solver == "symplectic" - # high accuracy symplectic - if dt == 0 - sol = solve(prob, IRKGaussLegendre.IRKGL16(maxtrials=4); adaptive = true, reltol, abstol, save_everystep = false) - #@show sol.retcode - if sol.retcode == :Failure - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt = abs(tf-t0)/100) - end - else - sol = solve(prob, IRKGaussLegendre.IRKGL16(); adaptive = false, reltol, abstol, save_everystep = false, dt) - end else if reltol > 1.e-4 # low accuracy automatic selection @@ -1539,8 +1523,8 @@ function muladdcsymgr!(y::AbstractVector, x::AbstractVector, A::AbstractMatrix, end """ pclqofc_hr(psys, Q, R, nh = 0; K = 1, sdeg = 0, G = I, vinit, optimizer, stabilizer, - maxiter, vtol, Jtol, gtol, show_trace, solver, reltol, abstol, dt, - N = 128, intpolmeth = "cubic", quad = false ) -> (Fopt,info) + maxiter, vtol, Jtol, gtol, show_trace, solver, reltol, abstol, + N = 128, quad = false ) -> (Fopt,info) Compute the optimal periodic stabilizing gain matrix `Fopt(t)`, such that for a continuous-time periodic state-space model `psys` of the form @@ -1633,9 +1617,7 @@ If `K = 1` (default), the single shooting method is employed to compute periodic If `K > 1`, the multiple-shooting method of [2] is employed, first, to convert the continuous-time periodic Lyapunov differential equations into discrete-time periodic Lyapunov equations satisfied by the generator solution in `K` grid points and then to compute the solution by solving an appropriate discrete-time periodic Lyapunov equation using the periodic Schur method of [3]. If quad = true, a quadrature-based evaluation of gradients is used, as proposed in [1], in conjunction with -interpolation techniques. The interpolation method to be used can be specified using the keyword parameter `intpolmeth` (default: `intpolmeth = "cubic"`). -Other interpolation options are `"intpolmeth = quadratic"`, `intpolmeth = "linear"` and `intpolmeth = "constant"`. -The number of sample values to be used for interpolation can be specified with the keyword parameter `N` (deafult: `N = 128`). +interpolation techniques. The number of sample values to be used for interpolation can be specified with the keyword parameter `N` (deafult: `N = 128`). The ODE solver to be employed to convert the continuous-time problem into a discrete-time problem can be specified using the keyword argument `solver`, together with the required relative accuracy `reltol` (default: `reltol = 1.e-4`), @@ -1650,9 +1632,7 @@ The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/Ordi `solver = "stiff"` - use a solver for stiff problems (`Rodas4()` or `KenCarp58()`); -`solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). Parallel computation of the matrices of the discrete-time problem can be alternatively performed by starting Julia with several execution threads. @@ -1672,7 +1652,7 @@ function pclqofc_hr(psys::PeriodicStateSpace{PM}, Q::Union{AbstractMatrix,PM1}, nh::Int = 0; K::Int = 1, sdeg::Real = 0, G = I, vinit::Union{AbstractVector{<:Real},Missing} = missing, optimizer = LBFGS(;alphaguess = LineSearches.InitialStatic(;scaled=true)), stabilizer = LBFGS(;alphaguess = LineSearches.InitialStatic(;scaled=true)), maxiter = 1000, vtol = 0., Jtol = 0., gtol = 1e-5, show_trace = false, - solver = "", reltol = 1.e-5, abstol = 1.e-7, dt = 0, N = 128, intpolmeth = "cubic", quad = false) where + solver = "auto", reltol = 1.e-5, abstol = 1.e-7, N = 128, intpolmeth = "cubic", quad = false) where {PM <: HarmonicArray, PM1 <: HarmonicArray, PM2 <: HarmonicArray} period = psys.period n = size(psys.A,2) @@ -1740,7 +1720,7 @@ function pclqofc_hr(psys::PeriodicStateSpace{PM}, Q::Union{AbstractMatrix,PM1}, if sd >= stlim shift = min(-sd*1.01,-0.001) - options = (solver = solver, reltol = reltol, abstol = abstol, N = 128, dt = dt, intpolmeth = "cubic", quad = true) + options = (solver = solver, reltol = reltol, abstol = abstol, N = 128, intpolmeth = intpolmeth, quad = true) nit = 10 it = 1 evs = max(sd,0) @@ -1763,7 +1743,7 @@ function pclqofc_hr(psys::PeriodicStateSpace{PM}, Q::Union{AbstractMatrix,PM1}, xopt = x sd = evs sd = sdeg < 0 ? maximum(real(psceig(psys.A+Bu*KK*C,100))) : evs - options = (solver = solver, reltol = reltol, abstol = abstol, N = N, dt = dt, intpolmeth = intpolmeth, quad = quad) + options = (solver = solver, reltol = reltol, abstol = abstol, N = N, intpolmeth = intpolmeth, quad = quad) par = (K, A, Bu, C, R, Q, X0, options) #fopt = nothing @@ -1773,7 +1753,7 @@ function pclqofc_hr(psys::PeriodicStateSpace{PM}, Q::Union{AbstractMatrix,PM1}, else maxit = maxiter - options = (solver = solver, reltol = reltol, abstol = abstol, N = N, dt = dt, intpolmeth = intpolmeth, quad = quad) + options = (solver = solver, reltol = reltol, abstol = abstol, N = N, intpolmeth = intpolmeth, quad = quad) par = (K, A, Bu, C, R, Q, X0, options) result = optimize(Optim.only_fg!((F,G,x) -> pclqofchrfungrad!(F, G, x, par)), x, optimizer, Optim.Options(x_tol = vtol, f_tol = Jtol, g_tol = gtol, iterations = maxit, show_trace=show_trace)) @@ -1809,7 +1789,7 @@ function fungradhr!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, X0, options) whe #(WSGrad, X, Y, At, xt, QW, pschur_ws) = WORK1 if isnothing(Grad) try - P = pgclyap(Ar, QR, K; adj = true, solver = "options.solver", reltol = options.reltol, abstol = options.abstol, dt = options.dt, stability_check = true) + P = pgclyap(Ar, QR, K; adj = true, solver = options.solver, reltol = options.reltol, abstol = options.abstol, stability_check = true) return tr(P(0)*X0) catch te isnothing(findfirst("stability",te.msg)) && (@show te; error("unknown error")) @@ -1817,7 +1797,7 @@ function fungradhr!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, X0, options) whe end end Z, Y = try - pgclyap2(Ar, X0, QR, K; solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt, stability_check = true) + pgclyap2(Ar, X0, QR, K; solver = options.solver, reltol = options.reltol, abstol = options.abstol, stability_check = true) catch te isnothing(findfirst("stability",te.msg)) && (@show te; error("unknown error")) return 1.e20 @@ -1826,6 +1806,7 @@ function fungradhr!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, X0, options) whe # Grad = 2*(B'*pmshift(X)*Ar + RFC + ST)*Y*C' if K >= 32 P = convert(PeriodicFunctionMatrix, Y, method = options.intpolmeth) + #P = PeriodicMatrixEquations.pclyap_intpol(Ar, QR, Y; N = 1, adj = true, solver = options.solver, reltol = options.reltol, abstol = options.abstol) if quad Xts = Z.values Xts = [Xts; [Xts[1]-X0]] @@ -1833,10 +1814,11 @@ function fungradhr!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, X0, options) whe end else N = max(options.N,32) - Yt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, Y, Ar, QR; adj = true, solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt),period) + Yt = PeriodicFunctionMatrix(t->PeriodicMatrixEquations.tvclyap_eval(t, Y, Ar, QR; adj = true, solver = options.solver, reltol = options.reltol, abstol = options.abstol),period) P = convert(PeriodicFunctionMatrix,convert(PeriodicTimeSeriesMatrix,Yt;ns=N), method = options.intpolmeth) if quad - Xt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, Z, Ar, Z(0); adj = false, solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt),A.period) + #Xt = PeriodicFunctionMatrix(t->PeriodicMatrixEquations.tvclyap_eval(t, Z, Ar, Z(0); adj = false, solver = options.solver, reltol = options.reltol, abstol = options.abstol),A.period) + Xt = PeriodicFunctionMatrix(t->PeriodicMatrixEquations.tvclyap_eval(t, Z, Ar; solver = options.solver, reltol = options.reltol, abstol = options.abstol),A.period) Xts = Xt.f.((0:N-1)*period/N) Xts = [Xts; [Xts[1]-X0]] X = PeriodicSystems.ts2fm(Xts, period; method = options.intpolmeth) @@ -1847,7 +1829,7 @@ function fungradhr!(Fun, Grad, K, x, A::PM, B::PM, C::PM, R, Q, X0, options) whe if quad Grad[:] .= QuadGK.quadgk(t-> 2*(B(t)'*P(t)+RFC(t))*X(t)*C(t)', t0, tf)[1][:] else - Grad[:] .= tvgrad!(V, Ar, B, C, RFC, P, tf, t0; solver = options.solver, reltol = options.reltol, abstol = options.abstol, dt = options.dt)[:] + Grad[:] .= tvgrad!(V, Ar, B, C, RFC, P, tf, t0; solver = options.solver, reltol = options.reltol, abstol = options.abstol)[:] end end diff --git a/src/pstimeresp.jl b/src/pstimeresp.jl index 80ac49a..d9141b7 100644 --- a/src/pstimeresp.jl +++ b/src/pstimeresp.jl @@ -47,7 +47,7 @@ The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/Ordi `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). For large numbers of product terms, parallel computation of factors can be alternatively performed by starting Julia with several execution threads. @@ -56,7 +56,7 @@ or by using the `JULIA_NUM_THREADS` environment variable. """ function pstimeresp(psys::PeriodicStateSpace{PM}, u::AbstractVecOrMat{<:Number}, t::AbstractVector{<:Real}, x0::AbstractVector{<:Number} = zeros(T,psys.nx[1]); - state_history::Bool = false, solver::String = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where {Domain,T, PM <: AbstractPeriodicArray{Domain,T}} + state_history::Bool = false, solver::String = "auto", reltol = 1e-4, abstol = 1e-7, dt = 0) where {Domain,T, PM <: AbstractPeriodicArray{Domain,T}} T1 = T <: BlasFloat ? T : promote_type(Float64,T) n = psys.nx[1] @@ -214,7 +214,7 @@ The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/Ordi `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). For large numbers of product terms, parallel computation of factors can be alternatively performed by starting Julia with several execution threads. @@ -224,8 +224,8 @@ or by using the `JULIA_NUM_THREADS` environment variable. function psstepresp(psys::PeriodicStateSpace{PM}, tfinal::Real = 0; x0::AbstractVector{<:Number} = zeros(T,psys.nx[1]), ustep::AbstractVector{<:Number} = ones(T,psys.nu[1]), state_history::Bool = false, timesteps::Int = 100, - solver::String = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {T, PM <: Union{PeriodicArray{:d,T},PeriodicMatrix{:d,T},PeriodicFunctionMatrix{:c,T},HarmonicArray{:c,T},FourierFunctionMatrix{:c,T}}} + solver::String = "auto", reltol = 1e-4, abstol = 1e-7, dt = 0) where + {T, PM <: Union{PeriodicArray{:d,T},PeriodicMatrix{:d,T},PeriodicFunctionMatrix{:c,T},HarmonicArray{:c,T}}} T1 = T <: BlasFloat ? T : promote_type(Float64,T) n, p, m = psys.nx[1], psys.ny[1], psys.nu[1] @@ -392,11 +392,11 @@ The following solvers from the [OrdinaryDiffEq.jl](https://github.com/SciML/Ordi `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); -`solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). +`solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). """ function pstimeresp(psys::PeriodicStateSpace{PM}, u::Function, t::AbstractVector{<:Real}, x0::AbstractVector{<:Number} = zeros(T,psys.nx[1]); - state_history::Bool = false, solver::String = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where {T, PM <: AbstractPeriodicArray{:c,T}} + state_history::Bool = false, solver::String = "auto", reltol = 1e-4, abstol = 1e-7, dt = 0) where {T, PM <: AbstractPeriodicArray{:c,T}} T1 = T <: BlasFloat ? T : promote_type(Float64,T) n = psys.nx p, m = psys.ny, psys.nu @@ -425,8 +425,8 @@ function pstimeresp(psys::PeriodicStateSpace{PM}, u::Function, t::AbstractVector end return y, tout, x end -function tvtimeresp(A::PM, B::PM, tf, t0, u, x0::AbstractVector; solver = "", reltol = 1e-4, abstol = 1e-7, dt = 0) where - {PM <: Union{PeriodicFunctionMatrix,HarmonicArray,FourierFunctionMatrix,PeriodicSwitchingMatrix,PeriodicTimeSeriesMatrix}} +function tvtimeresp(A::PM, B::PM, tf, t0, u, x0::AbstractVector; solver = "auto", reltol = 1e-4, abstol = 1e-7, dt = 0) where + {PM <: Union{PeriodicFunctionMatrix,HarmonicArray,PeriodicSwitchingMatrix,PeriodicTimeSeriesMatrix}} """ tvtimeresp(A, B, tf, t0, u, x0; solver, reltol, abstol) -> x::Vector @@ -449,7 +449,7 @@ function tvtimeresp(A::PM, B::PM, tf, t0, u, x0::AbstractVector; solver = "", re `solver = "symplectic"` - use a symplectic Hamiltonian structure preserving solver (`IRKGL16()`); - `solver = ""` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). + `solver = "auto"` - use the default solver, which automatically detects stiff problems (`AutoTsit5(Rosenbrock23())` or `AutoVern9(Rodas5())`). """ n = size(A,1) n == size(A,2) || error("the periodic matrix A must be square") diff --git a/src/types/PeriodicStateSpace.jl b/src/types/PeriodicStateSpace.jl index 73a2660..3d1f7c0 100644 --- a/src/types/PeriodicStateSpace.jl +++ b/src/types/PeriodicStateSpace.jl @@ -151,15 +151,15 @@ function PeriodicStateSpace(A::PFM1, B::PFM2, C::PFM3, D::PFM4) where {PFM1 <: P (period == D.period && T == eltype(D)) ? D : PeriodicFunctionMatrix{:c,T}(D,period), Float64(period)) end -function PeriodicStateSpace(A::FFM1, B::FFM2, C::FFM3, D::FFM4) where {FFM1 <: FourierFunctionMatrix, FFM2 <: FourierFunctionMatrix, FFM3 <: FourierFunctionMatrix, FFM4 <: FourierFunctionMatrix} - period = ps_validation(A, B, C, D) - T = promote_type(eltype(A),eltype(B),eltype(C),eltype(D)) - PeriodicStateSpace{FourierFunctionMatrix{:c,T}}((period == A.period && T == eltype(A)) ? A : FourierFunctionMatrix{:c,T}(A,period), - (period == B.period && T == eltype(B)) ? B : FourierFunctionMatrix{:c,T}(B,period), - (period == C.period && T == eltype(C)) ? C : FourierFunctionMatrix{:c,T}(C,period), - (period == D.period && T == eltype(D)) ? D : FourierFunctionMatrix{:c,T}(D,period), - Float64(period)) -end +# function PeriodicStateSpace(A::FFM1, B::FFM2, C::FFM3, D::FFM4) where {FFM1 <: FourierFunctionMatrix, FFM2 <: FourierFunctionMatrix, FFM3 <: FourierFunctionMatrix, FFM4 <: FourierFunctionMatrix} +# period = ps_validation(A, B, C, D) +# T = promote_type(eltype(A),eltype(B),eltype(C),eltype(D)) +# PeriodicStateSpace{FourierFunctionMatrix{:c,T}}((period == A.period && T == eltype(A)) ? A : FourierFunctionMatrix{:c,T}(A,period), +# (period == B.period && T == eltype(B)) ? B : FourierFunctionMatrix{:c,T}(B,period), +# (period == C.period && T == eltype(C)) ? C : FourierFunctionMatrix{:c,T}(C,period), +# (period == D.period && T == eltype(D)) ? D : FourierFunctionMatrix{:c,T}(D,period), +# Float64(period)) +# end function PeriodicStateSpace(A::PSM, B::PSM, C::PSM, D::PSM) where {PSM <: PeriodicSymbolicMatrix} period = ps_validation(A, B, C, D) @@ -400,47 +400,47 @@ Base.show(io::IO, sys::PeriodicStateSpace{PM}) where {PM <: Union{PeriodicMatrix,PeriodicArray,PeriodicTimeSeriesMatrix,PeriodicSymbolicMatrix,PeriodicFunctionMatrix,HarmonicArray}} = show(io, MIME("text/plain"), sys) -function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, sys::PeriodicStateSpace{<:FourierFunctionMatrix}) - summary(io, sys); println(io) - n = size(sys.A,1) - p, m = size(sys.D) - T = eltype(sys) - if n > 0 - nperiod = sys.A.nperiod - println(io, "\nState matrix A::$T($n×$n): subperiod: $(sys.A.period/nperiod) #subperiods: $nperiod ") - PeriodicMatrices.isconstant(sys.A) ? show(io, mime, sys.A.M(0)) : show(io, mime, sys.A.M) - if m > 0 - nperiod = sys.B.nperiod - println(io, "\n\nInput matrix B::$T($n×$m): $(sys.B.period/nperiod) #subperiods: $nperiod ") - PeriodicMatrices.isconstant(sys.B) ? show(io, mime, sys.B.M(0)) : show(io, mime, sys.B.M) - else - println(io, "\n\nEmpty input matrix B.") - end +# function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, sys::PeriodicStateSpace{<:FourierFunctionMatrix}) +# summary(io, sys); println(io) +# n = size(sys.A,1) +# p, m = size(sys.D) +# T = eltype(sys) +# if n > 0 +# nperiod = sys.A.nperiod +# println(io, "\nState matrix A::$T($n×$n): subperiod: $(sys.A.period/nperiod) #subperiods: $nperiod ") +# PeriodicMatrices.isconstant(sys.A) ? show(io, mime, sys.A.M(0)) : show(io, mime, sys.A.M) +# if m > 0 +# nperiod = sys.B.nperiod +# println(io, "\n\nInput matrix B::$T($n×$m): $(sys.B.period/nperiod) #subperiods: $nperiod ") +# PeriodicMatrices.isconstant(sys.B) ? show(io, mime, sys.B.M(0)) : show(io, mime, sys.B.M) +# else +# println(io, "\n\nEmpty input matrix B.") +# end - if p > 0 - nperiod = sys.C.nperiod - println(io, "\n\nOutput matrix C::$T($p×$n): $(sys.C.period/nperiod) #subperiods: $nperiod ") - PeriodicMatrices.isconstant(sys.C) ? show(io, mime, sys.C.M(0)) : show(io, mime, sys.C.M) - else - println(io, "\n\nEmpty output matrix C.") - end - if m > 0 && p > 0 - nperiod = sys.D.nperiod - println(io, "\n\nFeedthrough matrix D::$T($p×$m): $(sys.D.period/nperiod) #subperiods: $nperiod ") - PeriodicMatrices.isconstant(sys.D) ? show(io, mime, sys.D.M(0)) : show(io, mime, sys.D.M) - else - println(io, "\n\nEmpty feedthrough matrix D.") - end - println(io, "\n\nContinuous-time periodic state-space model.") - elseif m > 0 && p > 0 - nperiod = sys.D.nperiod - println(io, "\nFeedthrough matrix D::$T($p×$m): $(sys.D.period/nperiod) #subperiods: $nperiod ") - PeriodicMatrices.isconstant(sys.D) ? show(io, mime, sys.D.M(0)) : show(io, mime, sys.D.M) - println(io, "\n\nTime-varying gain.") - else - println(io, "\nEmpty state-space model.") - end -end +# if p > 0 +# nperiod = sys.C.nperiod +# println(io, "\n\nOutput matrix C::$T($p×$n): $(sys.C.period/nperiod) #subperiods: $nperiod ") +# PeriodicMatrices.isconstant(sys.C) ? show(io, mime, sys.C.M(0)) : show(io, mime, sys.C.M) +# else +# println(io, "\n\nEmpty output matrix C.") +# end +# if m > 0 && p > 0 +# nperiod = sys.D.nperiod +# println(io, "\n\nFeedthrough matrix D::$T($p×$m): $(sys.D.period/nperiod) #subperiods: $nperiod ") +# PeriodicMatrices.isconstant(sys.D) ? show(io, mime, sys.D.M(0)) : show(io, mime, sys.D.M) +# else +# println(io, "\n\nEmpty feedthrough matrix D.") +# end +# println(io, "\n\nContinuous-time periodic state-space model.") +# elseif m > 0 && p > 0 +# nperiod = sys.D.nperiod +# println(io, "\nFeedthrough matrix D::$T($p×$m): $(sys.D.period/nperiod) #subperiods: $nperiod ") +# PeriodicMatrices.isconstant(sys.D) ? show(io, mime, sys.D.M(0)) : show(io, mime, sys.D.M) +# println(io, "\n\nTime-varying gain.") +# else +# println(io, "\nEmpty state-space model.") +# end +# end function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, sys::PeriodicStateSpace{<:HarmonicArray}) diff --git a/test/runtests.jl b/test/runtests.jl index a819366..8a40ee5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,10 +10,6 @@ include("test_psconversions.jl") include("test_pslifting.jl") include("test_psanalysis.jl") include("test_pstimeresp.jl") -include("test_pslyap.jl") -include("test_psclyap.jl") -include("test_pscric.jl") -include("test_psdric.jl") include("test_stabilization.jl") end diff --git a/test/test_psanalysis.jl b/test/test_psanalysis.jl index 8933e97..30dab0d 100644 --- a/test/test_psanalysis.jl +++ b/test/test_psanalysis.jl @@ -518,8 +518,8 @@ for PM in (PeriodicFunctionMatrix, HarmonicArray, PeriodicSymbolicMatrix, Period # psys = ps(Ap,Bp,Cp,Dp); psys = convert(PeriodicStateSpace{PM},psysc0) psys1 = convert(PeriodicStateSpace{PM},psysc1) - solver = "noidea" - #for solver in ("non-stiff", "symplectic", "noidea") + solver = "auto" + for solver in ("non-stiff", "auto") println("solver = $solver") @time nh2pq = psh2norm(psys,K; solver, reltol=1.e-10, abstol = 1.e-10, quad = true) @time nh2p = psh2norm(psys,K; solver, reltol=1.e-10, abstol = 1.e-10) @@ -540,7 +540,7 @@ for PM in (PeriodicFunctionMatrix, HarmonicArray, PeriodicSymbolicMatrix, Period @time nhinfpc1, fpeakpc1 = pslinfnorm(psys1,K; solver, reltol=1.e-10, abstol = 1.e-10, rtolinf) @test abs(nhi1-nhinfpc1) < rtolinf*nhi1 end - #end + end end @@ -660,7 +660,7 @@ end # nt = psh2norm(psys,200, adj = true, reltol=1.e-10, abstol = 1.e-10) # solver = "non-stiff" -# for solver in ("non-stiff", "stiff", "symplectic", "noidea") +# for solver in ("non-stiff", "stiff", "symplectic", "auto") # println("solver = $solver") # @time n1 = psh2norm(psys,100; solver, adj = false, reltol=1.e-10, abstol = 1.e-10) # @time n2 = psh2norm(psys,100; solver, adj = true, reltol=1.e-10, abstol = 1.e-10) @@ -670,7 +670,7 @@ end # psys1 = ps(PeriodicFunctionMatrix,a1,b1,c,d1); # nti1 = pslinfnorm(psys1,200, reltol=1.e-10, abstol = 1.e-10)[1] # solver = "non-stiff" -# for solver in ("non-stiff", "stiff", "symplectic", "noidea") +# for solver in ("non-stiff", "stiff", "symplectic", "auto") # println("solver = $solver") # @time n1 = pslinfnorm(psys1,100; solver, reltol=1.e-10, abstol = 1.e-10)[1] # @time n2 = pslinfnorm(psys1,100; solver, reltol=1.e-10, abstol = 1.e-10)[1] @@ -846,7 +846,7 @@ for PM in (PeriodicFunctionMatrix, HarmonicArray, FourierFunctionMatrix, Periodi # psys = ps(Ap,Bp,Cp,Dp); psys = convert(PeriodicStateSpace{PM},psys0) solver = "non-stiff" - for solver in ("non-stiff", "stiff", "symplectic", "noidea") + for solver in ("non-stiff", "stiff", "auto") println("solver = $solver") @time nhap = pshanorm(psys,K; solver, reltol=1.e-10, abstol = 1.e-10) @test nhapc ≈ nhap @@ -899,8 +899,8 @@ c = [1 0 0 0;0 1 0 0]; d = zeros(2,1); psys = ps(a-β*I,PeriodicFunctionMatrix(b,period),c,d); -@time n1 = pshanorm(psys,100; reltol=1.e-10, abstol = 1.e-10); -@time n2 = pshanorm(psys,200; reltol=1.e-10, abstol = 1.e-10); +@time n1 = pshanorm(psys,300; reltol=1.e-10, abstol = 1.e-10); +@time n2 = pshanorm(psys,500; reltol=1.e-10, abstol = 1.e-10); @test n1 ≈ n2 rtolinf = 1.e-8 diff --git a/test/test_psclyap.jl b/test/test_psclyap.jl deleted file mode 100644 index f033520..0000000 --- a/test/test_psclyap.jl +++ /dev/null @@ -1,707 +0,0 @@ -module Test_psclyap - -using PeriodicSystems -using DescriptorSystems -using Symbolics -using Test -using LinearAlgebra -using ApproxFun -using Symbolics - -println("Test_psclyap") - -@testset "pclyap, pcplyap" begin - -tt = Vector((1:500)*2*pi/500) -ts = [0.4459591888577492, 1.2072325802972004, 1.9910835248218244, 2.1998199838900527, 2.4360161318589695, - 2.9004720268745463, 2.934294124172935, 4.149208861412936, 4.260935465730602, 5.956614157549958] - -# generate symbolic periodic matrices -@variables t -A1 = [0 1; -10*cos(t)-1 -24-19*sin(t)] -As = PeriodicSymbolicMatrix(A1,2*pi) -X1 = [1+cos(t) 0; 0 1+sin(t)] -Xdots = [-sin(t) 0; 0 cos(t)] -# Xdots = Symbolics.derivative.(X1,t) -Xs = PeriodicSymbolicMatrix(X1, 2*pi) -Cds = PeriodicSymbolicMatrix(-(A1'*X1+X1*A1+Xdots),2*pi) -Cs = PeriodicSymbolicMatrix(Xdots - A1*X1-X1*A1', 2*pi) - -# @time Ys = pclyap(As,Cs; K = 100, reltol = 1.e-10, abstol = 1.e-10); -# @test Xs ≈ Ys && norm(As*Ys+Ys*As'+Cs - pmderiv(Ys)) < 1.e-6 - -# @time Ys = pclyap(As,Cds; adj = true, K = 100, reltol = 1.e-10, abstol = 1.e-10); -# @test Xs ≈ Ys && norm(As'*Ys+Ys*As+Cds + pmderiv(Ys)) < 1.e-6 - -# @time Ys = pfclyap(As,Cs; K = 100, reltol = 1.e-10); -# @test Xs ≈ Ys && norm(As*Ys+Ys*As'+Cs - pmderiv(Ys)) < 1.e-6 - -# @time Ys = prclyap(As,Cds; K = 100, reltol = 1.e-10); -# @test Xs ≈ Ys && norm(As'*Ys+Ys*As+Cds + pmderiv(Ys)) < 1.e-6 - - -@time Yt = pclyap(As,Cs; K = 512, reltol = 1.e-10, abstol = 1.e-10); -@time Yt1 = pclyap(As,Cs; K = 512, reltol = 1.e-10, abstol = 1.e-10,intpol=true); -Ys = convert(PeriodicSymbolicMatrix,Yt) -Ys1 = convert(PeriodicSymbolicMatrix,Yt1) -@test Ys ≈ Xs && Ys1 ≈ Xs - -@time Yt = pclyap(As,Cds; adj = true, K = 512, reltol = 1.e-10, abstol = 1.e-10); -Ys = convert(PeriodicSymbolicMatrix,Yt) -@test Xs ≈ Ys - -@time Yt = pfclyap(As,Cs; K = 512, reltol = 1.e-10); -Ys = convert(PeriodicSymbolicMatrix,Yt) -@test Ys ≈ Xs - -@time Ys = prclyap(As,Cds; K = 512, reltol = 1.e-10); -Ys = convert(PeriodicSymbolicMatrix,Yt) -@test Xs ≈ Ys - - -@time Ys = pclyap(As,Cs'*Cs; adj = true, K = 1000, reltol = 1.e-10, abstol = 1.e-10); -@time Us = pcplyap(As,Cs; adj = true, K = 1000, reltol = 1.e-10, abstol = 1.e-10); -@test norm(Us'*Us-Ys) < 1.e-7 - -# generate periodic function matrices -A(t) = [0 1; -10*cos(t)-1 -24-19*sin(t)] -X(t) = [1+cos(t) 0; 0 1+sin(t)] # desired solution -Xdot(t) = [-sin(t) 0; 0 cos(t)] # pmderiv of the desired solution -C(t) = [ -sin(t) -1-sin(t)-(-1-10cos(t))*(1+cos(t)); --1-sin(t)-(-1-10cos(t))*(1+cos(t)) cos(t)- 2(-24 - 19sin(t))*(1 + sin(t)) ] # corresponding C -Cd(t) = [ sin(t) -1-cos(t)-(-1-10cos(t))*(1+sin(t)); --1-cos(t)-(-1-10cos(t))*(1+sin(t)) -cos(t)-2(-24-19sin(t))*(1 + sin(t)) ] # corresponding Cd -At = PeriodicFunctionMatrix(A,2*pi) -Ct = PeriodicFunctionMatrix(C,2*pi) -Cdt = PeriodicFunctionMatrix(Cd,2*pi) -Xt = PeriodicFunctionMatrix(X,2*pi) -Xd = PeriodicFunctionMatrix(Xdot,2*pi) - -@time Yt = pclyap(At, Ct, K = 512, intpol = true, reltol = 1.e-12, abstol=1.e-12); -@time Yt1 = pclyap(At,Ct; K = 512, reltol = 1.e-12, abstol = 1.e-12,intpol=false); -@test norm(Xt - Yt) < 1.e-7 && norm(Xt - Yt1) < 1.e-7 - -@time Yt1 = pclyap(At, Ct, K = 1, reltol = 1.e-12, abstol=1.e-12, intpol = false); -@test Xt ≈ Yt1 && Xd ≈ pmderiv(Yt1) - -@time Yt1 = pclyap(At, Cdt, K = 1, adj = true, reltol = 1.e-12, abstol=1.e-12, intpol = false) -@test Xt ≈ Yt1 && Xd ≈ pmderiv(Yt1) - -@time Yt = pfclyap(At, Ct, K = 500, reltol = 1.e-12, abstol=1.e-12); -@test Xt ≈ Yt - -@time Y = prclyap(At, Cdt, K = 500, reltol = 1.e-12, abstol=1.e-12) -@test Xt ≈ Yt - -@time Yt = pclyap(At,Ct*Ct'; adj = false, K = 512, reltol = 1.e-14, abstol = 1.e-14, intpol=false); -@time Ut = pcplyap(At,Ct; adj = false, K = 512, reltol = 1.e-14, abstol = 1.e-14, intpol=false); -@test Ut*Ut' ≈ Yt - -@time Yt = pclyap(At,Ct*Ct'; adj = false, K = 512, reltol = 1.e-14, abstol = 1.e-14, intpol=true); -@time Ut = pcplyap(At,Ct; adj = false, K = 512, reltol = 1.e-14, abstol = 1.e-14, intpol=true); -@test all(norm.(Ut.(ts).*Ut'.(ts).-Yt.(ts),Inf) .< 1.e-5) - - -@time Yt = pclyap(At,Ct'*Ct; adj = true, K = 1000, reltol = 1.e-14, abstol = 1.e-14, intpol=false); -@time Ut = pcplyap(At,Ct; adj = true, K = 1000, reltol = 1.e-14, abstol = 1.e-14, intpol=false); -@test Ut'*Ut ≈ Yt - -@time Yt = pclyap(At,Ct'*Ct; adj = true, K = 1000, reltol = 1.e-14, abstol = 1.e-14, intpol=true); -@time Ut = pcplyap(At,Ct; adj = true, K = 1000, reltol = 1.e-14, abstol = 1.e-14, intpol=true); -@test all(norm.(Ut'.(ts).*Ut.(ts).-Yt.(ts),Inf) .< 1.e-5) - - -# # check implicit solver based solution -# @time Yt = pclyap(At,Ct*Ct'; adj = false, K = 512, reltol = 1.e-14, abstol = 1.e-14, intpol=false); -# @time Ut = pcplyap(At,Ct; adj = false, K = 512, reltol = 1.e-10, abstol = 1.e-10, intpol=false, implicit = true); -# Xt2 = Ut*Ut'; -# @test norm(At*Yt+Yt*At'+Ct*Ct' - pmderiv(Yt)) < 1.e-4 -# @test Xt2 ≈ Yt && norm(At*Xt2+Xt2*At'+Ct*Ct' - pmderiv(Ut)*Ut'-Ut*pmderiv(Ut)') < 1.e-4*norm(Xt2) - - -# @time Yt = pclyap(At,Ct'*Ct; adj = true, K = 512, reltol = 1.e-14, abstol = 1.e-14, intpol=false); -# @time Ut = pcplyap(At,Ct; adj = true, K = 512, reltol = 1.e-10, abstol = 1.e-10, intpol=false, implicit = true); -# Xt2 = Ut'*Ut; -# @test norm(At'*Yt+Yt*At+Ct'*Ct + pmderiv(Yt)) < 1.e-4 -# @test Xt2 ≈ Yt && norm(At'*Xt2+Xt2*At+Ct'*Ct + pmderiv(Ut)'*Ut+Ut'*pmderiv(Ut)) < 1.e-4*norm(Xt2) - -# singular factors -At1 = [[At zeros(2,2)]; [zeros(2,2) At]]; Ct1 = [Ct; -Ct]; -Ut1 = pgcplyap(At1, Ct1, 64; adj = false, solver = "", reltol = 1.e-10, abstol = 1.e-10); -@test all(cond.(Ut1.values) .> 1.e-6) -@time Ut2 = pcplyap(At1,Ct1; adj = false, K = 64, reltol = 1.e-10, abstol = 1.e-10, intpol=false); -ti = collect((0:63)*(2pi/64)).+eps(20.); -@test all(isapprox.(Ut1.(ti),Ut2.(ti),rtol=1.e-6)) - - -# # check explicit solver based solution # fails but still works without errors -# @time Yt = pclyap(At,Ct*Ct'; adj = false, K = 512, reltol = 1.e-14, abstol = 1.e-14, intpol=false); -# @time Ut = pcplyap(At,Ct; adj = false, K = 512, solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-6, intpol=false, implicit = false); -# Xt2 = Ut*Ut'; -# @test norm(At*Yt+Yt*At'+Ct*Ct' - pmderiv(Yt)) < 1.e-4 -# @test Xt2 ≈ Yt && norm(At*Xt2+Xt2*At'+Ct*Ct' - pmderiv(Ut)*Ut'-Ut*pmderiv(Ut)') < 1.e-4*norm(Xt2) - - -# @time Yt = pclyap(At,Ct'*Ct; adj = true, K = 512, reltol = 1.e-14, abstol = 1.e-14, intpol=false); -# @time Ut = pcplyap(At,Ct; adj = true, K = 512, reltol = 1.e-5, abstol = 1.e-5, intpol=false, implicit =false); -# Xt2 = Ut'*Ut; -# @test norm(At'*Yt+Yt*At+Ct'*Ct + pmderiv(Yt)) < 1.e-4 -# @test Xt2 ≈ Yt && norm(At'*Xt2+Xt2*At+Ct'*Ct + pmderiv(Ut)'*Ut+Ut'*pmderiv(Ut)) < 1.e-4*norm(Xt2) - - -K = 500 -@time W0 = pgclyap(At, Ct, K; adj = false, solver = "", reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001); -Ts = 2pi/K -success = true -for i = 1:K - Y = PeriodicSystems.tvclyap(At, Ct, i*Ts, (i-1)*Ts, W0.values[mod(i+K-1,K)+1]; solver = "", adj = false, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001) - iw = i+1; iw > K && (iw = 1) - success = success && norm(Y-W0.values[iw]) < 1.e-7 -end -@test success - -K = 10 -@time W0 = pgclyap(At, Ct, K; adj = false, solver = "", reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001); -ns = 10; ts = sort([0.; rand(ns-1)*2*pi; 2*pi]) -success = true -for t in ts - Xtval = PeriodicSystems.tvclyap_eval(t, W0, At, Ct; solver = "", adj = false, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001) - success = success && norm(Xtval-Xt(t)) < 1.e-7 -end -@test success -@time XXt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, At, Ct; solver = "", adj = false, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001),2*pi) -@test norm((At*XXt+XXt*At'+Ct-pmderiv(XXt)).(ts),Inf) < 1.e-5 - - - -K = 500 -W0 = pgclyap(At, Cdt, K; adj = true, solver = "", reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001); -Ts = 2pi/K -success = true -for i = K:-1:1 - iw = i+1; iw > K && (iw = 1) - Y = PeriodicSystems.tvclyap(At, Cdt, (i-1)*Ts, i*Ts, W0.values[iw]; solver = "", adj = true, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001) - success = success && norm(Y-W0.values[i]) < 1.e-7 -end -@test success - -K = 10 -W0 = pgclyap(At, Cdt, K; adj = true, solver = "", reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001); -ns = 10; ts = sort([0.; rand(ns-1)*2*pi; 2*pi]) -success = true -for t in ts - Xtval = PeriodicSystems.tvclyap_eval(t, W0, At, Cdt; solver = "", adj = true, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001) - success = success && norm(Xtval-Xt(t)) < 1.e-7 -end -@test success - -XXt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, At, Cdt; solver = "", adj = true, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001),2*pi) -@test norm((At'*XXt+XXt*At+Cdt+pmderiv(XXt)).(ts),Inf) < 1.e-5 - - -solver = "non-stiff" -for solver in ("non-stiff", "stiff", "symplectic", "noidea") - println("solver = $solver") - @time Yt = pclyap(At, Ct; solver, K = 500, reltol = 1.e-10, abstol = 1.e-10); - @test Xt ≈ Yt - #@test maximum(norm.(Yt.f.(tt).-Xt.f.(tt))) < 1.e-6 -end - - -# solve using harmonic arrays -Ah = convert(HarmonicArray,At); -Ch = convert(HarmonicArray,Ct); -Cdh = convert(HarmonicArray,Cdt); -Xh = convert(HarmonicArray,Xt); -Xdh = convert(HarmonicArray,Xd); - -@time Yt = pclyap(Ah, Ch, K = 500, reltol = 1.e-10, abstol = 1.e-10); -@time Yt1 = pclyap(Ah,Ch; K = 512, reltol = 1.e-10, abstol = 1.e-10,intpol=true); -Yh = convert(HarmonicArray,Yt); -Yh1 = convert(HarmonicArray,Yt1); -@test Xh ≈ Yh && Xh ≈ Yh1 - -@time Yt = pclyap(Ah, Cdh, K = 500, adj = true, reltol = 1.e-10, abstol = 1.e-10); -Yh = convert(HarmonicArray,Yt); -@test Xh ≈ Yh && Xdh ≈ pmderiv(Yh) - -@time Yt = pfclyap(Ah, Ch, K = 500, reltol = 1.e-10, abstol = 1.e-10); -Yh = convert(HarmonicArray,Yt); -@test Xh ≈ Yh - -@time Yt = prclyap(Ah, Cdh, K = 500, reltol = 1.e-10, abstol = 1.e-10); -Yh = convert(HarmonicArray,Yt); -@test Xh ≈ Yh -# # solve using Fourier function matrices -# Af = convert(FourierFunctionMatrix,At); -# Cf = convert(FourierFunctionMatrix,Ct); -# Cdf = convert(FourierFunctionMatrix,Cdt) -# Xf = convert(FourierFunctionMatrix,Xt); -# Xdf = convert(FourierFunctionMatrix,Xd); - -# @time Yt = pclyap(Af, Cf, K = 500, reltol = 1.e-10, abstol = 1.e-10); -# @time Yt1 = pclyap(Af,Cf; K = 512, reltol = 1.e-10, abstol = 1.e-10,intpol=true); -# Yf = convert(FourierFunctionMatrix,Yt); -# @test Xf ≈ Yf && Xdf ≈ pmderiv(Yf) && norm(Af*Yf+Yf*Af'+Cf-pmderiv(Yf)) < 1.e-7 && norm(Yt1-Yt) < 1.e-7 - -# @time Yt = pclyap(Af, Cdf, K = 500, adj = true, reltol = 1.e-10, abstol = 1.e-10) -# Yf = convert(FourierFunctionMatrix,Yt); -# @test Xf ≈ Yf && Xdf ≈ pmderiv(Yf) && norm(Af'*Yf+Yf*Af+Cdf+pmderiv(Yf)) < 1.e-7 - -# @time Yt = pfclyap(Af, Cf, K = 500, reltol = 1.e-10, abstol = 1.e-10); -# Yf = convert(FourierFunctionMatrix,Yt); -# @test Xf ≈ Yf && norm(Af*Yf+Yf*Af'+Cf-pmderiv(Yf)) < 1.e-7 - -# @time Yt = prclyap(Af, Cdf, K = 500, reltol = 1.e-10, abstol = 1.e-10) -# Yf = convert(FourierFunctionMatrix,Yt); -# @test Xf ≈ Yf && norm(Af'*Yf+Yf*Af+Cdf+pmderiv(Yf)) < 1.e-7 - - -# solve using periodic time-series matrices -K = 512 -Ats = convert(PeriodicTimeSeriesMatrix,At;ns = K); -Cts = convert(PeriodicTimeSeriesMatrix,Ct;ns = K); -Cdts = convert(PeriodicTimeSeriesMatrix,Cdt;ns = K); -ts = [0.4459591888577492, 1.2072325802972004, 1.9910835248218244, 2.1998199838900527, 2.4360161318589695, - 2.9004720268745463, 2.934294124172935, 4.149208861412936, 4.260935465730602, 5.956614157549958] - - -@time Yt1 = pclyap(Ats, Cts; K = 1, reltol = 1.e-14, abstol = 1.e-14); -@test norm((Ats.(ts).*Yt1.(ts)).+(Yt1.(ts).*(Ats').(ts)).+Cts.(ts).-pmderiv(Yt1).(ts),Inf) < 1.e-5 -#@test norm((Ats*Yt1+Yt1*Ats'+Cts-pmderiv(Yt1)).(ts)) < 1.e-5 - -@time Yt = pclyap(Ats,Cts; K, reltol = 1.e-14, abstol = 1.e-14,intpol=true); -@test norm((Ats*Yt+Yt*Ats'+Cts-pmderiv(Yt)).(ts)) < 1.e-5 - -Xt1 = pclyap(Ats, Cts; K = 128, reltol = 1.e-14, abstol = 1.e-14,intpol=true); -@test norm((Ats*Xt1+Xt1*Ats'+Cts-pmderiv(Xt1)).(ts)) < 1.e-5 - -Xt2 = pclyap(Ats, Cts; K = 256, reltol = 1.e-14, abstol = 1.e-14,intpol=true); -@test norm((Ats*Xt2+Xt2*Ats'+Cts-pmderiv(Xt2)).(ts)) < 1.e-5 -@test norm((Xt1-Xt2).(ts)) < 1.e-7 - -Ka = 10; Kc = 1; -Ats1 = convert(PeriodicTimeSeriesMatrix,At;ns = Ka); -Cts1 = convert(PeriodicTimeSeriesMatrix,Ct;ns = Kc); -Xt1 = pclyap(Ats1, Cts1; K = 10, reltol = 1.e-14, abstol = 1.e-14); -@test norm((Ats1*Xt1+Xt1*Ats1'+Cts1-pmderiv(Xt1)).(ts)) < 1.e-5 - -Xt2 = pclyap(Ats1, Cts1; K = 2000, reltol = 1.e-14, abstol = 1.e-14); -@test norm((Ats1*Xt2+Xt2*Ats1'+Cts1-pmderiv(Xt2)).(ts)) < 1.e-5 -#@test norm((Xt1-Xt2).(ts)) < 1.e-7 - -# K = 1000; -# Ats2 = convert(PeriodicTimeSeriesMatrix,At;ns = K); -# Cts2 = convert(PeriodicTimeSeriesMatrix,Ct;ns = K); -# Xt1 = pclyap(Ats2, Cts2; K = 1, reltol = 1.e-14, abstol = 1.e-14); -# @time Xt2 = pfclyap(Ats2, Cts2; reltol = 1.e-14, abstol = 1.e-14); -# @test norm((Xt1-Xt).(ts)) < 1.0e-3 && -# norm((Xt2-Xt).(ts)) < 1.0e-3 - -# Xt1 = pclyap(Ats, Cdts; K = 100, adj = true, reltol = 1.e-14, abstol = 1.e-14,intpol=true); -# Xt2 = pclyap(Ats, Cdts; K = 200, adj = true, reltol = 1.e-14, abstol = 1.e-14,intpol=true); -# @test norm((Xt1-Xt2).(ts)) < 1.e-7 - - -# Cdts1 = convert(PeriodicTimeSeriesMatrix,Cdt;ns = Kc); -# Xt1 = pclyap(Ats1, Cdts1; K = 100, adj = true, reltol = 1.e-14, abstol = 1.e-14,intpol=true); -# Xt2 = pclyap(Ats1, Cdts1; K = 200, adj = true, reltol = 1.e-14, abstol = 1.e-14,intpol=true); -# @test norm((Xt1-Xt2).(ts)) < 1.e-5 - -# Cdts2 = convert(PeriodicTimeSeriesMatrix,Cdt;ns = 10000); -# Xt1 = pclyap(Ats2, Cdts2; K = 1, adj = true, reltol = 1.e-14, abstol = 1.e-14); -# @time Xt2 = prclyap(Ats2, Cdts2; K = 1, reltol = 1.e-14, abstol = 1.e-14); -# @test norm((Xt1-Xt2).(ts)) < 1.e-5 - -# @time Yt = pfclyap(Ats, Cts; reltol = 1.e-14, abstol = 1.e-14); -# @test norm((Ats*Yt+Yt*Ats'+Cts-pmderiv(Yt)).(ts)) < 1.e-5 - -# @time Yt = prclyap(Ats, Cdts; reltol = 1.e-14, abstol = 1.e-14); -# @test norm((Ats'*Yt+Yt*Ats+Cdts+pmderiv(Yt)).(ts)) < 1.e-5 - - -# # solve using periodic switching matrices -# Asw = convert(PeriodicSwitchingMatrix,Ats) -# Csw = convert(PeriodicSwitchingMatrix,Cts) -# Cdsw = convert(PeriodicSwitchingMatrix,Cdts) - - -# ts = [0.4459591888577492, 1.2072325802972004, 1.9910835248218244, 2.1998199838900527, 2.4360161318589695, -# 2.9004720268745463, 2.934294124172935, 4.149208861412936, 4.260935465730602, 5.956614157549958] - -# @time Yt = pclyap(Asw, Csw; K = 2, reltol = 1.e-14, abstol = 1.e-14); -# @test norm((Asw*Yt+Yt*Asw'+Csw-pmderiv(Yt)).(ts)) < 1.e-5 - -# @time Yt = pclyap(Asw, Cdsw; K = 2, adj = true, reltol = 1.e-14, abstol = 1.e-14) -# @test norm((Asw'*Yt+Yt*Asw+Cdsw+pmderiv(Yt)).(ts)) < 1.e-5 - -# @time Yt = pfclyap(Asw, Csw; reltol = 1.e-14, abstol = 1.e-14); -# @test norm((Asw*Yt+Yt*Asw'+Csw-pmderiv(Yt)).(ts)) < 1.e-5 - -# @time Yt = prclyap(Asw, Cdsw; reltol = 1.e-14, abstol = 1.e-14) -# @test norm((Asw'*Yt+Yt*Asw+Cdsw+pmderiv(Yt)).(ts)) < 1.e-5 - - -A4(t) = [0 1; -cos(t)-1 -2-sin(t)] -C4(t) = [ -sin(t) -1-sin(t)-(-1-10cos(t))*(1+cos(t)); --1-sin(t)-(-1-10cos(t))*(1+cos(t)) cos(t)- 2(-24 - 19sin(t))*(1 + sin(t)) ] -# C(t) = [ -sin(t) -1-sin(t); -# -1-sin(t) cos(t)] -#C(t) = [ 1 0;0 1. ] -tsa = [0., pi/4, pi/2, 3pi/2]; Ats = [A4(t) for t in tsa] -tsc = [0., 3pi/4, pi]; Cts = [C4(t) for t in tsc] -Asw = PeriodicSwitchingMatrix(Ats,tsa,2pi) -Csw = PeriodicSwitchingMatrix(Cts,tsc,2pi) -Ast = convert(PeriodicFunctionMatrix,Asw) -Cst = convert(PeriodicFunctionMatrix,Csw) -ts = [0.4459591888577492, 1.2072325802972004, 1.9910835248218244, 2.1998199838900527, 2.4360161318589695, - 2.9004720268745463, 2.934294124172935, 4.149208861412936, 4.260935465730602, 5.956614157549958] - - - -K = 500 -W0 = pgclyap(Ast, Cst, K; adj = false, solver = "", reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001); -Ts = 2pi/K -success = true -for i = 1:K - Y = PeriodicSystems.tvclyap(Ast, Cst, i*Ts, (i-1)*Ts, W0.values[mod(i+K-1,K)+1]; solver = "", adj = false, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001) - iw = i+1; iw > K && (iw = 1) - success = success && norm(Y-W0.values[iw]) < 1.e-3 -end -@test success -Xst = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, Ast, Cst; solver = "", adj = false, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001),2*pi) -@test norm((Ast*Xst+Xst*Ast'+Cst-pmderiv(Xst)).(ts)) < 1.e-6 - - -K = 100; -K = 1 -W1 = pgclyap(Asw, Csw, K; adj = false, solver = "", reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001); -success = true -Ks = length(W1.ts) -for i = 1:Ks - tf = i == Ks ? W1.period/W1.nperiod : W1.ts[i+1] - Y = PeriodicSystems.tvclyap(Asw, Csw, tf, W1.ts[i], W1.values[mod(i+Ks-1,Ks)+1]; solver = "", adj = false, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001) - iw = i+1; iw > Ks && (iw = 1) - success = success && norm(Y-W1.values[iw]) < 1.e-3 -end -@test success - -XXt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W1, Asw, Csw; solver = "", adj = false, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001),2*pi) -@test norm((Ast*XXt+XXt*Ast'+Cst-pmderiv(XXt)).(rand(10)*2*pi)) < 1.e-6 -@test norm((Xst-XXt).(ts)) < 1.e-3 - -# using Plots -# t = [0;sort(rand(200)*2*pi);2*pi]; n = length(t) -# y1 = Xt.(t); -# y2 = XXt.(t); - -# # y1 = pmderiv(Xt).(t); -# # y2 = pmderiv(XXt).(t); - - -# plot(t,[y1[i][1,1] for i in 1:n]) -# plot!(t,[y2[i][1,1] for i in 1:n]) - -# t2 = [W1.ts;2*pi] -# ns2 = length(t2) -# x2 = W1.(t2) -# plot!(t2,[x2[i][1,1] for i in 1:ns2]) - -K = 500 -@time W0 = pgclyap(Ast, Cst, K; adj = true, solver = "", reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001); -Xst = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W0, Ast, Cst; solver = "", adj = true, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001),2*pi) -@test norm((Ast'*Xst+Xst*Ast+Cst+pmderiv(Xst)).(ts)) < 1.e-6 - -K = 100; -K = 1 -W1 = pgclyap(Asw, Csw, K; adj = true, solver = "", reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001); -Ks = length(W1.ts) -success = true -for i = Ks:-1:1 - iw = i+1; iw > Ks && (iw = 1) - t0 = i == Ks ? W1.period/W1.nperiod : W1.ts[i+1] - Y = PeriodicSystems.tvclyap(Asw, Csw, W1.ts[i], t0, W1.values[iw]; solver = "", adj = true, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001) - success = success && norm(Y-W1.values[i]) < 1.e-7 -end -@test success - -XXt = PeriodicFunctionMatrix(t->PeriodicSystems.tvclyap_eval(t, W1, Asw, Csw; solver = "", adj = true, reltol = 1.e-10, abstol = 1.e-10, dt = 0.0001),2*pi) -@test norm((Ast'*XXt+XXt*Ast+Cst+pmderiv(XXt)).(ts)) < 1.e-6 -@test norm(Xst-XXt) < 1.e-3 - - - -@time Xt = pclyap(Asw, Csw; K = 10, reltol = 1.e-10, abstol=1.e-10); -@test norm((Asw*Xt+Xt*Asw'+Csw-pmderiv(Xt)).(ts)) < 1.e-6 - -@time Xt1 = pclyap(Asw, Csw; adj = true, K = 10, reltol = 1.e-10, abstol=1.e-10); -@test norm((Asw'*Xt1+Xt1*Asw+Csw+pmderiv(Xt1)).(ts)) < 1.e-6 - - - - -# generate periodic function matrices -# A(t) = [0 1; -10*cos(t)-1 -24-19*sin(t)] -# X(t) = [1+cos(t) 0; 0 1+sin(t)] # desired solution -# Xdot(t) = [-sin(t) 0; 0 cos(t)] # pmderiv of the desired solution -# C(t) = [ -sin(t) -1-sin(t)-(-1-10cos(t))*(1+cos(t)); -# -1-sin(t)-(-1-10cos(t))*(1+cos(t)) cos(t)- 2(-24 - 19sin(t))*(1 + sin(t)) ] # corresponding C -# Cd(t) = [ sin(t) -1-cos(t)-(-1-10cos(t))*(1+sin(t)); -# -1-cos(t)-(-1-10cos(t))*(1+sin(t)) -cos(t)-2(-24-19sin(t))*(1 + sin(t)) ] # corresponding Cd -At = PeriodicFunctionMatrix(A,2*pi) -Ct = PeriodicFunctionMatrix(C,2*pi) -Cdt = PeriodicFunctionMatrix(Cd,2*pi) -Xt = PeriodicFunctionMatrix(X,2*pi) -Xd = PeriodicFunctionMatrix(Xdot,2*pi) - - -for K in (1, 16, 64, 128, 256) - @time Y = pclyap(At,Ct; K, reltol = 1.e-10); - tt = Vector((1:K)*2*pi/K) - println(" Acc = $(maximum(norm.(Y.f.(tt).-Xt.f.(tt)))) ") - @test maximum(norm.(Y.f.(tt).-Xt.f.(tt))) < 1.e-7 -end - -for K in (1, 16, 64, 128, 256) - @time Y = pclyap(At,Cdt; K, adj = true, reltol = 1.e-10); - tt = Vector((1:K)*2*pi/K) - println(" Acc = $(maximum(norm.(Y.f.(tt).-Xt.f.(tt)))) ") - @test maximum(norm.(Y.f.(tt).-Xt.f.(tt))) < 1.e-7 -end - -for (na,nc) in ((1,1),(1,2),(2,1),(2,2)) - At = PeriodicFunctionMatrix(A,4*pi; nperiod = na) - Ct = PeriodicFunctionMatrix(C,4*pi; nperiod = nc) - Cdt = PeriodicFunctionMatrix(Cd,4*pi; nperiod = nc) - Xt = PeriodicFunctionMatrix(X,4*pi; nperiod = gcd(na,nc)) - - @time Y = pclyap(At,Ct,K = 500, reltol = 1.e-10); - - tt = Vector((1:500)*4*pi/500/Y.nperiod) - @test maximum(norm.(Y.f.(tt).-Xt.f.(tt))) < 1.e-7 - - @time Y = pclyap(At,Cdt,K = 500, adj = true, reltol = 1.e-10); - - tt = Vector((1:500)*4*pi/500/Y.nperiod) - @test maximum(norm.(Y.f.(tt).-Xt.f.(tt))) < 1.e-7 - -end - -# # generate symbolic periodic matrices -# using Symbolics -# @variables t -# As = [0 1; -1 -24] -# Xs = [1+cos(t) 0; 0 1+sin(t)] -# Xdots = [-sin(t) 0; 0 cos(t)] -# Cds = -(As'*Xs+Xs*As+Xdots) -# Cs = Xdots - As*Xs-Xs*As' - -A1 = [0 1; -1 -24] -X1(t) = [1+cos(t) 0; 0 1+sin(t)] # desired solution -X1dot(t) = [-sin(t) 0; 0 cos(t)] # pmderiv of the desired solution -C1(t) = [ -sin(t) cos(t)-sin(t); -cos(t)-sin(t) 48+48sin(t)+cos(t) ] # corresponding C -Cd1(t) = [ sin(t) -cos(t)+sin(t); --cos(t)+sin(t) 48+48sin(t)-cos(t) ] # corresponding Cd - - -At = PeriodicFunctionMatrix(A1,2*pi) -Ct = PeriodicFunctionMatrix(C1,2*pi) -Cdt = PeriodicFunctionMatrix(Cd1,2*pi) -Xt = PeriodicFunctionMatrix(X1,2*pi) - -@time Y = pclyap(At,Ct,K = 500, reltol = 1.e-10); - -tt = Vector((1:500)*2*pi/500) -@test maximum(norm.(Y.f.(tt).-Xt.f.(tt))) < 1.e-7 - -@time Y = pclyap(At,Cdt,K = 500, adj = true, reltol = 1.e-10) - -tt = Vector((1:500)*2*pi/500) -@test maximum(norm.(Y.f.(tt).-Xt.f.(tt))) < 1.e-7 - - -A2 = [0 1; -1 -24] -X2 = [1 0; 0 1] # desired solution -X2dot = [0 0; 0 0] # pmderiv of the desired solution -C2 = [ 0 0; 0 48 ] # corresponding C -Cd2 = [ 0 0; 0 48 ] # corresponding Cd - -At = PeriodicFunctionMatrix(A2,2*pi) -Ct = PeriodicFunctionMatrix(C2,2*pi) -Cdt = PeriodicFunctionMatrix(Cd2,2*pi) -Xt = PeriodicFunctionMatrix(X2,2*pi) - -@time Y = pclyap(At,Ct,K = 500, reltol = 1.e-10); - -tt = Vector((1:500)*2*pi/500) -@test maximum(norm.(Y.f.(tt).-Xt.f.(tt))) < 1.e-7 - -@time Y = pclyap(At,Cdt,K = 500, adj = true, reltol = 1.e-10) - -tt = Vector((1:500)*2*pi/500) -@test maximum(norm.(Y.f.(tt).-Xt.f.(tt))) < 1.e-7 - - -# Perturbed Pitelkau's example - singular Lyapunov equations -ω = 0.00103448 -period = 2*pi/ω -β = 0.01; -a = [ 0 0 5.318064566757217e-02 0 - 0 0 0 5.318064566757217e-02 - -1.352134256362805e-03 0 0 -7.099273035392090e-02 - 0 -7.557182037479544e-04 3.781555288420663e-02 0 -]; -b = t->[0; 0; 0.1389735*10^-6*sin(ω*t); -0.3701336*10^-7*cos(ω*t)]; -c = [1 0 0 0;0 1 0 0]; -d = zeros(2,1); -psysc = ps(a-β*I,PeriodicFunctionMatrix(b,period),c,d); - -# observability Gramian -@time Qc = prclyap(psysc.A,transpose(psysc.C)*psysc.C,K = 120, abstol = 1.e-12, reltol = 1.e-12, intpol = true); -# controlability Gramian -@time Rc = pfclyap(psysc.A,psysc.B*transpose(psysc.B), K = 120, abstol = 1.e-10, reltol = 1.e-10, intpol = true); -@test norm(psysc.A'*Qc+Qc*psysc.A+psysc.C'*psysc.C + pmderiv(Qc)) < 1.e-7 && - norm(psysc.A*Rc+Rc*psysc.A'+psysc.B*psysc.B' - pmderiv(Rc)) < 1.e-7 - -# # singular case -# psysc = ps(a,PeriodicFunctionMatrix(b,period),c,d); -# @time Qc = prclyap(psysc.A,transpose(psysc.C)*psysc.C,K = 120, reltol = 1.e-10); -# @time Rc = pfclyap(psysc.A,psysc.B*transpose(psysc.B), K = 120, reltol = 1.e-10); -# @test norm(Qc) > 1.e3 && norm(Rc) > 1.e2 - -# K = 120; -# @time psys = psc2d(psysc,period/K,reltol = 1.e-10); - -# @time Qd = prdlyap(psys.A,transpose(psys.C)*psys.C); -# @time Rd = pfdlyap(psys.A, psys.B*transpose(psys.B)); -# @test norm(Qd) > 1.e3 && norm(Rd) > 1.e3 - - - - -A0(t) = [-cos(t)-1] -C0(t) = [ 1-sin(t)] # corresponding C -At = PeriodicFunctionMatrix(A0,2*pi) -Ct = PeriodicFunctionMatrix(C0,2*pi) - -# @time Yt = pclyap(At,Ct*Ct'; adj = false, K = 500, reltol = 1.e-12, abstol = 1.e-12); -@time Yt = pclyap(At,Ct*Ct'; adj = false, K = 100, intpol=false, reltol = 1.e-10, abstol = 1.e-10); - -@time Ut = pcplyap(At,Ct; adj = false, K = 100, reltol = 1.e-10, abstol = 1.e-10); -@test norm(At*Yt+Yt*At'+Ct*Ct' - pmderiv(Yt)) < 1.e-6 -@test norm(Yt-Ut*Ut') < 1.e-6 - -@time Yt = pclyap(At,Ct'*Ct; adj = true, K = 500, reltol = 1.e-12, abstol = 1.e-12); -@time Ut = pcplyap(At,Ct; adj = true, K = 100, reltol = 1.e-10, abstol = 1.e-10); -@test norm(At'*Yt+Yt*At+Ct'*Ct + pmderiv(Yt)) < 1.e-6 -@test norm(Yt-Ut'*Ut) < 1.e-6 - - -# generate periodic function matrices -A3(t) = [0 1; -1 -2-sin(t)] -#A(t) = [0 -1; 1 2+sin(t)] -# A(t) = [0 1; -1 -2] -# A(t) = [0 -1; 1 2] -B3(t) = [ 1-0.9*sin(t); -1] # corresponding B -C3(t) = [ 10-sin(t) -1-sin(t)] # corresponding C -At = PeriodicFunctionMatrix(A3,2*pi) -Bt = PeriodicFunctionMatrix(B3,2*pi) -Ct = PeriodicFunctionMatrix(C3,2*pi) - -#@time Yt = pclyap(At,Bt*Bt'; adj = false, K = 500, reltol = 1.e-12, abstol = 1.e-12); -@time Yt = pclyap(At,Bt*Bt'; adj = false, K = 100, intpol = false, reltol = 1.e-10, abstol = 1.e-10); -@test norm(At*Yt+Yt*At'+Bt*Bt' - pmderiv(Yt)) < 1.e-6 -@time Ut = pcplyap(At,Bt; adj = false, K = 100, reltol = 1.e-10, abstol = 1.e-10); -Xt = Ut*Ut'; -@test norm(At*Xt+Xt*At'+Bt*Bt' - pmderiv(Xt)) < 1.e-6 -@test norm(Yt-Xt) < 1.e-6 -# @time Ut = pcplyap(At,Bt; adj = false, K = 100, implicit = true, reltol = 1.e-13, abstol = 1.e-13); -# Xt = Ut*Ut'; -# @test norm(At*Xt+Xt*At'+Bt*Bt' - pmderiv(Xt)) < 1.e-6 -# @test norm(Yt-Xt) < 1.e-6 -# @time Ut = pcplyap(At,Bt; adj = false, K = 100, implicit = false, solver = "non-stiff", reltol = 1.e-7, abstol = 1.e-7); -# Xt = Ut*Ut'; -# @test norm(At*Xt+Xt*At'+Bt*Bt' - pmderiv(Xt)) < 1.e-6 -# @test norm(Yt-Xt) < 1.e-6 - - -# @time Yt = pclyap(At,Ct'*Ct; adj = true, K = 500, reltol = 1.e-12, abstol = 1.e-12); -tt = Vector((1:20)*2*pi/20) -@time Yt = pclyap(At,Ct'*Ct; adj = true, K = 100, intpol = false, reltol = 1.e-10, abstol = 1.e-10); -@test norm((At'*Yt+Yt*At+Ct'*Ct + pmderiv(Yt)).(tt),Inf) < 1.e-5 -@time Ut = pcplyap(At,Ct; adj = true, K = 100, reltol = 1.e-10, abstol = 1.e-10); -Xt = Ut'*Ut; -@test norm((At'*Xt+Xt*At+Ct'*Ct + pmderiv(Xt)).(tt),Inf) < 1.e-5 -@test norm((Yt-Xt).(tt)) < 1.e-6 -# @time Ut = pcplyap(At,Ct; adj = true, K = 100, implicit = true, reltol = 1.e-13, abstol = 1.e-13); -# Xt = Ut'*Ut; -# @test norm(At'*Xt+Xt*At+Ct'*Ct + pmderiv(Xt)) < 1.e-5 -# @test norm(Yt-Xt) < 1.e-5 -# @time Ut = pcplyap(At,Ct; adj = true, K = 100, implicit = false, solver = "non-stiff", reltol = 1.e-5, abstol = 1.e-5); -# Xt = Ut'*Ut; -# @test norm(At'*Xt+Xt*At+Ct'*Ct + pmderiv(Xt)) < 1.e-6 -# @test norm(Yt-Xt) < 1.e-6 - - - - - - -# Ah = convert(HarmonicArray,At); -# Bh = convert(HarmonicArray,Bt); -# Ch = convert(HarmonicArray,Ct); -# @time Yh = pclyap(Ah,Bh*Bh'; adj = false, K = 200, reltol = 1.e-10, abstol = 1.e-10); -# @test norm(Ah*Yh+Yh*Ah'+Bh*Bh' - pmderiv(Yh)) < 1.e-6 -# @time Uh = pcplyap(Ah,Bh; adj = false, K = 200, reltol = 1.e-10, abstol = 1.e-10); -# Xh = Uh*Uh'; -# @test norm(Ah*Xh+Xh*Ah'+Bh*Bh' - pmderiv(Xh)) < 1.e-6 -# @test norm(Yh-Uh*Uh') < 1.e-6 -# @time Uh = pcplyap(Ah,Bh; adj = false, K = 200, implicit=true,reltol = 1.e-10, abstol = 1.e-10); -# Xh = Uh*Uh'; -# @test norm(Ah*Xh+Xh*Ah'+Bh*Bh' - pmderiv(Xh)) < 1.e-6 -# @test norm(Yh-Uh*Uh') < 1.e-6 - -# @time Yh = pclyap(Ah,Ch'*Ch; adj = true, K = 100, reltol = 1.e-10, abstol = 1.e-10); -# @test norm(Ah'*Yh+Yh*Ah+Ch'*Ch + pmderiv(Yh)) < 1.e-6 -# @time Uh = pcplyap(Ah,Ch; adj = true, K = 100, reltol = 1.e-10, abstol = 1.e-10); -# Xh = Uh'*Uh; -# @test norm(Ah'*Xh+Xh*Ah+Ch'*Ch + pmderiv(Xh)) < 1.e-6 -# @test norm(Yh-Xh) < 1.e-6 -# @time Uh = pcplyap(Ah,Ch; adj = true, K = 100, implicit = true, reltol = 1.e-13, abstol = 1.e-13); -# Xh = Uh'*Uh; -# @test norm(Ah'*Xh+Xh*Ah+Ch'*Ch + pmderiv(Xh)) < 1.e-6 -# @test norm(Yh-Xh) < 1.e-6 - - - - -# A(t) = [-1;;] -# #A(t) = [0 -1; 1 2+sin(t)] -# # A(t) = [0 1; -1 -2] -# # A(t) = [0 -1; 1 2] -# B(t) = [ 0.5;;] # corresponding B -# C(t) = [ 1.0;;] # corresponding C -# At = PeriodicFunctionMatrix(A,2*pi) -# Bt = PeriodicFunctionMatrix(B,2*pi) -# Ct = PeriodicFunctionMatrix(C,2*pi) - -# @time Yt = pclyap(At,Bt*Bt'; adj = false, K = 100, reltol = 1.e-10, abstol = 1.e-10); -# @test norm(At*Yt+Yt*At'+Bt*Bt' - pmderiv(Yt)) < 1.e-6 -# @time Ut = pcplyap(At,Bt; adj = false, K = 100, reltol = 1.e-10, abstol = 1.e-10); -# Xt = Ut*Ut'; -# @test norm(At*Xt+Xt*At'+Bt*Bt' - pmderiv(Xt)) < 1.e-6 -# @test norm(Yt-Xt) < 1.e-6 -# @time Ut = pcplyap(At,Bt; adj = false, K = 100, implicit = true, reltol = 1.e-13, abstol = 1.e-13); -# Xt = Ut*Ut'; -# @test norm(At*Xt+Xt*At'+Bt*Bt' - pmderiv(Xt)) < 1.e-6 -# @test norm(Yt-Xt) < 1.e-6 -# @time Ut = pcplyap(At,Bt; adj = false, K = 100, implicit = false, reltol = 1.e-10, abstol = 1.e-10); -# Xt = Ut*Ut'; -# @test norm(At*Xt+Xt*At'+Bt*Bt' - pmderiv(Xt)) < 1.e-6 -# @test norm(Yt-Xt) < 1.e-6 - - -end # psclyap - -end diff --git a/test/test_psconversions.jl b/test/test_psconversions.jl index 655386e..0374c2b 100644 --- a/test/test_psconversions.jl +++ b/test/test_psconversions.jl @@ -1,6 +1,7 @@ module Test_conversions using PeriodicSystems +using ApproxFun using DescriptorSystems using Symbolics using Test diff --git a/test/test_pscric.jl b/test/test_pscric.jl deleted file mode 100644 index 9fb51da..0000000 --- a/test/test_pscric.jl +++ /dev/null @@ -1,235 +0,0 @@ -module Test_pscric - -using PeriodicSystems -using DescriptorSystems -using MatrixEquations -using Symbolics -using Test -using LinearAlgebra -using ApproxFun -using Symbolics -#using JLD - -println("Test_pcric") - -@testset "pcric" begin - -# example Johanson et al. 2007 -A = [1 0.5; 3 5]; B = [3;1;;]; Q = [1. 0;0 1]; R = [1.;;] -period = π; -ω = 2. ; - -Xref, EVALSref, Fref = arec(A,B,R,Q); - -# Ac = PeriodicTimeSeriesMatrix(A,period) -# Bc = PeriodicTimeSeriesMatrix(B,period) -# @time Xc, EVALSc, Fc = prcric(Ac, Bc, R, Q) -# @test iszero(Xc-Xref) && iszero(Fc-Fref) && iszero(EVALSc-EVALSref) - - -# @variables t -# P = PeriodicSymbolicMatrix([cos(ω*t) sin(ω*t); -sin(ω*t) cos(ω*t)],period); PM = PeriodicSymbolicMatrix - -P1 = PeriodicFunctionMatrix(t->[cos(ω*t) sin(ω*t); -sin(ω*t) cos(ω*t)],period); -P1dot = PeriodicFunctionMatrix(t->[-ω*sin(t*ω) ω*cos(t*ω); -ω*cos(t*ω) -ω*sin(t*ω)],period); -# PM = PeriodicFunctionMatrix -# PM = PeriodicTimeSeriesMatrix - -#P = convert(HarmonicArray,P); -PM = HarmonicArray - -#P = convert(FourierFunctionMatrix,P); PM = FourierFunctionMatrix -# PM = PeriodicSymbolicMatrix - -for PM in (PeriodicFunctionMatrix, HarmonicArray, PeriodicSymbolicMatrix, FourierFunctionMatrix, PeriodicTimeSeriesMatrix) - println("type = $PM") - N = PM == PeriodicTimeSeriesMatrix ? 128 : 200 - P = convert(PM,P1); - Pdot = convert(PM,P1dot); - - #Ap = pmderiv(P)*inv(P)+P*A*inv(P); - Ap = Pdot*inv(P)+P*A*inv(P); - Bp = P*B - Qp = inv(P)'*Q*inv(P); Qp = (Qp+Qp')/2 - Rp = PM(R, Ap.period) - Xp = inv(P)'*Xref*inv(P) - Fp = Bp'*Xp - # if PM == PeriodicTimeSeriesMatrix - # Xp = inv(P1)'*Xref*inv(P1) - # Fp = (P1*B)'*Xp - # end - if PM == PeriodicTimeSeriesMatrix - @test norm(Ap'*Xp+Xp*Ap+Qp-Xp*Bp*Bp'*Xp + pmderiv(Xp)) < 1.e-5*norm(Xp) - else - @test Ap'*Xp+Xp*Ap+Qp-Xp*Bp*Bp'*Xp ≈ -pmderiv(Xp) - @test norm(sort(real(psceig(Ap-Bp*Fp,100))) - sort(EVALSref)) < 1.e-6 - end - - solver = "symplectic" - #N = length(Xp.values) - ti = collect((0:N-1)*Xp.period/N)*(1+eps(10.)) - for solver in ("non-stiff", "stiff", "symplectic", "linear", "noidea") - println("solver = $solver") - @time X, EVALS, F = prcric(Ap, Bp, Rp, Qp; K = N, solver, reltol = 1.e-10, abstol = 1.e-10, fast = true) #error - Errx = norm(X.(ti)-Xp.(ti))/norm(Xp); Errf = norm(F.(ti)-Fp.(ti))/norm(Fp) - println("Errx = $Errx Errf = $Errf") - @test Errx < 1.e-7 && Errf < 1.e-6 && norm(sort(real(EVALS)) - sort(EVALSref)) < 1.e-2 - @time X, EVALS, F = prcric(Ap, Bp, Rp, Qp; K = N, solver, reltol = 1.e-10, abstol = 1.e-10, fast = false) - Errx = norm(X.(ti)-Xp.(ti))/norm(Xp); Errf = norm(F.(ti)-Fp.(ti))/norm(Fp) - println("Errx = $Errx Errf = $Errf") - @test Errx < 1.e-7 && Errf < 1.e-6 && norm(sort(real(EVALS)) - sort(EVALSref)) < 1.e-2 - end -end - -A = [1 0.5; 3 5]; C = [3 1]; Q = [1. 0;0 1]; R = [1.;;] -period = π; -ω = 2. ; - -Xref, EVALSref, Fref = arec(A', C', R, Q); Fref = copy(Fref') -@test norm(A*Xref+Xref*A'-Xref*C'*inv(R)*C*Xref +Q) < 1.e-7 - -# Ac = PeriodicTimeSeriesMatrix(A,period) -# Cc = PeriodicTimeSeriesMatrix(C,period) -# @time Xc, EVALSc, Fc = pfcric(Ac, Cc, R, Q) -# @test iszero(Xc-Xref) && iszero(Fc-Fref) && iszero(EVALSc-EVALSref) - - -P1 = PeriodicFunctionMatrix(t->[cos(ω*t) sin(ω*t); -sin(ω*t) cos(ω*t)],period); -P1dot = PeriodicFunctionMatrix(t->[-ω*sin(t*ω) ω*cos(t*ω); -ω*cos(t*ω) -ω*sin(t*ω)],period); - -# PM = HarmonicArray -# PM = PeriodicTimeSeriesMatrix -# PM = PeriodicSymbolicMatrix -PM = FourierFunctionMatrix - -for PM in (PeriodicFunctionMatrix, HarmonicArray, PeriodicSymbolicMatrix, FourierFunctionMatrix, PeriodicTimeSeriesMatrix) - println("type = $PM") - N = PM == PeriodicTimeSeriesMatrix ? 128 : 200 - P = convert(PM,P1); - Pdot = convert(PM,P1dot); - - Ap = Pdot*inv(P)+P*A*inv(P); - Cp = C*inv(P) - Qp = P*Q*P'; Qp = (Qp+Qp')/2 - Rp = PM(R, Ap.period) - Xp = P*Xref*P' - Gp = Cp'*inv(Rp)*Cp - Fp = Xp*Cp' - if PM == PeriodicTimeSeriesMatrix - @test norm(Ap*Xp+Xp*Ap'+Qp-Xp*Gp*Xp - pmderiv(Xp)) < 1.e-5*norm(Xp) - else - @test Ap*Xp+Xp*Ap'+Qp-Xp*Gp*Xp ≈ pmderiv(Xp) - @test norm(sort(real(psceig(Ap-Fp*Cp,100))) - sort(EVALSref)) < 1.e-6 - end - - - solver = "symplectic" - #solver = "stiff" - ti = collect((0:N-1)*Xp.period/N)*(1+eps(10.)) - for solver in ("non-stiff", "stiff", "symplectic", "linear", "noidea") - println("solver = $solver") - @time X, EVALS, F = pfcric(Ap, Cp, Rp, Qp; K = N, solver, reltol = 1.e-10, abstol = 1.e-10, fast = true) - Errx = norm(X.(ti)-Xp.(ti))/norm(Xp); Errf = norm(F.(ti)-Fp.(ti))/norm(Fp) - println("Errx = $Errx Errf = $Errf") - @test Errx < 1.e-7 && Errf < 1.e-6 && norm(sort(real(EVALS)) - sort(EVALSref)) < 1.e-2 - @time X, EVALS, F = pfcric(Ap, Cp, Rp, Qp; K = N, solver, reltol = 1.e-10, abstol = 1.e-10, fast = false) - #@test norm(Ap*X+X*Ap'+Qp-X*Gp*X -pmderiv(X)) < 1.e-6 ##&& norm(sort(real(psceig(Ap-F*Cp))) - sort(EVALSref)) < 1.e-2 - Errx = norm(X.(ti)-Xp.(ti))/norm(Xp); Errf = norm(F.(ti)-Fp.(ti))/norm(Fp) - println("Errx = $Errx Errf = $Errf") - @test Errx < 1.e-7 && Errf < 1.e-6 && norm(sort(real(EVALS)) - sort(EVALSref)) < 1.e-2 - end -end - -## this seems to take infinite time -# random examples - -n = 20; m = 5; nh = 2; period = π; -#n = 5; m = 5; nh = 2; period = π; -ts = [0.4459591888577492, 1.2072325802972004, 1.9910835248218244, 2.1998199838900527, 2.4360161318589695, - 2.9004720268745463, 2.934294124172935, 4.149208861412936, 4.260935465730602, 5.956614157549958]*0.5 - - -A = pmrand(n, n, period; nh) -B = pmrand(n, m, period; nh) -Q = collect(Float64.(I(n))); R = collect(Float64.(I(m))); Qt = HarmonicArray(Q,pi) -ev = psceig(A,100) -@time X, EVALS, F = prcric(A, B, R, Q; K = 100, solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10, fast = true); - -@test all(real(psceig(A-B*F,100)) .< 0) - -Xdot = pmderiv(X); -@test norm(A'.(ts).*X.(ts).+X.(ts).*A.(ts).+Qt.(ts).-X.(ts).*B.(ts).*B'.(ts).*X.(ts) .+ Xdot.(ts),Inf)/norm(X.(ts),Inf) < 1.e-7 - -@time X, EVALS, F = prcric(A, B, R, Q; K = 100, solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10, fast = false); - -@test all(real(psceig(A-B*F,100)) .< 0) - -Xdot = pmderiv(X); -@test norm(A'.(ts).*X.(ts).+X.(ts).*A.(ts).+Qt.(ts).-X.(ts).*B.(ts).*B'.(ts).*X.(ts) .+ Xdot.(ts),Inf)/norm(X.(ts),Inf) < 1.e-7 - - -# Pitelkau's example - singular Lyapunov equations -ω = 0.00103448 -period = 2*pi/ω -a = [ 0 0 5.318064566757217e-02 0 - 0 0 0 5.318064566757217e-02 - -1.352134256362805e-03 0 0 -7.099273035392090e-02 - 0 -7.557182037479544e-04 3.781555288420663e-02 0 -]; -b = t->[0; 0; 0.1389735*10^-6*sin(ω*t); -0.3701336*10^-7*cos(ω*t)]; -c = [1 0 0 0;0 1 0 0]; -d = zeros(2,1); -q = [2 0 0 0; 0 1 0 0; 0 0 0 0;0 0 0 0]; r = [1.e-11;;]; - -ev = psceig(HarmonicArray(a,2pi)) -PM = HarmonicArray -PM = PeriodicFunctionMatrix -ts = [0.4459591888577492, 1.2072325802972004, 1.9910835248218244, 2.1998199838900527, 2.4360161318589695, - 2.9004720268745463, 2.934294124172935, 4.149208861412936, 4.260935465730602, 5.956614157549958]/ω - - -for PM in (PeriodicFunctionMatrix, HarmonicArray) - println("PM = $PM") - psysc = ps(a,convert(PM,PeriodicFunctionMatrix(b,period)),c,d); - Qt = PM(q,period) - Rit = PM(inv(r),period) - - @time X, EVALS, F = prcric(psysc.A, psysc.B, r, q; K = 100, solver = "symplectic", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true,intpol = true); - #@time X, EVALS, F = prcric(psysc.A, psysc.B, r, q; K = 100, solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true); - clev = psceig(psysc.A-psysc.B*F,500) - @test norm(sort(real(clev)) - sort(real(EVALS))) < 1.e-7 && norm(sort(imag(clev)) - sort(imag(EVALS))) < 1.e-7 - @test norm(psysc.A'.(ts).*X.(ts).+X.(ts).*psysc.A.(ts).+Qt.(ts).-X.(ts).*psysc.B.(ts).*Rit.(ts).*psysc.B'.(ts).*X.(ts).+pmderiv(X).(ts))/norm(X.(ts)) < 1.e-5 - - # this test covers the experimental code provided in PeriodicSchurDecompositions package and occasionally fails - @time X, EVALS, F = prcric(psysc.A, psysc.B, r, q; K = 100, solver = "symplectic", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = false,intpol = true ); - #@time X, EVALS, F = prcric(psysc.A, psysc.B, r, q; K = 100, solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = false ); - clev = psceig(psysc.A-psysc.B*F,500) - println("EVALS = $EVALS, clev = $clev") - @test norm(sort(real(clev)) - sort(real(EVALS))) < 1.e-7 && norm(sort(imag(clev)) - sort(imag(EVALS))) < 1.e-7 - @test norm(psysc.A'.(ts).*X.(ts).+X.(ts).*psysc.A.(ts).+Qt.(ts).-X.(ts).*psysc.B.(ts).*Rit.(ts).*psysc.B'.(ts).*X.(ts).+pmderiv(X).(ts))/norm(X.(ts)) < 1.e-5 - - - @time X, EVALS, F = prcric(psysc.A, psysc.B, r, q; K = 100, solver = "symplectic", reltol = 1.e-10, abstol = 1.e-10, fast = true,intpol = true); - clev = psceig(psysc.A-psysc.B*F,500) - @test norm(sort(real(clev)) - sort(real(EVALS))) < 1.e-1 && norm(sort(imag(clev)) - sort(imag(EVALS))) < 1.e-1 - @test norm(psysc.A'.(ts).*X.(ts).+X.(ts).*psysc.A.(ts).+Qt.(ts).-X.(ts).*psysc.B.(ts).*Rit.(ts).*psysc.B'.(ts).*X.(ts).+pmderiv(X).(ts))/norm(X.(ts)) < 1.e-5 -end - -# psysc = ps(a,convert(PM,PeriodicFunctionMatrix(b,period)),c,d); - -# X1, EVALS, F = prcric(psysc.A, psysc.B, r, q; K = 100, solver = "symplectic", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true); -# Xts, ev = pgcric(psysc.A, psysc.B*inv(r)*psysc.B', HarmonicArray(q,psysc.period), 100; adj = true); -# @test X1(0) ≈ Xts(0) && EVALS ≈ ev - -# T = psysc.period/100 -# @test X1(T) ≈ Xts(T) -# t = T*100*rand(); -# @test convert(HarmonicArray,Xts)(t) ≈ X1(t) -# Xt = PeriodicFunctionMatrix(t->PeriodicSystems.tvcric_eval(t,Xts,psysc.A, psysc.B*inv(r)*psysc.B', HarmonicArray(q,2pi); adj = true, solver = "symplectic", reltol = 1e-10, abstol = 1e-10),Xts.period) -# @test Xt(t) ≈ X1(t) - - -end # test - -end # module - diff --git a/test/test_psdric.jl b/test/test_psdric.jl deleted file mode 100644 index b4171fb..0000000 --- a/test/test_psdric.jl +++ /dev/null @@ -1,299 +0,0 @@ -module Test_psdric - -using PeriodicSystems -using DescriptorSystems -using MatrixEquations -using Symbolics -using Test -using LinearAlgebra -#using JLD - -println("Test_psdric") - -@testset "prdric" begin - -p = 10; n = 10; m = 5; period = 10; -A = PeriodicMatrix([rand(n,n) for i in 1:p],period); -B = PeriodicMatrix([rand(n,m) for i in 1:p],period); -R = PeriodicMatrix(eye(Float64,m),period; nperiod = period); -C = PeriodicMatrix([rand(m,n) for i in 1:p],period); -Q = C'*C; Q = (Q+Q')/2 - -@time X, EVALS, G = prdric(A,B,R,Q); - -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,R,Q,fast = false, nodeflate = false); - -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,R,Q,fast = false, nodeflate = true); - -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - - -p = 5; na = [10, 8, 6, 4, 2]; ma = circshift(na,-1); -m = 5; period = 10; -A = PeriodicMatrix([rand(ma[i],na[i]) for i in 1:p],period); -B = PeriodicMatrix([rand(ma[i],m) for i in 1:p],period); -R = PeriodicMatrix(eye(Float64,m),period; nperiod = rationalize(A.period/A.Ts).num); -C = PeriodicMatrix([rand(m,na[i]) for i in 1:p],period); -Q = C'*C; Q = (Q+Q')/2 - -@time X, EVALS, G = prdric(A,B,R,Q); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -nc = length(EVALS) -@test norm(res) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev[1:nc]))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev[1:nc]))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,R,Q,fast = false,nodeflate = false); - -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -nc = minimum(na) - -@test norm(res) < 1.e-6 && norm(sort(real(EVALS[1:nc]))-sort(real(ev[1:nc]))) < 1.e-6 && - norm(sort(imag(EVALS[1:nc]))-sort(imag(ev[1:nc]))) < 1.e-6 - - -@time X, EVALS, G = prdric(A,B,R,Q,fast = false,nodeflate = true); - -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -nc = minimum(na) - -@test norm(res) < 1.e-6 && norm(sort(real(EVALS[1:nc]))-sort(real(ev[1:nc]))) < 1.e-6 && - norm(sort(imag(EVALS[1:nc]))-sort(imag(ev[1:nc]))) < 1.e-6 - - - -p = 5; na = [2, 6, 4, 5, 8]; ma = circshift(na,-1); -m = 2; period = 10; -A = PeriodicMatrix([rand(ma[i],na[i]) for i in 1:p],period); -B = PeriodicMatrix([rand(ma[i],m) for i in 1:p],period); -R = PeriodicMatrix(eye(Float64,m),period; nperiod = rationalize(A.period/A.Ts).num); -C = PeriodicMatrix([rand(m,na[i]) for i in 1:p],period); -Q = C'*C; Q = (Q+Q')/2 - -@time X, EVALS, G = prdric(A,B,R,Q; fast = true); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -nc = minimum(na) -@test norm(res) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev[1:nc]))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev[1:nc]))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,R,Q,fast = false); - -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -nc = minimum(na) - -@test norm(res) < 1.e-6 && norm(sort(real(EVALS[1:nc]))-sort(real(ev[1:nc]))) < 1.e-6 && - norm(sort(imag(EVALS[1:nc]))-sort(imag(ev[1:nc]))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,R,Q,fast = false,nodeflate = true); - -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -nc = minimum(na) - -@test norm(res) < 1.e-6 && norm(sort(real(EVALS[1:nc]))-sort(real(ev[1:nc]))) < 1.e-6 && - norm(sort(imag(EVALS[1:nc]))-sort(imag(ev[1:nc]))) < 1.e-6 - - -# Hench & Laub's IEEE TAC 1994: Example 2 -A1 = [-3 2 9; 0 0 -4; 3 -2 3]; B1 = [1;1;0;;]; C1 = [1 0 0]; R1 = [1;;]; -A2 = [6 -3 0; 4 -2 2; 2 -1 4]; B2 = [0;1;0;;]; C2 = [0 1 0]; R2 = [1;;]; -A3 = [2 -3 -3; 4 -15 -3; -2 9 1]; B3 = [0;1;1;;]; C3 = [0 0 1]; R3 = [1;;]; -A = PeriodicMatrix([A1, A2, A3],10); -B = PeriodicMatrix([B1, B2, B3],10); -R = PeriodicMatrix([R1, R2, R3],10); -Q = PeriodicMatrix([C1'*C1, C2'*C2, C3'*C3],10); - - -@time X, EVALS, G = prdric(A,B,R,Q; fast = true); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - - -# Pitelkau's example -ω = 0.00103448 -period = 2*pi/ω -a = [ 0 0 5.318064566757217e-02 0 - 0 0 0 5.318064566757217e-02 - -1.352134256362805e-03 0 0 -7.099273035392090e-02 - 0 -7.557182037479544e-04 3.781555288420663e-02 0 -]; -b = t->[0; 0; 0.1389735*10^-6*sin(ω*t); -0.3701336*10^-7*cos(ω*t)]; -c = [1 0 0 0;0 1 0 0]; -d = zeros(2,1); -psysc = ps(a,PeriodicFunctionMatrix(b,period),c,d); - -K = 120; -@time psysa = psc2d(psysc,period/K,reltol = 1.e-10); -psys = convert(PeriodicStateSpace{PeriodicMatrix},psysa); - -A = psys.A; B = psys.B; -q = [2 0 0 0; 0 1 0 0; 0 0 0 0;0 0 0 0]; r = [1.e-11;;]; -Q = PeriodicMatrix(q, period; nperiod=K); R = PeriodicMatrix(r,period; nperiod=K); -Qa = PeriodicArray(q, period; nperiod=K); Ra = PeriodicArray(r,period; nperiod=K); - -@time X, EVALS, G = prdric(A,B,R,Q,itmax = 2); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res) < 1.e-6 && issymmetric(X) && - norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,r,q; itmax = 2); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(r+B'*Xs*B)*B'*Xs)*A-q; -@test norm(res)/norm(X) < 1.e-6 && issymmetric(X) && - norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - -A1 = A'; C1 = B'; -@time X1, EVALS1, G1 = pfdric(A1,C1,R,Q; itmax = 2); -ev1 = pseig(A1-G1*C1) -Xs1 = pmshift(X1) -res1 = Xs1-A1*(X1-X1*C1'*inv(R+C1*X1*C1')*C1*X1)*A1'-Q; -@test norm(res1)/norm(X1) < 1.e-6 && issymmetric(X1) && - norm(sort(real(EVALS1))-sort(real(ev1))) < 1.e-6 && - norm(sort(imag(EVALS1))-sort(imag(ev1))) < 1.e-6 - -@time X1, EVALS1, G1 = pfdric(A1,C1,r,q; itmax = 2); -Xs1 = pmshift(X1) -res1 = Xs1-A1*(X1-X1*C1'*inv(r+C1*X1*C1')*C1*X1)*A1'-q; -@test norm(res1)/norm(X1) < 1.e-6 - - -p = 10; n = 10; m = 5; period = 10; -A = PeriodicArray(rand(n,n,p),period) -B = PeriodicArray(rand(n,m,p),period) -C = PeriodicArray(rand(m,n,p),period) -R = PeriodicArray(eye(Float64,m),period; nperiod = period) -Q = C'*C; Q = (Q+Q')/2 -@time X, EVALS, G = prdric(A,B,R,Q); - -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - - -# Hench & Laub's IEEE TAC 1994: Example 2 -A1 = [-3 2 9; 0 0 -4; 3 -2 3]; B1 = [1;1;0;;]; C1 = [1 0 0]; R1 = [1;;]; -A2 = [6 -3 0; 4 -2 2; 2 -1 4]; B2 = [0;1;0;;]; C2 = [0 1 0]; R2 = [1;;]; -A3 = [2 -3 -3; 4 -15 -3; -2 9 1]; B3 = [0;1;1;;]; C3 = [0 0 1]; R3 = [1;;]; -A = PeriodicArray(reshape([A1 A2 A3],3,3,3),10); -B = PeriodicArray(reshape([B1 B2 B3],3,1,3),10); -R = PeriodicArray(reshape([R1 R2 R3],1,1,3),10); -Q = PeriodicArray(reshape([C1'*C1 C2'*C2 C3'*C3],3,3,3),10); - - -@time X, EVALS, G = prdric(A,B,R,Q; fast = true); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - - -@time X, EVALS, G = prdric(A,B,R,Q; fast = false); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res)/norm(X) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,R,Q; fast = false, nodeflate = true); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res)/norm(X) < 1.e-6 && norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - - -# Pitelkau's example -ω = 0.00103448 -period = 2*pi/ω -a = [ 0 0 5.318064566757217e-02 0 - 0 0 0 5.318064566757217e-02 - -1.352134256362805e-03 0 0 -7.099273035392090e-02 - 0 -7.557182037479544e-04 3.781555288420663e-02 0 -]; -b = t->[0; 0; 0.1389735*10^-6*sin(ω*t); -0.3701336*10^-7*cos(ω*t)]; -c = [1 0 0 0;0 1 0 0]; -d = zeros(2,1); -psysc = ps(a,PeriodicFunctionMatrix(b,period),c,d); - -K = 120; -psys = convert(PeriodicStateSpace{PeriodicArray},psc2d(psysc,period/K,reltol = 1.e-10)); -A = convert(PeriodicArray,psys.A); B = convert(PeriodicArray,psys.B); -q = [2 0 0 0; 0 1 0 0; 0 0 0 0;0 0 0 0]; r = [1.e-11;;]; -Q = PeriodicArray(q,period; nperiod=K); R = PeriodicArray(r,period; nperiod=K); - -@time X, EVALS, G = prdric(A,B,R,Q,itmax = 2, fast = true); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(R+B'*Xs*B)*B'*Xs)*A-Q; -@test norm(res) < 1.e-6 && issymmetric(X) && - norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,r,q; fast = false, itmax = 2); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(r+B'*Xs*B)*B'*Xs)*A-q; -@test norm(res)/norm(X) < 1.e-6 && issymmetric(X) && - norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - -@time X, EVALS, G = prdric(A,B,r,q; fast = false, nodeflate = true, itmax = 2); -ev = pseig(A-B*G) -Xs = pmshift(X) -res = X-A'*(Xs -Xs*B*inv(r+B'*Xs*B)*B'*Xs)*A-q; -@test norm(res)/norm(X) < 1.e-6 && issymmetric(X) && - norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && - norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 - -A1 = A'; C1 = B'; -@time X1, EVALS1, G1 = pfdric(A1,C1,R,Q; fast = false, nodeflate = true, itmax = 2); -ev1 = pseig(A1-G1*C1) -Xs1 = pmshift(X1) -res1 = Xs1-A1*(X1-X1*C1'*inv(R+C1*X1*C1')*C1*X1)*A1'-Q; -@test norm(res1)/norm(X1) < 1.e-6 && issymmetric(X1) && - norm(sort(real(EVALS1))-sort(real(ev1))) < 1.e-6 && - norm(sort(imag(EVALS1))-sort(imag(ev1))) < 1.e-6 - -@time X1, EVALS1, G1 = pfdric(A1,C1,r,q; fast = false, nodeflate = true, itmax = 2); -Xs1 = pmshift(X1) -res1 = Xs1-A1*(X1-X1*C1'*inv(r+C1*X1*C1')*C1*X1)*A1'-q; -@test norm(res1)/norm(X1) < 1.e-6 -end #prdare -end diff --git a/test/test_pslifting.jl b/test/test_pslifting.jl index 9c1d562..eecef5c 100644 --- a/test/test_pslifting.jl +++ b/test/test_pslifting.jl @@ -1,11 +1,11 @@ module Test_pslifting using PeriodicSystems +using ApproxFun using DescriptorSystems using Symbolics using Test using LinearAlgebra -using ApproxFun using SparseArrays diff --git a/test/test_pslyap.jl b/test/test_pslyap.jl deleted file mode 100644 index 9405e71..0000000 --- a/test/test_pslyap.jl +++ /dev/null @@ -1,1082 +0,0 @@ -module Test_pslyap - -using PeriodicSystems -using DescriptorSystems -using Symbolics -using Test -using LinearAlgebra -using ApproxFun -using FastLapackInterface - -println("Test_pslyap") - -@testset "dpsylv2, dpsylv2!, dpsylv2krsol!" begin - - -W = Matrix{Float64}(undef,2,14) -WZ = Matrix{Float64}(undef,8,8) -WY = Vector{Float64}(undef,8) -WX = Matrix{Float64}(undef,4,5) - -p = 2; -al = [-0.0028238980383030643;;; 0.3319882632937995] -ar = [-0.0028238980383030643;;; 0.3319882632937995] -q = rand(2,2,p); -REV = true -KSCHUR = 1 -n1 = 1; n2 = 1; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez1 = al[i1,i1,1]'*X[i1,i2,2]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X[i1,i2,1]*ar[i2,i2,2]-X[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez1 = al[i1,i1,1]'*X1[i1,i2,2]*ar[i2,i2,1]-X1[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X1[i1,i2,1]*ar[i2,i2,2]-X1[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - - - -p = 1; -al = 100*rand(2,2,p); ar = rand(2,2,p); q = rand(2,2,p); -REV = true - -KSCHUR = 1 -n1 = 1; n2 = 1; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez = al[i1,i1,1]'*X[i1,i2,1]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = al[i1,i1,1]*X[i1,i2,1]*ar[i2,i2,1]'-X[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 - - -n1 = 1; n2 = 2; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez = al[i1,i1,1]'*X[i1,i2,1]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = al[i1,i1,1]*X[i1,i2,1]*ar[i2,i2,1]'-X[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 - - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = al[i1,i1,1]'*X1[i1,i2,1]*ar[i2,i2,1]-X1[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 - - - -n1 = 2; n2 = 1; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez = al[i1,i1,1]'*X[i1,i2,1]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = al[i1,i1,1]*X[i1,i2,1]*ar[i2,i2,1]'-X[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = al[i1,i1,1]'*X1[i1,i2,1]*ar[i2,i2,1]-X1[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 - - -n1 = 2; n2 = 2; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez = al[i1,i1,1]'*X[i1,i2,1]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = al[i1,i1,1]*X[i1,i2,1]*ar[i2,i2,1]'-X[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = al[i1,i1,1]'*X1[i1,i2,1]*ar[i2,i2,1]-X1[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,1]+q[i1,i2,1] -@test norm(rez) < 1.e-7 - - -p = 2; -al = rand(2,2,p); al[:,:,1] = triu(al[:,:,1]); ar = rand(2,2,p); ar[:,:,1] = triu(ar[:,:,2]); -q = rand(2,2,p); -REV = true -KSCHUR = 2 -n1 = 1; n2 = 1; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez1 = al[i1,i1,1]'*X[i1,i2,2]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X[i1,i2,1]*ar[i2,i2,2]-X[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez1 = al[i1,i1,1]*X[i1,i2,1]*ar[i2,i2,1]'-X[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X[i1,i2,2]*ar[i2,i2,2]'-X[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez1 = al[i1,i1,1]'*X1[i1,i2,2]*ar[i2,i2,1]-X1[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X1[i1,i2,1]*ar[i2,i2,2]-X1[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez1 = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X1[i1,i2,2]*ar[i2,i2,2]'-X1[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - - -WUSD3 = Array{Float64,3}(undef,4,4,p) -WUD3 = Array{Float64,3}(undef,4,4,p) -WUL3 = Matrix{Float64}(undef,4*p,4) -WY1 = Vector{Float64}(undef,4*p) -W1 = Matrix{Float64}(undef,8,8) -qr_ws = QRWs(zeros(8), zeros(4)) -ormqr_ws = QROrmWs(zeros(4), qr_ws.τ) - - - -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez1 = al[i1,i1,1]'*X3[i1,i2,2]*ar[i2,i2,1]-X3[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X3[i1,i2,1]*ar[i2,i2,2]-X3[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez1 = al[i1,i1,1]*X3[i1,i2,1]*ar[i2,i2,1]'-X3[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X3[i1,i2,2]*ar[i2,i2,2]'-X3[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - - -n1 = 1; n2 = 2; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez1 = al[i1,i1,1]'*X[i1,i2,2]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X[i1,i2,1]*ar[i2,i2,2]-X[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez1 = al[i1,i1,1]*X[i1,i2,1]*ar[i2,i2,1]'-X[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X[i1,i2,2]*ar[i2,i2,2]'-X[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez1 = al[i1,i1,1]'*X1[i1,i2,2]*ar[i2,i2,1]-X1[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X1[i1,i2,1]*ar[i2,i2,2]-X1[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) # fails -rez1 = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X1[i1,i2,2]*ar[i2,i2,2]'-X1[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - - -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez1 = al[i1,i1,1]'*X3[i1,i2,2]*ar[i2,i2,1]-X3[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X3[i1,i2,1]*ar[i2,i2,2]-X3[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez1 = al[i1,i1,1]*X3[i1,i2,1]*ar[i2,i2,1]'-X3[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X3[i1,i2,2]*ar[i2,i2,2]'-X3[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - - -n1 = 2; n2 = 1; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez1 = al[i1,i1,1]'*X[i1,i2,2]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X[i1,i2,1]*ar[i2,i2,2]-X[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez1 = al[i1,i1,1]*X[i1,i2,1]*ar[i2,i2,1]'-X[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X[i1,i2,2]*ar[i2,i2,2]'-X[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez1 = al[i1,i1,1]'*X1[i1,i2,2]*ar[i2,i2,1]-X1[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X1[i1,i2,1]*ar[i2,i2,2]-X1[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez1 = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X1[i1,i2,2]*ar[i2,i2,2]'-X1[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - - -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez1 = al[i1,i1,1]'*X3[i1,i2,2]*ar[i2,i2,1]-X3[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X3[i1,i2,1]*ar[i2,i2,2]-X3[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez1 = al[i1,i1,1]*X3[i1,i2,1]*ar[i2,i2,1]'-X3[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X3[i1,i2,2]*ar[i2,i2,2]'-X3[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - - -n1 = 2; n2 = 2; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -rez1 = al[i1,i1,1]'*X[i1,i2,2]*ar[i2,i2,1]-X[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X[i1,i2,1]*ar[i2,i2,2]-X[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez1 = al[i1,i1,1]*X[i1,i2,1]*ar[i2,i2,1]'-X[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X[i1,i2,2]*ar[i2,i2,2]'-X[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez1 = al[i1,i1,1]'*X1[i1,i2,2]*ar[i2,i2,1]-X1[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X1[i1,i2,1]*ar[i2,i2,2]-X1[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez1 = al[i1,i1,1]*X1[i1,i2,1]*ar[i2,i2,1]'-X1[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X1[i1,i2,2]*ar[i2,i2,2]'-X1[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez1 = al[i1,i1,1]'*X3[i1,i2,2]*ar[i2,i2,1]-X3[i1,i2,1]+q[i1,i2,1] -rez2 = al[i1,i1,2]'*X3[i1,i2,1]*ar[i2,i2,2]-X3[i1,i2,2]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez1 = al[i1,i1,1]*X3[i1,i2,1]*ar[i2,i2,1]'-X3[i1,i2,2]+q[i1,i2,1] -rez2 = al[i1,i1,2]*X3[i1,i2,2]*ar[i2,i2,2]'-X3[i1,i2,1]+q[i1,i2,2] -@test norm(rez1) < 1.e-7 && norm(rez2) < 1.e-7 - - -p = 100; -WZ = Matrix{Float64}(undef,p*4,p*4) -WY = Vector{Float64}(undef,p*4) -W = Matrix{Float64}(undef,2,14) -WX = Matrix{Float64}(undef,4,5) - - -WUSD3 = Array{Float64,3}(undef,4,4,p) -WUD3 = Array{Float64,3}(undef,4,4,p) -WUL3 = Matrix{Float64}(undef,4*p,4) -WY1 = Vector{Float64}(undef,4*p) -W1 = Matrix{Float64}(undef,8,8) - - - -al = rand(2,2,p); ar = rand(2,2,p); q = rand(2,2,p); -[triu!(view(al,:,:,i)) for i in 2:p] -[triu!(view(ar,:,:,i)) for i in 2:p] -REV = true - -KSCHUR = 1 - -n1 = 1; n2 = 1; -@time X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX); -i1 = 1:n1; i2 = 1:n2 -ip = 1:p; ip1 = mod.(ip,p).+1; -rez = [ al[i1,i1,ip[i]]'*X[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = [ al[i1,i1,ip[i]]*X[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); @time dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]'*X1[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X1[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]*X1[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X1[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -X3 = copy(q[i1,i2,1:p]); @time dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]'*X3[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X3[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X3 = copy(q[i1,i2,1:p]); @time dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]*X3[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X3[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -n1 = 1; n2 = 2; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -ip = 1:p; ip1 = mod.(ip,p).+1; -rez = [ al[i1,i1,ip[i]]'*X[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = [ al[i1,i1,ip[i]]*X[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); @time dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]'*X1[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X1[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]*X1[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X1[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -X3 = copy(q[i1,i2,1:p]); @time dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]'*X3[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X3[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]*X3[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X3[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - - -n1 = 2; n2 = 1; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -ip = 1:p; ip1 = mod.(ip,p).+1; -rez = [ al[i1,i1,ip[i]]'*X[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = [ al[i1,i1,ip[i]]*X[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]'*X1[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X1[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]*X1[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X1[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]'*X3[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X3[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X3 = copy(q[i1,i2,1:p]); dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]*X3[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X3[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - - -n1 = 2; n2 = 2; -@time X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX); -i1 = 1:n1; i2 = 1:n2 -ip = 1:p; ip1 = mod.(ip,p).+1; -rez = [ al[i1,i1,ip[i]]'*X[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = [ al[i1,i1,ip[i]]*X[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:p]); @time dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]'*X1[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X1[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:p]); @time dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]*X1[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X1[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X3 = copy(q[i1,i2,1:p]); @time dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]'*X3[i1,i2,ip1[i]]*ar[i2,i2,ip[i]]-X3[i1,i2,ip[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X3 = copy(q[i1,i2,1:p]); @time dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]*X3[i1,i2,ip[i]]*ar[i2,i2,ip[i]]'-X3[i1,i2,ip1[i]]+q[i1,i2,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - - -#pq = 2; p = 1 -pq = 10; p = 2 -WZ = Matrix{Float64}(undef,pq*4,pq*4) -WY = Vector{Float64}(undef,pq*4) -W = Matrix{Float64}(undef,2,14) -WX = Matrix{Float64}(undef,4,5) - -WUSD3 = Array{Float64,3}(undef,4,4,pq) -WUD3 = Array{Float64,3}(undef,4,4,pq) -WUL3 = Matrix{Float64}(undef,4*pq,4) -WY1 = Vector{Float64}(undef,4*pq) - - -al = 0.1*rand(2,2,p); ar = rand(2,2,p); q = rand(2,2,pq); -[triu!(view(al,:,:,i)) for i in 2:p] -[triu!(view(ar,:,:,i)) for i in 2:p] -REV = true -KSCHUR = 1 - -n1 = 1; n2 = 1; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -ip = mod.(0:pq-1,p).+1; ip1 = mod.(ip,p).+1; ipq = mod.(0:pq-1,pq).+1; ipq1 = mod.(1:pq,pq).+1; -rez = [ al[i1,i1,ip[i]]'*X[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = [ al[i1,i1,ip[i]]*X[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:pq]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]'*X1[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X1[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:pq]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]*X1[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X1[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -X3 = copy(q[i1,i2,1:pq]); @time dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]'*X3[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X3[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X3 = copy(q[i1,i2,1:pq]); @time dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]*X3[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X3[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - - - -n1 = 1; n2 = 2; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -ip = mod.(0:pq-1,p).+1; ip1 = mod.(ip,p).+1; ipq = mod.(0:pq-1,pq).+1; ipq1 = mod.(1:pq,pq).+1; -rez = [ al[i1,i1,ip[i]]'*X[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = [ al[i1,i1,ip[i]]*X[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:pq]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) # fails -rez = [ al[i1,i1,ip[i]]'*X1[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X1[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:pq]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]*X1[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X1[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -X3 = copy(q[i1,i2,1:pq]); @time dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]'*X3[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X3[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X3 = copy(q[i1,i2,1:pq]); @time dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]*X3[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X3[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - - - - -n1 = 2; n2 = 1; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -ip = mod.(0:pq-1,p).+1; ip1 = mod.(ip,p).+1; ipq = mod.(0:pq-1,pq).+1; ipq1 = mod.(1:pq,pq).+1; -rez = [ al[i1,i1,ip[i]]'*X[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = [ al[i1,i1,ip[i]]*X[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:pq]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]'*X1[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X1[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:pq]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]*X1[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X1[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -X3 = copy(q[i1,i2,1:pq]); @time dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]'*X3[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X3[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X3 = copy(q[i1,i2,1:pq]); @time dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]*X3[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X3[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -n1 = 2; n2 = 2; -X = dpsylv2(REV, n1, n2, KSCHUR, al, ar, q, W, WX) -i1 = 1:n1; i2 = 1:n2 -ip = mod.(0:pq-1,p).+1; ip1 = mod.(ip,p).+1; ipq = mod.(0:pq-1,pq).+1; ipq1 = mod.(1:pq,pq).+1; -rez = [ al[i1,i1,ip[i]]'*X[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X = dpsylv2(!REV, n1, n2, KSCHUR, al, ar, q, W, WX) -rez = [ al[i1,i1,ip[i]]*X[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -X1 = copy(q[i1,i2,1:pq]); dpsylv2!(REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]'*X1[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X1[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X1 = copy(q[i1,i2,1:pq]); dpsylv2!(!REV, n1, n2, KSCHUR, al, ar, X1, WZ, WY) -rez = [ al[i1,i1,ip[i]]*X1[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X1[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -X3 = copy(q[i1,i2,1:pq]); @time dpsylv2krsol!(REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]'*X3[i1,i2,ipq1[i]]*ar[i2,i2,ip[i]]-X3[i1,i2,ipq[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 -X3 = copy(q[i1,i2,1:pq]); @time dpsylv2krsol!(!REV, n1, n2, KSCHUR, al, ar, X3, WUD3, WUSD3, WUL3, WY1, W1, qr_ws, ormqr_ws) -rez = [ al[i1,i1,ip[i]]*X3[i1,i2,ipq[i]]*ar[i2,i2,ip[i]]'-X3[i1,i2,ipq1[i]]+q[i1,i2,ipq[i]] for i in 1:pq] -@test norm(rez) < 1.e-7 - -end # dpsylv2 - - -@testset "prdlyap && pfdlyap" begin - -# constant dimensions -na = [5, 5]; ma = [5,5]; pa = 2; pc = 2; -Ad = PeriodicMatrix([rand(Float64,ma[i],na[i]) for i in 1:pa],pa); -x = [rand(na[i],na[i]) for i in 1:pc] -Qd = PeriodicMatrix([ x[i]+x[i]' for i in 1:pc],pc); -X2 = PeriodicMatrix(pslyapdkr(Ad.M, Qd.M; adj = true), lcm(pa,pc)); -@test norm(Ad'*pmshift(X2)*Ad-X2+Qd) < 1.e-7 - -# time-varying dimensions -na = [5, 3, 3, 4, 1]; ma = [3, 3, 4, 1, 5]; pa = 5; pc = 5; -#na = 5*na; ma = 5*ma; -Ad = PeriodicMatrix([rand(Float64,ma[i],na[i]) for i in 1:pa],pa); -x = [rand(na[i],na[i]) for i in 1:pc] -Qd = PeriodicMatrix([ x[i]+x[i]' for i in 1:pc],pc); -X = prdlyap(Ad, Qd); -@test norm(Ad'*pmshift(X)*Ad-X+Qd) < 1.e-7 - -Ad1 = convert(PeriodicArray,Ad); Qd1 = convert(PeriodicArray,Qd); -X1 = prdlyap(Ad1, Qd1); -@test norm(Ad1'*pmshift(X1)*Ad1-X1+Qd1) < 1.e-7 - -X2 = PeriodicMatrix(pslyapdkr(Ad.M, Qd.M; adj = true), lcm(pa,pc)); -@test norm(Ad'*pmshift(X2)*Ad-X2+Qd) < 1.e-7 && norm(X1-pm2pa(X2)) < 1.e-7 - -x = [rand(ma[i],ma[i]) for i in 1:pc] -Qd = PeriodicMatrix([ x[i]+x[i]' for i in 1:pc],pc); - -X = pfdlyap(Ad, Qd) -@test norm(Ad*X*Ad'- pmshift(X)+Qd) < 1.e-7 - -Ad1 = convert(PeriodicArray,Ad); Qd1 = convert(PeriodicArray,Qd); -X1 = pfdlyap(Ad1, Qd1); -@test norm(Ad1*X1*Ad1'-pmshift(X1)+Qd1) < 1.e-7 - -X2 = PeriodicMatrix(pslyapdkr(Ad.M, Qd.M; adj = false), lcm(pa,pc)); -@test norm(Ad*X2*Ad'-pmshift(X2)+Qd) < 1.e-7 && norm(X1-pm2pa(X2)) < 1.e-7 - - -# constant dimensions -n = 5; pa = 10; pc = 2; -Ad = 0.5*PeriodicArray(rand(Float32,n,n,pa),pa); -q = rand(n,n,pc); [q[:,:,i] = q[:,:,i]'+q[:,:,i] for i in 1:pc]; -Qd = PeriodicArray(q,pc); - -X = pdlyap(Ad, Qd, adj = true); -@test norm(Ad'*pmshift(X)*Ad-X+Qd) < 1.e-7 - -X = pdlyap(Ad, Qd, adj = false); -@test norm(Ad*X*Ad'-pmshift(X)+Qd) < 1.e-7 - - -X = prdlyap(Ad, Qd); -@test norm(Ad'*pmshift(X)*Ad-X+Qd) < 1.e-7 - -p = lcm(pa,pc) -ia = mod.(0:p-1,pa).+1; ipx = mod.(0:p-1,p).+1; -ipc = mod.(0:p-1,pc).+1; ipx1 = mod.(ipx,p).+1; -rez = [ Ad.M[:,:,ia[i]]'*X.M[:,:,ipx1[i]]*Ad.M[:,:,ia[i]]-X.M[:,:,ipx[i]]+Qd.M[:,:,ipc[i]] for i in ipx] -@test norm(rez) < 1.e-7 - -X1 = prdlyap(convert(PeriodicMatrix,Ad), convert(PeriodicMatrix,Qd)); -X = convert(PeriodicArray,X1) -@test norm(Ad'*pmshift(X)*Ad-X+Qd) < 1.e-7 - -X = pfdlyap(Ad, Qd) -@test norm(Ad*X*Ad'-pmshift(X)+Qd) < 1.e-7 - -X1 = pfdlyap(convert(PeriodicMatrix,Ad), convert(PeriodicMatrix,Qd)); -X = convert(PeriodicArray,X1) -@test norm(Ad*X*Ad'-pmshift(X)+Qd) < 1.e-7 - - -rez = [ Ad.M[:,:,ia[i]]*X.M[:,:,ipx[i]]*Ad.M[:,:,ia[i]]'-X.M[:,:,ipx1[i]]+Qd.M[:,:,ipc[i]] for i in ipx] -@test norm(rez) < 1.e-7 - -#pseig(Ad) - -n = 5; pa = 3; pc = 2; -Ad = 0.5*PeriodicArray(rand(Float64,n,n,pa),pa); -q = rand(n,n,pc); [q[:,:,i] = q[:,:,i]'+q[:,:,i] for i in 1:pc]; -Qd = PeriodicArray(q,pc); - -X = prdlyap(Ad, Qd); -@test norm(Ad'*pmshift(X)*Ad-X+Qd) < 1.e-7 - -X = pfdlyap(Ad, Qd) -@test norm(Ad*X*Ad'-pmshift(X)+Qd) < 1.e-7 - -#pseig(Ad) - -n = 5; pa = 3; pc = 1; -Ad = 0.5*PeriodicArray(rand(Float32,n,n,pa),pa); -q = rand(n,n); q = q'+q; - -X = prdlyap(Ad, q); -@test norm(Ad'*pmshift(X)*Ad-X+q) < 1.e-7 - -X = pfdlyap(Ad, q) -@test norm(Ad*X*Ad'-pmshift(X)+q) < 1.e-7 - - - -end - -@testset "pslyapd" begin - - -n = 5; pa = 1; pc = 1; p = lcm(pa,pc) -a = (1/(n*n*pa))*rand(n,n,pa); q = rand(n,n,pc); -[q[:,:,i] = q[:,:,i]'+q[:,:,i] for i in 1:pc]; - -X = pslyapd(a, q; adj = true) -ia = mod.(0:p-1,pa).+1; ipx = mod.(0:p-1,p).+1; -ipc = mod.(0:p-1,pc).+1; ipx1 = mod.(ipx,p).+1; - -rez = [ a[:,:,ia[i]]'*X[:,:,ipx1[i]]*a[:,:,ia[i]]-X[:,:,ipx[i]]+q[:,:,ipc[i]] for i in ipx] -@test norm(rez) < 1.e-7 - -X = pslyapd(a, q; adj = false) -ia = mod.(0:p-1,pa).+1; ipx = mod.(0:p-1,p).+1; -ipc = mod.(0:p-1,pc).+1; ipx1 = mod.(ipx,p).+1; - -rez = [ a[:,:,ia[i]]*X[:,:,ipx[i]]*a[:,:,ia[i]]'-X[:,:,ipx1[i]]+q[:,:,ipc[i]] for i in ipx] -@test norm(rez) < 1.e-7 - - -n = 5; pa = 5; pc = 10; p = lcm(pa,pc) -a = (1/(n*n*pa))*rand(n,n,pa); q = rand(n,n,pc); -[q[:,:,i] = q[:,:,i]'+q[:,:,i] for i in 1:pc]; - -X = pslyapd(a, q; adj = true) -ia = mod.(0:p-1,pa).+1; ipx = mod.(0:p-1,p).+1; -ipc = mod.(0:p-1,pc).+1; ipx1 = mod.(ipx,p).+1; - -rez = [ a[:,:,ia[i]]'*X[:,:,ipx1[i]]*a[:,:,ia[i]]-X[:,:,ipx[i]]+q[:,:,ipc[i]] for i in ipx] -@test norm(rez) < 1.e-7 - -X = pslyapd(a, q; adj = false) -ia = mod.(0:p-1,pa).+1; ipx = mod.(0:p-1,p).+1; -ipc = mod.(0:p-1,pc).+1; ipx1 = mod.(ipx,p).+1; - -rez = [ a[:,:,ia[i]]*X[:,:,ipx[i]]*a[:,:,ia[i]]'-X[:,:,ipx1[i]]+q[:,:,ipc[i]] for i in ipx] -@test norm(rez) < 1.e-7 - -X = pslyapdkr(a, q; adj = true) -ia = mod.(0:p-1,pa).+1; ipx = mod.(0:p-1,p).+1; -ipc = mod.(0:p-1,pc).+1; ipx1 = mod.(ipx,p).+1; - -rez = [ a[:,:,ia[i]]'*X[:,:,ipx1[i]]*a[:,:,ia[i]]-X[:,:,ipx[i]]+q[:,:,ipc[i]] for i in ipx] -@test norm(rez) < 1.e-7 - -X = pslyapdkr(a, q; adj = false) -ia = mod.(0:p-1,pa).+1; ipx = mod.(0:p-1,p).+1; -ipc = mod.(0:p-1,pc).+1; ipx1 = mod.(ipx,p).+1; - -rez = [ a[:,:,ia[i]]*X[:,:,ipx[i]]*a[:,:,ia[i]]'-X[:,:,ipx1[i]]+q[:,:,ipc[i]] for i in ipx] -@test norm(rez) < 1.e-7 - - - -# q = copy(qs); -# X1 = pslyapdkr(a,q) -# ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -# rez = [ a[:,:,ip[i]]'*X1[:,:,ip1[i]]*a[:,:,ip[i]]-X1[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -# @test norm(rez) < 1.e-7 - -# KSCHUR = 1 -# q = copy(qs); -# X = pdlyaps1!(KSCHUR, a, q; adj = true) -# ip = mod.(0:pc-1,p).+1; ip1 = mod.(ip,p).+1; -# ipc = mod.(0:pc-1,pc).+1; ipc1 = mod.(ipc,pc).+1; - -# rez = [ a[:,:,ip[i]]'*X[:,:,ipc1[i]]*a[:,:,ip[i]]-X[:,:,ipc[i]]+qs[:,:,ipc[i]] for i in ipc] -# @test norm(rez) < 1.e-7 - -# # q = copy(qs); -# # X1 = pslyapdkr(a,q; adj = false) -# # ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -# # rez = [ a[:,:,ip[i]]*X1[:,:,ip[i]]*a[:,:,ip[i]]'-X1[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -# # @test norm(rez) < 1.e-7 - - -# q = copy(qs); -# X = pdlyaps1!(KSCHUR, a, q; adj = false) -# rez = [ a[:,:,ip[i]]*X[:,:,ipc[i]]*a[:,:,ip[i]]'-X[:,:,ipc1[i]]+qs[:,:,ipc[i]] for i in ipc] -# @test norm(rez) < 1.e-7 - - -end # pdlyap - -@testset "pdlyaps1!" begin - - -p = 1; n = 1; -a = rand(n,n,p); q = rand(n,n,p); -KSCHUR = 1 -qs = copy(q) -X = pdlyaps1!(KSCHUR, a, q; adj = true) -rez = a[:,:,1]'*X[:,:,1]*a[:,:,1]-X[:,:,1]+qs[:,:,1] -@test norm(rez) < 1.e-7 -q = copy(qs) -X = pdlyaps1!(KSCHUR, a, q; adj = false) -rez = a[:,:,1]*X[:,:,1]*a[:,:,1]'-X[:,:,1]+qs[:,:,1] -@test norm(rez) < 1.e-7 - -X = copy(qs) -pdlyaps!(KSCHUR, a, X; adj = true) -rez = a[:,:,1]'*X[:,:,1]*a[:,:,1]-X[:,:,1]+qs[:,:,1] -@test norm(rez) < 1.e-7 -X = copy(qs) -pdlyaps!(KSCHUR, a, X; adj = false) -rez = a[:,:,1]*X[:,:,1]*a[:,:,1]'-X[:,:,1]+qs[:,:,1] -@test norm(rez) < 1.e-7 - - -p = 10; n = 1; -a = rand(n,n,p); q = rand(n,n,p); -KSCHUR = 1 -qs = copy(q) -X = pdlyaps1!(KSCHUR, a, q; adj = true) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]'*X[:,:,ip1[i]]*a[:,:,ip[i]]-X[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -q = copy(qs) -X = pdlyaps1!(KSCHUR, a, q; adj = false) -rez = [ a[:,:,ip[i]]*X[:,:,ip[i]]*a[:,:,ip[i]]'-X[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = true) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]'*X1[:,:,ip1[i]]*a[:,:,ip[i]]-X1[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = false) -rez = [ a[:,:,ip[i]]*X1[:,:,ip[i]]*a[:,:,ip[i]]'-X1[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -p = 10; n = 2; -a = rand(n,n,p); q = rand(n,n,p); -a[:,:,1] = 0.01*[1 -2;2 1]; [a[:,:,i] = triu(a[:,:,i]) for i in 2:p]; -[q[:,:,i] = q[:,:,i]'*q[:,:,i] for i in 1:p]; -KSCHUR = 1 -qs = copy(q) -X = pdlyaps1!(KSCHUR, a, q; adj = true) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]'*X[:,:,ip1[i]]*a[:,:,ip[i]]-X[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -q = copy(qs) -X = pdlyaps1!(KSCHUR, a, q; adj = false) -rez = [ a[:,:,ip[i]]*X[:,:,ip[i]]*a[:,:,ip[i]]'-X[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = true) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]'*X1[:,:,ip1[i]]*a[:,:,ip[i]]-X1[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = false) -rez = [ a[:,:,ip[i]]*X1[:,:,ip[i]]*a[:,:,ip[i]]'-X1[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -p = 2; n = 2; -a = rand(n,n,p); q = rand(n,n,p); x = rand(n,n,p); -a[:,:,1] = 0.01*schur(rand(n,n)).T; [a[:,:,i] = triu(a[:,:,i]) for i in 2:p]; -[q[:,:,i] = q[:,:,i]'*q[:,:,i] for i in 1:p]; - -# a[:,:,1] = [0.5 2;0 -0.5]; a[:,:,2] = [1 0;0 1.]; -# x[:,:,1] = [1 3;3 1.]; x[:,:,2] = [2 1;1 2.]; -# q[:,:,1] = -a[:,:,1]'*x[:,:,2]*a[:,:,1]+x[:,:,1]; -# q[:,:,2] = -a[:,:,2]'*x[:,:,1]*a[:,:,2]+x[:,:,2]; -# qs = copy(q); -# ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -# rez = [ a[:,:,ip[i]]'*x[:,:,ip1[i]]*a[:,:,ip[i]]-x[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] - -qs = copy(q); -X1 = pslyapdkr(a,q) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez1 = [ a[:,:,ip[i]]'*X1[:,:,ip1[i]]*a[:,:,ip[i]]-X1[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez1) < 1.e-7 - -KSCHUR = 1 -q = copy(qs); -X = pdlyaps1!(KSCHUR, a, q; adj = true) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]'*X[:,:,ip1[i]]*a[:,:,ip[i]]-X[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -q = copy(qs) -X = pdlyaps1!(KSCHUR, a, q; adj = false) -rez = [ a[:,:,ip[i]]*X[:,:,ip[i]]*a[:,:,ip[i]]'-X[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = true) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]'*X1[:,:,ip1[i]]*a[:,:,ip[i]]-X1[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = false) -rez = [ a[:,:,ip[i]]*X1[:,:,ip[i]]*a[:,:,ip[i]]'-X1[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -p = 5; n = 5; -a = rand(n,n,p); q = rand(n,n,p); x = rand(n,n,p); -a[:,:,1] = 0.01*schur(rand(n,n)).T; [a[:,:,i] = triu(a[:,:,i]) for i in 2:p]; -[q[:,:,i] = q[:,:,i]'*q[:,:,i] for i in 1:p]; - -qs = copy(q); -X1 = pslyapdkr(a,q) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]'*X1[:,:,ip1[i]]*a[:,:,ip[i]]-X1[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -KSCHUR = 1 -q = copy(qs); -X = pdlyaps1!(KSCHUR, a, q; adj = true) -rez = [ a[:,:,ip[i]]'*X[:,:,ip1[i]]*a[:,:,ip[i]]-X[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = true) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]'*X1[:,:,ip1[i]]*a[:,:,ip[i]]-X1[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -q = copy(qs); -X1 = pslyapdkr(a,q; adj = false) -ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -rez = [ a[:,:,ip[i]]*X1[:,:,ip[i]]*a[:,:,ip[i]]'-X1[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -q = copy(qs); -X = pdlyaps1!(KSCHUR, a, q; adj = false) -rez = [ a[:,:,ip[i]]*X[:,:,ip[i]]*a[:,:,ip[i]]'-X[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = false) -rez = [ a[:,:,ip[i]]*X1[:,:,ip[i]]*a[:,:,ip[i]]'-X1[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -@test norm(rez) < 1.e-7 - - -p = 5; n = 5; pc = 10 -a = rand(n,n,p); q = rand(n,n,pc); x = rand(n,n,pc); -a[:,:,1] = 0.01*schur(rand(n,n)).T; [a[:,:,i] = triu(a[:,:,i]) for i in 2:p]; -[q[:,:,i] = q[:,:,i]'*q[:,:,i] for i in 1:pc]; -qs = copy(q); - -# q = copy(qs); -# X1 = pslyapdkr(a,q) -# ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -# rez = [ a[:,:,ip[i]]'*X1[:,:,ip1[i]]*a[:,:,ip[i]]-X1[:,:,ip[i]]+qs[:,:,ip[i]] for i in ip] -# @test norm(rez) < 1.e-7 - -KSCHUR = 1 -q = copy(qs); -X = pdlyaps1!(KSCHUR, a, q; adj = true) -ip = mod.(0:pc-1,p).+1; ip1 = mod.(ip,p).+1; -ipc = mod.(0:pc-1,pc).+1; ipc1 = mod.(ipc,pc).+1; - -rez = [ a[:,:,ip[i]]'*X[:,:,ipc1[i]]*a[:,:,ip[i]]-X[:,:,ipc[i]]+qs[:,:,ipc[i]] for i in ipc] -@test norm(rez) < 1.e-7 - -X1 = copy(qs) -@time pdlyaps!(KSCHUR, a, X1; adj = true); -rez = [ a[:,:,ip[i]]'*X1[:,:,ipc1[i]]*a[:,:,ip[i]]-X1[:,:,ipc[i]]+qs[:,:,ipc[i]] for i in ipc] -@test norm(rez) < 1.e-7 - -# q = copy(qs); -# X1 = pslyapdkr(a,q; adj = false) -# ip = mod.(0:p-1,p).+1; ip1 = mod.(ip,p).+1; -# rez = [ a[:,:,ip[i]]*X1[:,:,ip[i]]*a[:,:,ip[i]]'-X1[:,:,ip1[i]]+qs[:,:,ip[i]] for i in ip] -# @test norm(rez) < 1.e-7 - - -q = copy(qs); -X = pdlyaps1!(KSCHUR, a, q; adj = false) -rez = [ a[:,:,ip[i]]*X[:,:,ipc[i]]*a[:,:,ip[i]]'-X[:,:,ipc1[i]]+qs[:,:,ipc[i]] for i in ipc] -@test norm(rez) < 1.e-7 - -X1 = copy(qs) -pdlyaps!(KSCHUR, a, X1; adj = false) -rez = [ a[:,:,ip[i]]*X1[:,:,ipc[i]]*a[:,:,ip[i]]'-X1[:,:,ipc1[i]]+qs[:,:,ipc[i]] for i in ip] -@test norm(rez) < 1.e-7 - -for adj in (false,true) - @time pdlyaps!(KSCHUR, a, copy(qs); adj); - @time pdlyaps1!(KSCHUR, a, copy(qs); adj); - @time pdlyaps2!(KSCHUR, a, copy(qs); adj); - @time pdlyaps3!(KSCHUR, a, copy(qs); adj); -end - - -# benchmark -# using BenchmarkTools -# using MatrixEquations -# p = 5; n = 400; pc = 5 -# p = 200; n = 8; pc = 200 -# a = 0.1*rand(n,n,p); q = rand(n,n,pc); x = rand(n,n,pc); [x[:,:,i] = x[:,:,i]'+x[:,:,i] for i in 1:pc]; -# a[:,:,1] = 0.01*schur(rand(n,n)).T; [a[:,:,i] = triu(a[:,:,i]) for i in 2:p]; -# ip = mod.(0:pc-1,p).+1; ip1 = mod.(ip,p).+1; -# ipc = mod.(0:pc-1,pc).+1; ipc1 = mod.(ipc,pc).+1; -# [q[:,:,i] = -a[:,:,ip[i]]'*x[:,:,ipc1[i]]*a[:,:,ip[i]]-x[:,:,ipc[i]] for i in ipc]; -# [q[:,:,i] = 0.5*(q[:,:,i]'+q[:,:,i]) for i in 1:pc]; -# qs = copy(q); - -# KSCHUR = 1 -# X = copy(qs); -# pdlyaps1!(KSCHUR, a, X; adj = true) -# ip = mod.(0:pc-1,p).+1; ip1 = mod.(ip,p).+1; -# ipc = mod.(0:pc-1,pc).+1; ipc1 = mod.(ipc,pc).+1; -# rez = [ a[:,:,ip[i]]'*X[:,:,ipc1[i]]*a[:,:,ip[i]]-X[:,:,ipc[i]]+qs[:,:,ipc[i]] for i in ipc] -# @test norm(rez) < 1.e-5 - -# X1 = copy(qs); -# pdlyaps!(KSCHUR, a, X1; adj = true) -# rez = [ a[:,:,ip[i]]'*X1[:,:,ipc1[i]]*a[:,:,ip[i]]-X1[:,:,ipc[i]]+qs[:,:,ipc[i]] for i in ipc] -# @test norm(rez) < 1.e-5 - -# X2 = copy(qs) -# pdlyaps2!(KSCHUR, a, X2; adj = true) -# rez = [ a[:,:,ip[i]]'*X2[:,:,ipc1[i]]*a[:,:,ip[i]]-X2[:,:,ipc[i]]+qs[:,:,ipc[i]] for i in ipc] -# @test norm(rez) < 1.e-5 - - -# KSCHUR = 1 -# @btime pdlyaps!(KSCHUR, a, copy(qs); adj = true); -# @btime pdlyaps1!(KSCHUR, a, copy(qs); adj = true); -# @btime pdlyaps2!(KSCHUR, a, copy(qs); adj = true); -# @btime pdlyaps3!(KSCHUR, a, copy(qs); adj = true); - -# @btime pdlyaps!(KSCHUR, a, copy(qs); adj = false); -# @btime pdlyaps1!(KSCHUR, a, copy(qs); adj = false); -# @btime pdlyaps2!(KSCHUR, a, copy(qs); adj = false); -# @btime pdlyaps3!(KSCHUR, a, copy(qs); adj = false); - -p = 100; n = 4; pc = 100 -a = 0.1*rand(n,n,p); q = rand(n,n,pc); x = rand(n,n,pc); [x[:,:,i] = x[:,:,i]'+x[:,:,i] for i in 1:pc]; -a[:,:,1] = 0.01*schur(rand(n,n)).T; [a[:,:,i] = triu(a[:,:,i]) for i in 2:p]; -ip = mod.(0:pc-1,p).+1; ip1 = mod.(ip,p).+1; -ipc = mod.(0:pc-1,pc).+1; ipc1 = mod.(ipc,pc).+1; -[q[:,:,i] = -a[:,:,ip[i]]'*x[:,:,ipc1[i]]*a[:,:,ip[i]]-x[:,:,ipc[i]] for i in ipc]; -[q[:,:,i] = 0.5*(q[:,:,i]'+q[:,:,i]) for i in 1:pc]; -qs = copy(q); - - - -# q = copy(qs); -# A1 = copy(a[:,:,1]); X1 = copy(q[:,:,1]); -# lyapds!(A1, X1; adj = true) -# rez1 = a[:,:,1]'*X1*a[:,:,1]- X1 +qs[:,:,1] -# @test norm(rez1) < 1.e-6 - -# @btime lyapds!(A1, copy(X1); adj = true); - - -end # pdlyaps1! - -@testset "pdplyap, prdplyap && pfdplyap" begin -# dperiod = 1 -A = [rand(2,2)/2]; B = [rand(2,3)]; -U = psplyapd(A,B; adj = false); -@test norm(U[1]*U[1]'-A[1]*U[1]*U[1]'*A[1]'-B[1]*B[1]') < 1.e-7 -A = [rand(2,2)/2]; B = [rand(2,1)]; -U = psplyapd(A,B; adj = false); -@test norm(U[1]*U[1]'-A[1]*U[1]*U[1]'*A[1]'-B[1]*B[1]') < 1.e-7 -A = [.1*rand(2,2)]; C = [rand(3,2)]; -U = psplyapd(A,C; adj = true); -@test norm(U[1]'*U[1]-A[1]'*U[1]'*U[1]*A[1]-C[1]'*C[1]) < 1.e-7 -A = [.1*rand(2,2)]; C = [rand(1,2)]; -U = psplyapd(A,C; adj = true); -@test norm(U[1]'*U[1]-A[1]'*U[1]'*U[1]*A[1]-C[1]'*C[1]) < 1.e-7 - -# constant dimensions -na = [5, 5]; ma = [5,5]; p = 2; m = 2; pa = 2; pc = 2; pb = 2; -Ad = PeriodicMatrix([0.1*rand(Float64,ma[i],na[i]) for i in 1:pa],pa); -Bd = PeriodicMatrix([rand(ma[i],m) for i in 1:pb],pb); -Cd = PeriodicMatrix([rand(p,na[i]) for i in 1:pc],pc); -# A*X*A' + B*B' = σX, if adj = false, -U = pdplyap(Ad, Bd; adj = false) -X = U*U'; -@test norm(Ad*X*Ad'-pmshift(X)+Bd*Bd') < 1.e-7 -# A'σXA + C'*C = X, if adj = true, -U = pdplyap(Ad, Cd; adj = true); -X = U'*U; -@test norm(Ad'*pmshift(X)*Ad-X+Cd'*Cd) < 1.e-7 - -U = pfdplyap(Ad, Bd) -X = U*U'; -@test norm(Ad*X*Ad'-pmshift(X)+Bd*Bd') < 1.e-7 -# A'σXA + C'*C = X, if adj = true, -U = prdplyap(Ad, Cd); -X = U'*U; -@test norm(Ad'*pmshift(X)*Ad-X+Cd'*Cd) < 1.e-7 - - -# time-varying dimensions -na = [5, 3, 3, 4, 1]; ma = [3, 3, 4, 1, 5]; pa = 5; pc = 5; pb = 5; p = 2; m = 2; -#na = 5*na; ma = 5*ma; -Ad = PeriodicMatrix([0.1*rand(Float64,ma[i],na[i]) for i in 1:pa],pa); -Bd = PeriodicMatrix([rand(ma[i],m) for i in 1:pb],pb); -Cd = PeriodicMatrix([rand(p,na[i]) for i in 1:pc],pc); -# A*X*A' + B*B' = σX, if adj = false, -U = pdplyap(Ad, Bd; adj = false); -X = U*U'; -@test norm(Ad*X*Ad'-pmshift(X)+Bd*Bd') < 1.e-7 -# A'σXA + C'*C = X, if adj = true, -@time U = pdplyap(Ad, Cd; adj = true); -X = U'*U; -@test norm(Ad'*pmshift(X)*Ad-X+Cd'*Cd) < 1.e-7 - -U = pfdplyap(Ad, Bd) -X = U*U'; -@test norm(Ad*X*Ad'-pmshift(X)+Bd*Bd') < 1.e-7 -# A'σXA + C'*C = X, if adj = true, -U = prdplyap(Ad, Cd); -X = U'*U; -@test norm(Ad'*pmshift(X)*Ad-X+Cd'*Cd) < 1.e-7 - - -# constant dimensions -n = 5; p = 2; m = 2; pa = 2; pc = 2; pb = 2; -Ad = PeriodicArray(0.1*rand(Float64,n,n,pa),pa); -Bd = PeriodicArray(rand(Float64,n,m,pb),pb); -Cd = PeriodicArray(rand(Float64,p,n,pc),pc); -# A*X*A' + B*B' = σX, if adj = false, -U = pdplyap(Ad, Bd; adj = false) -X = U*U'; -@test norm(Ad*X*Ad'-pmshift(X)+Bd*Bd') < 1.e-7 -# A'σXA + C'*C = X, if adj = true, -U = pdplyap(Ad, Cd; adj = true); -X = U'*U; -@test norm(Ad'*pmshift(X)*Ad-X+Cd'*Cd) < 1.e-7 - -U = pfdplyap(Ad, Bd) -X = U*U'; -@test norm(Ad*X*Ad'-pmshift(X)+Bd*Bd') < 1.e-7 -# A'σXA + C'*C = X, if adj = true, -U = prdplyap(Ad, Cd); -X = U'*U; -@test norm(Ad'*pmshift(X)*Ad-X+Cd'*Cd) < 1.e-7 - - - -n = 5; p = 2; m = 2; pa = 5; pc = 2; pb = 1; -Ad = PeriodicArray(0.1*rand(Float64,n,n,pa),pa); -Bd = PeriodicArray(rand(Float64,n,m,pb),pb); -Cd = PeriodicArray(rand(Float64,p,n,pc),pc); -# A*X*A' + B*B' = σX, if adj = false, -U = pdplyap(Ad, Bd; adj = false) -X = U*U'; -@test norm(Ad*X*Ad'-pmshift(X)+Bd*Bd') < 1.e-7 -# A'σXA + C'*C = X, if adj = true, -U = pdplyap(Ad, Cd; adj = true); -X = U'*U; -@test norm(Ad'*pmshift(X)*Ad-X+Cd'*Cd) < 1.e-7 - - -U = pfdplyap(Ad, Bd) -X = U*U'; -@test norm(Ad*X*Ad'-pmshift(X)+Bd*Bd') < 1.e-7 -# A'σXA + C'*C = X, if adj = true, -U = prdplyap(Ad, Cd); -X = U'*U; -@test norm(Ad'*pmshift(X)*Ad-X+Cd'*Cd) < 1.e-7 - - - -end - -end # module \ No newline at end of file diff --git a/test/test_pstimeresp.jl b/test/test_pstimeresp.jl index 9148a9f..d012423 100644 --- a/test/test_pstimeresp.jl +++ b/test/test_pstimeresp.jl @@ -1,11 +1,12 @@ module Test_pstimeresp using PeriodicSystems +using ApproxFun using DescriptorSystems using Symbolics using Test using LinearAlgebra -using ApproxFun + println("Test_pstimeresp") @@ -147,7 +148,7 @@ ustep = 1.e5*ones(1); @test norm(yp1-yps1,Inf) < 1.e-5 && norm(xp1-xps1,Inf) < 1.e-5 solver = "non-stiff" -for solver in ("non-stiff", "stiff", "symplectic", "noidea") +for solver in ("non-stiff", "stiff", "symplectic", "auto") println("solver = $solver") @time yp2, toutp2, xp2 = pstimeresp(psysc, t-> [1.e5], toutp, x0; state_history = true, solver, reltol = 1.e-8, abstol = 1.e-8); @test norm(yp1-yp2,Inf) < 1.e-5 && norm(xp1-xp2,Inf) < 1.e-5 diff --git a/test/test_psutils.jl b/test/test_psutils.jl index b0f32c8..18d7d6f 100644 --- a/test/test_psutils.jl +++ b/test/test_psutils.jl @@ -7,7 +7,6 @@ using Test using LinearAlgebra using LinearAlgebra: BlasInt using ApproxFun -#using PeriodicMatrices using MatrixPencils #using BenchmarkTools @@ -196,179 +195,6 @@ psys = ps(Ad,Bd,Cd); psys = ps(convert(PeriodicMatrix,Ad), Bd, convert(PeriodicMatrix,Cd), Dd); psys = ps(convert(PeriodicMatrix,Ad), Bd, convert(PeriodicMatrix,Cd)); - -# # symbolic periodic -# @variables t -# A = [cos(t) 1; 1 1-sin(t)]; -# B = [cos(t)+sin(t); 1-sin(t)]; -# C = [sin(t)+cos(2*t) 1]; -# Ap = PeriodicSymbolicMatrix(A,2*pi); -# Bp = PeriodicSymbolicMatrix(B,2*pi); -# Cp = PeriodicSymbolicMatrix(C,2*pi); - -# # functional expressions -# tA(t::Real) = [cos(t) 1; 1 1-sin(t)]; -# tB(t::Real) = [cos(t)+sin(t); 1-sin(t)]; -# tC(t::Real) = [sin(t)+cos(2*t) 1]; -# # store snapshots as 3d arrays -# N = 200; -# tg = collect((0:N-1)*2*pi/N); -# time = (0:N-1)*2*pi/N; -# # At = reshape(hcat(A.(t)...),2,2,N); -# # Bt = reshape(hcat(B.(t)...),2,1,N); -# # Ct = reshape(hcat(C.(t)...),1,2,N); - -# # time series expressions -# At = PeriodicTimeSeriesMatrix(tA.(time),2*pi); -# Bt = PeriodicTimeSeriesMatrix(tB.(time),2*pi); -# Ct = PeriodicTimeSeriesMatrix(tC.(time),2*pi); - -# # harmonic expressions -# @time Ahr = ts2hr(At); -# @test Ahr.values[:,:,1] ≈ [0. 1; 1 1] && Ahr.values[:,:,2] ≈ [1. 0; 0 -im] -# @time Bhr = ts2hr(Bt); -# @test Bhr.values[:,:,1] ≈ [0.; 1] && Bhr.values[:,:,2] ≈ [1.0+im; -im] -# @time Chr = ts2hr(Ct); -# @test Chr.values[:,:,1] ≈ [0. 1] && Chr.values[:,:,2] ≈ [im 0] && Chr.values[:,:,3] ≈ [1 0] - -#@time Affm = ts2ffm(At); - -# nperiod = 24 -# time1 = (0:N-1)*2*pi*nperiod/N; -# At1 = PeriodicTimeSeriesMatrix(tA.(time1),2*pi*nperiod); -# Ahr1 = ts2hr(At1); -# @test convert(PeriodicFunctionMatrix,Ahr1).f(1) ≈ convert(PeriodicFunctionMatrix,Ahr).f(1) - -# @test iszero(hr2psm(Ahr,1:1) + hr2psm(Ahr,0:0) - hr2psm(Ahr)) -# @test iszero(hr2psm(Ahr1,1:1) + hr2psm(Ahr1,0:0) - hr2psm(Ahr1)) - -# # harmonic vs. symbolic -# @test norm(substitute.(convert(PeriodicSymbolicMatrix,Ahr).F - A, (Dict(t => rand()),))) < 1e-15 -# @test norm(substitute.(convert(PeriodicSymbolicMatrix,Bhr).F - B, (Dict(t => rand()),))) < 1e-15 -# @test norm(substitute.(convert(PeriodicSymbolicMatrix,Chr).F - C, (Dict(t => rand()),))) < 1e-15 - -# # harmonic vs. time series -# @test all(norm.(tvmeval(Ahr,tg).-At.values) .< 1.e-7) -# @test all(norm.(tvmeval(Bhr,tg).-Bt.values) .< 1.e-7) -# @test all(norm.(tvmeval(Chr,tg).-Ct.values) .< 1.e-7) - -# # check time values on the grid -# for method in ("constant", "linear", "quadratic", "cubic") -# @test all(norm.(tvmeval(At,tg; method).-At.values) .< 1.e-7) -# @test all(norm.(tvmeval(Bt,tg; method).-Bt.values) .< 1.e-7) -# @test all(norm.(tvmeval(Ct,tg; method).-Ct.values) .< 1.e-7) -# end -# # check interpolated values: time series vs. harmonic -# tt = rand(10)*2pi; -# for method in ("linear", "quadratic", "cubic") -# @test all(norm.(tvmeval(At,tt; method).-tvmeval(Ahr,tt; exact = true)) .< 1.e-3) -# @test all(norm.(tvmeval(Bt,tt; method).-tvmeval(Bhr,tt; exact = false)) .< 1.e-3) -# @test all(norm.(tvmeval(Ct,tt; method).-tvmeval(Chr,tt; exact = true)) .< 1.e-3) -# end - -# # check conversion to function form -# Amat = convert(PeriodicFunctionMatrix,Ahr); -# @test all(norm.(tvmeval(At,tt; method = "linear").-tvmeval(Ahr,tt)) .< 1.e-3) -# @test iszero(convert(PeriodicSymbolicMatrix,Amat).F-Ap.F) - -# Amat = convert(PeriodicFunctionMatrix,At); -# @test all(norm.(tvmeval(At,tt; method = "linear").-Amat.f.(tt)) .< 1.e-3) -# #@test iszero(convert(PeriodicSymbolicMatrix,Amat).F-Ap.F) - -# Amat = PeriodicFunctionMatrix(tA,2pi); -# @test all(norm.(tvmeval(At,tt; method = "linear").-tvmeval(Amat,tt)) .< 1.e-3) -# @test iszero(convert(PeriodicSymbolicMatrix,Amat).F-Ap.F) - -# Amat = convert(PeriodicFunctionMatrix,Ap); -# @test all(norm.(tvmeval(At,tt; method = "linear").-tvmeval(Ap,tt)) .< 1.e-3) -# @test iszero(convert(PeriodicSymbolicMatrix,Amat).F-Ap.F) -# @test size(Amat) == size(Ap) - - -# for method in ("constant", "linear", "quadratic", "cubic") -# Amat = ts2pfm(At; method); -# @test all(norm.(At.values.-Amat.f.(tg)) .< 1.e-10) -# Bmat = ts2pfm(Bt; method); -# @test all(norm.(Bt.values.-Bmat.f.(tg)) .< 1.e-10) -# Cmat = ts2pfm(Ct; method); -# @test all(norm.(Ct.values.-Cmat.f.(tg)) .< 1.e-10) -# end - -# # example of Colaneri -# at(t) = [0 1; -10*cos(t) -24-10*sin(t)]; -# Afun=PeriodicFunctionMatrix(at,2pi); -# ev = pseig(Afun; solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10) -# # @time Φ = tvstm(Afun.f, Afun.period; reltol = 1.e-10) -# cvals = log.(complex(ev))/2/pi -# println("cvals = $cvals -- No one digit accuracy!!!") -# @test maximum(abs.(ev)) ≈ 1 - -# # using ApproxFun -# s = Fourier(0..2π) -# At = FourierFunctionMatrix(Fun(t -> [0 1; -10*cos(t) -24-10*sin(t)],s), 2pi) -# Atfun = convert(PeriodicFunctionMatrix,At) -# Ahrfun = convert(PeriodicFunctionMatrix,pfm2hr(Afun)) - -# @time cvals = psceig(Afun, 500; solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10) -# @time cvals1 = psceig(Atfun, 500; solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10) -# @time cvals2 = psceig(Ahrfun, 500; solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10) -# @test cvals ≈ cvals1 ≈ cvals2 -# Tt = Fun(t -> [12+5*sin(t) 1/2; 1 0],s) -# Tinvt=inv(Tt) -# Atilde=Tt*At.M*Tinvt+Tt'*Tinvt -# Aref = Fun(t -> [0 0; 2 -24-10*sin(t)],s) -# @test norm(Aref-Atilde) < 1.e-10 - -# # example Floquet analysis from ApproxFun.jl -# a=0.15 -# at1(t) = -[0 -1 0 0; (2+a*cos(2t)) 0 -1 0; 0 0 0 -1; -1 0 (2+a*cos(2t)) 0] -# Afun1=PeriodicFunctionMatrix(at1,pi); -# ev1 = pseig(Afun1; solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10) -# cvals1 = log.(complex(ev1))/pi - -# a=0.15 -# at2(t) = -[0 -1 0 0; (2+a*cos(2t)) 0 -1 0; 0 0 0 -1; -1 0 (2+a*cos(2t)) 0] -# Afun2=PeriodicFunctionMatrix(at2,2*pi;nperiod=2); -# ev2 = pseig(Afun2; solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10) -# cvals2 = log.(complex(ev2))/(2pi) -# @test ev1.^2 ≈ ev2 && real(cvals1) ≈ real(cvals2) - - -# full accuracy characteristic exponents -# solver = "symplectic" -# for solver in ("non-stiff", "stiff", "linear", "symplectic", "noidea") -# @time M = monodromy(Afun, 500; solver, reltol = 1.e-10, abstol = 1.e-10); -# cvals = log.(complex(pseig(M)))/2/pi -# #println("solver = $solver cvals = $cvals") -# @test isapprox(cvals, [0; -24], atol = 1.e-7) -# end - -# solver = "non-stiff" -# #for solver in ("non-stiff", "stiff", "linear", "symplectic", "noidea") -# for solver in ("non-stiff", "stiff", "linear", "noidea") -# println("solver = $solver") -# @time cvals = psceig(Afun, 500; solver, reltol = 1.e-10, abstol = 1.e-10) -# @test isapprox(cvals, [0; -24], atol = 1.e-7) -# end - -# # Vinograd example: unstable periodic system with all A(t) stable -# #at(t) = [3.5 6;-6 -5.5]+[-4.5 6; 6 4.5]*cos(12*t)+[6 4.5;4.5 -6]*sin(12*t); T = pi/6; # does not work -# function at(t::Real) -# [-1-9*(cos(6*t))^2+12*sin(6*t)*cos(6*t) 12*(cos(6*t))^2+9*sin(6*t)*cos(6*t); -# -12*(sin(6*t))^2+9*sin(6*t)*cos(6*t) -1-9*(sin(6*t))^2-12*sin(6*t)*cos(6*t)] -# end -# @test eigvals(at(rand())) ≈ [-10,-1] -# T = pi/3; -# Afun=PeriodicFunctionMatrix(at,T); -# #for solver in ("non-stiff", "stiff", "linear", "symplectic", "noidea") -# for solver in ("non-stiff", "stiff", "linear", "noidea") -# @time cvals = psceig(Afun, 500; solver, reltol = 1.e-10) -# @test cvals ≈ [2; -13] -# end - - - - end end diff --git a/test/test_stabilization.jl b/test/test_stabilization.jl index 3b85150..5584e48 100644 --- a/test/test_stabilization.jl +++ b/test/test_stabilization.jl @@ -515,7 +515,7 @@ end end -println("Test_stabilization") +println("Test_state_feedback_stabilization") @testset "Test_state_feedback_stabilization" begin @@ -644,28 +644,28 @@ println("Test_stabilization") #psyscw = ps(PM,dss(a,bw,c,dw),period); psyscw = ps(a,[convert(PM,PeriodicFunctionMatrix(b,period)) bw],c,[d dw]); - @time F, EVALS = pclqr(psysc, q, r; K = 100, solver = "symplectic", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true); + @time F, EVALS = pclqr(psysc, q, r; K = 100, reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true); clev = psceig(psysc.A+psysc.B*F,500) @test norm(sort(real(clev)) - sort(real(EVALS))) < 1.e-7 && norm(sort(imag(clev)) - sort(imag(EVALS))) < 1.e-7 # this test covers the experimental code provided in PeriodicSchurDecompositions package and occasionally fails - @time F, EVALS = pclqr(psysc, q, r; K = 100, solver = "symplectic", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = false ); + @time F, EVALS = pclqr(psysc, q, r; K = 100, reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = false ); clev = psceig(psysc.A+psysc.B*F,500) #println("EVALS = $EVALS, clev = $clev") @test norm(sort(real(clev)) - sort(real(EVALS))) < 1.e-7 && norm(sort(imag(clev)) - sort(imag(EVALS))) < 1.e-7 # low accuracy computation - @time F, EVALS = pclqr(psysc, q, r; K = 100, solver = "symplectic", reltol = 1.e-10, abstol = 1.e-10, fast = true); + @time F, EVALS = pclqr(psysc, q, r; K = 100, reltol = 1.e-10, abstol = 1.e-10, fast = true); clev = psceig(psysc.A+psysc.B*F,500) @test norm(sort(real(clev)) - sort(real(EVALS))) < 1.e-1 && norm(sort(imag(clev)) - sort(imag(EVALS))) < 1.e-1 - @time F, EVALS = pclqry(psysc, qy, r; K = 100, solver = "symplectic", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true); + @time F, EVALS = pclqry(psysc, qy, r; K = 100, reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true); clev = psceig(psysc.A+psysc.B*F,500) @test norm(sort(real(clev)) - sort(real(EVALS))) < 1.e-7 && norm(sort(imag(clev)) - sort(imag(EVALS))) < 1.e-7 qw = Matrix(I(4)); rv = 1.e-1*Matrix(I(2)); #qw = [0 0 0 0; 0 0 0 0; 0 0 0.05 0;0 0 0 1.e5]; rv = 1.e-13*Matrix(I(2)); - @time L, EVALS = pckeg(psysc, qw, rv; K = 100, solver = "stiff", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true, intpol=true); + @time L, EVALS = pckeg(psysc, qw, rv; K = 100, solver = "non-stiff", reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true, intpol=true); ev = psceig(psysc.A-L*psysc.C) @test norm(sort(real(EVALS))-sort(real(ev))) < 1.e-6 && norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-6 @@ -679,7 +679,7 @@ println("Test_stabilization") qw = 1.e-2*Matrix(I(2)); rv = 1.e0*Matrix(I(2)); - @time L, EVALS = pckegw(psyscw, qw, rv; K = 100, solver = "symplectic", intpol=true, reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true); + @time L, EVALS = pckegw(psyscw, qw, rv; K = 100, intpol=true, reltol = 1.e-10, abstol = 1.e-10, fast = false, PSD_SLICOT = true); ev = psceig(psyscw.A-L*psyscw.C) @test norm(sort(real(EVALS))-sort(real(ev))) < 1.e-4 && norm(sort(imag(EVALS))-sort(imag(ev))) < 1.e-4