### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

# ╔═╡ bb6a9f88-2134-11ec-3337-2bdd2c6747a9
begin
	using PlutoUI
end

# ╔═╡ 7a46f79a-c4f4-4cea-8187-839337a9c962
PlutoUI.LocalResource("./figs/Rankine.png")

# ╔═╡ 88b9e9f5-f1ed-4146-a63d-f941be18c559
md"""

#### Example: Compute the steady-state rate of shaft work $\dot{W}_{sh}$ (and some other stuff)

Compute the steady-state rate of shaft work $\dot{W}_{sh}$ (units: kW), and the irreversible enthlapy change from an insulated turbine with efficiency of $\eta$ =86\%. The working fluid is water with a steady-state flow rate of $\dot{m}$ = 4.0 kg/s. Let the input temperature
T$_{2}$ = 500$^{\circ}$C and pressure P$_{2}$ = 11 MPa. The temperature of the exit stream from the turbine T$_{3}$ = 100$^{\circ}$C.

"""

# ╔═╡ 5cf4b776-e31a-48c3-be81-1d7517e9db8b
md"""
#### Approach

The steady-state open first law (energy balance) relates the heat, work and enthalpy for the system:

$$\dot{Q}+\dot{W}^{*}_{sh} + \sum_{s}^{\mathcal{S}}v_{s}\dot{m}_{s}H_{s} = 0$$

where $\dot{W}^{*}_{sh}$ denotes the rate of _actual_ shaft work recovered from the turbine. If the turbine is operating _reversibly_ then the rate of shaft work that we actually recover is equal to the theoretical maximum shaft work. However, in this problem, we know that the turbine is not operating reversibly, thus we use $\dot{W}^{*}_{sh}$.

Let's begin by throwing out terms in the open energy balance, and solving for the rate of shaft work $\dot{W}^{*}_{sh}$. The problem says the turbine is insulated, thus $\dot{Q}$ = 0. Second, we know that $\mathcal{S}$ = 2, and that the mass flow rate into the turbine equals mass flow rate out teh turbine $\dot{m}_{2}=\dot{m}_{3}\equiv{\dot{m}}$. Solving for the rate of shaft work gives the expression:

$$\dot{W}^{*}_{sh} = \dot{m}\left(H^{\prime}_{3} - H_{2}\right)_{I}$$

where the $\left(\star\right)_{I}$ term denotes the enthalpy difference for the _actual irreversible_ process, and $H^{\prime}_{3}$ denotes the _actual_ operating point 3. However, we do not know what the _actual irreversible_ enthalpy difference is i.e., we don't know $H^{\prime}_{3}$.

However, we do know:

* the turbine efficiency $\eta_{T}$ and
* that a reversible adiabatic process is _constant entropy_ ($S_{2}=S_{3}$).

##### How can we compute $\left(\star\right)_{I}$?

The turbine efficiency is given by:

$$\eta_{T} = \frac{\Delta{H}_{I}}{\Delta{H}_{R}}$$

where $\Delta{H}_{R}$ denotes the enthalpy difference for a _reversible_ process. Using the efficiency expression gives us a method to compute
$\Delta{H}_{I}$, the _irreversible_ enthalpy difference:

$$\Delta{H}_{I} = \eta_{T}\Delta{H}_{R}$$

which we can substitute into the expression for work to give:

$$\dot{W}^{*}_{sh} = \dot{m}\eta_{T}\left(H_{3} - H_{2}\right)_{R}$$

__Why__? Because we can follow along the line of constant entropy on a PH-diagram until we hit the outlet pressure (or temperature), which gives $H_{3}$.

"""

# ╔═╡ 75376d0d-c4eb-4bc5-a58b-752187aae593
md"""

##### Estimate $\Delta{H}_{R}$ from the Pressure-Enthalpy (PH) diagram

"""

# ╔═╡ c0d0a66d-4651-4317-9e08-c16ffa290a68
PlutoUI.LocalResource("./figs/PH-diagram-Water-OPs.png")

# ╔═╡ e449301c-c198-4506-9512-02da59cac3f1
md"""
##### Estimate $\Delta{H}_{I}$ and visualize enthalpy difference on a PH-diagram
"""

# ╔═╡ b7ddbd1a-de34-4cd1-ae89-e0c70f3e6c7e
begin

	# Data from the problem and the PH-diagram -
	mdot = 4.0 	# units: kg/s
	H₂ = 3400  	# units: kJ/kg
	H₃= 2400  	# units: kJ/kg
	ηₜ = 0.86;	# units: dimensionless

	# return -
	nothing
end

