text:
#   This file is part of UnitSystems.jl. It is licensed under the MIT license
#   UnitSystems Copyright (C) 2020 Michael Reed

"""
    kilograms(m::Real) = $(slug)m

Converts mass `m` from slugs to kilogram (kg).
"""
@pure kilograms(m::Real,U::UnitSystem=English) = mass(m,Metric,U)

"""
    slugs(m::Real) = $(1/slug)m

Converts mass `m` from kilograms to slugs (slug).
"""
@pure slugs(m::Real,U::UnitSystem=Metric) = mass(m,English,U)

"""
    feet(d) = $(1/ft)d

Converts distance `d` from meters to feet (ft).
"""
@pure feet(d,U::UnitSystem=Metric) = length(d,English,U)

"""
    meters(d) = $(ft)d

Converts distance `d` from feet to meters (m).
"""
@pure meters(d,U::UnitSystem=English) = length(d,Metric,U)

# special exact cases

# IAU to SI
@pure length(U::UnitSystem{kB,ħ,𝘤} where {kB,ħ},S::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ}) = length(U,S,1/au)
@pure time(U::UnitSystem{kB,ħ,𝘤} where {kB,ħ},S::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ}) = time(U,S,1/day)

# SI to IAU
@pure length(U::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ},S::UnitSystem{kB,ħ,𝘤} where {kB,ħ}) = length(U,S,au)
@pure time(U::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ},S::UnitSystem{kB,ħ,𝘤} where {kB,ħ}) = time(U,S,day)

# IAU to CGS
@pure length(::UnitSystem{kB,ħ,100𝘤} where {kB,ħ},S::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ}) = length(U,S,1/au)
@pure time(::UnitSystem{kB,ħ,100𝘤} where {kB,ħ},S::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ}) = time(U,S,1/day)

# CGS to IAU
@pure length(U::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ},S::UnitSystem{kB,ħ,100𝘤} where {kB,ħ}) = length(U,S,au)
@pure time(U::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ},S::UnitSystem{kB,ħ,100𝘤} where {kB,ħ}) = time(U,S,day)

# IAU to English
@pure length(U::UnitSystem{kB,ħ,𝘤/ft} where {kB,ħ},S::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ}) = length(U,S,ft/au)
@pure time(U::UnitSystem{kB,ħ,𝘤/ft} where {kB,ħ},S::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ}) = time(U,S,1/day)

# English to IAU
@pure length(U::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ},S::UnitSystem{kB,ħ,𝘤/ft} where {kB,ħ}) = length(U,S,au/ft)
@pure time(U::UnitSystem{kB,ħ,day*𝘤/au} where {kB,ħ},S::UnitSystem{kB,ħ,𝘤/ft} where {kB,ħ}) = time(U,S,day)

# base unit conversions

convertext(unit,fun) = """
```Julia
$unit(U::UnitSystem,S::UnitSystem) = $fun
$unit(v::Real,U::UnitSystem,S::UnitSystem) = v/$unit(U,S)
```
"""

# spacetime

@pure length(U::UnitSystem,S::UnitSystem,l=1) = unit((planckreduced(S)*electronmass(U)*lightspeed(U))/(planckreduced(U)*electronmass(S)*lightspeed(S)),l)
@doc """
$(convertext(:length,"planck(U,S)/mass(U,S)/speed(U,S)"))

Extent of one-dimensional shape or `length` (m), unit conversion factor.

```Julia
julia> length(CGS,Metric) # m⋅cm⁻¹
$(length(CGS,Metric))

julia> length(IAU,Metric) # m⋅au⁻¹
$(length(IAU,Metric))

julia> length(English,Metric) # m⋅ft⁻¹
$(length(English,Metric))

julia> length(EnglishUS,English) # ft⋅ftUS⁻¹
$(length(EnglishUS,English))

julia> length(PlanckGauss,Metric) # m⋅ℓP⁻¹
$(length(PlanckGauss,Metric))
```
""" length, ft, ftUS, au

"""
$(convertext(:area,"length(U,S)^2"))

Extent of two-dimensional shape or `area` (m²), unit conversion factor.

```Julia
julia> area(CGS,Metric) # m²⋅cm⁻²
$(area(CGS,Metric))

julia> area(English,Metric) # m²⋅ft⁻²
$(area(English,Metric))

julia> area(EnglishUS,English) # ft²⋅ftUS⁻²
$(area(EnglishUS,English))
```
"""
@pure area(U::UnitSystem,S::UnitSystem) = unit(length(U,S)^2)

"""
$(convertext(:volume,"length(U,S)^3"))

Extent of three-dimensional shape or `volume` (m³), unit conversion factor.

```Julia
julia> volume(CGS,Metric) # m³⋅cm⁻³
$(volume(CGS,Metric))

julia> volume(English,Metric) # m³⋅ft⁻³
$(volume(English,Metric))

julia> volume(EnglishUS,English) # ft³⋅ftUS⁻³
$(volume(EnglishUS,English))
```
"""
@pure volume(U::UnitSystem,S::UnitSystem) = unit(length(U,S)^3)

"""
$(convertext(:wavenumber,"1/length(U,S)"))

Number of occurences per unit of space (m⁻¹), unit conversion factor.

```Julia
julia> wavenumber(CGS,Metric) # cm⋅m⁻¹
$(wavenumber(CGS,Metric))

julia> wavenumber(English,Metric) # ft⋅m⁻¹
$(wavenumber(English,Metric))
```
"""
@pure wavenumber(U::UnitSystem,S::UnitSystem) = unit(length(S,U))

"""
$(convertext(:fuelefficiency,"1/area(U,S)"))

Distance per volume or `fuelefficiency` (m⋅m⁻³, m⁻²), unit conversion factor.

```Julia
julia> fuelefficiency(CGS,Metric) # cm²⋅m⁻²
$(fuelefficiency(CGS,Metric))

julia> fuelefficiency(English,Metric) # ft²⋅m⁻²
$(fuelefficiency(English,Metric))
```
"""
@pure fuelefficiency(U::UnitSystem,S::UnitSystem) = area(S,U)

@pure time(U::UnitSystem,S::UnitSystem,t=1) = unit(length(U,S)/lightspeed(U,S),1)
@doc """
$(convertext(:time,"length(U,S)/speed(U,S)"))

Dimension along which events are ordered or `time` (s), unit conversion factor.

```Julia
julia> time(IAU,Metric) # s⋅day⁻¹
$(time(IAU,Metric))

julia> time(PlanckGauss,Metric) # s⋅tP⁻¹
$(time(PlanckGauss,Metric))
```
""" time, day

"""
$(convertext(:frequency,"1/time(U,S)"))

Number of occurences per unit of time (Hz or s⁻¹), unit conversion factor.

```Julia
julia> frequency(IAU,Metric) day⋅s⁻¹
$(frequency(IAU,Metric))
```
"""
@pure frequency(U::UnitSystem,S::UnitSystem) = time(S,U)

"""
$(convertext(:frequencydrift,"1/time(U,S)^2"))

Drift of `frequency` per `time` or `frequencydrift` (Hz⋅s⁻¹, s⁻²), unit conversion factor.
```Julia
julia> frequencydrift(IAU,Metric) day²⋅Hz⋅s⁻¹
$(frequencydrift(IAU,Metric))
```
"""
@pure frequencydrift(U::UnitSystem,S::UnitSystem) = unit(time(S,U)^2)

"""
$(convertext(:speed,"lightspeed(S)/lightspeed(U)"))

Velocity or `length` per `time` or `speed` (m⋅s⁻¹), unit conversion factor.

```Julia
julia> speed(CGS,Metric) # m⋅cm⁻¹
$(speed(CGS,Metric))

julia> speed(IAU,Metric) # m⋅day⋅s⁻¹⋅au⁻¹
$(speed(IAU,Metric))

julia> speed(English,Metric) # m⋅ft⁻¹
$(speed(English,Metric))

julia> speed(EnglishUS,English) # ft⋅ftUS⁻¹
$(speed(EnglishUS,English))
```
"""
@pure speed(U::UnitSystem,S::UnitSystem) = lightspeed(U,S)

@doc """
$(convertext(:acceleration,"speed(U,S)/time(U,S)"))

Specific force or `speed` per `time` or `acceleration` (m⋅s⁻²), unit conversion factor.

```Julia
julia> acceleration(CGS,Metric) # m⋅s⁻¹⋅gal⁻¹
$(acceleration(CGS,Metric))

julia> acceleration(IAU,Metric) # m⋅day²⋅s⁻²⋅au⁻¹
$(acceleration(IAU,Metric))

julia> acceleration(English,Metric) # m⋅ft⁻¹
$(acceleration(English,Metric))

julia> acceleration(EnglishUS,English) # ft⋅ftUS⁻¹
$(acceleration(EnglishUS,English))
```
"""
@pure acceleration(U::UnitSystem,S::UnitSystem) = unit(speed(U,S)/time(U,S))

"""
$(convertext(:jerk,"speed(U,S)/time(U,S)^2"))

Jolt or `acceleration` per `time` or `jerk` (m⋅s⁻³), unit conversion factor.

```Julia
julia> jerk(CGS,Metric) # m⋅cm⁻¹
$(jerk(CGS,Metric))

julia> jerk(IAU,Metric) # m⋅day³⋅s⁻³⋅au⁻¹
$(jerk(IAU,Metric))

julia> jerk(English,Metric) # m⋅ft⁻¹
$(jerk(English,Metric))

julia> jerk(EnglishUS,English) # ft⋅ftUS⁻¹
$(jerk(EnglishUS,English))
```
"""
@pure jerk(U::UnitSystem,S::UnitSystem) = unit(speed(U,S)/time(U,S)^2)

"""
$(convertext(:snap,"speed(U,S)/time(U,S)^3"))

Jounce or `jerk` per `time` or `snap` (m⋅s⁻⁴), unit conversion factor.

```Julia
julia> snap(CGS,Metric) # m⋅cm⁻¹
$(snap(CGS,Metric))

julia> snap(IAU,Metric) # m⋅day⁴⋅s⁻⁴⋅au⁻¹
$(snap(IAU,Metric))

julia> snap(English,Metric) # m⋅ft⁻¹
$(snap(English,Metric))

julia> snap(EnglishUS,English) # ft⋅ftUS⁻¹
$(snap(EnglishUS,English))
```
"""
@pure snap(U::UnitSystem,S::UnitSystem) = unit(speed(U,S)/time(U,S)^3)

"""
$(convertext(:volumeflow,"area(U,S)*speed(U,S)"))

Volumetric flow rate or `volumeflow` (m³⋅s⁻¹), unit conversion factor.

```Julia
julia> volumeflow(CGS,Metric) # m³⋅cm⁻³
$(volume(CGS,Metric))

julia> volumeflow(English,Metric) # m³⋅ft⁻³
$(volume(English,Metric))

julia> volumeflow(EnglishUS,English) # ft³⋅ftUS⁻³
$(volume(EnglishUS,English))
```
"""
@pure volumeflow(U::UnitSystem,S::UnitSystem) = unit(area(U,S)*speed(U,S))

"""
$(convertext(:specificenergy,"speed(U,S)^2"))

Massic energy or `energy` per `mass` or `specificenergy` (J⋅kg⁻¹), unit conversion factor.

```Julia
julia> specificenergy(CGS,Metric) # m²⋅cm⁻²
$(specificenergy(CGS,Metric))

julia> specificenergy(IAU,Metric) # m²⋅day²⋅s⁻²⋅au⁻²
$(specificenergy(IAU,Metric))

julia> specificenergy(English,Metric) # m²⋅ft⁻²
$(specificenergy(English,Metric))

julia> specificenergy(EnglishUS,English) # ft²⋅ftUS⁻²
$(specificenergy(EnglishUS,English))
```
"""
@pure specificenergy(U::UnitSystem,S::UnitSystem) = unit(speed(U,S)^2)

# kinematic

@doc """
$(convertext(:mass,"electronmass(S)/electronmass(U)"))

Inertal `mass` or matter quantity or resistance to aceleration (kg), unit conversion factor.

```Julia
julia> mass(CGS,Metric) # kg⋅g⁻¹
$(mass(CGS,Metric))

julia> mass(CODATA,Metric) # kg⋅kg⁻¹
$(mass(CODATA,Metric))

julia> mass(Conventional,Metric) # kg⋅kg⁻¹
$(mass(Conventional,Metric))

julia> mass(English,Metric) # kg⋅slug⁻¹
$(mass(English,Metric))

julia> mass(IAU,Metric) # kg⋅m⊙⁻¹
$(mass(IAU,Metric))

julia> mass(PlanckGauss,Metric) # kg⋅mP⁻¹
$(mass(PlanckGauss,Metric))
```
""" mass, slug, mₛ

@pure energy(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)*specificenergy(U,S))
"""
$(convertext(:energy,"mass(U,S)*specificenergy(U,S)"))

Work or heat or `energy` (J, N⋅m, kg⋅m²⋅s⁻²), unit conversion factor.

```Julia
julia> energy(CGS,Metric) # J⋅erg⁻¹
$(energy(CGS,Metric))

julia> energy(CGS,English) # ft⋅lb⋅erg⁻¹
$(energy(CGS,English))

julia> energy(English,Metric) # J⋅ft⁻¹⋅lb⁻¹
$(energy(English,Metric))

julia> 0.001/3600 # J⋅kW⁻¹⋅h⁻¹
$(0.001/3600)

julia> 1/kcal # J⋅kcalₜₕ⁻¹
$(1/kcal)

julia> 1/BTUJ # J⋅BTU⁻¹
$(1/1055.036345118633)

julia> 1/charge(SI2019) # J⋅eV⁻¹
$(1/charge(SI2019))
```
""" energy, cal, kcal, BTUJ, BTUftlb

"""
$(convertext(:power,"energy(U,S)/time(U,S))"))

Radiant flux or `power` or `energy` per `time` (W, J⋅s⁻¹, kg⋅m²⋅s⁻³), unit conversion factor.

```Julia
julia> power(CGS,Metric) # W⋅s⋅erg⁻¹
$(power(CGS,Metric))

julia> power(English,Metric) # W⋅s⋅ft⁻¹⋅lb⁻¹
$(power(English,Metric))
```
"""
@pure power(U::UnitSystem,S::UnitSystem) = unit(energy(U,S)/time(U,S))

@pure force(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)*acceleration(U,S))
"""
$(convertext(:force,"mass(U,S)*acceleration(U,S)"))

Weight or force or `mass` times `acceleration` (N, kg⋅m⋅s⁻²), unit conversion factor.

```Julia
julia> force(CGS,Metric) # N⋅dyn⁻¹
$(force(CGS,Metric))

julia> force(CGS,English) # lb⋅dyn⁻¹
$(force(CGS,English))

julia> force(English,Metric) # N⋅lb⁻¹
$(force(English,Metric))

julia> force(1/lbm,Metric,English) # pdl⋅N⁻¹
$(force(1/lbm,Metric,English))

julia> g₀ # kp⋅N⁻¹
$g₀
```
""" force, g₀, g0, lbm

