# # Computational homogenization
#
# ![](rve_homogenization.png)
#
# *Figure 1*: von Mises stress in an RVE with 5 stiff inclusions embedded in a softer matrix
# material that is loaded in shear. The problem is solved by using homogeneous Dirichlet
# boundary conditions (left) and (strong) periodic boundary conditions (right).
#
#-
#md # !!! tip
#md #     This example is also available as a Jupyter notebook:
#md #     [`computational_homogenization.ipynb`](@__NBVIEWER_ROOT_URL__/examples/computational_homogenization.ipynb).
#-
#
# ## Introduction
#
# In this example we will solve the Representative Volume Element (RVE) problem for
# computational homogenization of linear elasticity and compute the effective/homogenized
# stiffness of an RVE with 5 stiff circular inclusions embedded in a softer matrix material
# (see Figure 1).
#
# It is possible to obtain upper and lower bounds on the stiffness analytically, see for
# example [Rule of mixtures](https://en.wikipedia.org/wiki/Rule_of_mixtures). An upper
# bound is obtained from the Voigt model, where the *strain* is assumed to be the same in
# the two constituents,
#
# ```math
# \mathsf{E}_\mathrm{Voigt} = v_\mathrm{m} \mathsf{E}_\mathrm{m} +
# (1 - v_\mathrm{m}) \mathsf{E}_\mathrm{i}
# ```
#
# where ``v_\mathrm{m}`` is the volume fraction of the matrix material, and where
# ``\mathsf{E}_\mathrm{m}`` and ``\mathsf{E}_\mathrm{i}`` are the individual stiffness for
# the matrix material and the inclusions, respectively. The lower bound is obtained from
# the Reuss model, where the *stress* is assumed to be the same in the two constituents,
#
# ```math
# \mathsf{E}_\mathrm{Reuss} = \left(v_\mathrm{m} \mathsf{E}_\mathrm{m}^{-1} +
# (1 - v_\mathrm{m}) \mathsf{E}_\mathrm{i}^{-1} \right)^{-1}.
# ```
#
# However, neither of these assumptions are, in general, very close to the "truth" which is
# why it is of interest to computationally find the homogenized properties for a given RVE.
#
# The canonical version of the RVE problem can be formulated as follows:
# For given homogenized field ``\bar{\boldsymbol{u}}``, ``\bar{\boldsymbol{\varepsilon}} =
# \boldsymbol{\varepsilon}[\bar{\boldsymbol{u}}]``, find ``\boldsymbol{u} \in
# \mathbb{U}_\Box``, ``\boldsymbol{t} \in \mathbb{T}_\Box`` such that
#
# ```math
# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}]
# : \mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega
# - \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{u} \cdot
# \boldsymbol{t}\ \mathrm{d}\Gamma = 0 \quad
# \forall \delta \boldsymbol{u} \in \mathbb{U}_\Box,\quad (1\mathrm{a})\\
# - \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{t} \cdot
# \boldsymbol{u}\ \mathrm{d}\Gamma = - \bar{\boldsymbol{\varepsilon}} :
# \left[ \frac{1}{|\Omega_\Box|} \int_{\Gamma_\Box}\delta \boldsymbol{t} \otimes
# [\boldsymbol{x} - \bar{\boldsymbol{x}}]\ \mathrm{d}\Gamma \right]
# \quad \forall \delta \boldsymbol{t} \in \mathbb{T}_\Box, \quad (1\mathrm{b})
# ```
#
# where ``\boldsymbol{u} = \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} -
# \bar{\boldsymbol{x}}] + \boldsymbol{u}^\mu``, where ``\Omega_\Box`` and ``|\Omega_\Box|``
# are the domain and volume of the RVE, where ``\Gamma_\Box`` is the boundary, and where
# ``\mathbb{U}_\Box``, ``\mathbb{T}_\Box`` are set of "sufficiently regular" functions
# defined on the RVE.
#
# This system is not solvable without introducing extra restrictions on ``\mathbb{U}_\Box``,
# ``\mathbb{T}_\Box``. In this example we will consider the common cases of Dirichlet
# boundary conditions and (strong) periodic boundary conditions.
#
# **Dirichlet boundary conditions**
#
# We can introduce the more restrictive sets of ``\mathbb{U}_\Box``:
#
# ```math
# \begin{align*}
# \mathbb{U}_\Box^\mathrm{D} &:= \left\{\boldsymbol{u} \in \mathbb{U}_\Box|\ \boldsymbol{u}
# = \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}]
# \ \mathrm{on}\ \Gamma_\Box\right\},\\
# \mathbb{U}_\Box^{\mathrm{D},0} &:= \left\{\boldsymbol{u} \in \mathbb{U}_\Box|\ \boldsymbol{u}
# = \boldsymbol{0}\ \mathrm{on}\ \Gamma_\Box\right\},
# \end{align*}
# ```
#
# and use these as trial and test sets to obtain a solvable RVE problem pertaining to
# Dirichlet boundary conditions. Eq. ``(1\mathrm{b})`` is trivially fulfilled, the second
# term of Eq. ``(1\mathrm{a})`` vanishes, and we are left with the following problem:
# Find ``\boldsymbol{u} \in \mathbb{U}_\Box^\mathrm{D}`` that solve
#
# ```math
# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}]
# : \mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega = 0
# \quad \forall \delta \boldsymbol{u} \in \mathbb{U}_\Box^{\mathrm{D},0}.
# ```
#
# Note that, since ``\boldsymbol{u} = \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} -
# \bar{\boldsymbol{x}}] + \boldsymbol{u}^\mu``, this problem is equivalent to solving for
# ``\boldsymbol{u}^\mu \in \mathbb{U}_\Box^{\mathrm{D},0}``, which is what we will do in
# the implementation.
#
# **Periodic boundary conditions**
#
# The RVE problem pertaining to periodic boundary conditions is obtained by restricting
# ``\boldsymbol{u}^\mu`` to be periodic, and ``\boldsymbol{t}`` anti-periodic across the
# RVE. Similarly as for Dirichlet boundary conditions, Eq. ``(1\mathrm{b})`` is directly
# fulfilled, and the second term in Eq. ``(1\mathrm{a})`` vanishes, with these restrictions,
# and we are left with the following problem:
# Find ``\boldsymbol{u}^\mu \in \mathbb{U}_\Box^{\mathrm{P},0}`` such that
#
# ```math
# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}\boldsymbol{\varepsilon}[\delta\boldsymbol{u}]
# : \mathsf{E} : (\bar{\boldsymbol{\varepsilon}} + \boldsymbol{\varepsilon}
# [\boldsymbol{u}^\mu])\ \mathrm{d}\Omega = 0
# \quad \forall \delta \boldsymbol{u} \in \mathbb{U}_\Box^{\mathrm{P},0},
# ```
#
# where
#
# ```math
# \mathbb{U}_\Box^{\mathrm{P},0} := \left\{\boldsymbol{u} \in \mathbb{U}_\Box|
# \ \llbracket \boldsymbol{u} \rrbracket_\Box = \boldsymbol{0}
# \ \mathrm{on}\ \Gamma_\Box^+\right\}
# ```
#
# where ``\llbracket \bullet \rrbracket_\Box = \bullet(\boldsymbol{x}^+) -
# \bullet(\boldsymbol{x}^-)`` defines the "jump" over the RVE, i.e. the difference between
# the value on the image part ``\Gamma_\Box^+`` (coordinate ``\boldsymbol{x}^+``) and the
# mirror part ``\Gamma_\Box^-`` (coordinate ``\boldsymbol{x}^-``) of the boundary.
# To make sure this restriction holds in a strong sense we need a periodic mesh.
#
# Note that it would be possible to solve for the total ``\boldsymbol{u}`` directly by
# instead enforcing the jump to be equal to the jump in the macroscopic part,
# ``\boldsymbol{u}^\mathrm{M}``, i.e.
#
# ```math
# \llbracket \boldsymbol{u} \rrbracket_\Box =
# \llbracket \boldsymbol{u}^\mathrm{M} \rrbracket_\Box =
# \llbracket \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x} - \bar{\boldsymbol{x}}]
# \rrbracket_\Box =
# \bar{\boldsymbol{\varepsilon}} \cdot [\boldsymbol{x}^+ - \boldsymbol{x}^-].
# ```
#
# **Homogenization of effective properties**
#
# In general it is necessary to compute the homogenized stress and the stiffness on the fly,
# but since we in this example consider linear elasticity it is possible to compute the
# effective properties once and for all for a given RVE configuration. We do this by
# computing sensitivity fields for every independent strain component (6 in 3D, 3 in 2D).
# Thus, for a 2D problem, as in the implementation below, we compute sensitivities
# ``\hat{\boldsymbol{u}}_{11}``, ``\hat{\boldsymbol{u}}_{22}``, and
# ``\hat{\boldsymbol{u}}_{12} = \hat{\boldsymbol{u}}_{21}`` by using
#
# ```math
# \bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}1 & 0\\ 0 & 0\end{pmatrix}, \quad
# \bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}0 & 0\\ 0 & 1\end{pmatrix}, \quad
# \bar{\boldsymbol{\varepsilon}} = \begin{pmatrix}0 & 0.5\\ 0.5 & 0\end{pmatrix}
# ```
#
# as the input to the RVE problem. When the sensitivies are solved we can compute the
# entries of the homogenized stiffness as follows
#
# ```math
# \mathsf{E}_{ijkl} = \frac{\partial\ \bar{\sigma}_{ij}}{\partial\ \bar{\varepsilon}_{kl}}
# = \bar{\sigma}_{ij}(\hat{\boldsymbol{u}}_{kl}),
# ```
#
# where the homogenized stress, ``\bar{\boldsymbol{\sigma}}(\boldsymbol{u})``, is computed
# as the volume average of the stress in the RVE, i.e.
#
# ```math
# \bar{\boldsymbol{\sigma}}(\boldsymbol{u}) :=
# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box} \boldsymbol{\sigma}\ \mathrm{d}\Omega =
# \frac{1}{|\Omega_\Box|} \int_{\Omega_\Box}
# \mathsf{E} : \boldsymbol{\varepsilon}[\boldsymbol{u}]\ \mathrm{d}\Omega.
# ```

# ## Commented program
#
# Now we will see how this can be implemented in `Ferrite`. What follows is a program
# with comments in between which describe the different steps.
#md # You can also find the same program without comments at the end of the page,
#md # see [Plain program](@ref homogenization-plain-program).

using Ferrite, SparseArrays, LinearAlgebra
using Test #src

# We first load the mesh file [`periodic-rve.msh`](periodic-rve.msh)
# ([`periodic-rve-coarse.msh`](periodic-rve-coarse.msh) for a coarser mesh). The mesh is
# generated with [`gmsh`](https://gmsh.info/), and we read it in as a `Ferrite` grid using
# the [`FerriteGmsh`](https://github.com/Ferrite-FEM/FerriteGmsh.jl) package:

using FerriteGmsh
#src notebook: use coarse mesh to decrease build time
#src   script: use the fine mesh
#src markdown: use the coarse mesh to decrease build time, but make it look like the fine
#nb ## grid = saved_file_to_grid("periodic-rve.msh")
#nb grid = saved_file_to_grid("periodic-rve-coarse.msh")
#jl ## grid = saved_file_to_grid("periodic-rve-coarse.msh")
#jl grid = saved_file_to_grid("periodic-rve.msh")
#md grid = saved_file_to_grid("periodic-rve.msh")
#-
#md grid = redirect_stdout(devnull) do                #hide
#md     saved_file_to_grid("periodic-rve-coarse.msh") #hide
#md end                                               #hide