# ╔═╡ 571973ba-b1a3-4698-a02a-c8f84337d512
# What is the work recovered from the turbine?
Wdot = ηₜ*(mdot*(H₃ - H₂))

# ╔═╡ 5f4dff5a-5a4c-4948-82df-161e27456578
PlutoUI.LocalResource("./figs/PH-diagram-Water-OPs-IRR.png")

# ╔═╡ db4037dd-d4a3-42a3-a357-2540972294bd
# What is the irreversible enthlapy change?
ΔH_I = ηₜ*(H₃-H₂)

# ╔═╡ c285ccd0-bf5c-423f-9577-361ff21f29ad
md"""
##### What is the rate of entropy generation $\dot{S}_{G}$?

The open second law (entropy balance) at steady-state (with a single heat-transfer surface) is given by:

$$-\left(\sum_{s}^{\mathcal{S}}v_{s}\dot{m}_{s}S_{s} + \frac{\dot{Q}}{T_{\sigma}}\right) = \dot{S}_{G}\geq{0}$$

The turbine is adiabatic ($\dot{Q}$ = 0), is operating at steady-state, and has a single input and output ( $\dot{m}_{2}=\dot{m}_{3}\equiv{\dot{m}}$). Putting these ideas together, and throwing out terms in the open entropy balance that we don't need, gives an expression for the rate of entropy generation:

$$-\dot{m}\left(S_{2} - S_{3}\right) = \dot{S}_{G}$$

"""

# ╔═╡ 988ab1b3-a5e5-40df-abc5-c36c9d6cf9b9
begin
	# lookup entropy values from the PH diagram -
	S₂ = 6.5 	# units: kJ/kg-K
	S₃ = 7.0 	# units: kJ/kg-K

	# return -
	nothing
end

# ╔═╡ c787d47d-7a06-4603-8a12-cdaec354374e
# compute SG -
SG_dot = -1*mdot*(S₂-S₃)

# ╔═╡ bb9d6c95-1391-489c-8c00-d5f27ac832d2
md"""
##### What is the rate of lost work $\dot{W}_{lost}$?
From the drivation in the lectire notes, we know that the rate of lost work $\dot{W}_{lost}$ is given by:

$$\dot{W}_{lost} = \left(\eta_{T} - 1\right)\dot{W}_{sh}$$

However, $\dot{W}_{sh}$ is given by:

$$\dot{W}_{sh} = \dot{m}\Delta{H}_{R}$$

Substituting $\dot{W}_{sh}$ into the lost work expression gives:

$$\dot{W}_{lost} = \left(\eta_{T} - 1\right) \dot{m}\Delta{H}_{R}$$

"""

# ╔═╡ db18c3bf-15b3-47c9-be0b-1f543e07fc19
# compute the reversible (ideal) enthalpy change -
ΔH_R = H₃ - H₂

# ╔═╡ 3fc1d446-16dd-479e-bc80-03df6804f557
W_dot_lost = (ηₜ - 1)*ΔH_R

# ╔═╡ c6b5e534-d843-4637-aeb8-c26528605f7c
md"""
#### What is the surrounding temperature $T_{\sigma}$?

We know from the class notes that:

$$\dot{W}_{lost} = T_{\sigma}\dot{S}_{G}$$

thus we can solve for $T_{\sigma}$:

$$T_{\sigma} = \frac{\dot{W}_{lost}}{\dot{S}_{G}}$$

"""

# ╔═╡ e1738875-0394-486a-94c2-0a20647a6857
# Compute T_σ -
T_σ = W_dot_lost/SG_dot

# ╔═╡ e4681733-6926-44a9-9eea-ace0f74b797d
html"""<style>
main {
    max-width: 1200px;
  	width: 80%;
	margin: auto;
	font-family: "Roboto, monospace";
}

a {
	color: blue;
	text-decoration: none;
}

.H1 {
    padding: 0px 30px;
}
</style>
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
PlutoUI = "~0.7.12"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[HypertextLiteral]]
git-tree-sha1 = "72053798e1be56026b81d4e2682dbe58922e5ec9"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.0"

[[IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "9d8c00ef7a8d110787ff6f170579846f776133a9"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.4"

[[PlutoUI]]
deps = ["Base64", "Dates", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "f35ae11e070dbf123d5a6f54cbda45818d765ad2"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.12"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
"""