@pure pressure(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)/length(U,S)/time(U,S)^2)
@doc """
$(convertext(:pressure,"energy(U,S)/volume(U,S)"))

Pressure or stress or `force` per `area` (Pa, N⋅m⁻², kg⋅m⁻¹⋅s⁻²), unit conversion factor.

```Julia
julia> pressure(CGS,Metric) # Pa⋅Ba⁻¹
$(pressure(CGS,Metric))

julia> 1/atm # Pa⋅atm⁻¹
$(1/atm)

julia> pressure(English,Metric) # Pa⋅ft²⋅lb⁻¹
$(pressure(English,Metric))

julia> pressure(Metric,English)/12^2 # psi⋅Pa⁻¹
$(pressure(Metric,English)/12^2)
```
""" pressure, atm

# mechanical

"""
$(convertext(:momentum,"mass(U,S)*speed(U,S)"))

Linear `momentum` or `mass` times `speed` (N⋅s, kg⋅m⋅s⁻¹), unit conversion factor.

```Julia
julia> momentum(CGS,Metric) # N⋅dyn⁻¹
$(momentum(CGS,Metric))

julia> momentum(CGS,English) # lb⋅dyn⁻¹
$(momentum(CGS,English))

julia> momentum(English,Metric) # N⋅lb⁻¹
$(momentum(English,Metric))
```
"""
@pure momentum(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)*speed(U,S))

"""
$(convertext(:angularmomentum,"momentum(U,S)*length(U,S)"))

Rotational momentum or `angularmomentum` (N⋅m⋅s, kg⋅m²⋅s⁻¹), unit conversion factor.

```Julia
julia> momentum(CGS,Metric) # N⋅m⋅dyn⁻¹⋅cm⁻¹
$(momentum(CGS,Metric))

julia> momentum(CGS,English) # lb⋅ft⋅dyn⁻¹⋅cm⁻¹
$(momentum(CGS,English))

julia> momentum(English,Metric) # N⋅m⋅lb⁻¹⋅ft⁻¹
$(momentum(English,Metric))
```
"""
@pure angularmomentum(U::UnitSystem,S::UnitSystem) = unit(momentum(U,S)*length(U,S))

"""
$(convertext(:yank,"mass(U,S)*jerk(U,S)"))

Rate of change of `force` or `yank` (N⋅s⁻¹, kg⋅m⋅s⁻³), unit conversion factor.

```Julia
julia> yank(CGS,Metric) # N⋅dyn⁻¹
$(yank(CGS,Metric))

julia> yank(CGS,English) # lb⋅dyn⁻¹
$(yank(CGS,English))

julia> yank(English,Metric) # N⋅lb⁻¹⋅
$(yank(English,Metric))
```
"""
@pure yank(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)*jerk(U,S))

"""
$(convertext(:areadensity,"mass(U,S)/area(U,S)"))

Surface or `areadensity` or `mass` per `area` (kg⋅m⁻²), unit conversion factor.

```Julia
julia> areadensity(CGS,Metric) # kg⋅cm²⋅g⁻¹⋅m⁻²
$(areadensity(CGS,Metric))

julia> areadensity(CGS,English) # slug⋅cm²⋅g⁻¹⋅ft⁻²
$(areadensity(CGS,English))

julia> areadensity(English,Metric) # kg⋅ft²⋅slug⁻¹⋅m⁻²
$(areadensity(English,Metric))
```
"""
@pure areadensity(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)/area(U,S))

"""
$(convertext(:density,"mass(U,S)/volume(U,S)"))

Specific mass or `mass` per `volume` or `density` (kg⋅m⁻³), unit conversion factor.

```Julia
julia> density(CGS,Metric) # kg⋅cm³⋅g⁻¹⋅m⁻³
$(density(CGS,Metric))

julia> density(CGS,English) # slug⋅cm³⋅g⁻¹⋅ft⁻³
$(density(CGS,English))

julia> density(English,Metric) # kg⋅ft³⋅slug⁻¹⋅m⁻³
$(density(English,Metric))
```
"""
@pure density(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)/volume(U,S))

"""
$(convertext(:specificvolume,"volume(U,S)/mass(U,S)"))

Reciprocal `density` or `volume` per `mass` or `specificvolume` (m³⋅kg), unit conversion factor.

```Julia
julia> specificvolume(CGS,Metric) # g⋅m³⋅kg⁻¹⋅cm⁻³
$(specificvolume(CGS,Metric))

julia> specificvolume(CGS,English) # kg⋅ft³⋅slug⁻¹⋅cm⁻³
$(specificvolume(CGS,English))

julia> specificvolume(English,Metric) # slug⋅m³⋅kg⁻¹⋅ft⁻³
$(specificvolume(English,Metric))
```
"""
@pure specificvolume(U::UnitSystem,S::UnitSystem) = unit(volume(U,S)/mass(U,S))

"""
$(convertext(:action,"energy(U,S)*time(U,S)"))

Integrated `momentum` over `length` or `action` (J⋅s, N⋅m⋅s), unit conversion factor.

```Julia
julia> action(CGS,Metric) # J⋅erg⁻¹
$(action(CGS,Metric))

julia> action(CGS,English) # ft⋅lb⋅erg⁻¹
$(action(CGS,English))

julia> action(English,Metric) # J⋅ft⁻¹⋅lb⁻¹
$(action(English,Metric))
```
"""
@pure action(U::UnitSystem,S::UnitSystem) = unit(momentum(U,S)*length(U,S))

"""
$(convertext(:stiffness,"mass(U,S)/time(U,S)^2"))

Amount of `force` per `length` or `stiffness` (N⋅m⁻¹, J⋅m⁻², kg⋅s⁻²), unit conversion factor.

```Julia
julia> stiffness(CGS,Metric) # kg⋅g⁻¹
$(stiffness(CGS,Metric))

julia> stiffness(CGS,English) # slug⋅g⁻¹
$(stiffness(CGS,English))

julia> stiffness(English,Metric) # kg⋅slug⁻¹
$(stiffness(English,Metric))
```
"""
@pure stiffness(U::UnitSystem,S::UnitSystem) = unit(energy(U,S)/area(U,S))

"""
$(convertext(:intensity,"power(U,S)/area(U,S)"))

Heat flux density or irradiance or `power` per `area` (W⋅m⁻², kg⋅s⁻³), unit conversion factor.

```Julia
julia> intensity(CGS,Metric) # kg⋅g⁻¹
$(intensity(CGS,Metric))

julia> intensity(CGS,English) # slug⋅g⁻¹
$(intensity(CGS,English))

julia> intensity(English,Metric) # kg⋅slug⁻¹
$(intensity(English,Metric))
```
"""
@pure intensity(U::UnitSystem,S::UnitSystem) = unit(power(U,S)/area(U,S))

"""
$(convertext(:diffusivity,"planck(U,S)/mass(U,S)"))

Thermal `diffusivity` or kinematic viscostiy (m²⋅s⁻¹), unit conversion factor.

```Julia
julia> diffusivity(CGS,Metric) # m²⋅cm⁻²
$(diffusivity(CGS,Metric))

julia> diffusivity(English,Metric) # m²⋅ft⁻²
$(diffusivity(English,Metric))

julia> diffusivity(EnglishUS,English) # ft²⋅ftUS⁻²
$(diffusivity(EnglishUS,English))
```
"""
@pure diffusivity(U::UnitSystem,S::UnitSystem) = unit((planckreduced(S)*electronmass(U))/(planckreduced(U)*electronmass(S)))

"""
$(convertext(:viscosity,"mass(U,S)/length(U,S)/time(U,S)^2"))

Resistance to deformation or `viscosity` (Pa⋅s, kg⋅m⁻¹⋅s⁻¹), unit conversion factor.

```Julia
julia> viscosity(CGS,Metric) # Pa⋅Ba⁻¹
$(viscosity(CGS,Metric))

julia> viscosity(English,Metric) # Pa⋅ft²⋅lb⁻¹
$(viscosity(English,Metric))
```
"""
@pure viscosity(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)/length(U,S)/time(U,S))

"""
$(convertext(:lineardensity,"mass(U,S)/length(U,S)"))

Amount of `lineardensity` or `mass` per `length` (kg⋅m⁻¹), unit conversion factor.

```Julia
julia> lineardensity(CGS,Metric) # kg⋅cm¹⋅g⁻¹⋅m⁻¹
$(lineardensity(CGS,Metric))

julia> lineardensity(CGS,English) # slug⋅cm¹⋅g⁻¹⋅ft⁻¹
$(lineardensity(CGS,English))

julia> lineardensity(English,Metric) # kg⋅ft¹⋅slug⁻¹⋅m⁻¹
$(lineardensity(English,Metric))
```
"""
@pure lineardensity(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)/length(U,S))

"""
$(convertext(:massflow,"mass(U,S)/time(U,S)"))

Rate of `massflow` or `mass` per `time` (kg⋅s⁻¹), unit conversion factor.

```Julia
julia> massflow(CGS,Metric) # kg⋅g⁻¹
$(massflow(CGS,Metric))

julia> massflow(CODATA,Metric) # kg⋅kg⁻¹
$(massflow(CODATA,Metric))

julia> massflow(Conventional,Metric) # kg⋅kg⁻¹
$(massflow(Conventional,Metric))

julia> massflow(English,Metric) # kg⋅slug⁻¹
$(massflow(English,Metric))
```
"""
@pure massflow(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)/time(U,S))

"""
$(convertext(:spectralflux,"power(U,S)/length(U,S)"))

Spectral power or `radiantflux` per wave `length` (W⋅m⁻¹), unit conversion factor.

```Julia
julia> spectralflux(CGS,Metric) # kg⋅m⋅g⁻¹⋅cm⁻¹
$(spectralflux(CGS,Metric))

julia> spectralflux(CGS,English) # slug⋅ft⋅g⁻¹⋅cm⁻¹
$(spectralflux(CGS,English))

julia> spectralflux(English,Metric) # kg⋅m⋅slug⁻¹⋅ft⁻¹
$(spectralflux(English,Metric))
```
"""
@pure spectralflux(U::UnitSystem,S::UnitSystem) = unit(power(U,S)/length(U,S))

"""
$(convertext(:powerdensity,"power(U,S)/volume(U,S)"))

Spectral irradiance (volume) or `powerdensity` (W⋅m⁻³), unit conversion factor.

```Julia
julia> powerdensity(CGS,Metric) # kg⋅cm⋅g⁻¹⋅m⁻¹
$(powerdensity(CGS,Metric))

julia> powerdensity(CGS,English) # slug⋅cm⋅g⁻¹⋅ft⁻¹
$(powerdensity(CGS,English))

julia> powerdensity(English,Metric) # kg⋅ft⋅slug⁻¹⋅m⁻¹
$(powerdensity(English,Metric))
```
"""
@pure powerdensity(U::UnitSystem,S::UnitSystem) = unit(power(U,S)/volume(U,S))

"""
$(convertext(:compressibility,"1/pressure(U,S)"))

Relative volume change or `compressibility` (Pa⁻¹), unit conversion factor.

```Julia
julia> compressibility(CGS,Metric) # Ba⋅Pa⁻¹
$(compressibility(CGS,Metric))

julia> compressibility(English,Metric) # lb⋅ft⁻²⋅Pa⁻¹
$(compressibility(English,Metric))

julia> compressibility(Metric,English)/12^2 # Pa⋅psi⁻¹
$(compressibility(Metric,English)/12^2)
```
"""
@pure compressibility(U::UnitSystem,S::UnitSystem) = pressure(S,U)

"""
$(convertext(:fluence,"energy(U,S)/area(U,S"))

Radiant exposure or `energy` per `area` or `fluence` (J⋅m⁻²), unit conversion factor.

```Julia
julia> fluence(CGS,Metric) # kg⋅g⁻¹
$(mass(CGS,Metric))

julia> fluence(CODATA,Metric) # kg⋅kg⁻¹
$(mass(CODATA,Metric))

julia> fluence(Conventional,Metric) # kg⋅kg⁻¹
$(fluence(Conventional,Metric))

julia> fluence(English,Metric) # kg⋅slug⁻¹
$(fluence(English,Metric))
```
"""
@pure fluence(U::UnitSystem,S::UnitSystem) = unit(energy(U,S)/area(U,S))

"""
$(convertext(:rotationalinertia,"mass(U,S)*area(U,S)"))

Moment of inertia or `rotationalinertia` (kg⋅m²), unit conversion factor.

```Julia
julia> rotationalinertia(CGS,Metric) # kg⋅m²⋅g⁻¹⋅cm⁻²
$(rotationalinertia(CGS,Metric))

julia> rotationalinertia(CGS,English) # slug⋅ft²⋅g⁻¹⋅cm⁻²
$(rotationalinertia(CGS,English))

julia> rotationalinertia(English,Metric) # kg⋅m²⋅slug⁻¹⋅ft⁻²
$(rotationalinertia(English,Metric))
```
"""
@pure rotationalinertia(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)*area(U,S))

# acoustic

"""
$(convertext(:soundexposure,"pressure(U,S)^2*time(U,S)"))

Square of `pressure` by `time` or `soundexposure` (Pa²⋅s, N²⋅m⁻⁴), unit conversion factor.

```Julia
julia> soundexposure(CGS,Metric) # Pa²⋅Ba⁻²
$(soundexposure(CGS,Metric))

julia> soundexposure(English,Metric) # Pa²⋅ft⁴⋅lb⁻²
$(soundexposure(English,Metric))
```
"""
@pure soundexposure(U::UnitSystem,S::UnitSystem) = unit(time(U,S)*pressure(U,S)^2)

"""
$(convertext(:specificimpedance,"pressure(U,S)/speed(U,S)"))

Characteristic specific acoustic impedance (Rayl, Pa⋅s⋅m⁻¹), unit conversion factor.

```Julia
julia> specificimpedance(CGS,Metric) # Pa⋅cm⋅m⁻¹⋅Ba⁻¹
$(specificimpedance(CGS,Metric))

julia> specificimpedance(English,Metric) # Pa⋅ft³⋅m⁻¹⋅lb⁻¹
$(specificimpedance(English,Metric))
```
"""
@pure specificimpedance(U::UnitSystem,S::UnitSystem) = unit(pressure(U,S)/speed(U,S))

"""
$(convertext(:impedance,"specificimpedance(U,S)/area(U,S)"))

Acoustic `impedance` (Rayl⋅m⁻², Pa⋅s⋅m⁻³, kg⋅s⁻¹⋅m⁻⁴), unit conversion factor.

```Julia
julia> impedance(CGS,Metric) # Pa⋅cm³⋅m⁻³⋅Ba⁻¹
$(impedance(CGS,Metric))

julia> impedance(English,Metric) # Pa⋅ft⁵⋅m⁻³⋅lb⁻¹
$(impedance(English,Metric))
```
"""
@pure impedance(U::UnitSystem,S::UnitSystem) = unit(specificimpedance(U,S)/area(U,S))

"""
$(convertext(:admittance,"area(U,S)/specificimpedance(U,S)"))

Acoustic `admittance` (m²⋅Rayl⁻¹, m³⋅s⁻¹⋅Pa⁻¹, m⁴⋅s⋅kg⁻¹), unit conversion factor.

```Julia
julia> admittance(CGS,Metric) # Ba⋅m³⋅cm⁻³⋅Pa⁻¹
$(admittance(CGS,Metric))

julia> admittance(English,Metric) # lb⋅m³⋅ft⁻⁵⋅Pa⁻¹
$(admittance(English,Metric))
```
"""
@pure admittance(U::UnitSystem,S::UnitSystem) = unit(area(U,S)/specificimpedance(U,S))