grid = saved_file_to_grid("periodic-rve.msh") #src

# Next we construct the interpolation and quadrature rule, and combining them into
# cellvalues as usual:

dim = 2
ip = Lagrange{dim, RefTetrahedron, 1}()
qr = QuadratureRule{dim, RefTetrahedron}(2)
cellvalues = CellVectorValues(qr, ip);

# We define a dof handler with a displacement field `:u`:
dh = DofHandler(grid)
push!(dh, :u, 2)
close!(dh);

# Now we need to define boundary conditions. As discussed earlier we will solve the problem
# using (i) homogeneous Dirichlet boundary conditions, and (ii) periodic Dirichlet boundary
# conditions. We construct two different constraint handlers, one for each case. The
# [`Dirichlet`](@ref) boundary condition we have seen in many other examples. Here we simply
# define the condition that the field, `:u`, should have both components prescribed to `0`
# on the full boundary:

ch_dirichlet = ConstraintHandler(dh)
dirichlet = Dirichlet(
    :u,
    union(getfaceset.(Ref(grid), ["left", "right", "top", "bottom"])...),
    (x, t) ->  [0, 0],
    [1, 2]
)
add!(ch_dirichlet, dirichlet)
close!(ch_dirichlet)
update!(ch_dirichlet, 0.0)

# For periodic boundary conditions we use the [`PeriodicDirichlet`](@ref) constraint type,
# which is very similar to the `Dirichlet` type, but instead of a passing a faceset we pass
# a vector with "face pairs", i.e. the mapping between mirror and image parts of the
# boundary. In this example the `"left"` and `"bottom"` boundaries are mirrors, and the
# `"right"` and `"top"` boundaries are the mirrors.

ch_periodic = ConstraintHandler(dh);
periodic = PeriodicDirichlet(
    :u,
    ["left" => "right", "bottom" => "top"],
    [1, 2]
)
add!(ch_periodic, periodic)
close!(ch_periodic)
update!(ch_periodic, 0.0)

# This will now constrain any degrees of freedom located on the mirror boundaries to
# the matching degree of freedom on the image boundaries. Internally this will create
# a number of [`AffineConstraint`](@ref)s of the form `u_i = 1 * u_j + 0`:
# ```julia
# a = AffineConstraint(u_m, [u_i => 1], 0)
# ```
# where `u_m` is the degree of freedom on the mirror and `u_i` the matching one on the
# image part. `PeriodicDirichlet` is thus simply just a more convenient way of
# constructing such affine constraints since it computes the degree of freedom mapping
# automatically.
#
# To simplify things we group the constraint handlers into a named tuple

ch = (dirichlet = ch_dirichlet, periodic = ch_periodic);

# We can now construct the sparse matrix. Note that, since we are using affine constraints,
# which need to modify the matrix sparsity pattern in order to account for the constraint
# equations, we construct the matrix for the periodic case by passing both the dof handler
# and the constraint handler.

K = (
    dirichlet = create_sparsity_pattern(dh),
    periodic  = create_sparsity_pattern(dh, ch.periodic),
);

# We define the fourth order elasticity tensor for the matrix material, and define the
# inclusions to have 10 times higher stiffness

λ, μ = 1e10, 7e9 # Lamé parameters
δ(i,j) = i == j ? 1.0 : 0.0
Em = SymmetricTensor{4, 2}(
    (i,j,k,l) -> λ * δ(i,j) * δ(k,l) + μ * (δ(i,k) * δ(j,l) + δ(i,l) * δ(j,k))
)
Ei = 10 * Em;

# As mentioned above, in order to compute the apparent/homogenized stiffness we will solve
# the problem repeatedly with different macroscale strain tensors to compute the sensitvity
# of the homogenized stress, ``\bar{\boldsymbol{\sigma}}``, w.r.t. the macroscopic strain,
# ``\bar{\boldsymbol{\varepsilon}}``. The corresponding unit strains are defined below,
# and will result in three different right-hand-sides:

εᴹ = [
      SymmetricTensor{2,2}([1.0 0.0; 0.0 0.0]), # ε_11 loading
      SymmetricTensor{2,2}([0.0 0.0; 0.0 1.0]), # ε_22 loading
      SymmetricTensor{2,2}([0.0 0.5; 0.5 0.0]), # ε_12/ε_21 loading
];

# The assembly function is nothing strange, and in particular there is no impact from the
# choice of boundary conditions, so the same function can be used for both cases. Since
# we want to solve the system 3 times, once for each macroscopic strain component, we
# assemble 3 right-hand-sides.

function doassemble!(cellvalues::CellVectorValues, K::SparseMatrixCSC, dh::DofHandler, εᴹ)

    n_basefuncs = getnbasefunctions(cellvalues)
    ndpc = ndofs_per_cell(dh)
    Ke = zeros(ndpc, ndpc)
    fe = zeros(ndpc, length(εᴹ))
    f = zeros(ndofs(dh), length(εᴹ))
    assembler = start_assemble(K)

    for cell in CellIterator(dh)

        E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em
        reinit!(cellvalues, cell)
        fill!(Ke, 0)
        fill!(fe, 0)

        for q_point in 1:getnquadpoints(cellvalues)
            dΩ = getdetJdV(cellvalues, q_point)
            for i in 1:n_basefuncs
                δεi = shape_symmetric_gradient(cellvalues, q_point, i)
                for j in 1:n_basefuncs
                    δεj = shape_symmetric_gradient(cellvalues, q_point, j)
                    Ke[i, j] += (δεi ⊡ E ⊡ δεj) * dΩ
                end
                for (rhs, ε) in enumerate(εᴹ)
                    σᴹ = E ⊡ ε
                    fe[i, rhs] += ( - δεi ⊡ σᴹ) * dΩ
               end
            end
        end

        cdofs = celldofs(cell)
        assemble!(assembler, cdofs, Ke)
        f[cdofs, :] .+= fe
    end
    return f
end;

# We can now assemble the system. The assembly function modifies the matrix in-place, but
# return the right hand side(s) which we collect in another named tuple.

rhs = (
    dirichlet = doassemble!(cellvalues, K.dirichlet, dh, εᴹ),
    periodic  = doassemble!(cellvalues, K.periodic,  dh, εᴹ),
);

# The next step is to solve the systems. Since application of boundary conditions, using
# the [`apply!`](@ref) function, modifies both the matrix and the right hand sides we can
# not use it directly in this case since we want to reuse the matrix again for the next
# right hand sides. We could of course re-assemble the matrix for every right hand side,
# but that would not be very efficient. Instead we will use the [`get_rhs_data`](@ref)
# function, together with [`apply_rhs!`](@ref) in a later step. This will extract the
# necessary data from the matrix such that we can apply it for all the different right
# hand sides. Note that we call `apply!` with just the matrix and no right hand side.

rhsdata = (
    dirichlet = get_rhs_data(ch.dirichlet, K.dirichlet),
    periodic  = get_rhs_data(ch.periodic,  K.periodic),
)

apply!(K.dirichlet, ch.dirichlet)
apply!(K.periodic,  ch.periodic)

# We can now solve the problem(s). Note that we only use `apply_rhs!` in the loops below.
# The boundary conditions are already applied to the matrix above, so we only need to
# modify the right hand side.

u = (
    dirichlet = Vector{Float64}[],
    periodic  = Vector{Float64}[],
)

for i in 1:size(rhs.dirichlet, 2)
    rhs_i = @view rhs.dirichlet[:, i]                  # Extract this RHS
    apply_rhs!(rhsdata.dirichlet, rhs_i, ch.dirichlet) # Apply BC
    u_i = cholesky(Symmetric(K.dirichlet)) \ rhs_i     # Solve
    apply!(u_i, ch.dirichlet)                          # Apply BC on the solution
    push!(u.dirichlet, u_i)                            # Save the solution vector
end

for i in 1:size(rhs.periodic, 2)
    rhs_i = @view rhs.periodic[:, i]                   # Extract this RHS
    apply_rhs!(rhsdata.periodic, rhs_i, ch.periodic)   # Apply BC
    u_i = cholesky(Symmetric(K.periodic)) \ rhs_i      # Solve
    apply!(u_i, ch.periodic)                           # Apply BC on the solution
    push!(u.periodic, u_i)                             # Save the solution vector
end

# When the solution(s) are known we can compute the averaged stress,
# ``\bar{\boldsymbol{\sigma}}`` in the RVE. We define a function that does this, and also
# returns the von Mise stress in every quadrature point for visualization.

function compute_stress(cellvalues::CellVectorValues, dh::DofHandler, u, εᴹ)
    σvM_qpdata = zeros(getnquadpoints(cellvalues), getncells(dh.grid))
    σ̄Ω = zero(SymmetricTensor{2,2})
    Ω = 0.0 # Total volume
    for cell in CellIterator(dh)
        E = cellid(cell) in getcellset(dh.grid, "inclusions") ? Ei : Em
        reinit!(cellvalues, cell)
        for q_point in 1:getnquadpoints(cellvalues)
            dΩ = getdetJdV(cellvalues, q_point)
            εμ = function_symmetric_gradient(cellvalues, q_point, u[celldofs(cell)])
            σ = E ⊡ (εᴹ + εμ)
            σvM_qpdata[q_point, cellid(cell)] = sqrt(3/2 * dev(σ) ⊡ dev(σ))
            Ω += dΩ # Update total volume
            σ̄Ω += σ * dΩ # Update integrated stress
        end
    end
    σ̄ = σ̄Ω / Ω
    return σvM_qpdata, σ̄
end;

# We now compute the homogenized stress and von Mise stress for all cases

σ̄ = (
    dirichlet = SymmetricTensor{2,2}[],
    periodic  = SymmetricTensor{2,2}[],
)
σ = (
     dirichlet = Vector{Float64}[],
     periodic  = Vector{Float64}[],
)

projector = L2Projector(ip, grid)

for i in 1:3
    σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.dirichlet[i], εᴹ[i])
    proj = project(projector, σ_qp, qr; project_to_nodes=false)
    push!(σ.dirichlet, proj)
    push!(σ̄.dirichlet, σ̄_i)
end

for i in 1:3
    σ_qp, σ̄_i = compute_stress(cellvalues, dh, u.periodic[i], εᴹ[i])
    proj = project(projector, σ_qp, qr; project_to_nodes=false)
    push!(σ.periodic, proj)
    push!(σ̄.periodic, σ̄_i)
end

# The remaining thing is to compute the homogenized stiffness. As mentioned in the
# introduction we can find all the components from the average stress of the sensitivity
# fields that we have solved for
#
# ```math
# \mathsf{E}_{ijkl} = \bar{\sigma}_{ij}(\hat{\boldsymbol{u}}_{kl}).
# ```
#
# So we have now already computed all the components, and just need to gather the data in
# a fourth order tensor:

E_dirichlet = SymmetricTensor{4,2}((i, j, k, l) -> begin
    if k == l == 1
        σ̄.dirichlet[1][i, j] # ∂σ∂ε_**11
    elseif k == l == 2
        σ̄.dirichlet[2][i, j] # ∂σ∂ε_**22
    else
        σ̄.dirichlet[3][i, j] # ∂σ∂ε_**12 and ∂σ∂ε_**21
    end
end)

E_periodic = SymmetricTensor{4,2}((i, j, k, l) -> begin
    if k == l == 1
        σ̄.periodic[1][i, j]
    elseif k == l == 2
        σ̄.periodic[2][i, j]
    else
        σ̄.periodic[3][i, j]
    end
end);