# ╔═╡ Cell order:
# ╠═bb6a9f88-2134-11ec-3337-2bdd2c6747a9
# ╟─7a46f79a-c4f4-4cea-8187-839337a9c962
# ╟─88b9e9f5-f1ed-4146-a63d-f941be18c559
# ╟─5cf4b776-e31a-48c3-be81-1d7517e9db8b
# ╟─75376d0d-c4eb-4bc5-a58b-752187aae593
# ╟─c0d0a66d-4651-4317-9e08-c16ffa290a68
# ╟─e449301c-c198-4506-9512-02da59cac3f1
# ╠═b7ddbd1a-de34-4cd1-ae89-e0c70f3e6c7e
# ╠═571973ba-b1a3-4698-a02a-c8f84337d512
# ╟─5f4dff5a-5a4c-4948-82df-161e27456578
# ╠═db4037dd-d4a3-42a3-a357-2540972294bd
# ╟─c285ccd0-bf5c-423f-9577-361ff21f29ad
# ╠═988ab1b3-a5e5-40df-abc5-c36c9d6cf9b9
# ╠═c787d47d-7a06-4603-8a12-cdaec354374e
# ╟─bb9d6c95-1391-489c-8c00-d5f27ac832d2
# ╠═db18c3bf-15b3-47c9-be0b-1f543e07fc19
# ╠═3fc1d446-16dd-479e-bc80-03df6804f557
# ╟─c6b5e534-d843-4637-aeb8-c26528605f7c
# ╠═e1738875-0394-486a-94c2-0a20647a6857
# ╟─e4681733-6926-44a9-9eea-ace0f74b797d
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


module CalibrationErrors

using Reexport

using DataStructures
@reexport using Distances
@reexport using KernelFunctions
using StatsBase: StatsBase
using UnPack: @unpack

using LinearAlgebra
using Statistics

# estimation
export calibrationerror

# estimators
export ECE, BiasedSKCE, UnbiasedSKCE, BlockUnbiasedSKCE, UCME

# binning algorithms
export UniformBinning, MedianVarianceBinning

include("generic.jl")
include("binning/generic.jl")
include("binning/uniform.jl")
include("binning/medianvariance.jl")
include("ece.jl")

include("skce/generic.jl")
include("skce/biased.jl")
include("skce/unbiased.jl")

include("ucme.jl")

include("deprecated.jl")

end # module


module Finance
    export fortune

    function fortune(f0, p, c0, n, i)::Bool
        addpercent(current,percent) = current+(current*percent)÷100
        f = f0
        c = c0
        for year in 2:n
            f = addpercent(f,p)-c
            c = addpercent(c,i)
        end
        f ≥ 0
    end

end


facts("SS-HOPM") do

srand(1)
eigvec = randn(20)
eigval = vecnorm(eigvec)
eigvec /= eigval
T = eigval * eigvec .* eigvec' .* reshape(eigvec, 1, 1, 20)

context("Dense representation") do
    @time (lbd, x) = sshopm(T, 1, verbose=true)
    @fact vecnorm(eigvec - x) --> less_than(1e-5)
    @fact vecnorm(eigval - lbd) --> less_than(1e-5)
end

context("Sparse representation") do
    spT = SparseArray(T)

    @time (lbd, x) = sshopm(spT, 1, verbose=true)
    @fact vecnorm(eigvec - x) --> less_than(1e-5)
    @fact vecnorm(eigval - lbd) --> less_than(1e-5)
end

end


using FeedbackParticleFilters, Distributions, Random, PDMats

println("Testing LinearDiffusionStateModel.jl:")