"""
$(convertext(:compliance,"time(U,S)^2/mass(U,S)"))

Acoustic `compliance` is reciprocal of `stiffness` (m⋅N⁻¹, m³⋅Pa⁻¹), unit conversion factor.

```Julia
julia> compliance(CGS,Metric) # kg⋅g⁻¹
$(compliance(CGS,Metric))

julia> compliance(CGS,English) # slug⋅g⁻¹
$(compliance(CGS,English))

julia> compliance(English,Metric) # kg⋅slug⁻¹
$(compliance(English,Metric))
```
"""
@pure compliance(U::UnitSystem,S::UnitSystem) = unit(time(U,S)^2/mass(U,S))

"""
$(convertext(:inertance,"mass(U,S)/length(U,S)^4"))

Acoustic mass or `inertance` (kg⋅m⁴, Pa⋅s²⋅m⁻³), unit conversion factor.

```Julia
julia> inertance(CGS,Metric) # kg⋅cm⁴⋅g⁻¹⋅m⁻⁴
$(inertance(CGS,Metric))

julia> inertance(CGS,English) # slug⋅cm⁴⋅g⁻¹⋅ft⁻⁴
$(inertance(CGS,English))

julia> inertance(English,Metric) # kg⋅ft⁴⋅slug⁻¹⋅m⁻⁴
$(inertance(English,Metric))
```
"""
@pure inertance(U::UnitSystem,S::UnitSystem) = unit(mass(U,S)/length(U,S)^4)


text:
# Autogenerated wrapper script for GLPK_jll for armv7l-linux-gnueabihf
export libglpk