# We can check that the result are what we expect, namely that the stiffness with Dirichlet
# boundary conditions is higher than when using periodic boundary conditions, and that
# the Reuss assumption is an lower bound, and the Voigt assumption a upper bound. We first
# compute the volume fraction of the matrix, and then the Voigt and Reuss bounds:

function matrix_volume_fraction(grid, cellvalues)
    V  = 0.0 # Total volume
    Vm = 0.0 # Volume of the matrix
    for c in CellIterator(grid)
        reinit!(cellvalues, c)
        is_matrix = !(cellid(c) in getcellset(grid, "inclusions"))
        for qp in 1:getnquadpoints(cellvalues)
            dΩ = getdetJdV(cellvalues, qp)
            V += dΩ
            if is_matrix
                Vm += dΩ
            end
        end
    end
    return Vm / V
end

vm = matrix_volume_fraction(grid, cellvalues)
#-
E_voigt = vm * Em + (1-vm) * Ei
E_reuss = inv(vm * inv(Em) + (1-vm) * inv(Ei));

# We can now compare the different computed stiffness tensors. We expect
# ``E_\mathrm{Reuss} \leq E_\mathrm{PeriodicBC} \leq E_\mathrm{DirichletBC} \leq
# E_\mathrm{Voigt}``. A simple thing to compare are the eigenvalues of the tensors. Here
# we look at the first eigenvalue:

ev = (first ∘ eigvals).((E_reuss, E_periodic, E_dirichlet, E_voigt))
@test issorted(ev) #src
round.(ev; digits=-8)

# Finally, we export the solution and the stress field to a VTK file. For the export we
# also compute the macroscopic part of the displacement.

chM = ConstraintHandler(dh)
add!(chM, Dirichlet(:u, Set(1:getnnodes(grid)), (x, t) -> εᴹ[Int(t)] ⋅ x, [1, 2]))
close!(chM)
uM = zeros(ndofs(dh))

vtk_grid("homogenization", dh) do vtk
    for i in 1:3
        ## Compute macroscopic solution
        update!(chM, i)
        apply!(uM, chM)
        ## Dirichlet
        vtk_point_data(vtk, dh, uM + u.dirichlet[i], "_dirichlet_$i")
        vtk_point_data(vtk, projector, σ.dirichlet[i], "σvM_dirichlet_$i")
        ## Periodic
        vtk_point_data(vtk, dh, uM + u.periodic[i], "_periodic_$i")
        vtk_point_data(vtk, projector, σ.periodic[i], "σvM_periodic_$i")
    end
end;

# Just another way to compute the stiffness for testing purposes               #src
function homogenize_test(u::Matrix, dh, cv, E_incl, E_mat)                     #src
    ĒΩ = zero(SymmetricTensor{4,2})                                            #src
    Ω = 0.0                                                                    #src
    ue = zeros(ndofs_per_cell(dh), 3)                                          #src
    for cell in CellIterator(dh)                                               #src
        reinit!(cv, cell)                                                      #src
        for (localdof, globaldof) in enumerate(celldofs(cell))                 #src
            for i in 1:3                                                       #src
                ue[localdof, i] = u[globaldof, i]                              #src
            end                                                                #src
        end                                                                    #src
        E = cellid(cell) in getcellset(dh.grid, "inclusions") ? E_incl : E_mat #src
        for qp in 1:getnquadpoints(cv)                                         #src
            dΩ = getdetJdV(cv, qp)                                             #src
            Ω += dΩ                                                            #src
            ## compute u^ij and u^kl                                           #src
            Ē′ = SymmetricTensor{4,2}((i, j, k, l) -> begin                    #src
                ij = i == j == 1 ? 1 : i == j == 2 ? 2 : 3                     #src
                kl = k == l == 1 ? 1 : k == l == 2 ? 2 : 3                     #src
                εij = function_symmetric_gradient(cv, qp, view(ue, :, ij)) +   #src
                          symmetric((basevec(Vec{2}, i) ⊗ basevec(Vec{2}, j))) #src
                εkl = function_symmetric_gradient(cv, qp, view(ue, :, kl)) +   #src
                          symmetric((basevec(Vec{2}, k) ⊗ basevec(Vec{2}, l))) #src
                return (εij ⊡ E ⊡ εkl) * dΩ                                    #src
            end)                                                               #src
            ĒΩ += Ē′                                                           #src
        end                                                                    #src
    end                                                                        #src
    return ĒΩ / Ω                                                              #src
end                                                                            #src

@test homogenize_test(reduce(hcat, u.dirichlet), dh, cellvalues, Ei, Em) ≈ E_dirichlet #src
@test homogenize_test(reduce(hcat, u.periodic), dh, cellvalues, Ei, Em) ≈ E_periodic #src

#md # ## [Plain program](@id homogenization-plain-program)
#md #
#md # Here follows a version of the program without any comments.
#md # The file is also available here:
#md # [`computational_homogenization.jl`](computational_homogenization.jl).
#md #
#md # ```julia
#md # @__CODE__
#md # ```


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.3'
#       jupytext_version: 0.8.6
#   kernelspec:
#     display_name: Julia 1.0.3
#     language: julia
#     name: julia-1.0
# ---

using DifferentialEquations
using Random
using DataFrames
using Distributions

function continuous_time_SIR(β,γ,N,S0,I0,R0,tf)
    #Temp Variables for Time, Susceptible Individuals, Infected Individuals, Recovery
    t = 0
    S = S0
    I = I0
    R = R0
    #Array to store SIR and Time
    ta= Float64[]
    Sa= Float64[]
    Ia= Float64[]
    Ra= Float64[]

    while t < tf
        push!(ta,t)
        push!(Sa,S)
        push!(Ia,I)
        push!(Ra,R)
        pf1 = β*S*I
        pf2 = γ*I
        pf = pf1+pf2
        dt = rand(Exponential(1/pf))
        t = t+dt
        if t>tf
            break
        end
        ru = rand()
        if ru<(pf1/pf)
            S=S-1
            I=I+1
        else
            I=I-1
            R=R+1
        end
    end
    results = DataFrame()
    results[:time] = ta
    results[:S] = Sa
    results[:I] = Ia
    results[:R] = Ra
    return(results)
end

function main()
    Random.seed!(42);

    #continuous_time_SIR(Infected Rate, Recover Rate, Sample Size ,
    #Initial Susceptible Individuals, Initial Infected Rate, Initial Recovery Rate, Max Time Increment)
    sir_out = continuous_time_SIR(0.1/1000,0.05,1000,999,1,0,200);

    head_size = 6
    first(sir_out,head_size)

end


using Nystrom
using StaticArrays
using LinearAlgebra
using IterativeSolvers
using Plots

##
k = 3.3
pde = Maxwell(;dim=3,k)

# exact solution
G    = SingleLayerKernel(pde)
xs   = SVector(0.1,0.2,0.3)
x0   = SVector(5,5,5)
pol    = SVector(1+im,-2.,3.)
E    = (dof) -> G(dof,xs)*pol
exa  = E(x0)

# geometry and parameters
sph_radius = 2
geo = ParametricSurfaces.Sphere(;radius=sph_radius)
Ω = Geometry.Domain(geo)
Γ = boundary(Ω)

## CFIE
α = 1
β = im*k/sph_radius
order = 5         # quadrature order 1D
ndofs = Float64[]
errs = Float64[]
iterative = false;
##
for n in [4]
    M     = meshgen(Γ,(n,n))
    nmesh  = NystromMesh(view(M,Γ);order)
    N     = Nystrom.diagonal_ncross_matrix(nmesh)
    J     = Nystrom.diagonal_jacobian_matrix(nmesh)
    dualJ = Nystrom.diagonal_dualjacobian_matrix(nmesh)
    γ₀E   = γ₀(E,nmesh)
    S,D   = Nystrom.single_doublelayer_dim(pde,nmesh)
    Sm = Nystrom.materialize(S)
    Dm = Nystrom.materialize(D)
    rhs   = dualJ * ncross(γ₀E)

    @info "Assembling matrix..."
    L     = Nystrom.assemble_dim_nystrom_matrix_Luiz(nmesh, α, β, Dm, Sm)
    @info "Solving..."
    if iterative
        ϕ_coeff = gmres(L,rhs; verbose=true,maxiter=600,restart=600,abstol=1e-6)
    else
        ϕ_coeff = L \ rhs
    end
    ϕ     = J*ϕ_coeff
    Spot  = Nystrom.MaxwellEFIEPotential(pde,nmesh)
    Dpot  = Nystrom.MaxwellMFIEPotential(pde,nmesh)
    Eₐ    = (x) -> α*Dpot[ϕ](x) + β*Spot[ncross(ϕ)](x)
    er    = (Eₐ(x0) - exa)/norm(exa)

    ndof = length(Nystrom.dofs(nmesh))
    err = norm(er,Inf)

    push!(ndofs, ndof)
    push!(errs, err)
    @info "" ndof err
end

## Plot
sqrt_ndofs = sqrt.(ndofs)
fig = plot(sqrt_ndofs,errs,xscale=:log10,yscale=:log10,m=:o,label="error",lc=:black)
title = "k=$k, α=$α, β=$β, qorder=$order"
plot!(xlabel="√ndofs",ylabel="error",title=title)
for p in 1:5
    cc = errs[end]*sqrt_ndofs[end]^p
    plot!(fig,sqrt_ndofs,cc./sqrt_ndofs.^p,xscale=:log10,yscale=:log10,label="h^$p",ls=:dash)
end
display(fig)


@doc """
 COST 231- Cost-Hata Extension Model
 freq: signal frequency
 txH: base station's height
 rxH: mobile's height
 cityKind : cyte type(1-small, 2-medium e 3-large).
"""
type Cost231HataModel<:Model
  freq::Float64
  txH::Float64
  rxH::Float64
  areaKind::Int
  checkFreqRange::Bool
  Cost231HataModel() = new(1650,50.0,1.5,CityKind.Medium,true)
end

"Calculate Path loss for distance dist (km) with COST 231 Hata Extension Model"
function pathloss_op(model::Cost231HataModel, dist::AbstractFloat)
  #150 a 2000 MHz
  hm::Float64 = model.rxH
  hb::Float64 = model.txH
  d::Float64 = dist
  f::Float64 = model.freq
  C ::Float64 = model.areaKind==AreaKind.Urban ? 3 : 0
  ar::Float64 =(1.1*log10(f)-0.7)*hm-(1.56*log(f)-0.8)

  # Checking the frequency range
  if (model.checkFreqRange)
    if (f < 1500 || f > 2000)
     error("The frequency range for COST213 Hata Extension Model is 1500MHz-2000Mhz")
    end
  end
  #Pathloss
  L::Float64 = 46.3 +33.9*log10(f)-13.82*log10(hb)-ar+(44.9-6.55*log(hb))*log(d)+C
  return L

end


function randsdp_data(seed, m, n)
    Random.seed!(seed)
    # n = 15  # Instance size
    # m = 10  # Number of constraints
    # Objective function
    c_sqrt = rand(Float64, (n, n))
    C = c_sqrt * c_sqrt'
    # C[1, 2] *= 0.5
    # C[2, 1] = C[1, 2]
    # Generate m-dimensional feasible system
    A, b = Dict(), Dict()
    X_ = randn((n, n))
    X_ = X_ * X_'
    for i in 1:m
        A[i] = rand(Float64, (n, n))
        A[i] = A[i] * A[i]'
        b[i] = tr(A[i] * X_)
    end
    return A, b, C