@testset "LinearDiffusionStateModel.jl" begin

    A      = [0. 1.; -0.7 -0.3]
    B      = zeros(2,1)
    B[2,1] = sqrt(2.)
    init   = MvNormal(2, 1.)

    print("  inner constructor")
    mod = LinearDiffusionStateModel(A, B, init)
    print(".")
    @test mod isa LinearDiffusionStateModel{Float64,Array{Float64,2},Array{Float64,2},MvNormal{Float64,PDMats.ScalMat{Float64},Distributions.ZeroVector{Float64}}}
    print(".")
    B2 = zeros(Int, 2, 1)
    B2[2,1] = 2
    @test_throws MethodError LinearDiffusionStateModel(A, B2, init) # wrong element type of B2
    print(".")
    B3 = zeros(3, 1)
    @test_throws DimensionMismatch LinearDiffusionStateModel(A, B3, init) # wrong size of B3
    println("DONE.")

    print("  method find_stationary_variance")
    print(".")
    Sigma = find_stationary_variance(A, B*B')
    @test Sigma == [4.746796851882334 -5.543824348282954e-5; -5.543824348282954e-5 3.3192089048776765]
    println("DONE.")

    print("  method drift")
    print(".")
    @test drift(mod) == A
    println("DONE.")

    print("  method diffusion")
    print(".")
    @test diffusion(mod) == B
    println("DONE.")

    print("  method initial_condition")
    print(".")
    @test initial_condition(mod) == mod.init
    println("DONE.")

    print("  method initialize")
    print(".")
    Random.seed!(0)
    @test initialize(mod) == [0.6791074260357777, 0.8284134829000359]
    println("DONE.")

    print("  method state_dim")
    print(".")
    @test state_dim(mod) == 2
    println("DONE.")

    print("  method noise_dim")
    print(".")
    @test noise_dim(mod) == 1
    println("DONE.")

    print("  method drift_function")
    print(".")
    @test drift_function(mod)([1., 3.]) == [3.0, -1.5999999999999999]
    println("DONE.")

    print("  method diffusion_function")
    print(".")
    @test diffusion_function(mod)([1., 3.]) == hcat([0.0, 1.4142135623730951]...)'
    println("DONE.")

    print("  calling instance of DiffusionStateModel")
    print(".")
    Random.seed!(0)
    x0 = ones(2)
    @test mod(x0, 0.01) == [1.01, 1.086040293220808]
    print(".")
    Random.seed!(0)
    xx = hcat(ones(2), zeros(2))
    @test mod(xx, 0.01) == [1.01 0. ; 1.086040293220808 0.1171553582769963]
    println("DONE.")

    print("  method propagate!")
    print(".")
    Random.seed!(0)
    propagate!(x0, mod, 0.01)
    @test x0 == [1.01, 1.086040293220808]
    print(".")
    Random.seed!(0)
    propagate!(xx, mod, 0.01)
    @test xx == [1.01 0. ; 1.086040293220808 0.1171553582769963]
    println("DONE.")

end; #LinearDiffusionStateModel.jl


"""

# pcor

$(SIGNATURES)

Computes the partial correlation between two variables given a set of other
variables.

### Method
```julia
pcor(;
* `d::DAG`                             : DAG object
* `u::Vector{Symbol}`                  : Variables used to compute correlation
)
```
where:

  u[1], u[2]: Variables used to compute correlation between, remaining variables
  are the conditioning set

### Returns
```julia
* `res::Float64`                       : Correlation between u[1] and u[2]
```
# Extended help

### Example

### Correlation between vectors and algebra, conditioning on analysis and statistics
```julia
using StructuralCausalModels, CSV

df = DataFrame!(CSV.File(scm_path("..", "data", "marks.csv"));
S = cov(Array(df))

u = [2, 3, 4, 5]
pcor(u, S)
u = [:vectors, :algebra, :statistics, :analysis]
```
### Acknowledgements

Original author:                       Giovanni M. Marchetti

Translated to Julia:                   Rob J Goedman

### License

The R package ggm is licensed under License: GPL-2.

The Julia translation is licenced under: MIT.

Part of the api, not exported.
"""
function pcor(d::DAG, u::SymbolList)
  us = String.(u)
  k = inv(d.s[us, us])
  -k[1,2] / sqrt(k[1,1] * k[2,2])
end

export
  pcor


#------------------------------------------------------------------------------#
#                                   Includes                                   #
#------------------------------------------------------------------------------#

using Test
import HermitePolyTensor
import SymEngine
import Random
import AbstractAlgebra

#------------------------------------------------------------------------------#
#     Hermite Polynomial Tensor Component (HTC) Tests -- SymEngine backend     #
#------------------------------------------------------------------------------#

# Preparations
# ------------

x, y, z = SymEngine.symbols("x y z")

He3x = x^3 - 3x
He6x = x^6 - 15x^4 + 45x^2 - 15
He9x = x^9 - 36x^7 + 378x^5 - 1260x^3 + 945x

He3y = y^3 - 3y
He6y = y^6 - 15y^4 + 45y^2 - 15
He9y = y^9 - 36y^7 + 378y^5 - 1260y^3 + 945y

He3z = z^3 - 3z
He6z = z^6 - 15z^4 + 45z^2 - 15
He9z = z^9 - 36z^7 + 378z^5 - 1260z^3 + 945z

H_3x = 8x^3 - 12x
H_6x = 64x^6 - 480x^4 + 720x^2 - 120
H_9x = 512x^9 - 9216x^7 + 48384x^5 - 80640x^3 + 30240x

H_3y = 8y^3 - 12y
H_6y = 64y^6 - 480y^4 + 720y^2 - 120
H_9y = 512y^9 - 9216y^7 + 48384y^5 - 80640y^3 + 30240y

H_3z = 8z^3 - 12z
H_6z = 64z^6 - 480z^4 + 720z^2 - 120
H_9z = 512z^9 - 9216z^7 + 48384z^5 - 80640z^3 + 30240z

# Single-variable HTC Tests
# -------------------------

@testset "SVHP, SymEng, x-axis, prob. Hermite poly          " begin
    @test He3x == HermitePolyTensor.SVHP(3, be = "se", ax = 1, st = true)
    @test He6x == HermitePolyTensor.SVHP(6, be = "se", ax = 1, st = true)
    @test He9x == HermitePolyTensor.SVHP(9, be = "se", ax = 1, st = true)
end

@testset "SVHP, SymEng, y-axis, prob. Hermite poly          " begin
    @test He3y == HermitePolyTensor.SVHP(3, be = "se", ax = 2, st = true)
    @test He6y == HermitePolyTensor.SVHP(6, be = "se", ax = 2, st = true)
    @test He9y == HermitePolyTensor.SVHP(9, be = "se", ax = 2, st = true)
end

@testset "SVHP, SymEng, z-axis, prob. Hermite poly          " begin
    @test He3z == HermitePolyTensor.SVHP(3, be = "se", ax = 3, st = true)
    @test He6z == HermitePolyTensor.SVHP(6, be = "se", ax = 3, st = true)
    @test He9z == HermitePolyTensor.SVHP(9, be = "se", ax = 3, st = true)
end

@testset "SVHP, SymEng, x-axis, phys. Hermite poly          " begin
    @test H_3x == HermitePolyTensor.SVHP(3, be = "se", ax = 1, st = false)
    @test H_6x == HermitePolyTensor.SVHP(6, be = "se", ax = 1, st = false)
    @test H_9x == HermitePolyTensor.SVHP(9, be = "se", ax = 1, st = false)
end

@testset "SVHP, SymEng, y-axis, phys. Hermite poly          " begin
    @test H_3y == HermitePolyTensor.SVHP(3, be = "se", ax = 2, st = false)
    @test H_6y == HermitePolyTensor.SVHP(6, be = "se", ax = 2, st = false)
    @test H_9y == HermitePolyTensor.SVHP(9, be = "se", ax = 2, st = false)
end

@testset "SVHP, SymEng, z-axis, phys. Hermite poly          " begin
    @test H_3z == HermitePolyTensor.SVHP(3, be = "se", ax = 3, st = false)
    @test H_6z == HermitePolyTensor.SVHP(6, be = "se", ax = 3, st = false)
    @test H_9z == HermitePolyTensor.SVHP(9, be = "se", ax = 3, st = false)
end

# Hermite Polynomial Tensor Component Tests
#------------------------------------------

@testset "HTC, SymEng, Factorial, 3D, prob. Hermite T. Comp." begin
    for X in [(He3x, 3), (He6x, 6), (He9x, 9)]
        for Y in [(He3y, 3), (He6y, 6), (He9y, 9)]
            for Z in [(He3z, 3), (He6z, 6), (He9z, 9)]
                raw = "x"^X[2] * "y"^Y[2] * "z"^Z[2]    # raw (ordered) index set
                idx = join(Random.shuffle([i for i in raw])) # shuffled index set
                @test (
                    SymEngine.expand(X[1] * Y[1] * Z[1]) ==
                    HermitePolyTensor.HTC(idx, D = 3, be = "se", st = true)
                )
            end
        end
    end
end

@testset "HTC, SymEng, Factorial, 3D, phys. Hermite T. Comp." begin
    for X in [(H_3x, 3), (H_6x, 6), (H_9x, 9)]
        for Y in [(H_3y, 3), (H_6y, 6), (H_9y, 9)]
            for Z in [(H_3z, 3), (H_6z, 6), (H_9z, 9)]
                raw = "x"^X[2] * "y"^Y[2] * "z"^Z[2]    # raw (ordered) index set
                idx = join(Random.shuffle([i for i in raw])) # shuffled index set
                @test (
                    SymEngine.expand(X[1] * Y[1] * Z[1]) ==
                    HermitePolyTensor.HTC(idx, D = 3, be = "se", st = false)
                )
            end
        end
    end
end

#------------------------------------------------------------------------------#
#  Hermite Polynomial Tensor Component (HTC) Tests -- AbstractAlgebra backend  #
#------------------------------------------------------------------------------#

# Preparations
# ------------

R, (x, y, z) = AbstractAlgebra.PolynomialRing(AbstractAlgebra.QQ, ["x", "y", "z"])

He3x = x^3 - 3x
He6x = x^6 - 15x^4 + 45x^2 - 15
He9x = x^9 - 36x^7 + 378x^5 - 1260x^3 + 945x

He3y = y^3 - 3y
He6y = y^6 - 15y^4 + 45y^2 - 15
He9y = y^9 - 36y^7 + 378y^5 - 1260y^3 + 945y

He3z = z^3 - 3z
He6z = z^6 - 15z^4 + 45z^2 - 15
He9z = z^9 - 36z^7 + 378z^5 - 1260z^3 + 945z

H_3x = 8x^3 - 12x
H_6x = 64x^6 - 480x^4 + 720x^2 - 120
H_9x = 512x^9 - 9216x^7 + 48384x^5 - 80640x^3 + 30240x

H_3y = 8y^3 - 12y
H_6y = 64y^6 - 480y^4 + 720y^2 - 120
H_9y = 512y^9 - 9216y^7 + 48384y^5 - 80640y^3 + 30240y

H_3z = 8z^3 - 12z
H_6z = 64z^6 - 480z^4 + 720z^2 - 120
H_9z = 512z^9 - 9216z^7 + 48384z^5 - 80640z^3 + 30240z

# Single-variable HTC Tests
# -------------------------

@testset "SVHP, AbsAlg, x-axis, prob. Hermite poly          " begin
    @test He3x == HermitePolyTensor.SVHP(3, be = "aa", ax = 1, st = true)
    @test He6x == HermitePolyTensor.SVHP(6, be = "aa", ax = 1, st = true)
    @test He9x == HermitePolyTensor.SVHP(9, be = "aa", ax = 1, st = true)
end

@testset "SVHP, AbsAlg, y-axis, prob. Hermite poly          " begin
    @test He3y == HermitePolyTensor.SVHP(3, be = "aa", ax = 2, st = true)
    @test He6y == HermitePolyTensor.SVHP(6, be = "aa", ax = 2, st = true)
    @test He9y == HermitePolyTensor.SVHP(9, be = "aa", ax = 2, st = true)
end

@testset "SVHP, AbsAlg, z-axis, prob. Hermite poly          " begin
    @test He3z == HermitePolyTensor.SVHP(3, be = "aa", ax = 3, st = true)
    @test He6z == HermitePolyTensor.SVHP(6, be = "aa", ax = 3, st = true)
    @test He9z == HermitePolyTensor.SVHP(9, be = "aa", ax = 3, st = true)
end

@testset "SVHP, AbsAlg, x-axis, phys. Hermite poly          " begin
    @test H_3x == HermitePolyTensor.SVHP(3, be = "aa", ax = 1, st = false)
    @test H_6x == HermitePolyTensor.SVHP(6, be = "aa", ax = 1, st = false)
    @test H_9x == HermitePolyTensor.SVHP(9, be = "aa", ax = 1, st = false)
end

@testset "SVHP, AbsAlg, y-axis, phys. Hermite poly          " begin
    @test H_3y == HermitePolyTensor.SVHP(3, be = "aa", ax = 2, st = false)
    @test H_6y == HermitePolyTensor.SVHP(6, be = "aa", ax = 2, st = false)
    @test H_9y == HermitePolyTensor.SVHP(9, be = "aa", ax = 2, st = false)
end

@testset "SVHP, AbsAlg, z-axis, phys. Hermite poly          " begin
    @test H_3z == HermitePolyTensor.SVHP(3, be = "aa", ax = 3, st = false)
    @test H_6z == HermitePolyTensor.SVHP(6, be = "aa", ax = 3, st = false)
    @test H_9z == HermitePolyTensor.SVHP(9, be = "aa", ax = 3, st = false)
end

# Hermite Polynomial Tensor Component Tests
#------------------------------------------

@testset "HTC, AbsAlg, Factorial, 3D, prob. Hermite T. Comp." begin
    for X in [(He3x, 3), (He6x, 6), (He9x, 9)]
        for Y in [(He3y, 3), (He6y, 6), (He9y, 9)]
            for Z in [(He3z, 3), (He6z, 6), (He9z, 9)]
                raw = "x"^X[2] * "y"^Y[2] * "z"^Z[2]    # raw (ordered) index set
                idx = join(Random.shuffle([i for i in raw])) # shuffled index set
                @test (
                    (X[1] * Y[1] * Z[1]) ==
                    HermitePolyTensor.HTC(idx, D = 3, be = "aa", st = true)
                )
            end
        end
    end
end

@testset "HTC, AbsAlg, Factorial, 3D, phys. Hermite T. Comp." begin
    for X in [(H_3x, 3), (H_6x, 6), (H_9x, 9)]
        for Y in [(H_3y, 3), (H_6y, 6), (H_9y, 9)]
            for Z in [(H_3z, 3), (H_6z, 6), (H_9z, 9)]
                raw = "x"^X[2] * "y"^Y[2] * "z"^Z[2]    # raw (ordered) index set
                idx = join(Random.shuffle([i for i in raw])) # shuffled index set
                @test (
                    (X[1] * Y[1] * Z[1]) ==
                    HermitePolyTensor.HTC(idx, D = 3, be = "aa", st = false)
                )
            end
        end
    end
end

#------------------------------------------------------------------------------#
#                     Hermite Polynomial Tensor (HT) Tests                     #
#------------------------------------------------------------------------------#

function nChooseK(n, k)
    return Int(factorial(big(n)) / (factorial(big(k)) * factorial(big(n - k))))
end

function nMultiChooseK(n, k)
    return nChooseK(n + k - 1, n)
end

@testset "HT, unique tensor components                      " begin
    for theD in 1:3
        for theN in 1:10
            @test (
                length(HermitePolyTensor.HT(theN, D = theD)) ==
                nMultiChooseK(theN, theD)
            )
        end
    end
end

function nMultiPerms(M)
    return Int(factorial(big(sum(M))) / prod(map(factorial, map(big, M))))
end

@testset "HT, unique component multiplicities               " begin
    for theD in 1:3
        for theN in 1:10
            H = HermitePolyTensor.HT(theN, D = theD)
            for id in keys(H)
                xc = count(i -> (i == 'x'), id)
                yc = theD > 1 ? count(i -> (i == 'y'), id) : 0
                zc = theD > 1 ? count(i -> (i == 'z'), id) : 0
                @test length(H[id].set) == nMultiPerms((xc, yc, zc))
            end
        end
    end
end

@testset "HT, total tensor components                       " begin
    for theD in 1:3
        for theN in 1:10
            @test (
                sum(length(k.set)
                    for k in values(HermitePolyTensor.HT(theN, D = theD))
                ) ==
                theD^theN
            )
        end
    end
end


module CellDofsTests

using Test
using FillArrays
using Gridap.Helpers
using Gridap.Fields
using Gridap.ReferenceFEs
using Gridap.Arrays
using Gridap.Integration
using Gridap.CellData
using Gridap.TensorValues
using Gridap.Geometry
using LinearAlgebra

domain = (0,1,0,1,0,1)
cells = (2,2,2)
model = simplexify(CartesianDiscreteModel(domain,cells))

trian = Triangulation(model)

v = GenericCellField(get_cell_shapefuns(trian),trian,ReferenceDomain())

ctype_to_reffe, cell_to_ctype = compress_cell_data(get_cell_reffe(trian))
ctype_to_dofbasis = map(get_dof_basis,ctype_to_reffe)
cell_dofbasis = expand_cell_data(ctype_to_dofbasis,cell_to_ctype)

s = CellDof(cell_dofbasis,trian,ReferenceDomain())

a = s(v)
r = fill(Matrix(I,4,4),num_cells(trian))
test_array(a,r)

acell_to_cell = [1,3,5,3]
atrian = Triangulation(trian,acell_to_cell)
s_a = change_domain(s,atrian,DomainStyle(s))
v_a = change_domain(v,atrian,DomainStyle(v))

a = s_a(v_a)
r = fill(Matrix(I,4,4),num_cells(atrian))
test_array(a,r)

end # module


# rotate quaternion
quaternion_rotate(q1::Quaternion,q2::Quaternion) = q2 * q1 / q2

# rotate vector
vector_rotate(v::AbstractVector,q::Quaternion) = Vmat(quaternion_rotate(Quaternion(v), q))
∂vector_rotate∂q(p::AbstractVector, q::Quaternion) = VLmat(q) * Lmat(Quaternion(p)) * Tmat() + VRᵀmat(q) * Rmat(Quaternion(p))

# rotate matrix
function matrix_rotate(A::AbstractMatrix,q::Quaternion)
    c1 = Vmat(quaternion_rotate(Quaternion(A[SVector{3}(1,2,3)]), q))
    c2 = Vmat(quaternion_rotate(Quaternion(A[SVector{3}(4,5,6)]), q))
    c3 = Vmat(quaternion_rotate(Quaternion(A[SVector{3}(7,8,9)]), q))

    return [c1 c2 c3]
end

# rotation matrix
rotation_matrix(q::Quaternion) = VRᵀmat(q) * LVᵀmat(q)
function ∂rotation_matrix∂q(q::Quaternion, p::AbstractVector{T}) where T #; attjac::Bool=false) where T
    M = ∂VRᵀmat∂q(LVᵀmat(q) * p) + VRᵀmat(q) * ∂LVᵀmat∂q(p)
    # if attjac
    #     return (M * LVᵀmat(q))::SMatrix{3,3,T,9}
    # else
    #     return M::SMatrix{3,4,T,12}
    # end
end
function ∂rotation_matrix_inv∂q(q::Quaternion, p::AbstractVector{T}) where T #; attjac::Bool=false) where T
    M = ∂rotation_matrix∂q(inv(q), p) * Tmat()
    # if attjac
    #     return (M * LVᵀmat(q))::SMatrix{3,3,T,9}
    # else
    #     return M::SMatrix{3,4,T,12}
    # end
end


using Revise
using ADCME
using ADCMEKit
using NNFEM
using PyPlot
using ProgressMeter

#----------------------------------------------------------------------------
# step 1: create a computational domain
# fixed on the bottom and push from right

m = 40
n = 20
h = 1/n
NT = 50
Δt = 0.01/NT

elements = []
# prop = Dict("name"=> "PlaneStrain", "rho"=> 2000, "E"=> 1e10, "nu"=> 0.35)
prop = Dict("name"=> "ViscoelasticityMaxwell", "rho"=> 2000, "E"=> 1e10, "nu"=> 0.35, "eta"=>1e5)

coords = zeros((m+1)*(n+1), 2)
for j = 1:n
    for i = 1:m
        idx = (m+1)*(j-1)+i
        elnodes = [idx; idx+1; idx+1+m+1; idx+m+1]
        ngp = 2
        nodes = [
        (i-1)*h (j-1)*h
        i*h (j-1)*h
        i*h j*h
        (i-1)*h j*h
        ]
        coords[elnodes, :] = nodes
        push!(elements, SmallStrainContinuum(nodes, elnodes, prop, ngp))
    end
end

# fixed on the bottom, push on the right
EBC = zeros(Int64, (m+1)*(n+1), 2)
FBC = zeros(Int64, (m+1)*(n+1), 2)
g = zeros((m+1)*(n+1), 2)
f = zeros((m+1)*(n+1), 2)

for i = 1:m+1
    EBC[i+n*(m+1),:] .= -1
end

Edge_Traction_Data = Array{Int64}[]
for i = 1:n
  elem = elements[(i-1)*m + m]
  for k = 1:4
    if elem.coords[k,1]>2-0.001 && elem.coords[k+1>4 ? 1 : k+1,1]>2-0.001
      push!(Edge_Traction_Data, [(i-1)*m + m, k, 1])
    end
  end
end
Edge_Traction_Data = hcat(Edge_Traction_Data...)'|>Array

ndims = 2
domain = Domain(coords, elements, ndims, EBC, g, FBC, f, Edge_Traction_Data)
globaldata = example_global_data(domain)

# visualize_boundary(domain)
#----------------------------------------------------------------------------
# step 2: Simulation

Dstate = zeros(domain.neqs)
state = zeros(domain.neqs)
velo = zeros(domain.neqs)
acce = zeros(domain.neqs)
EBC_func = nothing
FBC_func = nothing
Body_func = nothing
function Edge_func(x, y, t, idx)
  return [-1e6*ones(length(x)) zeros(length(x))]
end

globaldata = GlobalData(state, Dstate, velo, acce, domain.neqs, EBC_func, FBC_func,Body_func, Edge_func)
assembleMassMatrix!(globaldata, domain)

@showprogress for i = 1:NT
    global globaldata, domain = GeneralizedAlphaSolverStep(globaldata, domain, Δt)
end

#----------------------------------------------------------------------------
# step 3: visualize
d_ = hcat(domain.history["state"]...)'|>Array
# visualize_von_mises_stress_on_scoped_body(d_, domain, scale_factor=10.0)
p = visualize_total_deformation_on_scoped_body(d_, domain, scale_factor=10.0)
saveanim(p, "julia.gif")
close("all")

#----------------------------------------------------------------------------
# step 4: verification with AdFem


function linmin2( pw, Vpot, α_t, psi, d, g )

    psic = ortho_gram_schmidt(psi + α_t*d)
    gt = calc_grad( pw, Vpot, psic )
    denum = real(sum(conj(g-gt).*d))
    if denum != 0.0
      α = abs(α_t*real(sum(conj(g).*d))/denum)
    else
      α = 0.0
    end

    return α

end


function Mdot(m, v::Array{Float64, 1})
  w = ones(size(m, 1), size(m, 2))
  for i in 1:size(m, 1)
    for j in 1:size(m, 2)
      w[i, j] = (m[i, j, :]'*v)[1]
    end
  end
  return w
end