using GMP_jll
JLLWrappers.@generate_wrapper_header("GLPK")
JLLWrappers.@declare_library_product(libglpk, "libglpk.so.40")
function __init__()
    JLLWrappers.@generate_init_header(GMP_jll)
    JLLWrappers.@init_library_product(
        libglpk,
        "lib/libglpk.so",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()

ext: jl


text:
module tst_pre
    using Test
    using CloudCovErr
    using Random

    # Random generators do not promise the same streams between
    # different Julia releases... that is highly frustrating.

    @testset "imagePrep" begin
        out = kstar_circle_mask(3;rlim=1)
        ref = [
         true   false   true   ;
         false  false   false  ;
         true   false   true   ;
         ]
        @test out == ref

        ref = [
         1.1   1.2   1.1 ;
         3.2  6.0   1.9  ;
         2.2   2.5   2.0 ;
         ]
        @test findmaxpsf(ref;thr=20) == 6.25

        cx = (0:17) * ones(18)'
        cy = ones(18) * (0:17)'
        out = im_subrng(2,4,cx,cy,16,16,2,2,4,4,1,1,4,4)
        @test out[1] == 4:9
        @test out[2] == 12:17
        @test length(out[3]) == 20
        out = im_subrng(1,1,cx,cy,16,16,2,2,4,4,1,1,4,4)
        @test out[1] == 0:5
        out = im_subrng(4,4,cx,cy,15,15,2,2,4,4,1,1,4,4)
        @test out[1] == 12:16

        refin = [
         11.0  12.0   11.0 ;
         32.0  60.0   19.0 ;
         22.0  25.0   20.0 ;
         ]
        add_noise!(refin,2;seed=2021)
        ref = [
         12.0   9.5  10.0 ;
         31.5  70.5  18.0 ;
         19.0  25.5  19.5 ;
         ]
        @test abs(refin[2,2]-70.5) < 10

        skyim = 10 .*ones(3,3)
        maskim = zeros(Bool,3,3)
        maskim[1,:] .= true
        testim2 = [
          1.0  1.0  1.0;
         -1.0  1.0  1.0;
          1.0  1.0  1.0;
        ]
        add_sky_noise!(testim2,maskim,skyim,4;seed=2021)
        ref = [
         0.25  1.25  2.25 ;
         1.0  1.0   -1.0 ;
         -1.0  1.0   -1.0 ;
         ]
        @test all(abs.(ref) .< 5)

        ttt_testim = ones(33,33)
        ttt_bmaskim = zeros(Bool,33,33)
        ttt_bmaskim[16,16] = true
        ttt_bimage = ones(33,33)
        ttt_bimageI = ones(Int,33,33)
        ttt_testim2 = zeros(33,33)
        ttt_bmaskim2 = zeros(Bool,33,33)
        ttt_goodpix = ones(Bool,33,33)
        prelim_infill!(ttt_testim,ttt_bmaskim,ttt_bimage,ttt_bimageI,ttt_testim2,ttt_bmaskim2,ttt_goodpix,"N4",widx=19,widy=19)
        @test ttt_testim2[16,16] == 1.0
        prelim_infill!(ttt_testim,ttt_bmaskim,ttt_bimage,ttt_bimageI,ttt_testim2,ttt_bmaskim2,ttt_goodpix,"N4",widx=1,widy=1)
        @test ttt_testim2[16,16] == 1.0
        prelim_infill!(ttt_testim,ttt_bmaskim,ttt_bimage,ttt_bimageI,ttt_testim2,ttt_bmaskim2,ttt_goodpix,"N4",widx=19,widy=19,ftype=64)
        @test ttt_testim2[16,16] == 1.0

        psfstamp = zeros(31,31)
        psfstamp[16,16] = 1
        ttt_maskim2 = zeros(Bool,51,51);
        gen_mask_staticPSF!(ttt_maskim2,psfstamp,[26],[26],[200])
        @test ttt_maskim2[26,26]

        psfstamp = zeros(33,33)
        psfstamp[16,16] = 100
        psfstamp[16,1] = 2

        psfstamp1 = zeros(31,31)
        psfstamp1[16,16] = 100
        psfstamp1[1,8] = 1

        ttt_maskim2 = zeros(Bool,51,51);
        gen_mask_staticPSF2!(ttt_maskim2,psfstamp,psfstamp1,[26],[26],[20])
        @test ttt_maskim2[26,26]
        @test !ttt_maskim2[26,26-16]

        psfstamp = zeros(33,33)
        psfstamp[17,17] = 100
        psfstamp[17,1] = 2

        psfstamp1 = zeros(31,31)
        psfstamp1[16,16] = 100
        psfstamp1[1,8] = 1

        ttt_maskim2 = zeros(Bool,51,51);
        gen_mask_staticPSF2!(ttt_maskim2,psfstamp,psfstamp1,[26],[26],[200])
        @test ttt_maskim2[26,26]
        @test ttt_maskim2[26,26-16]
    end
end


text:
module Traits

using ..Prerequisites: atol, rtol

export commontype
export parametercount, parametername, parameterorder, parametertype, parameterpair, isparameterbound, hasparameter
export parameternames, parametertypes, parameterpairs, isparameterbounds, reparameter, promoteparameters, rawtype, fulltype
export contentcount, contentname, contentorder, contenttype, hascontent, getcontent, contentnames, contenttypes, dissolve
export efficientoperations

"""
    commontype(f::Function, types, ::Type{T}=Any) where T

Find the common return type of a function.
"""
function commontype(f::Function, types, ::Type{T}=Any) where T
    result = Union{}
    for rtype in Base.return_types(f, types)
        result = promote_type(result, commontype(rtype))
    end
    @assert result<:T "commontype error: wrong common type."
    return result
end
@inline commontype(t) = t
@inline commontype(t::Union) = promote_type(commontype(t.a), commontype(t.b))

"""
    DataType(T::DataType) -> DataType
    DataType(T::UnionAll) -> DataType

Get the DataType.
"""
@inline Base.DataType(T::DataType) = T
@inline Base.DataType(T::UnionAll) = DataType(T.body)

"""
    supertype(T, termination::Symbol) -> DataType

Get the supertype of `T` till termination.
"""
@inline Base.supertype(::Type{T}, termination::Symbol) where T = _supertype(T, Val(termination))
@inline @generated function _supertype(::Type{T}, ::Val{termination}) where {T, termination}
    result = T
    while nameof(result) != termination && nameof(result) != :Any
        result = supertype(result)
    end
    (nameof(result) != termination) && error("_supertype error: termination is not the name of a valid supertype of $(nameof(T)).")
    return result
end

@inline _rawtype(T::DataType) = T.name.wrapper
@inline _rawtype(T::UnionAll) = _rawtype(T.body)
"""
    rawtype(::Type{T}) where T -> DataType/UnionAll

Get the "raw part" of a type. That is, the type without all its type parameters.
"""
@inline @generated rawtype(::Type{T}) where T = _rawtype(T)

"""
    parametercount(::Type{T}) where T -> Int

For a type `T`, get the number of its type parameters.
"""
@inline @generated parametercount(::Type{T}) where T = length(DataType(T).parameters)

"""
    parametername(::Type{T}, i::Integer) where T -> Symbol

For a type `T`, get the name of its ith type parameter.
"""
@inline parametername(::Type{T}, i::Integer) where T = _parametername(parameternames(T)|>Val, Val(i))
@inline @generated _parametername(::Val{names}, ::Val{i}) where {names, i} = QuoteNode(names[i])

"""
    parameterorder(::Type{T}, name::Symbol) where T -> Int

For a type `T`, get the order of one of its type parameters.
"""
@inline parameterorder(::Type{T}, name::Symbol) where T = _order(Val(name), parameternames(T)|>Val)
@inline @generated _order(::Val{name}, ::Val{names}) where {name, names} = findfirst(isequal(name), names)::Int

"""
    parametertype(::Type{T}, name::Symbol) where T
    parametertype(::Type{T}, i::Integer) where T

For a type `T`, get the type of one of its type parameters.
"""
@inline parametertype(::Type{T}, name::Symbol) where T = parametertype(T, parameterorder(T, name))
@inline parametertype(::Type{T}, i::Integer) where T = _parametertype(T, Val(i))
@inline @generated function _parametertype(::Type{T}, ::Val{i}) where {T, i}
    result = DataType(T).parameters[i]
    return isa(result, TypeVar) ? result.ub : result
end

"""
    parameterpair(::Type{T}, name::Symbol) where T
    parameterpair(::Type{T}, i::Integer) where T

For type `T`, get the name-type pair of one of its type parameters.

The result is stored in the type parameters of a `Pair`.
"""
@inline parameterpair(::Type{T}, name::Symbol) where T = Pair{name, parametertype(T, name)}
@inline parameterpair(::Type{T}, i::Integer) where T = Pair{parametername(T, i), parametertype(T, i)}

"""
    isparameterbound(::Type{T}, i::Integer, D) where T -> Bool
    isparameterbound(::Type{T}, name::Symbol, D) where T -> Bool
    isparameterbound(::Type{}, ::Val{}, ::Any) -> Bool

For a type `T`, judge whether a type `D` should be considered as the upper bound of one of its type parameters.
"""
@inline isparameterbound(::Type{T}, i::Integer, D) where T = isparameterbound(T, Val(i), D)
@inline isparameterbound(::Type{T}, name::Symbol, D) where T = isparameterbound(T, Val(name), D)
@inline isparameterbound(::Type{}, ::Val{}, ::Any) = false

"""
    hasparameter(::Type{T}, name::Symbol) where T -> Bool

For type `T`, judge whether it has a type parameter specified by `name`.
"""
@inline hasparameter(::Type{T}, name::Symbol) where T = _hasparameter(Val(name), parameternames(T)|>Val)
@inline _hasparameter(::Val{name}, ::Val{names}) where {name, names} = name∈names

"""
    parameternames(::Type{T}) where T -> Tuple{Vararg{Symbol}}

For a type `T`, get the names of all its type parameters.
"""
@inline parameternames(::Type{T}) where T = error("parameternames error: not defined for $(nameof(T)).")

"""
    parametertypes(::Type{T}) where T

For a type `T`, get the types of all its type parameters.

The returned types are stored in the type parameters of a `Tuple`.
"""
@inline parametertypes(::Type{T}) where T = _parametertypes(T, parametercount(T)|>Val)
@inline @generated function _parametertypes(::Type{T}, ::Val{C}) where {T, C}
    exprs = []
    for i = 1:C
        push!(exprs, :(parametertype(T, $i)))
    end
    return Expr(:curly, :Tuple, exprs...)
end

"""
    parameterpairs(::Type{T}) where T

For a type `T`, get the name-type pairs of all its type parameters.

The return types are stored in the type parameters of a `NamedTuple`.
"""
@inline parameterpairs(::Type{T}) where T = NamedTuple{parameternames(T), parametertypes(T)}

"""
    isparameterbounds(::Type{T}, ::Type{PS}) where {T, PS<:Tuple} -> Tuple{Vararg{Bool}}
    isparameterbounds(::Type{T}, ::Type{PS}) where {T, PS<:NamedTuple} -> Tuple{Vararg{Bool}}

For a type `T`, judge whether the types specified by `PS` should be considered as the upper bounds of its corresponding type parameters.
"""
@inline isparameterbounds(::Type{T}, ::Type{PS}) where {T, PS<:Tuple} = _isparameterbounds(T, PS, parametercount(T)|>Val)
@inline isparameterbounds(::Type{T}, ::Type{PS}) where {T, PS<:NamedTuple} = _isparameterbounds(T, PS, parameternames(T)|>Val)
@inline @generated function _isparameterbounds(::Type{T}, ::Type{PS}, ::Val{C}) where {T, PS<:Tuple, C}
    exprs = []
    for i = 1:C
        N = Val(i)
        P = fieldtype(PS, i)
        push!(exprs, :(isparameterbound(T, $N, $P)))
    end
    return Expr(:tuple, exprs...)
end
@inline @generated function _isparameterbounds(::Type{T}, ::Type{PS}, ::Val{names}) where {T, PS<:NamedTuple, names}
    exprs = []
    for i = 1:length(names)
        N = Val(names[i])
        P = fieldtype(PS, names[i])
        push!(exprs, :(isparameterbound(T, $N, $P)))
    end
    return Expr(:tuple, exprs...)
end

"""
    reparameter(::Type{T}, i::Integer, P, ub::Bool=isparameterbound(T, i, P)) where T
    reparameter(::Type{T}, name::Symbol, P, ub::Bool=isparameterbound(T, name, P)) where T

For a type `T`, replace the type of its ith type parameter with `P`. Here, `ub` determines whether `P` should be considered as the upper bound.
"""
@inline reparameter(::Type{T}, i::Integer, P, ub::Bool=isparameterbound(T, i, P)) where T = _reparameter(T, Val(i), Tuple{P}, Val(ub))
@inline reparameter(::Type{T}, name::Symbol, P, ub::Bool=isparameterbound(T, name, P)) where T = _reparameter(T, parameterorder(T, name)|>Val, Tuple{P}, Val(ub))
@inline @generated function _reparameter(::Type{T}, ::Val{i}, ::Type{P}, ::Val{ub}) where {T, i, P, ub}
    params = collect(DataType(T).parameters)
    params[i] = ub ? TypeVar(gensym(), fieldtype(P, 1)) : fieldtype(P, 1)
    V = Core.apply_type(rawtype(T), params...)
    for k = 1:length(params)
        isa(params[k], TypeVar) && (V = UnionAll(params[k], V))
    end
    return V
end

"""
    promoteparameters(::Type{T1}, ::Type{T2}) where {T1<:NamedTuple, T2<:NamedTuple}

Promote the types specified by two named tuples with the same names accordingly.

The result is stored in the type parameters of a `NamedTuple`.
"""
@inline @generated function promoteparameters(::Type{T1}, ::Type{T2}) where {T1<:NamedTuple, T2<:NamedTuple}
    exprs = []
    names = Tuple(union(fieldnames(T1), fieldnames(T2)))
    for (i, name) in enumerate(names)
        hasfield(T1, name) && (F1 = fieldtype(T1, name))
        hasfield(T2, name) && (F2 = fieldtype(T2, name))
        if hasfield(T1, name) && hasfield(T2, name)
            push!(exprs, :(promote_type($F1, $F2)))
        else
            push!(exprs, hasfield(T1, name) ? F1 : F2)
        end
    end
    return Expr(:curly, :NamedTuple, names, Expr(:curly, :Tuple, exprs...))
end

"""
    fulltype(::Type{T}, ::Type{PS}, ubs::Tuple{Vararg{Bool}}=isparameterbounds(T, PS)) where {T, PS<:Tuple}
    fulltype(::Type{T}, ::Type{PS}, ubs::Tuple{Vararg{Bool}}=isparameterbounds(T, PS)) where {T, PS<:NamedTuple}

Get the full type of type `T` with the type parameters replaced by those of `PS`.

Here, `ubs` determines whether the new type parameter should be considered as the upper bound accordingly.
"""
@inline function fulltype(::Type{T}, ::Type{PS}, ubs::Tuple{Vararg{Bool}}=isparameterbounds(T, PS)) where {T, PS<:Tuple}
    @assert parametercount(T) == fieldcount(PS) == length(ubs) "fulltype error: length-mismatched input parameters."
    return _fulltype(T, PS, Val(ubs))
end
@inline function fulltype(::Type{T}, ::Type{PS}, ubs::Tuple{Vararg{Bool}}=isparameterbounds(T, PS)) where {T, PS<:NamedTuple}
    _fulltype(T, PS, parameternames(T)|>Val, Val(ubs))
end
@inline @generated function _fulltype(::Type{T}, ::Type{PS}, ::Val{ubs}) where {T, PS, ubs}
    VS = [ubs[i] ? TypeVar(gensym(), fieldtype(PS, i)) : fieldtype(PS, i) for i=1:length(ubs)]
    V = Core.apply_type(rawtype(T), VS...)
    for i = 1:length(VS)
        ubs[i] && (V = UnionAll(VS[i], V))
    end
    return V
end
@inline @generated function _fulltype(::Type{T}, ::Type{PS}, ::Val{names}, ::Val{ubs}) where {T, PS<:NamedTuple, names, ubs}
    VS = [(ubs[i] ? TypeVar(gensym(), fieldtype(PS, names[i])) : fieldtype(PS, names[i])) for i=1:length(names)]
    V = Core.apply_type(rawtype(T), VS...)
    for i = 1:length(VS)
        ubs[i] && (V = UnionAll(VS[i], V))
    end
    return V
end

"""
    contentcount(::Type{T}) where T -> Int

For a type `T`, get the number of its predefined contents.
"""
@inline contentcount(::Type{T}) where T = length(contentnames(T))

"""
    contentname(::Type{T}, i::Integer) where T -> Symbol

For a type `T`, get the name of its ith predefined content.
"""
@inline contentname(::Type{T}, i::Integer) where T = contentnames(T)[i]

"""
    contentorder(::Type{T}, name::Symbol) where T -> Int

For a type `T`, get the position order of a predefined content by the name.
"""
@inline contentorder(::Type{T}, name::Symbol) where T = _order(Val(name), contentnames(T)|>Val)

"""
    contenttype(::Type{T}, name::Symbol) where T
    contenttype(::Type{T}, ::Val{name}) where {T, name}

For a type `T`, get the type of a predefined content by the name.
"""
@inline contenttype(::Type{T}, name::Symbol) where T = contenttype(T, Val(name))
@inline contenttype(::Type{T}, ::Val{name}) where {T, name} = fieldtype(T, name)

"""
    hascontent(::Type{T}, name::Symbol) where T -> Bool

For a type `T`, judge whether it has a predefined content specified by `name`.
"""
@inline hascontent(::Type{T}, name::Symbol) where T = _hascontent(name|>Val, contentnames(T)|>Val)
@inline @generated _hascontent(::Val{name}, ::Val{names}) where {name, names} = name∈names

"""
    getcontent(m, i::Integer)
    getcontent(m, name::Symbol)
    getcontent(m, ::Val{name}) where name

Get the value of the predefined content of `m`.
"""
@inline getcontent(m, i::Integer) = getcontent(m, contentname(typeof(m), i))
@inline getcontent(m, name::Symbol) = getcontent(m, Val(name))
@inline getcontent(m, ::Val{name}) where name = getfield(m, name)

"""
    contentnames(::Type{T}) where T -> Tuple{Vararg{Symbol}}

For a type `T`, define the names of its predefined contents.
"""
@inline @generated contentnames(::Type{T}) where T = fieldnames(T)

"""
    contenttypes(::Type{T}) where T

For a type `T`, get the types of its predefined contents.
"""
@inline contenttypes(::Type{T}) where T = _contenttypes(T, contentnames(T)|>Val)
@inline @generated function _contenttypes(::Type{T}, ::Val{names}) where {T, names}
    exprs = []
    for name in QuoteNode.(names)
        push!(exprs, :(contenttype(T, $name)))
    end
    return Expr(:curly, :Tuple, exprs...)
end

"""
    dissolve(m, f::Function=identity, args::Tuple=(), kwargs::NamedTuple=NamedTuple()) -> Tuple

Convert `m` to a tuple by the function `f` applied elementally to its contents with the extra positional arguments (`args`) and keyword arguments (`kwargs`).

The underlying called interface is the `dissolve` function when `f` is applied to each content of `m`:
```julia
dissolve(m, Val(name), f, args, kwargs)
```
Here, `name` is the name of a content of `m`.

Basically, the rule of how `f` operates on each field of `m` can be overridden by redefining the above `dissolve` function.
!!!note
   The default `dissolve` function ignores the operation of function `f` and just return the content value of `m`.
"""
@inline function dissolve(m, f::Function=identity, args::Tuple=(), kwargs::NamedTuple=NamedTuple())
    dissolvehelper(m, f, args, kwargs, m|>typeof|>contentnames|>Val)
end
@inline @generated function dissolvehelper(m, f::Function, args::Tuple, kwargs::NamedTuple, ::Val{names}) where names
    exprs = []
    for name in names
        name = Val(name)
        push!(exprs, :(dissolve(m, $name, f, args, kwargs)))
    end
    return Expr(:tuple, exprs...)
end

"""
    dissolve(m, ::Val{name}, f::Function, args::Tuple, kwargs::NamedTuple) where name

Dissolve the content specified by `name` of `m` by the function `f` applied with the extra positional arguments (`args`) and keyword arguments (`kwargs`).
"""
@inline dissolve(m, ::Val{name}, f::Function, args::Tuple, kwargs::NamedTuple) where name = getcontent(m, name|>Val)

struct EfficientOperations end
"""
    efficientoperations

Indicate that the efficient operations, i.e. "=="/"isequal", "<"/"isless" or "replace", will be used.
"""
const efficientoperations = EfficientOperations()

"""
    ==(::EfficientOperations, o1, o2) -> Bool

Compare two objects and judge whether they are equivalent to each other.
"""
@inline @generated function Base.:(==)(::EfficientOperations, o1, o2)
    fcount = fieldcount(o1)
    if fcount == fieldcount(o2)
        if fcount == 0
            return true
        else
            expr = :(getfield(o1, 1) == getfield(o2, 1))
            for i = 2:fcount
                expr = Expr(:&&, expr, :(getfield(o1, $i) == getfield(o2, $i)))
            end
            return expr
        end
    else
        return false
    end
end

"""
    isequal(::EfficientOperations, o1, o2) -> Bool

Compare two objects and judge whether they are equivalent to each other.
"""
@inline @generated function Base.isequal(::EfficientOperations, o1, o2)
    fcount = fieldcount(o1)
    if fcount == fieldcount(o2)
        if fcount == 0
            return true
        else
            expr = :(isequal(getfield(o1, 1), getfield(o2, 1)))
            for i = 2:fcount
                expr = Expr(:&&, expr, :(isequal(getfield(o1, $i), getfield(o2, $i))))
            end
            return expr
        end
    else
        return false
    end
end

"""
    <(::EfficientOperations, o1, o2) -> Bool

Compare two objects and judge whether the first is less than the second.
"""
@inline @generated function Base.:<(::EfficientOperations, o1, o2)
    n1, n2 = fieldcount(o1), fieldcount(o2)
    N = min(n1, n2)
    expr = (n1 < n2) ? Expr(:if, :(getfield(o1, $N) == getfield(o2, $N)), true, false) : false
    expr = Expr(:if, :(getfield(o1, $N) < getfield(o2, $N)), true, expr)
    for i in range(N-1, stop=1, step=-1)
        expr = Expr(:if, :(getfield(o1, $i) > getfield(o2, $i)), false, expr)
        expr = Expr(:if, :(getfield(o1, $i) < getfield(o2, $i)), true, expr)
    end
    return expr
end

"""
    isless(::EfficientOperations, o1, o2) -> Bool

Compare two objects and judge whether the first is less than the second.
"""
@inline @generated function Base.isless(::EfficientOperations, o1, o2)
    n1, n2 = fieldcount(o1), fieldcount(o2)
    N = min(n1, n2)
    expr = n1 < n2 ? Expr(:if, :(getfield(o1, $N) == getfield(o2, $N)), true, false) : false
    expr = Expr(:if, :(isless(getfield(o1, $N), getfield(o2, $N))), true, expr)
    for i in range(N-1, stop=1, step=-1)
        expr = Expr(:if, :(isless(getfield(o2, $i), getfield(o1, $i))), false, expr)
        expr = Expr(:if, :(isless(getfield(o1, $i), getfield(o2, $i))), true, expr)
    end
    return expr
end

"""
    isapprox(::EfficientOperations, o1, o2; atol=atol, rtol=rtol) -> Bool
    isapprox(::EfficientOperations, fields::Union{Union{Nothing, Integer, Symbol}, Tuple{Vararg{Union{Integer, Symbol}}}}, o1, o2; atol=atol, rtol=rtol) -> Bool
    isapprox(::EfficientOperations, ::Val{fields}, o1, o2; atol=atol, rtol=rtol) where fields -> Bool

Compare two objects and judge whether they are inexactly equivalent to each other.
"""
@inline Base.isapprox(::EfficientOperations, o1, o2; atol=atol, rtol=rtol) = isapprox(efficientoperations, nothing, o1, o2, atol=atol, rtol=rtol)
@inline function Base.isapprox(::EfficientOperations, fields::Union{Nothing, Union{Integer, Symbol}, Tuple{Vararg{Union{Integer, Symbol}}}}, o1, o2; atol=atol, rtol=rtol)
    isapprox(efficientoperations, fields|>Val, o1, o2; atol=atol, rtol=rtol)
end
@inline @generated function Base.isapprox(::EfficientOperations, ::Val{fields}, o1, o2; atol=atol, rtol=rtol) where fields
    (fieldcount(o1)≠fieldcount(o2) || fieldnames(o1)≠fieldnames(o2)) && return false
    isnothing(fields) && (fields = ntuple(i->i, fieldcount(o1)))
    isa(fields, Union{Integer, Symbol}) && (fields = (fields,))
    fields = Set(isa(field, Symbol) ? findfirst(isequal(field), fieldnames(o1)) : field for field in fields)
    exprs = []
    for i = 1:fieldcount(o1)
        if i∈fields
            push!(exprs, :(isapprox(getfield(o1, $i), getfield(o2, $i); atol=atol, rtol=rtol)::Bool))
        else
            push!(exprs, :(isequal(getfield(o1, $i), getfield(o2, $i))::Bool))
        end
    end
    return Expr(:&&, exprs...)
end

"""
    replace(::EfficientOperations, o; kwargs...) -> typeof(o)

Return a copy of the input object with some of the field values replaced by the keyword arguments.
"""
@inline @generated function Base.replace(::EfficientOperations, o; kwargs...)
    exprs = [:(get(kwargs, $name, getfield(o, $name))) for name in QuoteNode.(fieldnames(o))]
    return :(rawtype(typeof(o))($(exprs...)))
end

end # module


text:
################################################################################
# Interface
# A Measurement must inherit from AbstractMeasurement and implement the
# following functions.

abstract type AbstractMeasurement end

"""
    measure!(measurement, mc, model, sweep_index)

Performs a measurement during the measurement phase.
"""
function measure!(m::AbstractMeasurement, mc, model, i)
    throw(MethodError(measure!, (m, mc, model)))
end

###################################
# You may also implement

"""
    prepare!(measurement, mc, model)

This method is called between the thermalization and the measurement phase.
"""
prepare!(m::AbstractMeasurement, mc, model) = nothing

"""
    finish!(measurement, mc, model)

Finish a measurement. This method is called after the measurement phase.
"""
finish!(m::AbstractMeasurement, mc, model) = nothing

"""
    observables(measurement)

Returns a dictionary of observables.

The default implementation searches for all fields `<: AbstractObservable` and
builds a dictionary pairs `name(obs) => obs`.
"""
function observables(m::AbstractMeasurement)
    fns = fieldnames(typeof(m))
    os = [getfield(m, fn) for fn in fns if getfield(m, fn) isa AbstractObservable]
    Dict{String, AbstractObservable}(MonteCarloObservable.name(o) => o for o in os)
end

"""
    save!(measurement, filename, entryname)

Saves a measurement to a jld-file `filename` in group `entryname`.

The default implementation uses `observables(measurement)` to find observables
and saves them using
`MonteCarloObservable.saveobs(obs, filename, entryname * "/" * obs.group)`.
The `entryname` passed to this function is equivalent to the structure given by
`measurements()`, e.g. `ME/config/` if `measurements(mc)[:ME][:config]` carries
a measurement.

See also [`save_measurements!`](@ref), [`observables`](@ref)
"""
function save!(m::AbstractMeasurement, filename::String, entryname::String)
    for (k, obs) in observables(m)
        saveobs(obs, filename, entryname * "/" * obs.group)
    end
    nothing
end

################################################################################
# A new model may implement the following for convenience

"""
    default_measurements(mc, model)

Return a dictionary of default measurements for a given Monte Carlo flavour and
model. If there is no implementation given for the specific Monte Carlo flavour
an empty dictionary will be returned.
"""
default_measurements(mc, model) = Dict{Symbol, AbstractMeasurement}()

################################################################################
# mc based, default measurements

"""
    ConfigurationMeasurement(mc, model[, rate=1])

Measures configurations of the given Monte Carlo flavour and model. The rate of
measurements can be reduced with `rate`. (e.g. `rate=10` means 1 measurement per
10 sweeps)
"""
struct ConfigurationMeasurement{OT <: Observable} <: AbstractMeasurement
    obs::OT
    rate::Int64
    function ConfigurationMeasurement(mc, model, rate=1)
        o = Observable(typeof(mc.conf), "Configurations")
        new{typeof(o)}(o, rate)
    end
end
function measure!(m::ConfigurationMeasurement, mc, model, i::Int64)
    (i % m.rate == 0) && push!(m.obs, conf(mc))
    nothing
end

################################################################################
# called by simulation

for function_name in (:prepare!, :finish!)
    @eval begin
        function $(function_name)(
                measurements::Dict{Symbol, AbstractMeasurement},
                mc, model
            )
            for (k, m) in measurements
                $(function_name)(m, mc, model)
            end
            nothing
        end
    end
end

function measure!(
        measurements::Dict{Symbol, AbstractMeasurement},
        mc, model, sweep_index::Int64
    )
    for (k, m) in measurements
        measure!(m, mc, model, sweep_index)
    end
    nothing
end

################################################################################
# other convenience functions

# printing
function Base.show(io::IO, m::AbstractMeasurement)
    #  no parametrization -v    v- no MonteCarlo.
    typename = typeof(m).name.name
    fnames = fieldnames(typeof(m))
    observables = [s for s in fnames if getfield(m, s) isa AbstractObservable]
    other = [s for s in fnames if !(getfield(m, s) isa AbstractObservable)]
    println(io, typename)
    for obs_fieldname in observables
        o = getfield(m, obs_fieldname)
        oname = MonteCarloObservable.name(o)
        otypename = typeof(o).name.name
        println(io, "\t", obs_fieldname, "::", otypename, "\t → \"", oname, "\"")
    end
    for fieldname in other
        println(io, "\t", fieldname, "::", typeof(getfield(m, fieldname)))
    end
    nothing
end

function Base.show(io::IO, ::MIME"text/plain", m::AbstractMeasurement)
    #small
    #  no parametrization -v    v- no MonteCarlo.
    typename = typeof(m).name.name
    fnames = fieldnames(typeof(m))
    temp = [s for s in fnames if getfield(m, s) isa AbstractObservable]
    observable_names = map(temp) do obs_fieldname
        MonteCarloObservable.name(getfield(m, obs_fieldname))
    end
    print(io, typename, "(\"", join(observable_names, "\", \""), "\")")
    nothing
end

"""
    measurements(mc)

Returns a nested dictionary of all measurements used in a given Monte Carlo
simulation `mc`. The thermalization stage is accessed by `:TH`, the measurement
stage by `:ME`.
"""
function measurements(mc::MonteCarloFlavor)
    return Dict(
        :TH => mc.thermalization_measurements,
        :ME => mc.measurements
    )
end

"""
    observables(mc::MonteCarloFlavor)

Returns a nested dictionary of all observables used in a given Monte Carlo
simulation `mc`. The result `obs` is indexed as `obs[stage][measurement][name]`,
where `stage` is `:TH` (thermalization stage) or `:ME` (measurement stage),
`measurement::Symbol` is the name of the measurement and `name::String` is the
name of the observable.
"""
function observables(mc::MonteCarloFlavor)
    th_obs = Dict{Symbol, Dict{String, AbstractObservable}}(
        k => observables(m) for (k, m) in mc.thermalization_measurements
    )
    me_obs = Dict{Symbol, Dict{String, AbstractObservable}}(
        k => observables(m) for (k, m) in mc.measurements
    )

    return Dict(:TH => th_obs, :ME => me_obs)
end

"""
    push!(mc, tag::Symbol => MT::Type{<:AbstractMeasurement}[, stage=:ME])

Adds a new pair `tag => MT(mc, model)`, where `MT` is a type
`<: AbstractMeasurement`, to either the thermalization or measurement `stage`
(`:TH` or `:ME`) of the simulation `mc`.

Examples:
```
push!(mc, :conf => ConfigurationMeasurement)
push!(mc, :conf => ConfigurationMeasurement, stage=:ME)
```

See also: [`unsafe_push!`](@ref)
"""
function Base.push!(mc::MonteCarloFlavor, p::Pair{Symbol, T}, stage=:ME) where T
    tag, MT = p
    p[2] <: AbstractMeasurement || throw(ErrorException(
        "The given `tag => MT` pair should be of type " *
        "`Pair{Symbol, Type(<: AbstractMeasurement)}`, but is " *
        "`Pair{Symbol, Type{$(p[2])}}`."
    ))
    unsafe_push!(mc, tag => MT(mc, mc.model), stage)
end

"""
    unsafe_push!(mc, tag::Symbol => m::AbstractMeasurement[, stage=:ME])

Adds a pair `tag => m` to either the thermalization or measurement stage (`:TH`
or `:ME`) of the given simulation `mc`.

Note that this function is unsafe as it does not test whether `m` is a valid
measurement for the given simulation.

Examples:
```
push!(mc, :conf => ConfigurationMeasurement(mc, model))
push!(mc, :conf => ConfigurationMeasurement(mc, model), stage=:ME)
```

See also: [`MonteCarlo.push!`](@ref)
"""
function unsafe_push!(mc::MonteCarloFlavor, p::Pair{Symbol, <:AbstractMeasurement}, stage=:ME)
    if stage in (:ME, :me, :Measurement, :measurement)
        push!(mc.measurements, p)
    elseif stage in (:TH, :th, :Thermalization, :thermalization, :thermalization_measurements)
        push!(mc.thermalization_measurements, p)
    else
        throw(ErrorException("`stage = $stage` is not valid."))
    end
end

"""
    delete!(mc, key[, stage=:MC])
    delete!(mc, MT::Type{<:AbstractMeasurement}[, stage=:MC])

Deletes a measurement from the given Monte Carlo simulation by key or by type.
When deleting by type, multiple measurements can be targeted using inheritance.
For example, `delete!(mc, IsingMeasurement)` wil delete all
`IsingEnergyMeasurement` and `IsingMagnmetizationMeasurement` objects.

Examples:
```
delete!(mc, :conf)
delete!(mc, ConfigurationMeasurement)
delete!(mc, ConfigurationMeasurement, stage=:ME)
```
"""
function Base.delete!(mc::MonteCarloFlavor, key::Symbol, stage=:ME)
    if stage in (:ME, :me, :Measurement, :measurement)
        delete!(mc.measurements, key)
    elseif stage in (:TH, :th, :Thermalization, :thermalization, :thermalization_measurements)
        delete!(mc.thermalization_measurements, key)
    else
        throw(ErrorException("`stage = $stage` is not valid."))
    end
end

function Base.delete!(mc::MonteCarloFlavor, MT::Type{<: AbstractMeasurement}, stage=:ME)
    if stage in (:ME, :me, :Measurement, :measurement)
        ks = collect(keys(mc.measurements))
        for k in ks
            if typeof(mc.measurements[k]) <: MT
                delete!(mc.measurements, k)
            end
        end
        mc.measurements
    elseif stage in (:TH, :th, :Thermalization, :thermalization, :thermalization_measurements)
        ks = collect(keys(mc.thermalization_measurements))
        for k in ks
            if typeof(mc.thermalization_measurements[k]) <: MT
                delete!(mc.thermalization_measurements, k)
            end
        end
        mc.thermalization_measurements
    else
        throw(ErrorException("`stage = $stage` is not valid."))
    end
end

"""
    save_measurements!(mc, filename[; force_overwrite=false, allow_rename=true])

Saves all measurements to `filename`.

If `force_overwrite = true` the file will
be overwritten if it already exists. If `allow_rename = true` random characters
will be added to the filename until it becomes unique.
"""
function save_measurements!(
        mc::MonteCarloFlavor, filename::String;
        force_overwrite = false, allow_rename = true
    )
    isfile(filename) && !force_overwrite && !allow_rename && throw(ErrorException(
        "Cannot save because \"$filename\" already exists. Consider setting " *
        "`allow_reanme = true` to adjust the filename or `force_overwrite = true`" *
        " to overwrite the file."
    ))
    if isfile(filename) && !force_overwrite && allow_rename
        while isfile(filename)
            # those map to 0-9, A-Z, a-z
            x = rand([(48:57)..., (65:90)..., (97:122)...])
            s = string(Char(x))
            filename = filename[1:end-4] * s * ".jld"
        end
    end

    measurement_dict = measurements(mc)
    for (k0, v0) in measurement_dict # :TH or :ME
        for (k1, meas) in v0 # Measurement name
            entryname = "Measurements/$k0/$k1"
            save!(meas, filename, entryname)
        end
    end
end

"""
    load_measurements(filename)

Loads recorded measurements for a given `filename`. The output is formated like
`observables(mc)`

See also [`observables`](@ref)
"""
function load_measurements(filename::String)
    input = load(filename)
    output = Dict{Symbol, Dict{Symbol, Dict{String, AbstractObservable}}}()
    for (k1, v1) in input["Measurements"]
        # k1 = TH or ME
        push!(output, Symbol(k1) => Dict{Symbol, Dict{String, AbstractObservable}}())
        for (k2, v2) in v1
            # k2 is the key for Measurement
            push!(output[Symbol(k1)], Symbol(k2) => v2)
        end
    end
    output
end


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

mutable struct IoK8sApiCoreV1Volume <: SwaggerModel
    awsElasticBlockStore::Any # spec type: Union{ Nothing, IoK8sApiCoreV1AWSElasticBlockStoreVolumeSource } # spec name: awsElasticBlockStore
    azureDisk::Any # spec type: Union{ Nothing, IoK8sApiCoreV1AzureDiskVolumeSource } # spec name: azureDisk
    azureFile::Any # spec type: Union{ Nothing, IoK8sApiCoreV1AzureFileVolumeSource } # spec name: azureFile
    cephfs::Any # spec type: Union{ Nothing, IoK8sApiCoreV1CephFSVolumeSource } # spec name: cephfs
    cinder::Any # spec type: Union{ Nothing, IoK8sApiCoreV1CinderVolumeSource } # spec name: cinder
    configMap::Any # spec type: Union{ Nothing, IoK8sApiCoreV1ConfigMapVolumeSource } # spec name: configMap
    csi::Any # spec type: Union{ Nothing, IoK8sApiCoreV1CSIVolumeSource } # spec name: csi
    downwardAPI::Any # spec type: Union{ Nothing, IoK8sApiCoreV1DownwardAPIVolumeSource } # spec name: downwardAPI
    emptyDir::Any # spec type: Union{ Nothing, IoK8sApiCoreV1EmptyDirVolumeSource } # spec name: emptyDir
    fc::Any # spec type: Union{ Nothing, IoK8sApiCoreV1FCVolumeSource } # spec name: fc
    flexVolume::Any # spec type: Union{ Nothing, IoK8sApiCoreV1FlexVolumeSource } # spec name: flexVolume
    flocker::Any # spec type: Union{ Nothing, IoK8sApiCoreV1FlockerVolumeSource } # spec name: flocker
    gcePersistentDisk::Any # spec type: Union{ Nothing, IoK8sApiCoreV1GCEPersistentDiskVolumeSource } # spec name: gcePersistentDisk
    gitRepo::Any # spec type: Union{ Nothing, IoK8sApiCoreV1GitRepoVolumeSource } # spec name: gitRepo
    glusterfs::Any # spec type: Union{ Nothing, IoK8sApiCoreV1GlusterfsVolumeSource } # spec name: glusterfs
    hostPath::Any # spec type: Union{ Nothing, IoK8sApiCoreV1HostPathVolumeSource } # spec name: hostPath
    iscsi::Any # spec type: Union{ Nothing, IoK8sApiCoreV1ISCSIVolumeSource } # spec name: iscsi
    name::Any # spec type: Union{ Nothing, String } # spec name: name
    nfs::Any # spec type: Union{ Nothing, IoK8sApiCoreV1NFSVolumeSource } # spec name: nfs
    persistentVolumeClaim::Any # spec type: Union{ Nothing, IoK8sApiCoreV1PersistentVolumeClaimVolumeSource } # spec name: persistentVolumeClaim
    photonPersistentDisk::Any # spec type: Union{ Nothing, IoK8sApiCoreV1PhotonPersistentDiskVolumeSource } # spec name: photonPersistentDisk
    portworxVolume::Any # spec type: Union{ Nothing, IoK8sApiCoreV1PortworxVolumeSource } # spec name: portworxVolume
    projected::Any # spec type: Union{ Nothing, IoK8sApiCoreV1ProjectedVolumeSource } # spec name: projected
    quobyte::Any # spec type: Union{ Nothing, IoK8sApiCoreV1QuobyteVolumeSource } # spec name: quobyte
    rbd::Any # spec type: Union{ Nothing, IoK8sApiCoreV1RBDVolumeSource } # spec name: rbd
    scaleIO::Any # spec type: Union{ Nothing, IoK8sApiCoreV1ScaleIOVolumeSource } # spec name: scaleIO
    secret::Any # spec type: Union{ Nothing, IoK8sApiCoreV1SecretVolumeSource } # spec name: secret
    storageos::Any # spec type: Union{ Nothing, IoK8sApiCoreV1StorageOSVolumeSource } # spec name: storageos
    vsphereVolume::Any # spec type: Union{ Nothing, IoK8sApiCoreV1VsphereVirtualDiskVolumeSource } # spec name: vsphereVolume

    function IoK8sApiCoreV1Volume(;awsElasticBlockStore=nothing, azureDisk=nothing, azureFile=nothing, cephfs=nothing, cinder=nothing, configMap=nothing, csi=nothing, downwardAPI=nothing, emptyDir=nothing, fc=nothing, flexVolume=nothing, flocker=nothing, gcePersistentDisk=nothing, gitRepo=nothing, glusterfs=nothing, hostPath=nothing, iscsi=nothing, name=nothing, nfs=nothing, persistentVolumeClaim=nothing, photonPersistentDisk=nothing, portworxVolume=nothing, projected=nothing, quobyte=nothing, rbd=nothing, scaleIO=nothing, secret=nothing, storageos=nothing, vsphereVolume=nothing)
        o = new()
        validate_property(IoK8sApiCoreV1Volume, Symbol("awsElasticBlockStore"), awsElasticBlockStore)
        setfield!(o, Symbol("awsElasticBlockStore"), awsElasticBlockStore)
        validate_property(IoK8sApiCoreV1Volume, Symbol("azureDisk"), azureDisk)
        setfield!(o, Symbol("azureDisk"), azureDisk)
        validate_property(IoK8sApiCoreV1Volume, Symbol("azureFile"), azureFile)
        setfield!(o, Symbol("azureFile"), azureFile)
        validate_property(IoK8sApiCoreV1Volume, Symbol("cephfs"), cephfs)
        setfield!(o, Symbol("cephfs"), cephfs)
        validate_property(IoK8sApiCoreV1Volume, Symbol("cinder"), cinder)
        setfield!(o, Symbol("cinder"), cinder)
        validate_property(IoK8sApiCoreV1Volume, Symbol("configMap"), configMap)
        setfield!(o, Symbol("configMap"), configMap)
        validate_property(IoK8sApiCoreV1Volume, Symbol("csi"), csi)
        setfield!(o, Symbol("csi"), csi)
        validate_property(IoK8sApiCoreV1Volume, Symbol("downwardAPI"), downwardAPI)
        setfield!(o, Symbol("downwardAPI"), downwardAPI)
        validate_property(IoK8sApiCoreV1Volume, Symbol("emptyDir"), emptyDir)
        setfield!(o, Symbol("emptyDir"), emptyDir)
        validate_property(IoK8sApiCoreV1Volume, Symbol("fc"), fc)
        setfield!(o, Symbol("fc"), fc)
        validate_property(IoK8sApiCoreV1Volume, Symbol("flexVolume"), flexVolume)
        setfield!(o, Symbol("flexVolume"), flexVolume)
        validate_property(IoK8sApiCoreV1Volume, Symbol("flocker"), flocker)
        setfield!(o, Symbol("flocker"), flocker)
        validate_property(IoK8sApiCoreV1Volume, Symbol("gcePersistentDisk"), gcePersistentDisk)
        setfield!(o, Symbol("gcePersistentDisk"), gcePersistentDisk)
        validate_property(IoK8sApiCoreV1Volume, Symbol("gitRepo"), gitRepo)
        setfield!(o, Symbol("gitRepo"), gitRepo)
        validate_property(IoK8sApiCoreV1Volume, Symbol("glusterfs"), glusterfs)
        setfield!(o, Symbol("glusterfs"), glusterfs)
        validate_property(IoK8sApiCoreV1Volume, Symbol("hostPath"), hostPath)
        setfield!(o, Symbol("hostPath"), hostPath)
        validate_property(IoK8sApiCoreV1Volume, Symbol("iscsi"), iscsi)
        setfield!(o, Symbol("iscsi"), iscsi)
        validate_property(IoK8sApiCoreV1Volume, Symbol("name"), name)
        setfield!(o, Symbol("name"), name)
        validate_property(IoK8sApiCoreV1Volume, Symbol("nfs"), nfs)
        setfield!(o, Symbol("nfs"), nfs)
        validate_property(IoK8sApiCoreV1Volume, Symbol("persistentVolumeClaim"), persistentVolumeClaim)
        setfield!(o, Symbol("persistentVolumeClaim"), persistentVolumeClaim)
        validate_property(IoK8sApiCoreV1Volume, Symbol("photonPersistentDisk"), photonPersistentDisk)
        setfield!(o, Symbol("photonPersistentDisk"), photonPersistentDisk)
        validate_property(IoK8sApiCoreV1Volume, Symbol("portworxVolume"), portworxVolume)
        setfield!(o, Symbol("portworxVolume"), portworxVolume)
        validate_property(IoK8sApiCoreV1Volume, Symbol("projected"), projected)
        setfield!(o, Symbol("projected"), projected)
        validate_property(IoK8sApiCoreV1Volume, Symbol("quobyte"), quobyte)
        setfield!(o, Symbol("quobyte"), quobyte)
        validate_property(IoK8sApiCoreV1Volume, Symbol("rbd"), rbd)
        setfield!(o, Symbol("rbd"), rbd)
        validate_property(IoK8sApiCoreV1Volume, Symbol("scaleIO"), scaleIO)
        setfield!(o, Symbol("scaleIO"), scaleIO)
        validate_property(IoK8sApiCoreV1Volume, Symbol("secret"), secret)
        setfield!(o, Symbol("secret"), secret)
        validate_property(IoK8sApiCoreV1Volume, Symbol("storageos"), storageos)
        setfield!(o, Symbol("storageos"), storageos)
        validate_property(IoK8sApiCoreV1Volume, Symbol("vsphereVolume"), vsphereVolume)
        setfield!(o, Symbol("vsphereVolume"), vsphereVolume)
        o
    end
end # type IoK8sApiCoreV1Volume

const _property_map_IoK8sApiCoreV1Volume = Dict{Symbol,Symbol}(Symbol("awsElasticBlockStore")=>Symbol("awsElasticBlockStore"), Symbol("azureDisk")=>Symbol("azureDisk"), Symbol("azureFile")=>Symbol("azureFile"), Symbol("cephfs")=>Symbol("cephfs"), Symbol("cinder")=>Symbol("cinder"), Symbol("configMap")=>Symbol("configMap"), Symbol("csi")=>Symbol("csi"), Symbol("downwardAPI")=>Symbol("downwardAPI"), Symbol("emptyDir")=>Symbol("emptyDir"), Symbol("fc")=>Symbol("fc"), Symbol("flexVolume")=>Symbol("flexVolume"), Symbol("flocker")=>Symbol("flocker"), Symbol("gcePersistentDisk")=>Symbol("gcePersistentDisk"), Symbol("gitRepo")=>Symbol("gitRepo"), Symbol("glusterfs")=>Symbol("glusterfs"), Symbol("hostPath")=>Symbol("hostPath"), Symbol("iscsi")=>Symbol("iscsi"), Symbol("name")=>Symbol("name"), Symbol("nfs")=>Symbol("nfs"), Symbol("persistentVolumeClaim")=>Symbol("persistentVolumeClaim"), Symbol("photonPersistentDisk")=>Symbol("photonPersistentDisk"), Symbol("portworxVolume")=>Symbol("portworxVolume"), Symbol("projected")=>Symbol("projected"), Symbol("quobyte")=>Symbol("quobyte"), Symbol("rbd")=>Symbol("rbd"), Symbol("scaleIO")=>Symbol("scaleIO"), Symbol("secret")=>Symbol("secret"), Symbol("storageos")=>Symbol("storageos"), Symbol("vsphereVolume")=>Symbol("vsphereVolume"))
const _property_types_IoK8sApiCoreV1Volume = Dict{Symbol,String}(Symbol("awsElasticBlockStore")=>"IoK8sApiCoreV1AWSElasticBlockStoreVolumeSource", Symbol("azureDisk")=>"IoK8sApiCoreV1AzureDiskVolumeSource", Symbol("azureFile")=>"IoK8sApiCoreV1AzureFileVolumeSource", Symbol("cephfs")=>"IoK8sApiCoreV1CephFSVolumeSource", Symbol("cinder")=>"IoK8sApiCoreV1CinderVolumeSource", Symbol("configMap")=>"IoK8sApiCoreV1ConfigMapVolumeSource", Symbol("csi")=>"IoK8sApiCoreV1CSIVolumeSource", Symbol("downwardAPI")=>"IoK8sApiCoreV1DownwardAPIVolumeSource", Symbol("emptyDir")=>"IoK8sApiCoreV1EmptyDirVolumeSource", Symbol("fc")=>"IoK8sApiCoreV1FCVolumeSource", Symbol("flexVolume")=>"IoK8sApiCoreV1FlexVolumeSource", Symbol("flocker")=>"IoK8sApiCoreV1FlockerVolumeSource", Symbol("gcePersistentDisk")=>"IoK8sApiCoreV1GCEPersistentDiskVolumeSource", Symbol("gitRepo")=>"IoK8sApiCoreV1GitRepoVolumeSource", Symbol("glusterfs")=>"IoK8sApiCoreV1GlusterfsVolumeSource", Symbol("hostPath")=>"IoK8sApiCoreV1HostPathVolumeSource", Symbol("iscsi")=>"IoK8sApiCoreV1ISCSIVolumeSource", Symbol("name")=>"String", Symbol("nfs")=>"IoK8sApiCoreV1NFSVolumeSource", Symbol("persistentVolumeClaim")=>"IoK8sApiCoreV1PersistentVolumeClaimVolumeSource", Symbol("photonPersistentDisk")=>"IoK8sApiCoreV1PhotonPersistentDiskVolumeSource", Symbol("portworxVolume")=>"IoK8sApiCoreV1PortworxVolumeSource", Symbol("projected")=>"IoK8sApiCoreV1ProjectedVolumeSource", Symbol("quobyte")=>"IoK8sApiCoreV1QuobyteVolumeSource", Symbol("rbd")=>"IoK8sApiCoreV1RBDVolumeSource", Symbol("scaleIO")=>"IoK8sApiCoreV1ScaleIOVolumeSource", Symbol("secret")=>"IoK8sApiCoreV1SecretVolumeSource", Symbol("storageos")=>"IoK8sApiCoreV1StorageOSVolumeSource", Symbol("vsphereVolume")=>"IoK8sApiCoreV1VsphereVirtualDiskVolumeSource")
Base.propertynames(::Type{ IoK8sApiCoreV1Volume }) = collect(keys(_property_map_IoK8sApiCoreV1Volume))
Swagger.property_type(::Type{ IoK8sApiCoreV1Volume }, name::Symbol) = Union{Nothing,eval(Meta.parse(_property_types_IoK8sApiCoreV1Volume[name]))}
Swagger.field_name(::Type{ IoK8sApiCoreV1Volume }, property_name::Symbol) =  _property_map_IoK8sApiCoreV1Volume[property_name]

function check_required(o::IoK8sApiCoreV1Volume)
    (getproperty(o, Symbol("name")) === nothing) && (return false)
    true
end

function validate_property(::Type{ IoK8sApiCoreV1Volume }, name::Symbol, val)
end


text:
# Mitigation applied after C > 500
# 7+1
function linearu!(du,u,p,t)
	I,C = u
	beta,sigmac,sigma,q,f,mbase,mlocation,mrate,cmax,c50 = p
    beta *= mbase + (1-mbase)/(1 + exp(mrate*(t - mlocation)))
    sigmac *= 1 + cmax*t/(c50+t)
    du[1] = dI = beta*(I+q*C) - sigmac*I - f*sigma*I
	du[2] = dC = sigmac*I - sigma*C
end
function nonlinearu!(du,u,p,t)
	I,C,S = u
	beta,sigmac,sigma,q,f,mbase,mlocation,mrate,cmax,c50 = p
    beta *= mbase + (1-mbase)/(1 + exp(mrate*(t - mlocation)))
    sigmac *= 1 + cmax*t/(c50+t)
    du[1] = dI = beta*(I+q*C)*S- sigmac*I - f*sigma*I
	du[2] = dC = sigmac*I - sigma*C
    du[3] = dI = beta*(I+q*C)*S
end

function linearrd!(du,u,p,t)
	I,C = u
	beta,sigmac,sigmar,sigmad,q,f,mbase,mlocation,mrate,cmax,c50 = p
    beta *= mbase + (1-mbase)/(1 + exp(mrate*(t - mlocation)))
    sigmac *= 1 + cmax*t/(c50+t)
    du[1] = dI = beta*(I+q*C) - sigmac*I - f*(sigmar+sigmad)*I
	du[2] = dC = sigmac*I - (sigmar+sigmad)*C
end

function nonlinearrd!(du,u,p,t)
	I,C,S = u
	beta,sigmac,sigmar,sigmad,q,f,mbase,mlocation,mrate,cmax,c50 = p
    beta *= mbase + (1-mbase)/(1 + exp(mrate*(t - mlocation)))
    sigmac *= 1 + cmax*t/(c50+t)
    du[1] = dI = beta*(I+q*C)*S - sigmac*I - f*(sigmar+sigmad)*I
	du[2] = dC = sigmac*I - (sigmar+sigmad)*C
    du[3] = dS = -beta*(I+q*C)*S
end

max_stars_repo_name: cannin/covid-sicr-test

max_issues_repo_name: cannin/covid-sicr-test

max_forks_repo_name: cannin/covid-sicr-test


text:
using DataStructures;
using Makie
using AbstractPlotting, GLMakie

struct Coord
  x::Int32
  y::Int32
  z::Int32
end
struct LapCoord
  coord
  lap::UInt64
end
function data_pos(sizes::Coord, offset::Int32)::Vec3{Int32}
  coffset::Int32 = offset
  z = div(coffset,sizes.x*sizes.y)
  coffset -= z*sizes.x*sizes.y
  y = div(coffset,sizes.x)
  coffset -= y*sizes.x
  x = coffset
  return Vec3{Int32}(x,y,z)
end
function data_offset(sizes::Coord, pos::Coord)::Int32
  return pos.x + pos.y*sizes.x + pos.z*sizes.x*sizes.y + 1; #+1 due to Julia counting from 1 instead of 0
end
function check_enqueue_vox(sizes::Coord, data::Array{UInt8}, queue::Queue{LapCoord}, vpos::Coord, lap::UInt64)
  offset::Int32 = data_offset(sizes, vpos)
  if (data[offset] == 1)
    data[offset] = 255
    #println(vpos)
    #println("offset:",data[offset])
    enqueue!(queue, LapCoord(vpos,lap))
  end
end
#45 rounds?
#srg(step,sizes,data,queue,45)
function srg(step::UInt64, sizes::Coord, data::Array{UInt8}, queue::Queue{LapCoord}, lastround::UInt64)
  while (isempty(queue) == false && first(queue).lap <= lastround) #FIFO has wrong command
    #println("step:",step," qsize:",length(queue))
    elem = dequeue!(queue)
    pos::Coord = elem.coord
    lap = elem.lap
    #println("lap:",lap)
    step += 1
    if (pos.x+1 < sizes.x)
      vpos = Coord(pos.x+1,pos.y,pos.z)
      check_enqueue_vox(sizes, data, queue, vpos, elem.lap+1)
    end
    if (pos.x-1 >= 0)
      vpos = Coord(pos.x-1,pos.y,pos.z)
      check_enqueue_vox(sizes, data, queue, vpos, elem.lap+1)
    end
    if (pos.y+1 < sizes.y)
      vpos = Coord(pos.x,pos.y+1,pos.z)
      check_enqueue_vox(sizes, data, queue, vpos, elem.lap+1)
    end
    if (pos.y-1 >= 0)
      vpos = Coord(pos.x,pos.y-1,pos.z)
      check_enqueue_vox(sizes, data, queue, vpos, elem.lap+1)
    end
    if (pos.z+1 < sizes.z)
      vpos = Coord(pos.x,pos.y,pos.z+1)
      check_enqueue_vox(sizes, data, queue, vpos, elem.lap+1)
    end
    if (pos.z-1 >= 0)
      vpos = Coord(pos.x,pos.y,pos.z-1)
      check_enqueue_vox(sizes, data, queue, vpos, elem.lap+1)
    end
  end
end
function computePlotSizes(size::Coord)::Coord
  return Coord(2*size.x + 1, 2*size.y + 1, 2*size.z + 1)
end
function transform_into_plotcoord(pos::Vec3{Int32})::Vec3{Int32}
  #return Vec3{Int32}(pos[1]*2+1,pos[2]*2+1,pos[3]*2+1)
  return Vec3{Int32}(pos[1]+1,pos[2]+1,pos[3]+1)
end
function Cube_surface_edges(vecmin::Vec3{Int32}, vecdist::Vec3{Int32}, outx::Array{Int32,1}, outy::Array{Int32,1}, outz::Array{Int32,1})
  vecmax::Vec3 = vecmin + vecdist
  #x000-x001-x011-x010 \\
  #x000-x100-x101-x001 \\
  #x000-x010-x110-x111 \\
  #x011-x111-x101-x100 \\
  #x110
  #
  #x000
  append!(outx, vecmin[1])
  append!(outy, vecmin[2])
  append!(outz, vecmin[3]) #
  append!(outx, vecmin[1])
  append!(outy, vecmin[2])
  append!(outz, vecmax[3]) #
  append!(outx, vecmin[1])
  append!(outy, vecmax[2])
  append!(outz, vecmax[3]) #
  append!(outx, vecmin[1])
  append!(outy, vecmax[2])
  append!(outz, vecmin[3]) #
  # x000
  append!(outx, vecmin[1])
  append!(outy, vecmin[2])
  append!(outz, vecmin[3]) #
  append!(outx, vecmax[1])
  append!(outy, vecmin[2])
  append!(outz, vecmin[3]) #
  append!(outx, vecmax[1])
  append!(outy, vecmin[2])
  append!(outz, vecmax[3]) #
  append!(outx, vecmin[1])
  append!(outy, vecmin[2])
  append!(outz, vecmax[3]) #
  # x000
  append!(outx, vecmin[1])
  append!(outy, vecmin[2])
  append!(outz, vecmin[3]) #
  append!(outx, vecmin[1])
  append!(outy, vecmax[2])
  append!(outz, vecmin[3]) #
  append!(outx, vecmax[1])
  append!(outy, vecmax[2])
  append!(outz, vecmin[3]) #
  append!(outx, vecmax[1])
  append!(outy, vecmax[2])
  append!(outz, vecmax[3]) #
  # x011
  append!(outx, vecmin[1])
  append!(outy, vecmax[2])
  append!(outz, vecmax[3]) #
  append!(outx, vecmax[1])
  append!(outy, vecmax[2])
  append!(outz, vecmax[3]) #
  append!(outx, vecmax[1])
  append!(outy, vecmin[2])
  append!(outz, vecmax[3]) #
  append!(outx, vecmax[1])
  append!(outy, vecmin[2])
  append!(outz, vecmin[3]) #
  # x110
  append!(outx, vecmax[1])
  append!(outy, vecmax[2])
  append!(outz, vecmin[3]) #
end
function plotVoxel(posmin::Vec3{Int32}, starting::Bool)
  xout::Array{Int32,1} = []
  yout::Array{Int32,1} = []
  zout::Array{Int32,1} = []
  #posmin = Vec3(0,0,0)
  posdist::Vec3{Int32} = Vec3{Int32}(1,1,1)
  Cube_surface_edges(posmin, posdist, xout,yout,zout)
  rect = Rect3D{Int32}(posmin, posdist)
  if (starting == true)
    mesh!(rect, color = (:red, 0.1), transparency = false) #true
  else
    mesh!(rect, color = (:blue, 0.1), transparency = false) #true
  end
  lines!(xout, yout, zout, color = :black)
end
function main(printcacheline::Bool)
  GLMakie.activate!()
  AbstractPlotting.inline!(false)
  lrs::Array{UInt64} = [0,1,2,4,8,12,16,20,24,28,32]#,36,40,44,48]
  cacheline_size::Int32 = 8 # simplify 64 Byte cache line
  #sizehint!(s, n) #???
  for i in lrs
    scene = Scene()
    queue = Queue{LapCoord}()
    seed = Coord(50,50,50)
    seedvec = Vec3{Int32}(seed.x,seed.y,seed.z)
    seedPlot = transform_into_plotcoord(seedvec);
    plotVoxel(seedPlot, true)
    sizes = Coord(100,100,100)
    data = ones(UInt8, 10^6) # 1-dim arary of size 10^6
    lap = 0
    enqueue!(queue, LapCoord(seed,lap))
    data[data_offset(sizes,seed)] = 255
    step::UInt64 = 0
    step += 1
    lastround::UInt64 = 0
    #lrs::Array{UInt64} = [4,5,6,7,8,9,10]
    #lrs::Array{UInt64} = [0,10,20,30,40]
    lastround = i
    srg(step,sizes,data,queue,lastround)
    #qcopy = Queue{LapCoord}()
    #next = iterate(queue) #reverse_iter
    #while next !== nothing
    #  (el,state) = next
    #  enqueue!(qcopy,el)
    #  next = iterate(queue,state)
    #end
    while (isempty(queue) == false)
      elem = dequeue!(queue)
      pos::Coord = elem.coord
      if printcacheline == false
        posvec = Vec3{Int32}(pos.x,pos.y,pos.z)
        posPlot = transform_into_plotcoord(posvec); #BEFOREACTIVE
        plotVoxel(posPlot, false) #BEFOREACTIVE
      else
        pos_do = data_offset(sizes,pos) - 1 #we need real memory offset for div and dont access the data
        offset::Int32 = mod(pos_do,cacheline_size)
        start_cacheline::Int32 = pos_do - offset
        for j in 0:cacheline_size-1
          pos_cache::Int32 = start_cacheline + j
          posvec = data_pos(sizes, pos_cache) #only for plotting!
          posPlot = transform_into_plotcoord(posvec);
          if pos_cache != pos_do
            plotVoxel(posPlot, false)
          else
            plotVoxel(posPlot, true)
          end
        end
      end
      #posvec = Vec3{Int32}(pos.x,pos.y,pos.z)
      #posPlot = transform_into_plotcoord(posvec); BEFOREACTIVE
      #plotVoxel(posPlot, false) BEFOREACTIVE
    end
    if printcacheline == false
      file = string("srgstep_t",string(lastround), ".png")
    else
      file = string("srgcache_step",string(lastround), ".png")
    end
    save(file, scene)
  end
end
printcacheline = true
main(printcacheline)

##TODO
# TESTING display(scene)
#  computation 64 * length(queue) + length(queue) * 3 * 32B
#  length(queue) * 160B


text:
using Base.Test

include("pangram.jl")

@testset "empty sentence" begin
    @test !ispangram("")
end

@testset "pangram with only lower case" begin
    @test ispangram("the quick brown fox jumps over the lazy dog")
end

@testset "missing character 'x'" begin
    @test !ispangram("a quick movement of the enemy will jeopardize five gunboats")
end

@testset "another missing character 'x'" begin
    @test !ispangram("the quick brown fish jumps over the lazy dog")
end

@testset "pangram with underscores" begin
    @test ispangram("the_quick_brown_fox_jumps_over_the_lazy_dog")
end

@testset "pangram with numbers" begin
    @test ispangram("the 1 quick brown fox jumps over the 2 lazy dogs")
end

@testset "missing letters replaced by numbers" begin
    @test !ispangram("7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog")
end

@testset "pangram with mixed case and punctuation" begin
    @test ispangram("\"Five quacking Zephyrs jolt my wax bed.\"")
end

@testset "upper and lower case versions of the same character should not be counted separately" begin
    @test !ispangram("the quick brown fox jumped over the lazy FOX")
end


text:
using Test
using Miletus
using Dates: today, Day

using CSV, DataFrames

s = SingleStock()
d1 = today()

bs = CSV.read("bs.csv")

# 1. Test with floats
calls = EuropeanCall.(d1 .+ Day.(bs.days), [s], float.(bs.strikeprice))
puts = EuropeanPut.(d1 .+ Day.(bs.days), [s], float.(bs.strikeprice))
m = GeomBMModel.([d1], float.(bs.startprice), bs.interestrate, bs.carryrate, bs.sigma)

atol=1e-6; rtol=1e-3

@test all(isapprox.(value.(m,calls), bs.c, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,puts), bs.p,  rtol=rtol,atol=atol))