end
function randsdp_eval(A,b,C,n,m,XX)
    @show minus_rank = length([eig for eig in eigen(XX).values if eig < -1e-10])

    @show rank = length([eig for eig in eigen(XX).values if eig > 1e-10])

    @show tr(C * XX)
    for i in 1:m
        @show tr(A[i] * XX)-b[i]
    end

    nothing
end


immutable HermiteInterpolation{T} <: Interpolator{T}

    x₀::T
    x₁::T
    a₀::T
    a₁::T
    b₀::T
    b₁::T

    num::Vector{T}

    function HermiteInterpolation(x₀, x₁, d)
        a₀ = 2/(x₀-x₁)
        a₁ = 2/(x₁-x₀)

        b₀ = one(T)/(x₀-x₁)^2
        b₁ = one(T)/(x₁-x₀)^2

        new(x₀, x₁, a₀, a₁, b₀, b₁, zeros(T, d))
    end
end

function HermiteInterpolation{T}(x₀::T, x₁::T, d::Int)
    HermiteInterpolation{T}(x₀, x₁, d)
end

function evaluate{T}(int::HermiteInterpolation{T}, y₀::Vector{T}, y₁::Vector{T}, f₀::Vector{T}, f₁::Vector{T}, x::T, y::Vector{T})
    local d₀::T
    local d₁::T
    local c₀::T
    local c₁::T
    local den::T

    # Interpolate y values at required locations
    if x == int.x₀
        simd_copy!(y₀, y)
    elseif x == int.x₁
        simd_copy!(y₁, y)
    else
        d₀ = one(T)/(x-int.x₀)
        c₁ = d₀*int.b₀
        c₀ = c₁*(d₀-int.a₀)

        simd_copy_scale!(c₀, y₀, int.num)
        simd_axpy!(c₁, f₀, int.num)
        den = c₀

        d₁ = one(T)/(x-int.x₁)
        c₁ = d₁*int.b₁
        c₀ = c₁*(d₁-int.a₁)

        simd_axpy!(c₀, y₁, int.num)
        simd_axpy!(c₁, f₁, int.num)
        den += c₀

        simd_copy_scale!(one(T)/den, int.num, y)
    end
end


# =============================================================================
# Max volume subject to superficial Area
#  ___
# |___|
#
# Luis Lopez Diaz - llopeza@uninorte.edu.co
# =============================================================================
using JuMP, Ipopt, Gurobi;
# Input data
As = 73;                                        # Max superficial area
X = 8;                                          # Max width
Y = 20;                                         # Max lenght
ml = Model(with_optimizer(Ipopt.Optimizer));    # Optimizer
@variable(ml, 0 ≤ x);                           # x dimension
@variable(ml, 0 ≤ y);                           # y dimension
@variable(ml, 0 ≤ z);                           # z dimension
@NLconstraint(ml, 2*(x*y + x*z + y*z) == As);   # constraint
@NLobjective(ml, Max, x*y*z);                   # Objetive function
optimize!(ml);                                  # Optimize
println("Length: ", value(x))
println("Width : ", value(y))
println("Height: ", value(z))


# ---------------------------------------------------------------
# Parts of the Julia code in this package is ported from the Lisp
# and C++ code in 'Calendrical Calculations' by Nachum Dershowitz
# and Edward M. Reingold, Software---Practice & Experience,
# vol. 20, no. 9 (September, 1990), pp. 899--928.
# This code is in the public domain, but any use of it should
# publicly acknowledge the above sources, which is done herewith.
# Copyright of this port Peter Luschny, 2022.
# Licensed under the MIT license.
# ---------------------------------------------------------------

module Calendars

export EC, CE, JD, AM, AH, ID, EN, JN, DN, XX
export Calendar, Year, Month, Day, Date, CDate, YMD
export CalendarDates, CalendarName, CDateStr, DateStr, DateTable
export DayNumberFromDate, DateFromDayNumber, ConvertDate
export isValidDate, Duration, DayOfYear, ConvertOrdinalDate
export PrintDateLine, PrintDateTable, PrintEuropeanMonth
export SaveEuropeanMonth, WeekDay, WeekDays, PrintIsoWeek, IDate
export ProfileYearAsEuropean

# Export only functions from CalendarUtils and from this file,
# which basically is a wrapper around a collection of calendars.
include("CalendarUtils.jl")

# Consider the following included files as 'intern', and
# do not export their functions directly. Their algorithms
# follow Dershowitz/Reingold and use RataDie-numbers
# whereas here we use EuroDay-numbers.
include("DayNumbers.jl")
include("GregorianCalendar.jl")
include("JulianCalendar.jl")
include("EuropeanCalendar.jl")
include("HebrewCalendar.jl")
include("IslamicCalendar.jl")
include("IsoCalendar.jl")
include("InteractiveCalendar.jl")

"""

```julia
DayNumberFromDate(date::Cdate, string::Bool, show::Bool)
```

Return the day number correspondig to the calendar date.
The parts of the date can also be given separately.

If the optional parameter `string` is `true` the function returns
the day number in string format, otherwise as an integer.
`string` is 'false' by default.

If the optional parameter `show` is 'true', date
and number are printed. `show` is 'false' by default.

For example:
```julia
julia> DayNumberFromDate("Gregorian", 1756, 1, 27)
```

Using the acronym CE for 'Common Epoch' which is synonymous with
'Gregorian', this can also be written as

```julia
julia> DayNumberFromDate(CE, 1756, 1, 27)
```

This returns the European day number 641027 as an integer by default.
If `string` is `true` the string "EN#641029" is returned. If `show`
is `true` the line "CE-1756-01-27 -> EN#641029" is printed.

If an error occurs 0 (representing the invalid day
number) is returned.
"""
function DayNumberFromDate(d::CDate, string=false, show=false)
    # Don't process invalid dates
    ! isValidDate(d) && return 0

    calendar = d[1]
    if calendar == CE
        dn = DayNumberGregorian(d)
    elseif calendar == JD
        dn = DayNumberJulian(d)
    elseif calendar == EC
        dn = DayNumberEuropean(d)
    elseif calendar == AM
        dn = DayNumberHebrew(d)
    elseif calendar == AH
        dn = DayNumberIslamic(d)
    elseif calendar == ID
        dn = DayNumberIso(d)
    elseif calendar == DN
        dn = d[4]
    elseif calendar == JN
        dn = JulianNumToFixNum(d[4])
    elseif calendar == EN
        dn = EuroNumToFixNum(d[4])
    else
        @warn("Unknown calendar: $calendar")
        return InvalidDayNumber
    end

    enum = FixNumToEuroNum(dn)

    if show
        println(CDateStr(d), " -> ", CDateStr(EN, enum))
    end

    return string ? CDateStr(EN, enum) : enum
end

DayNumberFromDate(cal::String, year::DPart, month::DPart, day::DPart, string=false,
show=false) = DayNumberFromDate((StringToSymbol(cal), year, month, day), string, show)

DayNumberFromDate(cal::String, d::YMD, string=false, show=false) =
DayNumberFromDate((StringToSymbol(cal), d[1], d[2], d[3]), string, show)

DayNumberFromDate(cal::DPart, year::DPart, month::DPart, day::DPart, string=false,
show=false) = DayNumberFromDate((cal, year, month, day), string, show)

"""

```julia
DateFromDayNumber(calendar::DPart, enum::DPart, string::Bool, show::Bool)
```

Return the date from a European day number expressed in the calendar.

The day number `enum` must be an integer >= 1.

If the optional parameter `string` is `true` the function returns the date
as a string, otherwise as an integer tuple. `string` is `false` by default.

If the optional parameter `show` is set to `true`, date and number
are printed. `show` is `false` by default.

For example:
```julia
julia> DateFromDayNumber("Gregorian", 641029)
```

Alternatively you can also write
```julia
julia> DateFromDayNumber(CE, 641029)
```

By default this returns the calendar representation (2, 1756, 1, 27).
If `string` is true the string "CE-1756-01-27" is returned.
If `show` is 'true' the line "EN#0641029 -> CE-1756-01-27"
is printed.

If an error occurs (0, 0, 0, 0) (representing the invalid date) is returned.
"""
function DateFromDayNumber(calendar::DPart, enum::DPart, string=false, show=false)
    # Use a symbol for the calendar name.
    dn = EuroNumToFixNum(enum)

    if calendar == CE
        date = DateGregorian(dn)
    elseif calendar == JD
        date = DateJulian(dn)
    elseif calendar == EC
        date = DateEuropean(dn)
    elseif calendar == AM
        date = DateHebrew(dn)
    elseif calendar == AH
        date = DateIslamic(dn)
    elseif calendar == ID
        date = DateIso(dn)
    elseif calendar == DN
        date = (DN, 0, 0, EuroNumToFixNum(enum))
    elseif calendar == EN
        date = (EN, 0, 0, enum)
    elseif calendar == JN
        date = (JN, 0, 0, EuroNumToJulianNum(enum))
    else
        @warn("Unknown calendar: $calendar")
        return InvalidDate
    end

    if show
        println(CDateStr(EN, enum), " -> ", CDateStr(date))
    end

    return string ? CDateStr(date) : date
end

DateFromDayNumber(calendar::String, enum::DPart, string=false, show=false) =
DateFromDayNumber(StringToSymbol(calendar, true), enum, string, show)

"""

```julia
ConvertDate(date::CDate, calendar::DPart, string::Bool, show::Bool)
```

Convert the given date to a date in the calendar `calendar`.
Admissible names for the calendar are listed in the CDate docstring.

If the optional parameter `string` is `true` the function returns the date
as a string, otherwise as an integer tuple. `string` is `false` by default.

If the optional parameter `show` is set to 'true', both dates
are printed. `show` is 'false' by default.

For example:
```julia
julia> ConvertDate(CE, 1756, 1, 27, AM)
```

Alternatively this can also be written as

```julia
julia> ConvertDate("Gregorian", 1756, 1, 27, "Hebrew")
```

This converts the Gregorian date (1756, 1, 27) to the Hebrew date (5516, 11, 25).
By default the calendar representation (4, 5516, 11, 25) is returned.

If `string` is true the string "AM-5516-11-25" is returned.
If `show` is 'true' the following line is printed:

```julia
    "CE-1756-01-27 -> AM-5516-11-25"
```

If an error occurs (0, 0, 0, 0) (representing the invalid date) is returned.
"""
function ConvertDate(date::CDate, calto::DPart, string=false, show=false)
    ! isValidDate(date) && return InvalidDate

    # TODO There should be a general validation method in Utils.
    if StringToSymbol(SymbolToString[calto]) != calto
        @warn("Unknown calendar: $calto")
        return InvalidDate
    end

    enum = DayNumberFromDate(date)
    rdate = DateFromDayNumber(calto, enum)
    show && println(CDateStr(date), " -> ", CDateStr(rdate))

    return string ? CDateStr(rdate) : rdate
end

ConvertDate(calendar::DPart, year::DPart, month::DPart, day::DPart, to::DPart,
string=false, show=false) = ConvertDate((calendar, year, month, day), to, string, show)

ConvertDate(calendar::String, year::DPart, month::DPart, day::DPart, to::String,
string=false, show=false) = ConvertDate((StringToSymbol(calendar), year, month, day),
StringToSymbol(to), string, show)

ConvertDate(calendar::String, d::YMD, to::String, string=false, show=false) =
ConvertDate((StringToSymbol(calendar), d[1], d[2], d[3]), StringToSymbol(to), string, show)

ConvertDate(date::CDate, calto::String, string=false, show=false) =
ConvertDate(date, StringToSymbol(calto), string, show)

"""

```julia
CalendarDates(date::CDate, show::Bool)
```

Return a table of the dates of all supported calendars
corresponding to `date`. If the optional parameter `show`
is set to 'true', the date table is printed. `show` is
'false' by default.

```julia
julia> CalendarDates("Gregorian", 1756, 1, 27, true)
```

Alternatively you can also write

```julia
julia> CalendarDates(CE, 1756, 1, 27, true)
```

This computes a `DateTable`, which is a tuple of six calendar
dates plus the Euro day number and the Julian day number.
If `show` is 'true' the table below will be printed.
```julia
    European  EC-1756-01-27
    Common    CE-1756-01-27
    Julian    JD-1756-01-16
    Hebrew    AM-5516-11-25
    Islamic   AH-1169-04-24
    IsoDate   ID-1756-05-02
    EuroNum   EN#0641029
    JulianNum JN#2362452
```
"""
function CalendarDates(date::CDate, show=false)::DateTable

    dn = DayNumberFromDate(date)
    Table = (
        DateFromDayNumber(EC, dn),
        DateFromDayNumber(CE, dn),
        DateFromDayNumber(JD, dn),
        DateFromDayNumber(AM, dn),
        DateFromDayNumber(AH, dn),
        DateFromDayNumber(ID, dn),
        DateFromDayNumber(EN, dn),
        DateFromDayNumber(JN, dn)
    )
    show && PrintDateTable(Table)
    return Table
end

CalendarDates(calendar::String, year::DPart, month::DPart, day::DPart,
show=false) = CalendarDates((StringToSymbol(calendar), year, month, day), show)

CalendarDates(calendar::String, d::YMD, show=false) =
CalendarDates((StringToSymbol(calendar), d[1], d[2], d[3]), show)

CalendarDates(cal::DPart, year::DPart, month::DPart, day::DPart,
show=false) = CalendarDates((cal, year, month, day), show)

"""

```julia
isValidDate(date::CDate, warn=true)::Bool
```

Query if the given `date`` is a valid calendar date.

For example:
```julia
julia> isValidDate("Gregorian", 1756, 1, 27)
```

Alternatively you can also write

```julia
julia> isValidDate(CE, 1756, 1, 27)
```

This query affirms that 1756-01-27 is a valid Gregorian date.
But the next two queries return 'false' and 'true', respectively.

```julia
julia> isValidDate(CE, 1900, 2, 29)

julia> isValidDate(JD, 1900, 2, 29)
```
"""
function isValidDate(d::CDate, warn=true)
    calendar = d[1]

    if calendar == CE
        val = isValidDateGregorian(d, warn)
    elseif calendar == JD
        val = isValidDateJulian(d, warn)
    elseif calendar == EC
        val = isValidDateEuropean(d, warn)
    elseif calendar == AM
        val = isValidDateHebrew(d, warn)
    elseif calendar == AH
        val = isValidDateIslamic(d, warn)
    elseif calendar == ID
        val = isValidDateIso(d, warn)
    elseif calendar == DN
        val = d[4] > 0
    elseif calendar == EN
        val = d[4] > 0
    elseif calendar == JN
        val = d[4] > 0
    else
        @warn("Unknown calendar: $calendar")
        return false
    end
    return val
end

isValidDate(calendar::String, year::DPart, month::DPart, day::DPart) =
isValidDate((StringToSymbol(calendar), year, month, day))

isValidDate(calendar::DPart, year::DPart, month::DPart, day::DPart) =
isValidDate((calendar, year, month, day))

isValidDate(calendar::String, d::YMD) =
isValidDate((StringToSymbol(calendar), d[1], d[2], d[3]))

"""

```julia
DayOfYear(date::CDate)
```

Return the ordinal of the day in the given calendar. Also known as the 'ordinal date' relative to the epoch of the calendar.

```julia
julia> DayOfYear(EC, 1756, 1, 27)
```

Alternatively you can write

```julia
julia> DayOfYear("European", 1756,  1, 27)
```

From the output we see that EC-1756-01-27 is the 27-th day
of the year 1756 in the European calendar. The same day
is in the Hebrew calendar the 144-th day of the year:

```julia
julia> DayOfYear(ConvertDate(EC, 1756, 1, 27, AM))
 ```

If an error occurs 0 (representing the invalid day of the year) is returned.
"""
function DayOfYear(date::CDate)
    if ! isValidDate(date)
        @warn(Warning(date))
        return InvalidDayOfYear
    end

    calendar, year, month, day = date
    # cname = StringToSymbol(calendar)
    if calendar == CE
        val = DayOfYearGregorian(year, month, day)
    elseif calendar == JD
        val = DayOfYearJulian(year, month, day)
    elseif calendar == EC
        val = DayOfYearEuropean(year, month, day)
    elseif calendar == AM
        val = DayOfYearHebrew(year, month, day)
    elseif calendar == AH
        val = DayOfYearIslamic(year, month, day)
    elseif calendar == ID
        val = DayOfYearIso(year, month, day)
    else
        @warn("Unknown calendar: $calendar")
        return InvalidDayOfYear
    end
    return val + 1
end

DayOfYear(calendar::String, year::DPart, month::DPart, day::DPart) =
DayOfYear((StringToSymbol(calendar), year, month, day))

DayOfYear(calendar::String, d::YMD) =
DayOfYear((StringToSymbol(calendar), d[1], d[2], d[3]))

DayOfYear(calendar::DPart, d::YMD) =
DayOfYear((calendar, d[1], d[2], d[3]))

"""

```julia
Duration(a::CDate, b::CDate, show=false)
```

The duration gives the number of days between the two dates `a` and `b`,
counting the first date but not the second. So it describes a right
half-open time interval. The start and end dates can be given in
different calendars.

If the optional parameter `show` is set to 'true', both dates are
printed. `show` is 'false' by default.

```julia
julia> Duration((CE, 2022, 1, 1), (ID, 2022, 1, 1), true)
```
Perhaps to the surprise of some, these dates are two days apart.
```julia
    CE-2022-01-01 <-> ID-2022-01-01 -> Duration 2
```
"""
function Duration(a::CDate, b::CDate, show=false)
    if isValidDate(a) && isValidDate(b)
        an = DayNumberFromDate(a)
        bn = DayNumberFromDate(b)
        dur = abs(bn - an)

        if show
            ad = CDateStr(a)
            bd = CDateStr(b)
            show && println(ad, " <-> ", bd, " -> Duration ", dur)
        end
        return dur
    end
    @warn("Invalid Date: $a $b")
    return InvalidDuration
end

"""
```julia
ConvertOrdinalDate(num::DPart, from::String, to::String)
```

`num` is an ordinal date, i.e. an integer counting the ellapsed days
since the beginning of a calendrial epoch.

`from` and `to` are ordinal date names. Currently only
the ordinal date names "EuroNum" and "JulianNum", respectively
their acronyms EN, JN, are admissible.

Examples:
Convert a Julian day number to an European day number.
```julia
julia> ConvertOrdinalDate(2440422, JN, EN)
```
The European day number of the first crewed moon landing is 719000.

Convert an European day number to an Julian day number.
```julia
julia> ConvertOrdinalDate(719000, EN, JN)
```
The Julian day number of the first crewed moon landing is 2440422.
"""
function ConvertOrdinalDate(num::DPart, from::DPart, to::DPart)

    if from == EN && to == JN
        re = EuroNumToJulianNum(num)
    elseif from == JN && to == EN
        re = JulianNumToEuroNum(num)
    elseif from == EN && to == DN
        re = EuroNumToFixNum(num)
    elseif from == DN && to == EN
        re = FixNumToEuroNum(num)
    elseif from == DN && to == JN
        re = FixNumToJulianNum(num)
    elseif from == JN && to == DN
        re = JulianNumToFixNum(num)
    else
        @warn("Unknown ordinal date name: ", from, " ", to)
        return InvalidDayNumber
    end
    return re
end

ConvertOrdinalDate(num::DPart, from::String, to::String) =
ConvertOrdinalDate(num, StringToSymbol(from), StringToSymbol(to))

"""
```julia
ProfileYearAsEuropean(calendar::DPart, year::DPart, show=false)
```
Return a 4-tuple (YearStart, YearEnd, MonthsInYear, DaysInYear) as represented in the European calendar.

Jewish New Year (Rosh HaShanah) begins the evening before the date returned. For the ISO-calendar read 'WeeksInYear' instead of 'MonthsInYear'.

Examples:
```julia
julia> ProfileYearAsEuropean(EC, 2022, true)
julia> ProfileYearAsEuropean(CE, 2022, true)
julia> ProfileYearAsEuropean(JD, 2022, true)
julia> ProfileYearAsEuropean(AM, 5783, true)
julia> ProfileYearAsEuropean(AH, 1444, true)
julia> ProfileYearAsEuropean(ID, 2022, true)
```
These commands return:
```julia
EC-2022 -> [EC-2022-01-01, EC-2022-12-31], 12, 365
CE-2022 -> [EC-2022-01-01, EC-2022-12-31], 12, 365
JD-2022 -> [EC-2022-01-14, EC-2023-01-13], 12, 365
AM-5783 -> [EC-2022-09-26, EC-2023-09-15], 12, 355
AH-1444 -> [EC-2022-07-30, EC-2023-07-18], 12, 354
ID-2022 -> [EC-2022-01-03, EC-2023-01-01], 52, 364
```
"""
function ProfileYearAsEuropean(calendar::DPart, year::DPart, show=false)

    if calendar == CE
        p = (
            DateFromDayNumber(EC, YearStartGregorian(year) + 2),
            DateFromDayNumber(EC, YearEndGregorian(year) + 2),
            MonthsInYearGregorian(year),
            DaysInYearGregorian(year)
        )
    elseif calendar == JD
        p = (
            DateFromDayNumber(EC, YearStartJulian(year) + 2),
            DateFromDayNumber(EC, YearEndJulian(year) + 2),
            MonthsInYearJulian(year),
            DaysInYearJulian(year)
        )
    elseif calendar == EC
        p = (
            DateFromDayNumber(EC, YearStartEuropean(year) + 2),
            DateFromDayNumber(EC, YearEndEuropean(year) + 2),
            MonthsInYearEuropean(year),
            DaysInYearEuropean(year)
        )
    elseif calendar == AM
        p = (
            DateFromDayNumber(EC, YearStartHebrew(year) + 2),
            DateFromDayNumber(EC, YearEndHebrew(year) + 2),
            MonthsInYearHebrew(year),
            DaysInYearHebrew(year)
        )
    elseif calendar == AH
        p = (
            DateFromDayNumber(EC, YearStartIslamic(year) + 2),
            DateFromDayNumber(EC, YearEndIslamic(year) + 2),
            MonthsInYearIslamic(year),
            DaysInYearIslamic(year)
        )
    elseif calendar == ID
        p = (
            DateFromDayNumber(EC, YearStartIso(year) + 2),
            DateFromDayNumber(EC, YearEndIso(year) + 2),
            WeeksInYearIso(year),
            DaysInYearIso(year)
        )
    else
        @warn("Unknown calendar: $calendar")
        return (InvalidDate, InvalidDate, 0, 0)
    end
    if show
        println(SymbolToString[calendar], "-", year, " -> [",
            CDateStr(p[1]), ", ", CDateStr(p[2]), "], ", p[3], ", ", p[4])
    end

    return p
end

ProfileYearAsEuropean(calendar::String, year::DPart, show=false) =
ProfileYearAsEuropean(StringToSymbol(calendar), year, show)

end # module


struct Wolff{TF<:AbstractFloat, TI<:Integer} <:AbstractSampler
    β::TF
    J::TF
    L::TI
    p::TF