# @test all(isapprox.(ivol.(m,calls,value.(m,calls)), bs.sigma))

crr = CSV.read("crr.csv")

eucalls = EuropeanCall.(d1 .+ Dates.Day.(crr.days), [s], float.(crr.strikeprice))
euputs  = EuropeanPut.(d1 .+ Dates.Day.(crr.days), [s], float.(crr.strikeprice ))
amcalls = AmericanCall.(d1 .+ Dates.Day.(crr.days), [s], float.(crr.strikeprice))
amputs  = AmericanPut.(d1 .+ Dates.Day.(crr.days), [s], float.(crr.strikeprice ))

m = CRRModel.(d1, d1 .+ Dates.Day.(crr.days), crr.nsteps, float.(crr.startprice), crr.interestrate, crr.carryrate, crr.sigma)

@test all(isapprox.(value.(m,eucalls) , crr.ce, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,euputs)  , crr.pe, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amcalls) , crr.ca, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amputs)  , crr.pa, rtol=rtol,atol=atol))

tian = CSV.read("tian.csv")

eucalls = EuropeanCall.(d1 .+ Dates.Day.(tian.days), [s], float.(tian.strikeprice))
euputs  = EuropeanPut.(d1 .+ Dates.Day.(tian.days), [s], float.(tian.strikeprice ))
amcalls = AmericanCall.(d1 .+ Dates.Day.(tian.days), [s], float.(tian.strikeprice))
amputs  = AmericanPut.(d1 .+ Dates.Day.(tian.days), [s], float.(tian.strikeprice ))