end

"""
    `Wolff(β, J, L)` - Set up the Wolff sampler

### Fields
* `β` - Inverse temperature
* `J` - Coupling constant>0
* `L` - Lattice size, L×L
"""
function Wolff(β::TF, J::TF, L::TI) where{TF<:AbstractFloat, TI<:Integer}
    return Wolff{TF, TI}(β, J, L, 1 - exp(-2 * β * J))
end

mutable struct WolffState{TI<:Integer, Tx<:AbstractArray{TI}} <:AbstractSamplerState
    x::Tx
    D::TI
end

function InitState!(x, sampler::Wolff)

    return WolffState(x, zero(eltype(x)));
end

function InitState(x₀, sampler::Wolff)

    return WolffState(deepcopy(x₀),zero(eltype(x₀)));
end

function UpdateState!(state::WolffState, sampler::Wolff)

    i = rand(1:sampler.L);
    j = rand(1:sampler.L);
    state.D = state.x[i,j];
    state.x[i,j] *=-1;

    k,l = mod1(i+1,sampler.L), j;
    if (state.x[k,l]==state.D)
        if (rand() < sampler.p)
            state.x[k,l] *=-1;
            FlipNeighbors!(state.x, k, l, sampler.p, state.D, sampler.L)
        end
    end

    k,l = mod1(i-1,sampler.L), j;
    if (state.x[k,l]==state.D)
        if (rand() < sampler.p)
            state.x[k,l] *=-1;
            FlipNeighbors!(state.x, k, l, sampler.p, state.D, sampler.L)
        end
    end

    k,l = i, mod1(j+1,sampler.L);
    if (state.x[k,l]==state.D)
        if (rand() < sampler.p)
            state.x[k,l] *=-1;
            FlipNeighbors!(state.x, k, l, sampler.p, state.D, sampler.L)
        end
    end

    k,l = i, mod1(j-1,sampler.L);
    if (state.x[k,l]==state.D)
        if (rand() < sampler.p)
            state.x[k,l] *=-1;
            FlipNeighbors!(state.x, k, l, sampler.p, state.D, sampler.L)
        end
    end
    state
end

"""
`FlipNeighbors!` - Recursively flip neighbors in the Wolff algorithm for 2D
square lattice Ising

### Fields
* `x` - state of the lattice
* `i` - i-coordinate of current spin
* `j` - j-coordinate of current spin
* `p` - Flipping probability
* `D` - Flipping direction
* `L` - Lattice size, N×N
"""
function FlipNeighbors!(x, i, j, p, D, L)

    k,l = mod1(i+1,L), j;
    if (x[k,l]==D)
        if (rand() < p)
            x[k,l] *=-1;
            FlipNeighbors!(x, k, l, p, D, L)
        end
    end

    k,l = mod1(i-1,L), j;
    if (x[k,l]==D)
        if (rand() < p)
            x[k,l] *=-1;
            FlipNeighbors!(x, k, l, p, D, L)
        end
    end

    k,l = i, mod1(j+1,L);
    if (x[k,l]==D)
        if (rand() < p)
            x[k,l] *=-1;
            FlipNeighbors!(x, k, l, p, D, L)
        end
    end

    k,l = i, mod1(j-1,L);
    if (x[k,l]==D)
        if (rand() < p)
            x[k,l] *=-1;
            FlipNeighbors!(x, k, l, p, D, L)
        end
    end
    x
end


# This file is part of the Julia package ModularForms.jl
#
# Copyright (c) 2018-2019: Lynn Engelberts and Alexandru Ghitza.

"""
    poly_to_power_series(f, K, prec=10)

Convert the polynomial `f` over `K` to a relative power series
to precision `prec` as a polynomial.

The power series has the same coefficient ring and variable
name as f. The input polynomial f must be correct up to
precision prec.

# Arguments
' `f::PolyElem`: polynomial over K, correct up to prec
- `K`: base ring of the output
- `prec::Integer=10`: precision of the output

# Examples still missing
"""
function poly_to_power_series(f::PolyElem, K, prec::Int=10)

   #initialize
   varname = string(gen(parent(f)))
   R, q = PowerSeriesRing(K, prec, varname)

   if f == 0
      return R(0)
   end

   d = min(degree(f), prec-1)
   c = [coeff(f, i) for i in 0:d]
   power_series = R(c, d+1, prec, 0)

   return power_series
end

function poly_to_power_series(f::PolyElem, prec::Int=10)
   return poly_to_power_series(f, base_ring(f), prec)
end


type NIGN
    N::Int
    sum_x::Float64
    sum_x_sq::Float64
    function NIGN()
        new(0, 0.0, 0.0)
    end
end

function incorporate!(comp::NIGN, x::Float64)
    comp.N += 1
    comp.sum_x += x
    comp.sum_x_sq += x*x
end

function unincorporate!(comp::NIGN, x::Float64)
    @assert comp.N > 0
    comp.N -= 1
    comp.sum_x -= x
    comp.sum_x_sq -= x*x
end

immutable NIGNParamsPrior
    m_prior::Normal
    r_prior::Gamma
    nu_prior::Gamma
    s_prior::Gamma
end

type NIGNParams
	m::Float64
	r::Float64
    nu::Float64
    s::Float64
    function NIGNParams(m::Float64, r::Float64, nu::Float64, s::Float64)
        new(m, r, nu, s)
    end
    function NIGNParams(prior::NIGNParamsPrior)
        m = rand(prior.m_prior)
        r = rand(prior.r_prior)
        nu = rand(prior.nu_prior)
        s = rand(prior.s_prior)
        new(m, r, nu, s)
    end
end

function log_probability_density(prior::NIGNParamsPrior, params::NIGNParams)
    l = logpdf(prior.m_prior, params.m)
    l += logpdf(prior.r_prior, params.r)
    l += logpdf(prior.nu_prior, params.nu)
    l += logpdf(prior.s_prior, params.s)
    l
end

function log_z(r::Float64, s::Float64, nu::Float64)
    lz = ((nu + 1.) / 2.) * log(2)
    lz += .5 * log(pi)
    lz -= .5 * log(r)
    lz -= (nu/2.) * log(s)
	lz += lgamma(nu/2.0)
    lz
end

function posterior_params(comp::NIGN, params::NIGNParams)
	rn = params.r + Float64(comp.N)
	nun = params.nu + Float64(comp.N)
	mn = (params.r*params.m + comp.sum_x)/rn
	sn = params.s + comp.sum_x_sq + params.r*params.m*params.m - rn*mn*mn
	if sn == Float64(0)
		sn = params.s
	end
	(mn, rn, sn, nun)
end

function log_probability_density(comp::NIGN, params::NIGNParams)
    (mn, rn, sn, nun) = posterior_params(comp, params)
    Z0 = log_z(params.r, params.s, params.nu)
    ZN = log_z(rn, sn, nun)
	-(comp.N/2.) * log(2 * pi) + ZN - Z0
end


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# ╔═╡ 1dffac12-ef71-11eb-3d44-537b4ed77dc9
using PlutoUI

# ╔═╡ 63456a8a-6821-446c-9279-8a9fc269b047
md"""# Pokémon Fights In Julia
Welcome great adventurer, in this notebook we will take you on a `Pokémon` journey. We will try to take adnvantage of the **multiple dispatch** system of Julia by implementing a very efficient Pokémon fight system.
"""

# ╔═╡ 8efc6f7a-1336-4118-acec-29f751cf7aac
md"""Something to get you in the right mood:\
$(Resource("https://vgmsite.com/soundtracks/pokemon-gameboy-sound-collection/gbhogmtx/107-battle%20%28vs%20wild%20pokemon%29.mp3"))"""

# ╔═╡ 0f6fc6b1-6d79-4ab7-9263-92adecb6e495
md"""## The Mechanics
Let us first take a look at the fighting mechanics of Pokémon.
As you can see in the image below there are different types and each type is effective against some types but weak against others.

$(Resource("https://img.pokemondb.net/images/typechart.png"))
*Image from https://img.pokemondb.net/images/typechart.png*

So let us first start by defining a general PokéType.
"""

# ╔═╡ fc131461-7d14-4301-aad5-1c91ed54c972
abstract type PokéType end

# ╔═╡ d53461a0-8124-4108-b8a9-909313c01b55
md"Next we will define all the separate types that we will need later on."

# ╔═╡ 7fed909f-d803-4059-b253-8b1f1fe930a3
begin
	struct Normal <: PokéType end
	struct Fire <: PokéType end
	struct Water <: PokéType end
	struct Electric <: PokéType end
	struct Grass <: PokéType end
	struct Ice <: PokéType end
	struct Fighting <: PokéType end
	struct Poison <: PokéType end
	struct Ground <: PokéType end
	struct Flying <: PokéType end
	struct Psychic <: PokéType end
	struct Bug <: PokéType end
	struct Rock <: PokéType end
	struct Ghost <: PokéType end
	struct Dragon <: PokéType end
	struct Dark <: PokéType end
	struct Steel <: PokéType end
	struct Fairy <: PokéType end
end

# ╔═╡ 78ca9717-a44c-4289-8827-4cd67274059f
md"""## The Core Function
Now it's time to create our effect function. This function will have as an input the type of the attacker and the type of the defender and then determine how effective the attack was.
"""

# ╔═╡ 8fd10c7d-e3e3-4575-8694-22b10f9a9c41
begin
	NO_EFFECT = 0
	NOT_VERY_EFFECTIVE = 0.5
	NORMAL_EFFECTIVE   = 1.0
	SUPER_EFFECTIVE    = 2.0
end;

# ╔═╡ 6fd02628-ccf5-4f85-a03c-2591fbdf3eb5
md"First we'll define the most general case, this way our function will always have an output for every Pokémon battle."

# ╔═╡ c4632665-51f2-46bf-b2aa-4849d635de43
function eff(atk::T1,def::T2) where {T1 <: PokéType, T2 <: PokéType}
	return NORMAL_EFFECTIVE
end

# ╔═╡ a3e53c87-5f15-47d0-a80c-4d5388b31eb8
md"Now we need to define all the other cases. Normally we this would mean defining 18x18 = $(18*18) cases. Luckily for us there are some patterns that make it so we don't need to define all of them. We will start by defining the cases where the attacker and defender are of the same type, then move on to all the `SUPER_EFFECTIVE` moves and so on."

# ╔═╡ 20ec0a99-9b6e-40a4-aa44-144765b86a3d
begin
	eff(atk::T,def::T) where {T <: PokéType} = NOT_VERY_EFFECTIVE
	eff(atk::Normal,def::Normal) = NORMAL_EFFECTIVE
	eff(atk::Fighting,def::Fighting) = NORMAL_EFFECTIVE
	eff(atk::Ground,def::Ground) = NORMAL_EFFECTIVE
	eff(atk::Flying,def::Flying) = NORMAL_EFFECTIVE
	eff(atk::Bug,def::Bug) = NORMAL_EFFECTIVE
	eff(atk::Rock,def::Rock) = NORMAL_EFFECTIVE
	eff(atk::Fairy,def::Fairy) = NORMAL_EFFECTIVE
	eff(atk::Ghost,def::Ghost) = SUPER_EFFECTIVE
	eff(atk::Dragon,def::Dragon) = SUPER_EFFECTIVE
end

# ╔═╡ af7579be-e3ee-408b-bb7e-2c1362a0c427
md"`SUPER_EFFECTIVE` fights:"

# ╔═╡ 9997973b-2992-4df2-ad71-b25fb80a3655
begin
	eff(atk::Fire,def::Grass) = SUPER_EFFECTIVE
	eff(atk::Fire,def::Ice) = SUPER_EFFECTIVE
	eff(atk::Fire,def::Bug) = SUPER_EFFECTIVE
	eff(atk::Fire,def::Steel) = SUPER_EFFECTIVE
	eff(atk::Water,def::Fire) = SUPER_EFFECTIVE
	eff(atk::Water,def::Ground) = SUPER_EFFECTIVE
	eff(atk::Water,def::Rock) = SUPER_EFFECTIVE
	eff(atk::Electric,def::Water) = SUPER_EFFECTIVE
	eff(atk::Electric,def::Flying) = SUPER_EFFECTIVE
	eff(atk::Grass,def::Water) = SUPER_EFFECTIVE
	eff(atk::Grass,def::Ground) = SUPER_EFFECTIVE
	eff(atk::Grass,def::Rock) = SUPER_EFFECTIVE
	eff(atk::Ice,def::Grass) = SUPER_EFFECTIVE
	eff(atk::Ice,def::Ground) = SUPER_EFFECTIVE
	eff(atk::Ice,def::Flying) = SUPER_EFFECTIVE
	eff(atk::Ice,def::Dragon) = SUPER_EFFECTIVE
	eff(atk::Fighting,def::Normal) = SUPER_EFFECTIVE
	eff(atk::Fighting,def::Ice) = SUPER_EFFECTIVE
	eff(atk::Fighting,def::Rock) = SUPER_EFFECTIVE
	eff(atk::Fighting,def::Dark) = SUPER_EFFECTIVE
	eff(atk::Fighting,def::Steel) = SUPER_EFFECTIVE
	eff(atk::Poison,def::Grass) = SUPER_EFFECTIVE
	eff(atk::Poison,def::Fairy) = SUPER_EFFECTIVE
	eff(atk::Ground,def::Fire) = SUPER_EFFECTIVE
	eff(atk::Ground,def::Electric) = SUPER_EFFECTIVE
	eff(atk::Ground,def::Poison) = SUPER_EFFECTIVE
	eff(atk::Ground,def::Rock) = SUPER_EFFECTIVE
	eff(atk::Ground,def::Steel) = SUPER_EFFECTIVE
	eff(atk::Flying,def::Grass) = SUPER_EFFECTIVE
	eff(atk::Flying,def::Fighting) = SUPER_EFFECTIVE
	eff(atk::Flying,def::Bug) = SUPER_EFFECTIVE
	eff(atk::Psychic,def::Fighting) = SUPER_EFFECTIVE
	eff(atk::Psychic,def::Poison) = SUPER_EFFECTIVE
	eff(atk::Bug,def::Grass) = SUPER_EFFECTIVE
	eff(atk::Bug,def::Psychic) = SUPER_EFFECTIVE
	eff(atk::Bug,def::Dark) = SUPER_EFFECTIVE
	eff(atk::Rock,def::Fire) = SUPER_EFFECTIVE
	eff(atk::Rock,def::Ice) = SUPER_EFFECTIVE
	eff(atk::Rock,def::Flying) = SUPER_EFFECTIVE
	eff(atk::Rock,def::Bug) = SUPER_EFFECTIVE
	eff(atk::Ghost,def::Psychic) = SUPER_EFFECTIVE
	eff(atk::Dark,def::Psychic) = SUPER_EFFECTIVE
	eff(atk::Dark,def::Ghost) = SUPER_EFFECTIVE
	eff(atk::Steel,def::Ice) = SUPER_EFFECTIVE
	eff(atk::Steel,def::Rock) = SUPER_EFFECTIVE
	eff(atk::Steel,def::Fairy) = SUPER_EFFECTIVE
	eff(atk::Fairy,def::Fighting) = SUPER_EFFECTIVE
	eff(atk::Fairy,def::Dragon) = SUPER_EFFECTIVE
	eff(atk::Fairy,def::Dark) = SUPER_EFFECTIVE

end

# ╔═╡ 1e075548-6898-457b-8cec-dbccc0fd8ec7
md"`NOT_VERY_EFFECTIVE` fights:"

# ╔═╡ eb578e80-f4fe-4c14-91b7-d7b267bd9046
begin
	eff(atk::Normal,def::Rock) = NOT_VERY_EFFECTIVE
	eff(atk::Normal,def::Steel) = NOT_VERY_EFFECTIVE
	eff(atk::Fire,def::Water) = NOT_VERY_EFFECTIVE
	eff(atk::Fire,def::Rock) = NOT_VERY_EFFECTIVE
	eff(atk::Fire,def::Dragon) = NOT_VERY_EFFECTIVE
	eff(atk::Water,def::Grass) = NOT_VERY_EFFECTIVE
	eff(atk::Water,def::Dragon) = NOT_VERY_EFFECTIVE
	eff(atk::Electric,def::Grass) = NOT_VERY_EFFECTIVE
	eff(atk::Electric,def::Dragon) = NOT_VERY_EFFECTIVE
	eff(atk::Grass,def::Fire) = NOT_VERY_EFFECTIVE
	eff(atk::Grass,def::Poison) = NOT_VERY_EFFECTIVE
	eff(atk::Grass,def::Bug) = NOT_VERY_EFFECTIVE
	eff(atk::Grass,def::Dragon) = NOT_VERY_EFFECTIVE
	eff(atk::Grass,def::Steel) = NOT_VERY_EFFECTIVE
	eff(atk::Grass,def::Flying) = NOT_VERY_EFFECTIVE
	eff(atk::Ice,def::Fire) = NOT_VERY_EFFECTIVE
	eff(atk::Ice,def::Water) = NOT_VERY_EFFECTIVE
	eff(atk::Ice,def::Steel) = NOT_VERY_EFFECTIVE
	eff(atk::Fighting,def::Poison) = NOT_VERY_EFFECTIVE
	eff(atk::Fighting,def::Flying) = NOT_VERY_EFFECTIVE
	eff(atk::Fighting,def::Psychic) = NOT_VERY_EFFECTIVE
	eff(atk::Fighting,def::Bug) = NOT_VERY_EFFECTIVE
	eff(atk::Fighting,def::Fairy) = NOT_VERY_EFFECTIVE
	eff(atk::Poison,def::Ground) = NOT_VERY_EFFECTIVE
	eff(atk::Poison,def::Rock) = NOT_VERY_EFFECTIVE
	eff(atk::Poison,def::Ghost) = NOT_VERY_EFFECTIVE
	eff(atk::Ground,def::Grass) = NOT_VERY_EFFECTIVE
	eff(atk::Ground,def::Bug) = NOT_VERY_EFFECTIVE
	eff(atk::Flying,def::Electric) = NOT_VERY_EFFECTIVE
	eff(atk::Flying,def::Rock) = NOT_VERY_EFFECTIVE
	eff(atk::Flying,def::Steel) = NOT_VERY_EFFECTIVE
	eff(atk::Psychic,def::Steel) = NOT_VERY_EFFECTIVE
	eff(atk::Bug,def::Fire) = NOT_VERY_EFFECTIVE
	eff(atk::Bug,def::Poison) = NOT_VERY_EFFECTIVE
	eff(atk::Bug,def::Fighting) = NOT_VERY_EFFECTIVE
	eff(atk::Bug,def::Flying) = NOT_VERY_EFFECTIVE
	eff(atk::Bug,def::Steel) = NOT_VERY_EFFECTIVE
	eff(atk::Bug,def::Fairy) = NOT_VERY_EFFECTIVE
	eff(atk::Bug,def::Ghost) = NOT_VERY_EFFECTIVE
	eff(atk::Rock,def::Fighting) = NOT_VERY_EFFECTIVE
	eff(atk::Rock,def::Ground) = NOT_VERY_EFFECTIVE
	eff(atk::Rock,def::Steel) = NOT_VERY_EFFECTIVE
	eff(atk::Ghost,def::Dark) = NOT_VERY_EFFECTIVE
	eff(atk::Dragon,def::Steel) = NOT_VERY_EFFECTIVE
	eff(atk::Dark,def::Fighting) = NOT_VERY_EFFECTIVE
	eff(atk::Dark,def::Fairy) = NOT_VERY_EFFECTIVE
	eff(atk::Steel,def::Fire) = NOT_VERY_EFFECTIVE
	eff(atk::Steel,def::Water) = NOT_VERY_EFFECTIVE
	eff(atk::Steel,def::Electric) = NOT_VERY_EFFECTIVE
	eff(atk::Fairy,def::Fire) = NOT_VERY_EFFECTIVE
	eff(atk::Fairy,def::Poison) = NOT_VERY_EFFECTIVE
	eff(atk::Fairy,def::Steel) = NOT_VERY_EFFECTIVE
end

# ╔═╡ f6c4ed81-0251-4973-bdc7-75951a2657c7
md"`NO_EFFECT` fights:"

# ╔═╡ 2e80f947-1447-4809-b31a-337abaedd955
begin
	eff(atk::Normal,def::Ghost) = NO_EFFECT
	eff(atk::Electric,def::Ground) = NO_EFFECT
	eff(atk::Fighting,def::Ghost) = NO_EFFECT
	eff(atk::Poison,def::Steel) = NO_EFFECT
	eff(atk::Ground,def::Flying) = NO_EFFECT
	eff(atk::Psychic,def::Dark) = NO_EFFECT
	eff(atk::Ghost,def::Normal) = NO_EFFECT
	eff(atk::Dragon,def::Fairy) = NO_EFFECT
end

# ╔═╡ db9ba5b4-0573-4bd0-9359-24cdc2885082
md"""Now that we have defined all of the effects, it's time to take a small brake and heal up at the PokéCenter!\
$(Resource("https://vgmsite.com/soundtracks/pokemon-gameboy-sound-collection/znqhqixi/111-pokemon%20recovery.mp3"))\
$(Resource("http://25.media.tumblr.com/tumblr_lzm9prI6UJ1qeh39oo1_500.gif"))\
*Gif from http://pokemondailylr.blogspot.com/2012/12/pokemon-center.html* """

# ╔═╡ 4c5e5e24-aea6-4573-b9a2-f25d83c22f19
md"""## The Actual Fight Function
Now we will define the last couple of functions that we need to implement the fighting system. First we'll make a function that makes a nice string based on the effectiveness of an attack, next we will use that function to complete our attack function.
"""

# ╔═╡ 7539d614-67d5-4a2a-b03a-00c5f873c8ae
function eff_string(effectiveness)
	if effectiveness == 0
		return "not effective"
	elseif effectiveness < 1.0
		return "not very effective ($effectiveness × damage)"
	elseif effectiveness == 1
		return "effective"
	else
		return "very effective  ($effectiveness × damage)"
	end
end

# ╔═╡ e9b22426-0c7f-4f15-8bd3-361b3b5ed5cf
md"We also want to correctly display the type of our Pokémon. For this reason we overload the `Base.show()` function."

# ╔═╡ 0be3dd43-188f-460d-8df8-3ae9ac8adae1
Base.string(x::PokéType) = split(string(typeof(x)),".")[end]

# ╔═╡ a5ebeeca-349e-4777-9acf-491da20efd88
Base.show(io::IO, x::PokéType) = string(x)

# ╔═╡ 2326a03a-3f53-420f-896b-cf3620f06a60
md"So now it's time for a small test:"

# ╔═╡ ae08b2e9-f864-458d-89e5-ebe07320f322
md"""## Dual Types
So now our function works for Pokémons with only one type but as we all know there are a lot of Pokémon with two types. Defining a function that lets us use our `attack!()` function for dual type Pokémon is actually very easy.

First we define what a dualtype is:
"""