m = TianModel.([d1],d1 .+ Dates.Day.(tian.days), tian.nsteps, float.(tian.startprice), tian.interestrate, tian.carryrate, tian.sigma)

@test all(isapprox.(value.(m,eucalls) , tian.ce, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,euputs)  , tian.pe, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amcalls) , tian.ca, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amputs)  , tian.pa, rtol=rtol,atol=atol))

# 2. Test with currencies
calls = EuropeanCall.(d1 .+ Day.(bs.days), [s], float.(bs.strikeprice.*[USD]))
puts = EuropeanPut.(d1 .+ Day.(bs.days), [s], float.(bs.strikeprice.*[USD]))
m = GeomBMModel.([d1], float.(bs.startprice.*[USD]), bs.interestrate, bs.carryrate, bs.sigma)

atol=1e-6; rtol=1e-3

@test all(isapprox.(value.(m,calls) ./ [USD], bs.c, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,puts) ./ [USD], bs.p,  rtol=rtol,atol=atol))

crr = CSV.read("crr.csv")

eucalls = EuropeanCall.(d1 .+ Dates.Day.(crr.days), [s], float.(crr.strikeprice .* [USD]))
euputs  = EuropeanPut.(d1 .+ Dates.Day.(crr.days), [s], float.(crr.strikeprice .* [USD]))
amcalls = AmericanCall.(d1 .+ Dates.Day.(crr.days), [s], float.(crr.strikeprice .* [USD]))
amputs  = AmericanPut.(d1 .+ Dates.Day.(crr.days), [s], float.(crr.strikeprice .* [USD]))

m = CRRModel.([d1],d1 .+ Dates.Day.(crr.days), crr.nsteps, float.(crr.startprice.*[USD]), crr.interestrate, crr.carryrate, crr.sigma)

@test all(isapprox.(value.(m,eucalls) ./ [USD], crr.ce, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,euputs)  ./ [USD], crr.pe, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amcalls) ./ [USD], crr.ca, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amputs)  ./ [USD], crr.pa, rtol=rtol,atol=atol))

tian = CSV.read("tian.csv")

eucalls = EuropeanCall.(d1 .+ Dates.Day.(tian.days), [s], float.(tian.strikeprice .* [USD]))
euputs  = EuropeanPut.(d1 .+ Dates.Day.(tian.days), [s], float.(tian.strikeprice .* [USD]))
amcalls = AmericanCall.(d1 .+ Dates.Day.(tian.days), [s], float.(tian.strikeprice .* [USD]))
amputs  = AmericanPut.(d1 .+ Dates.Day.(tian.days), [s], float.(tian.strikeprice .* [USD]))

m = TianModel.([d1],d1 .+ Dates.Day.(tian.days), tian.nsteps, float.(tian.startprice.*[USD]), tian.interestrate, tian.carryrate, tian.sigma)

@test all(isapprox.(value.(m,eucalls) ./ [USD], tian.ce, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,euputs)  ./ [USD], tian.pe, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amcalls) ./ [USD], tian.ca, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amputs)  ./ [USD], tian.pa, rtol=rtol,atol=atol))

jr = CSV.read("jr.csv")

eucalls = EuropeanCall.(d1 .+ Dates.Day.(jr.days), [s], float.(jr.strikeprice .* [USD]))
euputs  = EuropeanPut.(d1 .+ Dates.Day.(jr.days), [s], float.(jr.strikeprice .* [USD]))
amcalls = AmericanCall.(d1 .+ Dates.Day.(jr.days), [s], float.(jr.strikeprice .* [USD]))
amputs  = AmericanPut.(d1 .+ Dates.Day.(jr.days), [s], float.(jr.strikeprice .* [USD]))

m = JRModel.([d1],d1 .+ Dates.Day.(jr.days), jr.nsteps, float.(jr.startprice.*[USD]), jr.interestrate, jr.carryrate, jr.sigma)

@test all(isapprox.(value.(m,eucalls) ./ [USD], jr.ce, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,euputs)  ./ [USD], jr.pe, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amcalls) ./ [USD], jr.ca, rtol=rtol,atol=atol))
@test all(isapprox.(value.(m,amputs)  ./ [USD], jr.pa, rtol=rtol,atol=atol))

# Test Black-76 model
d2 = d1 + Day(120)
cm = CoreForwardModel(d1, d2, 110.00USD, 0.02)
m = GeomBMModel(cm, 0.3)
o = EuropeanCall( d2, s, 100.00USD)
@test isapprox(value(m,o), 13.1833USD, rtol=1e-5)


text:
export Caccessor, CStruct, CVector, CStructAccess
export pointer_from_vector

import Base: length, size, pointer, show, unsafe_convert, Fix1
import Base: propertynames, getproperty, setproperty!, getindex, setindex!

abstract type CStructAccess{T} end

"""
CStruct{T}(p::Ptr)

Given a C-type pointer `p` to a C-struct and the equivalent Julia struct
with the same memory layout `T`, provide read and write access to the fields.
`T` must be a bits type.

Example:
struct T <: Layout
    a::Cint
    b::Cdouble
end

a = Vector{UInt8}(undef, 100)
p = pointer_from_vector(a) # usually the data are coming from C
cs = CStruct{T}(p)

cs.a = 1234
cs.b = 3.5
"""
struct CStruct{T} <: CStructAccess{T}
    pointer::Ptr{Nothing}
    function CStruct{T}(p::Ptr) where T
        isbitstype(T) || throw(ArgumentError("$T is not a bitstype"))
        new{T}(p)
    end
    CStruct{T}(data) where T = CStruct{T}(pointer_from_vector(data))
    CStruct(data) = CStruct(pointer_from_vector(data))
    CStruct(p::Ptr{T}) where T = CStruct{T}(p)
end

struct CStructGuided{T,D} <: CStructAccess{T}
    cs::CStruct{T}
    guide::Vector{D}
    function CStructGuided{T}(data::Vector{D}) where {T,D<:Union{Integer,Ptr}}
        new{T,D}(CStruct{T}(data), data)
    end
end
CStructGuided(::Type{T}, veclens=()) where T = CStructGuided{T}(create_bytes(T, veclens))

"""
    CVector

Abstract vector type for julia objects used to access elements of C-vectors,
which are based by plain C memory. Memory layout is described by `Layout` structs.
"""
struct CVector{T} <: AbstractVector{T}
    pointer::Ptr{Nothing}
    length::Int
    function CVector{T}(p::Ptr, length::Integer=-1) where T
        isbitstype(T) || throw(ArgumentError("$T is not a bitstype"))
        new{T}(p, length)
    end
end

const CAccessor = Union{CStruct, CVector}
# accessing the fields represented by CStruct
# to access the pointer use function `pointer`
propertynames(::CStruct{T}) where T = fieldnames(T)
propertynames(::CStructGuided{T}) where T = fieldnames(T)

function getproperty(cs::CStruct{T}, field::Symbol) where T
    fp = pointer_for_field(cs, field)
    get_from_pointer(fp, cs)
end
getproperty(sg::CStructGuided, field::Symbol) = getproperty(getfield(sg, :cs), field)

function setproperty!(cs::CStruct{T}, field::Symbol, v) where T
    fp = pointer_for_field(cs, field)
    set_at_pointer!(fp, v)
end
setproperty!(sg::CStructGuided, field::Symbol, v) = setproperty!(getfield(sg, :cs), field, v)

function getindex(cv::CVector{T}, i::Integer) where T
    p = pointer_for_index(cv, i)
    get_from_pointer(p, cv)
end

function getindex(cv::CVector{T}, r::OrdinalRange) where T
    [getindex(cv, i) for i in r]
end

function setindex!(cv::CVector{T}, v, i::Integer) where T
    p = pointer_for_index(cv, i)
    set_at_pointer!(p, v)