# ╔═╡ b0f76cc7-0fb4-48ca-9844-c0fe186b32eb
struct DualType <: PokéType
	Type1::PokéType
	Type2::PokéType
end

# ╔═╡ c8f1021f-f230-428c-9685-0eb03a63def8
md"Let us also display the `DualType` correctly."

# ╔═╡ 0ce4e7e0-3d99-4ae4-8a11-905ce6e7708f
Base.show(io::IO, x::DualType) = "$(string(x.Type1))-$(string(x.Type2))"

# ╔═╡ e4870042-9c91-41d8-8770-47940cffce0a
md"Now we adjust our `effect` function so that it knows what to do with Dualtypes. When the Dual type is the attacker, it will choose its most effective attack."

# ╔═╡ 7ff0187e-12c2-497d-b5d1-29dd0770a8e1
eff(atk::DualType, def::PokéType) = max(eff(atk.Type1, def), eff(atk.Type2, def))

# ╔═╡ 33cb3990-d126-41e1-ac9f-c9f9d6b85ae5
md"When the dual type is the defender, we have to compute the correct effect of the attack."

# ╔═╡ bda2dba7-4be5-4ad0-bfdc-ad02a64297a4
eff(atk::PokéType, def::DualType) = eff(atk, def.Type1) * eff(atk, def.Type2)

# ╔═╡ aa5abb6c-2289-4c34-a062-2e84187559dd
function attack!(atk::PokéType, def::PokéType)
    effectiveness = eff(atk, def)
	println("A $(show(atk)) Pokémon used an attack")
    println("against a $(show(def)) Pokémon")
    println("it was $(eff_string(effectiveness))")
end

# ╔═╡ a5ecc6c7-d08b-4cf8-8a3b-580ada462edc
begin
	Squirtle = Water()
	Charmander = Fire()
	with_terminal() do
		attack!(Squirtle,Charmander)
	end
end

# ╔═╡ fd2d5874-46a0-47ae-9f13-45079f42cfab
md"So now we can very easily define a Pokémon with two types!"

# ╔═╡ faa0448f-7470-4d2e-84bf-f2dcaa92fc37
begin
	Venusaur = DualType(Grass(),Poison())

	with_terminal() do
		println("Venusaur vs Charmander:")
		attack!(Venusaur, Charmander)
		attack!(Charmander, Venusaur)
		println("")
		println("Venusaur vs Squirtle:")
		attack!(Venusaur, Squirtle)
		attack!(Squirtle, Venusaur)
	end
end

# ╔═╡ 4d67eb11-e859-4213-ae0b-9298d5c79bdf
show(Venusaur)

# ╔═╡ 88d51607-8d4f-4087-ab00-2113a9c65ad4
md"""As you can see the sky is the limit with these types and take note of the fact that it's all calculated very efficiently!\
For now this notebook is over but the Pokémon adventure never stops!\
$(Resource("https://vgmsite.com/soundtracks/pokemon-gameboy-sound-collection/gjqttmbp/108-victory%20%28vs%20wild%20pokemon%29.mp3"))
"""

# ╔═╡ 6209b50b-5ded-4529-a895-2ae533ef63b6
md"""[^1]This notebook was based on a great blogpost about Pokémon and Julia. You can find it here: [https://www.moll.dev/projects/effective-multi-dispatch/?s=03](https://www.moll.dev/projects/effective-multi-dispatch/?s=03).\
\
[^2]The sounds used in this notebook where all gathered from: [https://downloads.khinsider.com/game-soundtracks/album/pokemon-gameboy-sound-collection](https://downloads.khinsider.com/game-soundtracks/album/pokemon-gameboy-sound-collection)."""

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

[compat]
PlutoUI = "~0.7.9"
"""

# ╔═╡ 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"

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

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

[[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 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "1.1.0"

[[PlutoUI]]
deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.9"

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

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

[[Reexport]]
git-tree-sha1 = "5f6c21241f0f655da3952fd60aa18477cf96c220"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.1.0"

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

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

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

# ╔═╡ Cell order:
# ╟─1dffac12-ef71-11eb-3d44-537b4ed77dc9
# ╟─63456a8a-6821-446c-9279-8a9fc269b047
# ╟─8efc6f7a-1336-4118-acec-29f751cf7aac
# ╟─0f6fc6b1-6d79-4ab7-9263-92adecb6e495
# ╠═fc131461-7d14-4301-aad5-1c91ed54c972
# ╟─d53461a0-8124-4108-b8a9-909313c01b55
# ╠═7fed909f-d803-4059-b253-8b1f1fe930a3
# ╟─78ca9717-a44c-4289-8827-4cd67274059f
# ╠═8fd10c7d-e3e3-4575-8694-22b10f9a9c41
# ╟─6fd02628-ccf5-4f85-a03c-2591fbdf3eb5
# ╠═c4632665-51f2-46bf-b2aa-4849d635de43
# ╟─a3e53c87-5f15-47d0-a80c-4d5388b31eb8
# ╠═20ec0a99-9b6e-40a4-aa44-144765b86a3d
# ╟─af7579be-e3ee-408b-bb7e-2c1362a0c427
# ╠═9997973b-2992-4df2-ad71-b25fb80a3655
# ╟─1e075548-6898-457b-8cec-dbccc0fd8ec7
# ╠═eb578e80-f4fe-4c14-91b7-d7b267bd9046
# ╟─f6c4ed81-0251-4973-bdc7-75951a2657c7
# ╠═2e80f947-1447-4809-b31a-337abaedd955
# ╟─db9ba5b4-0573-4bd0-9359-24cdc2885082
# ╟─4c5e5e24-aea6-4573-b9a2-f25d83c22f19
# ╠═7539d614-67d5-4a2a-b03a-00c5f873c8ae
# ╠═aa5abb6c-2289-4c34-a062-2e84187559dd
# ╟─e9b22426-0c7f-4f15-8bd3-361b3b5ed5cf
# ╠═0be3dd43-188f-460d-8df8-3ae9ac8adae1
# ╠═a5ebeeca-349e-4777-9acf-491da20efd88
# ╟─2326a03a-3f53-420f-896b-cf3620f06a60
# ╠═a5ecc6c7-d08b-4cf8-8a3b-580ada462edc
# ╟─ae08b2e9-f864-458d-89e5-ebe07320f322
# ╠═b0f76cc7-0fb4-48ca-9844-c0fe186b32eb
# ╟─c8f1021f-f230-428c-9685-0eb03a63def8
# ╠═0ce4e7e0-3d99-4ae4-8a11-905ce6e7708f
# ╠═4d67eb11-e859-4213-ae0b-9298d5c79bdf
# ╟─e4870042-9c91-41d8-8770-47940cffce0a
# ╠═7ff0187e-12c2-497d-b5d1-29dd0770a8e1
# ╟─33cb3990-d126-41e1-ac9f-c9f9d6b85ae5
# ╠═bda2dba7-4be5-4ad0-bfdc-ad02a64297a4
# ╟─fd2d5874-46a0-47ae-9f13-45079f42cfab
# ╠═faa0448f-7470-4d2e-84bf-f2dcaa92fc37
# ╟─88d51607-8d4f-4087-ab00-2113a9c65ad4
# ╟─6209b50b-5ded-4529-a895-2ae533ef63b6
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


###
  Name     : c9_34_Kmean_randomNumbers.jl
  Book     : Hands-on Data Science with Anaconda )
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan and James Yan
  Date     : 4/6/2018
  email    : yany@canisius.edu
             paulyxy@hotmail.com
###

using Clustering
srand(1234)
nRow=5
nCol=1000
x = rand(nRow,nCol)
maxInter=200  #max interation
nCluster=20
R = kmeans(x,nCluster;maxiter=maxInter,display=:iter)
@assert nclusters(R) ==nCluster
c = counts(R)
clusters= R.centers


mutable struct KDensity{R<:AbstractRange}
    "Gridpoints for evaluating the density."
    x::R
    "Kernel density at corresponding gridpoints `x`."
    density::Vector{Float64}
end

function KDensity(v)
    k = KernelDensity.kde(v)
    probability = k.density .* step(k.x)
    KDensity(k.x, probability)
end

function trim_dist(vec; p_limit = 0.01)
    v = jump_missing(vec)
    k = kde(v)
    ik = InterpKDE(k)
    P = [ismissing(val) ? 1 : pdf(ik, val).*step(k.x) for val in vec]
    [x > p_limit for x in P]
end

function trim_dist(df::AbstractDataFrame,x::Symbol; p_limit = 0.01)
    v = df[:,x]
    idxs = trim_dist(v; p_limit = p_limit)
    df[idxs,:]
end

function trim_dist!(df::AbstractDataFrame,x::Symbol; p_limit = 0.01)
    v = df[:,x]
    idxs = trim_dist(v; p_limit = p_limit)
    delete!(df,Not(idxs))
end


type DiscretizeUniformCount <: DiscretizationAlgorithm
    nbins::Int
end

function binedges{N<:AbstractFloat}(alg::DiscretizeUniformCount, data::AbstractArray{N})

    nbins = alg.nbins

    n = length(data)
    n ≥ nbins || error("too many bins requested")

    p = sortperm(data)
    counts_per_bin, remainder = div(n,nbins), rem(n,nbins)
    retval = Array(N, nbins+1)
    retval[1] = data[p[1]]
    retval[end] = data[p[end]]

    ind = 0
    for i in 2 : nbins
        counts = counts_per_bin + (remainder > 0.0 ? 1 : 0)
        remainder -= 1.0
        ind += counts
        retval[i] = (data[p[ind]] + data[p[ind+1]])/2
        retval[i-1] != retval[i] || error("binedges non-unique") # TODO(tim): should make the algorithm handle this
    end

    retval
end
function binedges{N<:Integer}(alg::DiscretizeUniformCount, data::AbstractArray{N})

    nbins = alg.nbins

    n = length(data)
    n ≥ nbins || error("too many bins requested")

    p = sortperm(data)
    counts_per_bin, remainder = div(n,nbins), rem(n,nbins)
    retval = Array(N, nbins+1)
    retval[1] = data[p[1]]
    retval[end] = data[p[end]]

    ind = 0
    for i in 2 : nbins
        counts = counts_per_bin + (remainder > 0.0 ? 1 : 0)
        remainder -= 1.0
        ind += counts
        retval[i] = ceil(Int, (data[p[ind]] + data[p[ind+1]])/2) # value V will be placed in bin B if V ∈ [Bₗ Bᵣ)
        retval[i-1] != retval[i] || error("binedges non-unique") # TODO(tim): should make the algorithm handle this
    end

    retval
end


"""
	$(SIGNATURES)

Output of a Cobb-Douglas. Returns a vector of length 1 for xM with 1 row.
"""
function output_cobb_douglas(xM :: AbstractMatrix{F1},
    expV :: AbstractVector{F1}) where F1 <: Real

    T, n = size(xM);
    outputV = ones(F1, T);
    for j = 1 : n
        @views outputV .*= (xM[:,j] .^ expV[j]);
    end
    return outputV
end

# Returns scalar output.
function output_cobb_douglas(xV :: AbstractVector{F1},
    expV :: AbstractVector{F1}) where F1 <: Real

    n = length(xV);
    output = one(F1);
    for j = 1 : n
        output *= xV[j] ^ expV[j];
    end
    return output
end

function mproducts_cobb_douglas(xM :: AbstractMatrix{F1},
    expV :: AbstractVector{F1}) where F1 <: Real

    yV = output_cobb_douglas(xM, expV);
    mpM = (yV * expV') ./ xM;
    return mpM
end

# -----------