end

size(cv::CVector) = (length(cv),)

"""
    pointer(::Union{CStruct,CVector})
    length(::CVector)

get the internal fields of accessors
"""
pointer(cs::CAccessor) = getfield(cs, :pointer)
length(cv::CVector) = getfield(cv, :length)
pointer(sg::CStructGuided) = pointer(getfield(sg, :cs))

function show(io::IO, x::CStructAccess{T}) where T
    show(io, typeof(x))
    print(io, '(')
    nf = length(T.types)
    if !Base.show_circular(io, x)
        recur_io = IOContext(io, Pair{Symbol,Any}(:SHOWN_SET, x),
                                 Pair{Symbol,Any}(:typeinfo, Any))
        for i in 1:nf
            f = fieldname(T, i)
            show(recur_io, getproperty(x, f))
            if i < nf
                print(io, ", ")
            end
        end
    end
    print(io, ')')
end

function show(io::IO, x::CVector{T}) where T
    show(io, typeof(x))
    print(io, '[')
    nf = length(x)
    if nf < 0
        print(io, "#= unknown length =#")
    elseif !Base.show_circular(io, x)
        recur_io = IOContext(io, Pair{Symbol,Any}(:SHOWN_SET, x),
                                Pair{Symbol,Any}(:typeinfo, Any))
        for i in 1:nf
            show(recur_io, getindex(x, i))
            if i < nf
                print(io, ", ")
            end
        end
    end
    print(io, ']')
end

"""
    get_from_pointer(::Ptr{T})

For primitive types simply load value, convert to Julia accessor if required.
For struct types, create CStruct accessor.
For vector types, create CVector accessor.
"""
function get_from_pointer(fp::Ptr{FT}, parent) where FT <: Ptr
    v = unsafe_load(fp)
    v == C_NULL ? nothing : get_from_pointer(v, parent)
end

function get_from_pointer(fp::Ptr{FT}, parent) where {T,FT<:LVector{T}}
    CVector{T}(fp, length(FT, parent))
end

function get_from_pointer(fp::Ptr{FT}, parent) where FT <: Layout
    CStruct{FT}(fp)
end

function get_from_pointer(fp::Ptr{FT}, parent) where FT <: Cstring
    v = unsafe_load(fp)
    v == Cstring(C_NULL) ? "" : unsafe_string(Ptr{UInt8}(v))
end

function get_from_pointer(fp::Ptr{FT}, parent) where FT
    if isprimitivetype(FT)
        unsafe_load(fp)
    else
        throw(ArgumentError("not supported layout type: $FT"))
    end
end

"""
    set_at_pointer(:Ptr, value)

Convert to C primitive or composed object. Store bytes at memory position.
"""
function set_at_pointer!(fp::Ptr{FT}, v) where FT
    w = unsafe_convert(FT, Base.cconvert(FT, v))
    unsafe_store!(fp, w)
end

"""
    pointer_for_field(cs::CStruct{T}, fieldname) where T

For `cs` return pointer to member field `fieldname`.
The pointer has type `Ptr{fieldtype(T, i)}` with `i` the number of the field
within struct type `T`.
"""
function pointer_for_field(cs::CStruct{T}, field::Symbol) where T
    i = findfirst(Fix1(isequal, field), fieldnames(T))
    i === nothing && throw(ArgumentError("type $T has no field $field"))
    Ptr{fieldtype(T, i)}(getfield(cs, :pointer) + fieldoffset(T, i))
end

function pointer_for_index(cv::CVector{T}, i::Integer) where T
    Ptr{T}(getfield(cv, :pointer) + sizeof(T) * (i - 1))
end

unsafe_convert(::Type{Ptr{T}}, cs::CStructAccess{T}) where T = Ptr{T}(pointer(cs))
unsafe_convert(::Type{Ptr{Vector{T}}}, cs::CVector{T}) where T = Ptr{Vector{T}}(pointer(cs))
"""
    p = pointer_from_vector(a::Vector{T})::Ptr{T}

return pointer to `a[1]`. The existence of the resulting Ptr will not protect the object
from garbage collection, so you must ensure that the object remains referenced for the whole
time that the Ptr will be used.
The condition `a[i] === unsafe_load(p, i)` is usually true.
Given `p` it is possible to access arbitrary bits data by byte offset and type `S` using
`unsafe_load(Ptr{S}(p + offset))`.

This function is mainly used to simulate a C memory in the data
area of vector `a`.
"""
pointer_from_vector(a::Vector{T}) where T = unsafe_convert(Ptr{T}, a)


text:
# --------------------------------------------------------- #
# Stuff to construct basis matrices of complete polynomials #
# --------------------------------------------------------- #

"""
Construct basis matrix for complete polynomial of degree `d`, given
input data `z`. `z` is assumed to be the degree 1 realization of each
variable. For example, if variables are `q`, `r`, and `s`, then `z`
should be `z = [q r s]`

Output is a basis matrix. In our example, with `d` set to 2 we would have

```julia
out = [ones(size(z,1)) q q.^2 q.*r q.*s r r.^2 r.*s s s.^2]
```

TODO: Currently a bit more code repetition than is desireable. It would be
      nice to cut down on the repetition between the `complete_polynomial`
      functions
TODO: Current algorithm for computing derivatives is kind of slow -- Is
      there any obvious ways to improve this?

"""
:complete_polynomial

struct Degree{N} end
struct Derivative{D} end

function n_complete(n::Int, D::Int)
    out = 1
    for d=1:D
        tmp = 1
        for j=0:d-1
            tmp *= (n+j)
        end
        out += div(tmp, factorial(d))
    end
    out
end

n_complete(n::Int, ::Union{Degree{D},Type{Degree{D}}}) where {D} = n_complete(n, D)

#
# Generating basis functions
#
@generated function complete_polynomial!(out::Array{T, Ndim},
                                         z::AbstractArray{T,Ndim}, d::Degree{N}) where {N,T,Ndim}
    complete_polynomial_impl!(out, z, d)
end

function complete_polynomial!(out::Array{T,Ndim}, z::AbstractArray{T,Ndim},
                              d::Int) where {T, Ndim}
    complete_polynomial!(out, z, Degree{d}())

    return out
end

#
# Vector versions for generating basis functions
#
function complete_polynomial_impl!(out::Type{Vector{T}}, z::Type{Vector{T}},
                                   ::Type{Degree{N}}) where {T,N}
    outer_temp = Expr(:(=), Symbol("tmp_$(N+1)"), one(T))
    outer_i = Expr(:(=), Symbol("i_$(N+1)"), 1)
    quote
        nvar = length(z)
        if length(out) != (n_complete(nvar, $N))
            error("z, out not compatible")
        end

        # reset first column to ones
        out[1] = one($T)

        ix = 1
        $outer_temp
        $outer_i
        @nloops($N, # number of loops
                i,  # counter
                d->(i_{d+1}:nvar),  # ranges
                d->((begin
                        ix += 1
                        tmp_d = tmp_{d+1}*z[i_d]
                        out[ix] = tmp_d
                    end)),  # preexpr
                Expr(:block, :nothing)  # bodyexpr
                )
        out
    end
end

function complete_polynomial(z::AbstractVector{T}, d::Degree{N}) where {T,N}
    nvar = length(z)
    out = Array{T}(n_complete(nvar, d))
    complete_polynomial!(out, z, d)
    out
end

function complete_polynomial(z::AbstractVector, d::Int)
    complete_polynomial(z, Degree{d}())
end

#
# Matrix versions for generating basis functions
#
function complete_polynomial_impl!(out::Type{Matrix{T}}, z::Type{<:AbstractMatrix{T}},
                                   ::Type{Degree{N}}) where {T,N}
    outer_i = Expr(:(=), Symbol("i_$(N+1)"), 1)
    quote
        nobs, nvar = size(z)
        if size(out) != (nobs, n_complete(nvar, $N))
            error("z, out not compatible")
        end

        # reset first column to ones
        @inbounds for i=1:nobs
            out[i, 1] = one($T)
        end

        ix = 1
        $outer_i
        @nloops($N, # number of loops
                i,  # counter
                d->(i_{d+1}:nvar),  # ranges
                d->((begin
                        ix += 1
                        @inbounds @simd for r=1:nobs
                            tmp = one($T)
                            @nexprs $N-d+1 j->(tmp *= z[r, i_{$N-j+1}])
                            out[r, ix]=tmp
                        end
                    end)),  # preexpr
                Expr(:block, :nothing)  # bodyexpr
                )
        out
    end
end

function complete_polynomial(z::AbstractMatrix{T}, d::Degree{N}) where {T,N}
    nobs, nvar = size(z)
    out = Array{T}(nobs, n_complete(nvar, d))
    complete_polynomial!(out, z, d)
    out
end

function complete_polynomial(z::AbstractMatrix, d::Int)
    complete_polynomial(z, Degree{d}())
end

#
# Generating 1st derivative of basis functions
#
@generated function complete_polynomial!(out::Array{T, Ndim}, z::AbstractArray{T,Ndim},
                                         d::Degree{N}, der::Derivative{D}) where {N,D,T,Ndim}
    complete_polynomial_impl!(out, z, d, der)
end

function complete_polynomial!(out::Array{T,Ndim}, z::AbstractArray{T,Ndim}, d::Int, der::Int) where {T, Ndim}
    complete_polynomial!(out, z, Degree{d}(), Derivative{der}())::Array{T,Ndim}

    return out
end

#
# Vector versions for generating first derivative of basis functions
#
function complete_polynomial_impl!(out::Type{Vector{T}}, z::Type{Vector{T}},
                                   ::Type{Degree{N}}, ::Type{Derivative{D}}) where {T,N,D}
    notD_top = Expr(:(=), Symbol("notD_$(N+1)"), one(T))
    coeff_top = Expr(:(=), Symbol("coeff_$(N+1)"), zero(T))
    outer_i = Expr(:(=), Symbol("i_$(N+1)"), 1)
    quote
        nvar = length(z)
        if length(out) != (n_complete(nvar, $N))
            error("z, out not compatible")
        end

        # reset first element to zero
        out[1] = zero($T)

        ix = 1
        $notD_top
        $coeff_top
        $outer_i
        @nloops($N, # number of loops
                i,  # counter
                d->(i_{d+1}:nvar),  # ranges
                d->((begin
                        ix += 1
                        # Depending on what i_d is, update variables
                        if i_d == D
                            coeff_d = coeff_{d+1} + 1
                            notD_d = notD_{d+1}
                        else
                            coeff_d = coeff_{d+1}
                            notD_d = notD_{d+1}*z[i_d]
                        end

                        out[ix] = coeff_d == 0 ? zero(T) : coeff_d * z[D]^(coeff_d-1) * notD_d
                    end)),  # preexpr
                Expr(:block, :nothing)  # bodyexpr
                )
        out
    end
end

function complete_polynomial(
        z::AbstractVector{T}, d::Degree{N}, der::Derivative{D}
    ) where {N,D,T}
    nvar = length(z)
    out = Array{T}(n_complete(nvar, d))
    complete_polynomial!(out, z, d, der)
end

function complete_polynomial(z::AbstractVector, d::Int, der::Int)
    complete_polynomial(z, Degree{d}(), Derivative{der}())
end

#
# Matrix versions for generating first derivative of basis functions
#
function complete_polynomial_impl!(out::Type{Matrix{T}}, z::Type{<:AbstractMatrix{T}},
                                   ::Type{Degree{N}}, ::Type{Derivative{D}}) where {T,N,D}
    coeff_top = Expr(:(=), Symbol("coeff_$(N+1)"), zero(T))
    outer_i = Expr(:(=), Symbol("i_$(N+1)"), 1)
    quote
        nobs, nvar = size(z)
        if size(out) != (nobs, n_complete(nvar, $N))
            error("z, out not compatible")
        end

        # reset first element to zero
        @inbounds @simd for r=1:nobs
            out[r, 1] = zero($T)
        end

        ix = 1
        $coeff_top
        $outer_i
        @nloops($N, # number of loops
                i,  # counter
                d->(i_{d+1}:nvar),  # ranges
                d->((begin
                        ix += 1

                        # Depending on what i_d is, update variables
                        coeff_d = ifelse(i_d == D, coeff_{d+1} + 1, coeff_{d+1})

                        @inbounds @simd for r=1:nobs
                            tmp = one($T)
                            @nexprs $N-d+1 j->(tmp *= ifelse(i_{$N-j+1} != D, z[r, i_{$N-j+1}], one($T)))
                            out[r, ix] = coeff_d == 0 ? zero(T) : coeff_d * tmp * z[r, D]^(coeff_d-1)
                        end
                    end)),  # preexpr
                Expr(:block, :nothing)  # bodyexpr
                )
        out
    end
end

function complete_polynomial(
        z::AbstractMatrix{T}, d::Degree{N}, der::Derivative{D}
    ) where {N,D,T}
    nobs, nvar = size(z)
    out = Array{T}(nobs, n_complete(nvar, d))
    complete_polynomial!(out, z, d, der)
end

function complete_polynomial(z::AbstractMatrix, d::Int, der::Int)
    complete_polynomial(z, Degree{d}(), Derivative{der}())
end


text:
using jlmie
using Plots

# settings
# structure
nmat = 4  # refractive index of a sphere
radius = 100*1e-9

# environment
nenv = 1.00

# wavelenth
lbd0 = (400:1:1000)*1e-9; lbdp = lbd0.*1e9;

# angular range
theta = [0 pi]
phi = [0 0]

# other settings
nmax = -1  # -1: namx large enough (determined from x)

Isff_F,_,_ = jlmie_Isff(nmat,radius,lbd0,nenv,theta[1],phi[1],nmax)
Isff_B,_,_ = jlmie_Isff(nmat,radius,lbd0,nenv,theta[2],phi[2],nmax)

# show results
# validation can be done by comparing to ITMO Mie Calculator
# https://physics.itmo.ru/en/mie
plt = plot(lbdp,Isff_F,
    xlabel       = "Wavelength (nm)",
    ylabel       = "Scattering efficiency",
    # legend       = false,
    label        = "Forward",
    # xlims        =(-3,3),
    # ylims        =(-3,3),
    # aspect_ratio =0.5,
    # title        ="TITLE",
    # linecolor    =:blue,
    # linewidth    =5,
    # linestyle    =:dot,
    # size         =(400,300),
) # https://qiita.com/I_ppp/items/dca3552affa6a672e4bd
plot!(lbdp,Isff_B,
    label = "Backward",
)
display(plt)

# save
if true
    runningfilename = splitext(splitpath(@__FILE__)[end])[1]
    outfilename = ".\\result\\" * runningfilename
    savefig(plt,outfilename)
    println("Graph has been saved in result directory")
end

# find peaks
lbdp_peaks = zeros(2)
lbdp_peaks[1] = lbdp[findmax(Isff_F./Isff_B)[2]]
lbdp_peaks[2] = lbdp[200+findmax(Isff_B[201:end]./Isff_F[201:end])[2]]  # avoid high order modes

println("maximum of F/B ratio is at " * string(round(lbdp_peaks[1])))
println("maximum of B/F ratio is at " * string(round(lbdp_peaks[2])))

max_stars_repo_name: Hinamoooon/jlmie

max_issues_repo_name: Hinamoooon/jlmie

max_forks_repo_name: Hinamoooon/jlmie