text:
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# Script to prepare WTO tariff data from 2018
# -------------------------------------------------------------------------------------------------------------------------------------------------------------

using DataFrames, RData, XLSX, LinearAlgebra, Statistics, CSV

# dir = "X:/VIVES/1-Personal/Florian/git/IOTables/src/"
# include(dir * "model/transform_data.jl") # Script with functions to import and transform raw data
# include(dir * "model/price_hat.jl") # Script with function to obtain the price index
# include(dir * "model/wage_hat.jl") # Script with function to obtain the wages and gross output
# include(dir * "counterfactual/tariffs_function.jl") # Script with functions to create τ_hat_Z, τ_hat_F from tariff data
# include(dir * "counterfactual/head_ries_index.jl") # Script with functions to create bilateral Head-Ries index (symmetric bilateral trade costs)
# include(dir * "elasticity/elasticities_functions.jl") # Script with functions to compute statistics for estimating sectoral trade elasticities (method of Caliendo and Parro, 2015)

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

# Data specification
dir_raw = "C:/Users/u0148308/data/raw/" # location of raw data

# WIOD rev. 2013
source = "WIOD"
revision = "2013"
year = 2011 # specified year
N = 41 # number of countries
S = 35 # number of industries

# # WIOD rev. 2016
# source = "WIOD"
# revision = "2016"
# year = 2014 # specified year
# N = 44 # number of countries
# S = 56 # number of industries

# # OECD rev. 2021
# source = "OECD"
# revision = "2021"
# year = 1995 # specified year
# N = 71 # number of countries
# S = 45 # number of industries

year = 2018 # year for tariff data

# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# prepare tariff data from WTO for EU
# -------------------------------------------------------------------------------------------------------------------------------------------------------------

path = dir_raw * "WTO/EU.csv"

df2 = CSV.read(path, DataFrame, delim=',', quoted=true, quotechar='"')

# problem with reading data (one column gets split into multiple)
cols = ["Indicator", "Reporting Economy ISO3A Code", "Partner Economy ISO3A Code", "Product/Sector Code", "Year", "Value", "Column25", "Column26", "Column27", "Column28"]
df = df2[:, cols]

transform!(df, :Column28 => ByRow(x -> ifelse(ismissing(x), missing, string(x))), renamecols=false) # tariffs imported as Int
# remove all strings which are split over columns and fill with NaN instead
transform!(df, [:Value, :Column25, :Column26, :Column27, :Column28] .=> ByRow(x -> ifelse(ismissing(x) || !contains(x, " "), x, NaN)), renamecols=false)

# function to extract the tariff value or missing from the split columns
function extract_tariff_value(a, b, c, d, e)
    v = [a, b, c, d, e] # split columns which contain the tariff values
    index = findall(typeof.(v) .== String)
    value = ifelse(any(typeof.(v) .== String), parse.(Float64, v[index]), [missing])
    return value[1]
end

df.tariff = extract_tariff_value.(df.Value, df.Column25, df.Column26, df.Column27, df.Column28)

# format table
df = df[:, Not(["Value", "Column25", "Column26", "Column27", "Column28"])]
df.Indicator .= ifelse.(df.Indicator .== "HS MFN - Simple average with ad valorem equivalents (AVE)", "MFN", "BPT")
rename!(df, ["indicator", "reporter_ctry", "partner_ctry", "product_code", "year", "tariff"])
transform!(df, ["indicator", "reporter_ctry", "partner_ctry"] .=> ByRow(string), renamecols=false)
df.partner_ctry .= ifelse.(df.indicator .== "MFN", "WTO_MFN", df.partner_ctry)

# subset data for specified/closest year
println(" EU:")
years_with_MFN = unique(subset(df, :indicator => x -> x .== "MFN").year)
years_with_BPT = unique(subset(df, :indicator => x -> x .== "BPT").year)

if (year in years_with_MFN) & (year in years_with_BPT)
    closest_year_MFN = year
    closest_year_BPT = year
    println(" - Tariff data for $year is available for both MFN and BPT.")
else
    if length(years_with_MFN[years_with_MFN .< year]) != 0
        prev_years = years_with_MFN[years_with_MFN .< year] # take data for previous years since those are in effect
        closest_year_MFN = maximum(prev_years) # finds closest year for which data is available
    else
        prev_years = years_with_MFN[years_with_MFN .> year]
        closest_year_MFN = minimum(prev_years)
    end

    if length(years_with_BPT[years_with_BPT .< year]) != 0
        prev_years = years_with_BPT[years_with_BPT .< year]
        closest_year_BPT = maximum(prev_years)
    else
        prev_years = years_with_BPT[years_with_BPT .> year]
        closest_year_BPT = minimum(prev_years)
    end

    println(" - Tariff data for $year is not available, taking $closest_year_MFN for MFN and $closest_year_BPT for BPT.")
end

# use MFN tariff for tariffs which are missing
df_MFN = subset(df, :indicator => x -> x .== "MFN", :year => x -> x .== closest_year_MFN)
df_BPT = subset(df, :indicator => x -> x .== "BPT", :year => x -> x .== closest_year_BPT)

# ------

df_t = copy(df_MFN)
ctry = ["AUS", "BRA", "CAN", "CHN", "IDN", "IND", "JPN", "KOR", "RUS", "TUR", "TWN", "USA"] # countries with WTO data
ctry = [ctry; "EEC"] # add RoW and EU

for i in ctry
    df_loop = copy(df_MFN)
    df_loop.partner_ctry .= i
    df_loop.indicator .= "BPT"
    append!(df_t, df_loop)
end

# take BPT tariff if available
df_join = leftjoin(df_t, df_BPT[:, [:partner_ctry, :product_code, :tariff]], on=[:partner_ctry, :product_code], makeunique=true)
rename!(df_join, ["tariff", "tariff_1"] .=> ["MFN", "BPT"])
df_join.tariff = ifelse.(ismissing.(df_join.BPT), df_join.MFN, df_join.BPT)
df_tariffs = df_join[:, Not(["indicator", "BPT", "MFN"])] # new collection of tariffs for countries which use MFN + BPT

# ------

# conversion from HS2017 6digits to ISIC rev. 4 (using HS2012 for now)
df_corr = DataFrame(XLSX.readtable(dir_raw * "HS_ISIC_BEC_EUC.xlsx", "Sheet1")...)

df_corr = df_corr[:, ["HS_6digit", "ISIC_first_2_digits"]]
rename!(df_corr, ["product_code", "ISIC"])
transform!(df_corr, [:product_code, :ISIC] .=> ByRow(string), renamecols=false)

# ------

function aggregation(df::DataFrame, df_corr::DataFrame)
    df.product_code = lpad.(df.product_code, 6, '0')

    df_join = leftjoin(df, df_corr, on=:product_code)
    subset!(df_join, :ISIC => ByRow(x -> !ismissing(x) && x != "missing")) # take out missing values (MFN also includes 2-4-digit codes)
    gdf = groupby(df_join, [:reporter_ctry, :partner_ctry, :ISIC])
    df = combine(gdf, :tariff => (x -> mean(skipmissing(x))) => :tariff)
    sort!(df, [:partner_ctry, :ISIC])

    df.tariff .= ifelse.(df.reporter_ctry .== df.partner_ctry, 0.0, df.tariff)
    df.partner_ctry .= ifelse.(df.partner_ctry .== "WTO_MFN", "ZoW", df.partner_ctry) # take MFN tariffs for RoW

    return df
end

# ------

df = aggregation(df_tariffs, df_corr)

missing_values = round(count(ismissing.(df.tariff))/nrow(df)*100, digits=2)
non_zero = round((1 - count(skipmissing(df.tariff .== 0.0))/nrow(df))*100, digits=2)
println(" - $missing_values percent are missing. Out of non-missing values, $non_zero percent are non-zero tariffs.")

# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# prepare tariff data from WTO for other countries
# -------------------------------------------------------------------------------------------------------------------------------------------------------------

function transform_wto_data(path::String)

    df = CSV.read(path, DataFrame, delim=',', quoted=true, quotechar='"')
    cols = ["Indicator", "Reporting Economy ISO3A Code", "Partner Economy ISO3A Code", "Product/Sector Code", "Year", "Value"]
    df = df[:, cols]

    df.Indicator .= ifelse.(df.Indicator .== "HS MFN - Simple average with ad valorem equivalents (AVE)", "MFN", "BPT")
    rename!(df, ["indicator", "reporter_ctry", "partner_ctry", "product_code", "year", "tariff"])

    transform!(df, ["indicator", "reporter_ctry", "partner_ctry"] .=> ByRow(string), renamecols=false)
    df.partner_ctry .= ifelse.(df.indicator .== "MFN", "WTO_MFN", df.partner_ctry)

    # subset data for specified year

    if length(unique(df.indicator)) == 2 # both MFN and BPT are available

        years_with_MFN = unique(subset(df, :indicator => x -> x .== "MFN").year)
        years_with_BPT = unique(subset(df, :indicator => x -> x .== "BPT").year)

        if (year in years_with_MFN) & (year in years_with_BPT)
            closest_year_MFN = year
            closest_year_BPT = year
            println(" - Tariff data for $year is available for both MFN and BPT.")
        else
            if length(years_with_MFN[years_with_MFN .< year]) != 0
                prev_years = years_with_MFN[years_with_MFN .< year] # take data for previous years since those are in effect
                closest_year_MFN = maximum(prev_years) # finds closest year for which data is available
            else
                prev_years = years_with_MFN[years_with_MFN .> year]
                closest_year_MFN = minimum(prev_years)
            end

            if length(years_with_BPT[years_with_BPT .< year]) != 0
                prev_years = years_with_BPT[years_with_BPT .< year]
                closest_year_BPT = maximum(prev_years)
            else
                prev_years = years_with_BPT[years_with_BPT .> year]
                closest_year_BPT = minimum(prev_years)
            end

            println(" - Tariff data for $year is not available, taking $closest_year_MFN for MFN and $closest_year_BPT for BPT.")
        end

        df_MFN = subset(df, :indicator => x -> x .== "MFN", :year => x -> x .== closest_year_MFN)
        df_BPT = subset(df, :indicator => x -> x .== "BPT", :year => x -> x .== closest_year_BPT)

    else
        years_with_MFN = unique(subset(df, :indicator => x -> x .== "MFN").year) # at least need to have MFN data

        if year in years_with_MFN
            closest_year_MFN = year
            println(" - Tariff data for $year is available, but only MFN is available.")
        else
            if length(years_with_MFN[years_with_MFN .< year]) != 0
                prev_years = years_with_MFN[years_with_MFN .< year] # take data for previous years since those are in effect
                closest_year_MFN = maximum(prev_years) # finds closest year for which data is available
            else
                prev_years = years_with_MFN[years_with_MFN .> year]
                closest_year_MFN = minimum(prev_years)
            end

            println(" - Tariff data for $year is not available, taking $closest_year_MFN for MFN (BPT not available).")
        end

        df_MFN = subset(df, :indicator => x -> x .== "MFN", :year => x -> x .== closest_year_MFN)
        df_BPT = copy(df_MFN) # only have MFN tariffs

    end

    # ------

    df_t = copy(df_MFN) # initialize

    # create MFN tariffs for each country in IO Table and then match BPT
    for i in ctry
        df_loop = copy(df_MFN)
        df_loop.partner_ctry .= i
        df_loop.indicator .= "BPT"
        append!(df_t, df_loop)
    end

    # take BPT tariff if available
    df_join = leftjoin(df_t, df_BPT[:, [:partner_ctry, :product_code, :tariff]], on=[:partner_ctry, :product_code], makeunique=true)
    rename!(df_join, ["tariff", "tariff_1"] .=> ["MFN", "BPT"])
    df_join.tariff = ifelse.(ismissing.(df_join.BPT), df_join.MFN, df_join.BPT)
    df_tariffs = df_join[:, Not(["indicator", "BPT", "MFN"])] # new collection of tariffs for countries which use MFN + BPT

    df = aggregation(df_tariffs, df_corr)

    missing_values = round(count(ismissing.(df.tariff))/nrow(df)*100, digits=2)
    non_zero = round((1 - count(skipmissing(df.tariff .== 0.0))/nrow(df))*100, digits=2)
    println(" - $missing_values percent are missing. Out of non-missing values, $non_zero percent are non-zero tariffs.")

    return df
end

wto_ctry = ["AUS", "BRA", "CAN", "CHN", "IDN", "IND", "JPN", "KOR", "MEX", "RUS", "TUR", "TWN", "USA"] # countries in IO table

for i in wto_ctry
    path = dir_raw * "WTO/" * i * ".csv"
    println("\n $i:")
    append!(df, transform_wto_data(path))
end

# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# format tariffs to into NS×N matrix
# -------------------------------------------------------------------------------------------------------------------------------------------------------------

df.reporter_ctry .= ifelse.(df.reporter_ctry .== "CHT", "TWN", df.reporter_ctry)
df.partner_ctry .= ifelse.(df.partner_ctry .== "CHT", "TWN", df.partner_ctry)

ctry_names_2013 = ["AUS", "AUT", "BEL", "BGR", "BRA", "CAN", "CHN", "CYP", "CZE", "DEU", "DNK", "ESP", "EST", "FIN", "FRA", "GBR",
    "GRC", "HUN", "IDN", "IND", "IRL", "ITA", "JPN", "KOR", "LTU", "LUX", "LVA", "MEX", "MLT", "NLD", "POL", "PRT", "ROM", "RUS",
    "SVK", "SVN", "SWE", "TUR", "TWN", "USA"]

ctry_EU28 = ["AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST", "FIN", "FRA", "DEU", "GBR", "GRC", "HUN", "IRL", "ITA", "LVA",
    "LTU", "LUX", "MLT", "NLD", "POL", "PRT", "ROM", "SVK", "SVN", "ESP", "SWE"]

countries = [ctry_names_2013; "ZoW"]

industries = lpad.(1:S, 2, '0')

τ_Z = ones(N*S,N)

for (i, ctry_i) in enumerate(countries)
    for (s, ind) in enumerate(industries)
        for (j, ctry_j) in enumerate(countries)

            if ctry_i == ctry_j # within country trade
                τ_Z[(i-1)*S+s,j] = 0.0

            elseif ctry_i in ctry_EU28 && ctry_j in ctry_EU28 # within EU trade
                τ_Z[(i-1)*S+s,j] = 0.0

            else
                ctry_i = ifelse(ctry_i in ctry_EU28, "EEC", ctry_i)
                ctry_j = ifelse(ctry_j in ctry_EU28, "EEC", ctry_j)

                # assume zero tariffs for countries/industries which are not available
                # assume RoW imposes zero tariffs on all trading partners
                value = df.tariff[(df.reporter_ctry .== ctry_i) .& (df.partner_ctry .== ctry_j) .& (df.ISIC .== ind)]
                tariff = ifelse(isempty(value), 0.0, value)

                τ_Z[(i-1)*S+s,j] = tariff[1]
            end

        end
    end
end

# export data
rows = repeat(countries, inner=S) .* "__" .* repeat(industries, outer=N)
df_tariff_matrix = DataFrame([rows τ_Z], ["reporter"; countries])
XLSX.writetable(dir_raw * "WTO/tariff_matrix_" * string(year) * ".xlsx", df_tariff_matrix, overwrite=true)


text:
#FIXME: PyObject not found when pyscf only
import PySCF: scf_rhf #, pyscf
import PySCF: dipole_moment
import PySCF: population_analysis
import PySCF: number_pairs_j, number_pairs_xc, number_pairs

# Workaround: repeat the imports.
using PyCall: pyimport
pyscf = pyimport("pyscf")

@testset "SCF RHF dipole: H₂O" begin
   mol = pyscf.gto.Mole()
   mol.build(verbose = 0,
             basis = "sto-3g",
             atom = """8 0  0.    0
                       1 0 -0.757 0.587
                       1 0  0.757 0.587""",
            )

   mf = pyscf.scf.RHF(mol)

   rhf         = scf_rhf(mol,verbose=false)
   e_rhf_pyscf = mf.kernel()

   C = rhf.C
   nocc = mol.nelectron ÷ 2
   mocc = C[:,1:nocc]
   P  = mocc * mocc'
   dipole = dipole_moment(2P,mol)
   dipole_pyscf = mf.analyze(verbose=0,ncol=10, digits=9)[2]
   @test isapprox(dipole, dipole_pyscf, atol=1e-5) # [Debye]
end

@testset "SCF RHF Mulliken analysis: H₂O" begin
   mol = pyscf.gto.Mole()
   mol.build(verbose = 0,
             basis = "sto-3g",
             atom = """8 0  0.    0
                       1 0 -0.757 0.587
                       1 0  0.757 0.587""",
            )

   mf = pyscf.scf.RHF(mol)

   rhf         = scf_rhf(mol,verbose=false)
   e_rhf_pyscf = mf.kernel()

   s     = mol.intor("cint1e_ovlp_sph")

   C = rhf.C
   nocc = mol.nelectron ÷ 2
   mocc = C[:,1:nocc]
   P  = mocc * mocc'
   # In RHF, D = 2P, an occupied spatial orbital means 2 elec, α and β
   # PS is a "correct" representation of the 1RDM in AO basis
   Mulliken = 1; #Lowdin = 0.5
   QMulliken = population_analysis(2P,s,Mulliken,mol,verbose=false)

   QMulliken_pyscf = mf.analyze(verbose=0,ncol=10, with_meta_lowdin=false, digits=9)[1][2]
   @test isapprox(QMulliken, QMulliken_pyscf, atol=1e-7) # a.u.
end

@testset "SCF RHF Number of electron pairs: H₂O" begin
   mol = pyscf.gto.Mole()
   mol.build(verbose = 0,
             basis = "sto-3g",
             atom = """8 0  0.    0
                       1 0 -0.757 0.587
                       1 0  0.757 0.587""",
            )

   mf = pyscf.scf.RHF(mol)

   rhf         = scf_rhf(mol,verbose=false)
   e_rhf_pyscf = mf.kernel()

   s     = mol.intor("cint1e_ovlp_sph")

   C = rhf.C
   nocc = mol.nelectron ÷ 2
   mocc = C[:,1:nocc]
   P  = mocc * mocc'
   # In RHF, D = 2P, an occupied spatial orbital means 2 elec, α and β
   # PS is a "correct" representation of the 1RDM in AO basis

   s = mol.intor("int1e_ovlp")
   pairs_j  = number_pairs_j(2P,s)
   pairs_xc = number_pairs_xc(2P,s)
   pairs    = number_pairs(2P,s)
   nelec = mol.nelectron
   @test pairs_j ≈ nelec^2
   @test pairs_xc ≈ nelec
   @test pairs ≈ nelec*(nelec-1)
end

ext: jl


text:
ms = map(measures()) do m
    m.name
end
@test "LogLoss" in ms
@test "RootMeanSquaredError"  in ms

# test `M()` makes sense for all measure types `M` extracted from `name`,
@test all(Symbol.(ms)) do ex
    try
        eval(:($ex()))
        true
    catch
        false
    end
end

ms = map(measures(matching(categorical(1:3)))) do m
         m.name
end
@test "LogLoss" in ms
@test !("RootMeanSquaredError"  in ms)

ms = map(measures(matching(rand(3)))) do m
         m.name
end
@test !("LogLoss" in ms)
@test "RootMeanSquaredError"  in ms

true


text:
using LinearAlgebra, Random, Gurobi, DataFrames, CSV, Printf;
using Distributed
addprocs(50)
@everywhere using JuMP, BARON
include("Decomposition_with_AdaptiveSampling.jl")
@everywhere root=("//panfs//roc//groups//10//qizh//rishabh//Adaptive//")
@everywhere include("ASP.jl")
@everywhere include("Utilities.jl")

global rng = MersenneTwister(1234)
n = 25
S = 5
dim = n

for instance = 1:10
    noisyp = rand(rng,1:1000,dim)
    noisyp = noisyp/norm(noisyp, 1)
    A = zeros(Float64, (2*dim)+1, dim)
    b = zeros(Float64, (2*dim)+1)
    obj = rand(rng,1:1000,dim)
    obj = obj/ norm(obj, 1)

    # Only generate the first training sample here, rest will be determined through adaptive sampling.
    for i = 1:dim
        A[i, i] = 1
        b[i, 1] = 1
        A[dim+1, i] = rand(rng, 5000:15000)/100
    end
    b[dim+1, 1] = sum(A[dim+1, :])*0.6

    for i = 1:dim
        A[dim+1+i, i] = -1
        b[dim+1+i, 1] = 0
    end

    global n = length(A[1,:])
    global c = length(A[:,1])
    TS = 100
    testx  = zeros(Float32, TS, n)
    btest = zeros(Float64, TS, c)
    Atest = zeros(Float64, c, n, TS)

    for test = 1:TS
        btest[test, :] = b # Use the same b as the training dataset
        Atest[:, :, test] = A
        for i = 1:dim
            Atest[dim+1, i, test] = rand(rng, 5000:15000)/100
        end
        testx[test, 1:n] = SolveModel(Atest[:, :, test], obj, btest[test, :], n)
    end

    t = adaptive_sampling(A,b,obj,noisyp,Atest,btest,testx,instance,S)
end

max_stars_repo_name: ddolab/InvLP

max_issues_repo_name: ddolab/InvLP

max_forks_repo_name: ddolab/InvLP


text:
using NNFEM
using ADCME
using LinearAlgebra
using SparseArrays
using PyPlot
using Test
include("fem.jl")


text:
"""
Main module for ResumableFunctions.jl – C# style generators a.k.a. semi-coroutines for Julia
"""
module ResumableFunctions

  using MacroTools
  using MacroTools: combinedef, combinearg, flatten, postwalk

  export @resumable, @yield

  include("types.jl")
  include("transforms.jl")
  include("utils.jl")
  include("macro.jl")
end


text:
using Particles
using Documenter
using Weave

ENV["GKSwstype"] = "100"

weave("src/poisson.jmd",
         out_path = "src",
         doctype = "github")

makedocs(;
    modules=[Particles],
    authors="Michael Kraus",
    repo="https://github.com/JuliaGNI/Particles.jl/blob/{commit}{path}#L{line}",
    sitename="Particles.jl",
    format=Documenter.HTML(;
        prettyurls=get(ENV, "CI", "false") == "true",
        canonical="https://juliagni.github.io/Particles.jl",
        assets=String[],
    ),
    pages=[
        "Home" => "index.md",
        "Poisson Solvers" => "poisson.md",
        "Library" => "library.md",
    ],
)

deploydocs(;
    repo="github.com/JuliaGNI/Particles.jl",
)


text:
function _precompile_()
    ccall(:jl_generating_output, Cint, ()) == 1 || return nothing
    precompile(normal_mesh, (Tesselation{3,Float32,Cylinder{3,Float32},1},))
    precompile(normal_mesh, (Tesselation{3,Float32,HyperSphere{3,Float32},1},))
    precompile(normal_mesh, (HyperSphere{3,Float32},))
end


text:
# Tests for module InterestRates

using Test
using BusinessDays
using InterestRates
using Dates

BusinessDays.initcache(BusinessDays.Brazil())

@testset "errors" begin
    dt_curve = Date(2015,08,03)

    @test_throws AssertionError InterestRates.IRCurve("", InterestRates.Actual365(), InterestRates.ExponentialCompounding(),
        InterestRates.Linear(), dt_curve, [1, 2, 6, 5], [.1, .1, .1, .1])

    @test_throws AssertionError InterestRates.IRCurve("", InterestRates.Actual365(), InterestRates.ExponentialCompounding(),
        InterestRates.Linear(), dt_curve, [1, 2, 3, 4], [.1, .1, .1])

    @test_throws AssertionError InterestRates.IRCurve("", InterestRates.Actual365(), InterestRates.ExponentialCompounding(),
        InterestRates.Linear(), dt_curve, Vector{Int}() , [.1, .1, .1])

    @test_throws AssertionError InterestRates.IRCurve("", InterestRates.Actual365(), InterestRates.ExponentialCompounding(),
        InterestRates.Linear(), dt_curve, Vector{Int}(undef, 1) , Vector{Float64}())

    @test_throws AssertionError InterestRates.IRCurve("", InterestRates.Actual365(), InterestRates.ExponentialCompounding(),
        InterestRates.Linear(), dt_curve, Vector{Int}() , Vector{Float64}())
end

@testset "daycount equality" begin
    x = InterestRates.BDays252(BusinessDays.Brazil())
    y = InterestRates.BDays252(BusinessDays.Brazil())
    @test x == y

    z = InterestRates.Actual360()
    @test x != z
    @test y != z

    @test hash(x) == hash(y)
    @test hash(x) != hash(z)
    @test hash(y) != hash(z)
end

@testset "hash and equals" begin
    @test InterestRates.Linear() == InterestRates.Linear()
    @test InterestRates.Linear() != InterestRates.FlatForward()
    @test InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.FlatForward()) == InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.FlatForward())
    @test InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.FlatForward()) != InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.StepFunction())
    @test InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.FlatForward()) != InterestRates.Linear()

    @test hash(InterestRates.Linear()) == hash(InterestRates.Linear())
    @test hash(InterestRates.Linear()) != hash(InterestRates.FlatForward())
    @test hash(InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.FlatForward())) == hash(InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.FlatForward()))
    @test hash(InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.FlatForward())) != hash(InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.StepFunction()))
    @test hash(InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.FlatForward())) != hash(InterestRates.Linear())
end

@testset "Linear Interp" begin

    vert_x = [11, 15, 19, 23]
    vert_y = [0.10, 0.15, 0.20, 0.19]

    dt_curve = Date(2015,08,03)

    curve_b252_ec_lin = InterestRates.IRCurve("dummy-linear", InterestRates.BDays252(BusinessDays.Brazil()),
        InterestRates.ExponentialCompounding(), InterestRates.Linear(), dt_curve,
        vert_x, vert_y)

    @test InterestRates.curve_get_name(curve_b252_ec_lin) == "dummy-linear"
    @test InterestRates.curve_get_date(curve_b252_ec_lin) == dt_curve
    @test InterestRates.curve_get_method(curve_b252_ec_lin) == InterestRates.Linear()

    maturity_2_days = advancebdays(BusinessDays.Brazil(), dt_curve, vert_x[1] + 2)
    maturity_3_days = advancebdays(BusinessDays.Brazil(), dt_curve, vert_x[1] + 3)
    yrs = (vert_x[1] + 2) / 252.0
    zero_rate_2_days = 0.125
    disc_2_days = 1.0 / ( (1.0 + zero_rate_2_days)^yrs)
    @test zero_rate_2_days ≈ zero_rate(curve_b252_ec_lin, maturity_2_days) # Linear interpolation
    @test disc_2_days ≈ discountfactor(curve_b252_ec_lin, maturity_2_days)
    @test discountfactor(curve_b252_ec_lin, maturity_2_days, maturity_3_days) ≈ discountfactor(curve_b252_ec_lin, maturity_3_days) / discountfactor(curve_b252_ec_lin, maturity_2_days)
    @test zero_rate(curve_b252_ec_lin, advancebdays(BusinessDays.Brazil(), dt_curve, 11)) ≈ 0.10
    @test_throws AssertionError zero_rate(curve_b252_ec_lin, advancebdays(BusinessDays.Brazil(), dt_curve, -4)) # maturity before curve date
    @test zero_rate(curve_b252_ec_lin, advancebdays(BusinessDays.Brazil(), dt_curve, 11-4)) ≈ 0.05 # extrapolate before first vertice
    @test zero_rate(curve_b252_ec_lin, advancebdays(BusinessDays.Brazil(), dt_curve, 23+4)) ≈ 0.18 # extrapolate after last vertice

    dt_maturity = dt_curve+Dates.Day(30)
    @test ERF_to_rate(curve_b252_ec_lin, ERF(curve_b252_ec_lin, dt_maturity), InterestRates.yearfraction(curve_b252_ec_lin, dt_maturity)) ≈ zero_rate(curve_b252_ec_lin, dt_maturity)
    @test_throws AssertionError zero_rate(curve_b252_ec_lin, dt_curve - Dates.Day(10))
end

@testset "Flat Forward Interp" begin

    vert_x = [11, 15, 19, 23]
    vert_y = [0.10, 0.15, 0.20, 0.19]

    dt_curve = Date(2015,08,03)

    curve_ac360_cont_ff = InterestRates.IRCurve("dummy-cont-flatforward", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.FlatForward(), dt_curve,
        vert_x, vert_y)

    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(11)) ≈ 0.1
    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(15)) ≈ 0.15
    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(19)) ≈ 0.20
    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(23)) ≈ 0.19
    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(16)) > 0.15
    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(17)) < 0.20
    @test forward_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(11), dt_curve + Dates.Day(15)) ≈ 0.2875 # forward_rate calculation on vertices
    @test forward_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(11), dt_curve + Dates.Day(13)) ≈ 0.2875 # forward_rate calculation on interpolated maturity
    @test ERF(curve_ac360_cont_ff, dt_curve + Dates.Day(13)) ≈ 1.00466361875533 # ffwd interp on ERF
    @test discountfactor(curve_ac360_cont_ff, dt_curve + Dates.Day(20)) ≈ 0.9891083592630893

    dt_maturity = dt_curve+Dates.Day(30)
    @test ERF_to_rate(curve_ac360_cont_ff, ERF(curve_ac360_cont_ff, dt_maturity), InterestRates.yearfraction(curve_ac360_cont_ff, dt_maturity)) ≈ zero_rate(curve_ac360_cont_ff, dt_maturity)
    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(13)) ≈ 0.128846153846152 # ffwd interp as zero_rate
    @test ERF(curve_ac360_cont_ff, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ 1.00158458746737
    @test forward_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ 0.1425000000000040
    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(30)) ≈ 0.1789166666666680 # ffwd extrap after last vertice
    @test forward_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ forward_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(50), dt_curve + Dates.Day(51))
    @test forward_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ forward_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(50), dt_curve + Dates.Day(100))

    @test forward_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(11), dt_curve + Dates.Day(15)) ≈ 0.2875
    @test zero_rate(curve_ac360_cont_ff, dt_curve + Dates.Day(9)) ≈ 0.05833333333333 # ffwd extrap before first vertice

    @test discountfactor(curve_ac360_cont_ff, dt_curve) == 1
    @test isnan(ERF_to_rate(curve_ac360_cont_ff, 1.0, InterestRates.YearFraction(0.0)))
    @test isnullcurve(curve_ac360_cont_ff) == false
end

@testset "Null Curve" begin
    n_curve = InterestRates.NullIRCurve()
    @test n_curve == InterestRates.NullIRCurve() # Should be singleton
    @test ERF(n_curve, Date(2000,1,1)) == 1.0
    @test ER(n_curve, Date(2000,1,1)) == 0.0
    @test discountfactor(n_curve, Date(2000,1,1)) == 1.0
    @test InterestRates.curve_get_name(n_curve) == "NullCurve"
    @test isnullcurve(n_curve) == true
    @test forward_rate(n_curve, Date(2000,1,1), Date(2000,1,2)) == 0.0
    @test zero_rate(n_curve, Date(2000,1,1)) == 0.0
    @test zero_rate(n_curve, [Date(2000,1,1), Date(2000,1,2)]) == [ 0.0, 0.0 ]
end

@testset "Vector functions" begin
    dt_curve = Date(2015, 08, 07)

    vert_x = [11, 15, 19, 23]
    vert_y = [0.10, 0.15, 0.20, 0.19]

    curve_ac365_simple_linear = InterestRates.IRCurve("dummy-simple-linear", InterestRates.Actual365(),
        InterestRates.SimpleCompounding(), InterestRates.Linear(), dt_curve,
        vert_x, vert_y)
    mat_vec = [ Date(2015,08,17), Date(2015,08,18), Date(2015,08,19), Date(2015,08,20), Date(2015,08,21), Date(2015,08,22)]
    @test zero_rate(curve_ac365_simple_linear, mat_vec) ≈ [0.0875,0.1,0.1125,0.1250,0.1375,0.15]
    @test ERF(curve_ac365_simple_linear, mat_vec) ≈ [1.00239726027397, 1.00301369863014, 1.00369863013699, 1.00445205479452, 1.00527397260274, 1.00616438356164]
    @test discountfactor(curve_ac365_simple_linear, mat_vec) ≈ [0.997608472839084, 0.996995356459984, 0.996314999317592, 0.995567678145244, 0.994753696259454, 0.993873383253914]

    dt_maturity = dt_curve+Dates.Day(30)
    @test ERF_to_rate(curve_ac365_simple_linear, ERF(curve_ac365_simple_linear, dt_maturity), InterestRates.yearfraction(curve_ac365_simple_linear, dt_maturity)) ≈ zero_rate(curve_ac365_simple_linear, dt_maturity)

    comp = InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.StepFunction())
    curve_ac365_simple_comp = InterestRates.IRCurve("dummy-simple-linear", InterestRates.Actual365(),
        InterestRates.SimpleCompounding(), comp, dt_curve,
        vert_x, vert_y)
    @test zero_rate(curve_ac365_simple_comp, mat_vec) ≈ [0.1,0.1,0.1125,0.1250,0.1375,0.15]
    @test zero_rate(curve_ac365_simple_comp, Date(2100,2,2)) ≈ 0.19

    curve_step = InterestRates.IRCurve("step-curve", InterestRates.Actual365(),
        InterestRates.SimpleCompounding(), InterestRates.StepFunction(), dt_curve,
        vert_x, vert_y)
    mat_vec = [ Date(2015,08,08), Date(2015,08,12), Date(2015,08,17), Date(2015,08,18), Date(2015,08,19),
        Date(2015,08,21), Date(2015,08,22), Date(2015,08,23), Date(2015,08,25), Date(2015,08,26),
        Date(2015,08,27), Date(2015,08,29), Date(2015,08,30), Date(2015,08,31), Date(2015,09,26)]

    @test zero_rate(curve_step, mat_vec) ≈ [0.10, 0.10, 0.10, 0.10, 0.10, 0.10, 0.15, 0.15, 0.15, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19]

    dt_maturity = dt_curve+Dates.Day(30)
    @test ERF_to_rate(curve_step, ERF(curve_step, dt_maturity), InterestRates.yearfraction(curve_step, dt_maturity)) ≈ zero_rate(curve_step, dt_maturity)
end

@testset "Nelson Siegel" begin
    dt_curve = Date(2015, 08, 11)
    curve_NS = InterestRates.IRCurve("dummy-continuous-nelsonsiegel", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.NelsonSiegel(), dt_curve,
        [0.1, 0.2, 0.3, 0.5])

    mat_vec = [Date(2015,8,12), Date(2016,8,12)]
    @test zero_rate(curve_NS, mat_vec) ≈ [0.300069315921728, 0.311522078457982]
    @test discountfactor(curve_NS, mat_vec) ≈ [0.999166821408637, 0.727908844513432]

    dt_maturity = dt_curve+Dates.Day(30)
    @test ERF_to_rate(curve_NS, ERF(curve_NS, dt_maturity), InterestRates.yearfraction(curve_NS, dt_maturity)) ≈ zero_rate(curve_NS, dt_maturity)
end

@testset "Svensson" begin
    dt_curve = Date(2015, 08, 11)
    curve_sven = InterestRates.IRCurve("dummy-continuous-svensson", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.Svensson(), dt_curve,
        [0.1, 0.2, 0.3, 0.4, 0.5, 0.8])

    mat_vec = [Date(2015,8,12), Date(2016,8,12)]
    @test zero_rate(curve_sven, mat_vec) ≈ [0.300513102478340, 0.408050168725566]
    @test discountfactor(curve_sven, mat_vec) ≈ [0.999165589696054, 0.659690510410030]

    dt_maturity = dt_curve+Dates.Day(30)
    @test ERF_to_rate(curve_sven, ERF(curve_sven, dt_maturity), InterestRates.yearfraction(curve_sven, dt_maturity)) ≈ zero_rate(curve_sven, dt_maturity)
end

@testset "Splines" begin

    dt_curve = Date(2015, 08, 11)

    vert_x = [11, 15, 19, 23, 25]
    vert_y = [0.10, 0.12, 0.20, 0.22, 0.2]

    sp = InterestRates.splinefit([1, 2, 3, 4], [1.0, 2.0, 3.0, 4.0])
    InterestRates.splineint(sp, [5, 6])
    sp = InterestRates.splinefit([1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0])
    InterestRates.splineint(sp, [1.0, 1.5])

    sp = InterestRates.splinefit(vert_x, vert_y)
    y = InterestRates.splineint(sp, convert(Vector{Int}, 1:30))
    @test y[vert_x] ≈ vert_y

    y_benchmark = [0.09756098, 0.09780488, 0.09804878, 0.09829268, 0.09853659, 0.09878049, 0.09902439, 0.09926829, 0.09951220, 0.09975610, 0.10000000, 0.10054116, 0.10286585,
    0.10875762, 0.12000000, 0.13753049, 0.15890244, 0.18082317, 0.20000000, 0.21371189, 0.22152439, 0.22357470, 0.22000000, 0.21137195, 0.20000000, 0.18817073,
    0.17634146, 0.16451220, 0.15268293, 0.14085366]

    @test all(round.(y; digits=8) .== round.(y_benchmark; digits=8))

    curve_spline_rates = InterestRates.IRCurve("dummy-SplineOnRates", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.CubicSplineOnRates(), dt_curve,
        vert_x, vert_y)

    @test zero_rate(curve_spline_rates, dt_curve + Dates.Day(11)) ≈ 0.1
    @test zero_rate(curve_spline_rates, [dt_curve+Dates.Day(11), dt_curve+Dates.Day(15)]) ≈ vert_y[1:2]

    dt_maturity = dt_curve+Dates.Day(30)
    @test ERF_to_rate(curve_spline_rates, ERF(curve_spline_rates, dt_maturity), InterestRates.yearfraction(curve_spline_rates, dt_maturity)) ≈ zero_rate(curve_spline_rates, dt_maturity)

    mat_vec = [ Dates.Day(i) for i in 1:30 ] + dt_curve
    @test zero_rate(curve_spline_rates, mat_vec) ≈ y

    curve_spline_discount = InterestRates.IRCurve("dummy-SplineOnDiscountFactors", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.CubicSplineOnDiscountFactors(), dt_curve,
        vert_x, vert_y)

    @test zero_rate(curve_spline_discount, dt_curve + Dates.Day(11)) ≈ 0.1
    @test zero_rate(curve_spline_discount, [dt_curve+Dates.Day(11), dt_curve+Dates.Day(15)]) ≈ vert_y[1:2]

    @test InterestRates.advancedays(InterestRates.BDays252(BusinessDays.Brazil()), Date(2015,9,1), [0, 1, 3, 4, 5]) == [Date(2015,9,1),Date(2015,9,2),Date(2015,9,4),Date(2015,9,8),Date(2015,9,9)]
    @test InterestRates.advancedays(InterestRates.Actual360(), Date(2015,9,1), [0, 1, 3, 4, 5]) == [Date(2015,9,1),Date(2015,9,2),Date(2015,9,4),Date(2015,9,5),Date(2015,9,6)]
    @test InterestRates.advancedays(InterestRates.Actual365(), Date(2015,9,1), [0, 1, 3, 4, 5]) == [Date(2015,9,1),Date(2015,9,2),Date(2015,9,4),Date(2015,9,5),Date(2015,9,6)]
    @test InterestRates.advancedays(InterestRates.Thirty360(), Date(2015,9,1), [0, 1, 3, 4, 5]) == [Date(2015,9,1),Date(2015,9,2),Date(2015,9,4),Date(2015,9,5),Date(2015,9,6)]

    param = 10
    InterestRates.curve_set_dict_parameter!(curve_spline_discount, :custom_parameter, param)
    @test InterestRates.curve_get_dict_parameter(curve_spline_discount, :custom_parameter) == param
end

@testset "ComposeFactorCurve" begin
    dt_curve = Date(2015, 08, 11)

    vert_x = [11, 15, 19, 23, 25]
    vert_y = [0.10, 0.12, 0.20, 0.22, 0.2]

    curve_spline_rates = InterestRates.IRCurve("dummy-SplineOnRates", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.CubicSplineOnRates(), dt_curve,
        vert_x, vert_y)

    curve_NS = InterestRates.IRCurve("dummy-continuous-nelsonsiegel", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.NelsonSiegel(), dt_curve,
        [0.1, 0.2, 0.3, 0.5])

    @testset "ComposeMult" begin
        compose_mult_curve = InterestRates.ComposeProdFactorCurve(curve_spline_rates, curve_NS, InterestRates.BDays252(BusinessDays.BRSettlement()), InterestRates.ExponentialCompounding())

        curve_b252_ec_lin = InterestRates.IRCurve("dummy-linear", InterestRates.BDays252(BusinessDays.BRSettlement()),
            InterestRates.ExponentialCompounding(), InterestRates.Linear(), Date(2015, 8, 10),
            vert_x, vert_y)

        @test_throws AssertionError InterestRates.ComposeProdFactorCurve(curve_NS, curve_b252_ec_lin, InterestRates.BDays252(BusinessDays.BRSettlement()), InterestRates.ExponentialCompounding())
        @test InterestRates.curve_get_name(compose_mult_curve) == ""
        @test InterestRates.curve_get_date(compose_mult_curve) == InterestRates.curve_get_date(curve_NS)
        @test InterestRates.discountfactor(compose_mult_curve, Date(2015,10,1)) ≈ InterestRates.discountfactor(curve_NS, Date(2015,10,1)) * InterestRates.discountfactor(curve_spline_rates, Date(2015,10,1))
        @test InterestRates.ERF(compose_mult_curve, Date(2015,10,1)) == InterestRates.ERF(curve_NS, Date(2015,10,1)) * InterestRates.ERF(curve_spline_rates, Date(2015,10,1))

        let
            maturity = Date(2015,10,1)
            erf = InterestRates.ERF(compose_mult_curve, maturity)
            yf = BusinessDays.bdayscount(BusinessDays.BRSettlement(), dt_curve, maturity) / 252
            @test  InterestRates.zero_rate(compose_mult_curve, maturity) ≈ erf^(1/yf) - 1
        end

        compose_mult_named_curve = InterestRates.ComposeProdFactorCurve("curve-name", curve_spline_rates, curve_NS, InterestRates.BDays252(BusinessDays.BRSettlement()), InterestRates.ExponentialCompounding())
        @test InterestRates.curve_get_name(compose_mult_named_curve) == "curve-name"
    end

    @testset "ComposeDiv" begin
        compose_div_curve = InterestRates.ComposeDivFactorCurve(curve_spline_rates, curve_NS, InterestRates.BDays252(BusinessDays.BRSettlement()), InterestRates.ExponentialCompounding())
        @test InterestRates.discountfactor(compose_div_curve, Date(2015,10,1)) ≈ InterestRates.discountfactor(curve_spline_rates, Date(2015,10,1)) / InterestRates.discountfactor(curve_NS, Date(2015,10,1))
        @test InterestRates.ERF(compose_div_curve, Date(2015,10,1)) == InterestRates.ERF(curve_spline_rates, Date(2015,10,1)) / InterestRates.ERF(curve_NS, Date(2015,10,1))
        @test InterestRates.curve_get_name(compose_div_curve) == ""

        compose_div_named_curve = InterestRates.ComposeDivFactorCurve("curve-name", curve_spline_rates, curve_NS, InterestRates.BDays252(BusinessDays.BRSettlement()), InterestRates.ExponentialCompounding())
        @test InterestRates.curve_get_name(compose_div_named_curve) == "curve-name"
    end

    @testset "ComposeMult -> ComposeDiv" begin

        vert_x = [11, 15, 19, 23]
        vert_y = [0.10, 0.15, 0.20, 0.19]

        curve_ac360_cont_ff = InterestRates.IRCurve("dummy-cont-flatforward", InterestRates.Actual360(),
            InterestRates.ContinuousCompounding(), InterestRates.FlatForward(), dt_curve,
            vert_x, vert_y)

        compose_mult_curve = InterestRates.ComposeProdFactorCurve(curve_spline_rates, curve_NS, InterestRates.BDays252(BusinessDays.BRSettlement()), InterestRates.ExponentialCompounding())
        compose_div_mult_curve = InterestRates.ComposeDivFactorCurve(compose_mult_curve, curve_ac360_cont_ff, InterestRates.BDays252(BusinessDays.BRSettlement()), InterestRates.ExponentialCompounding())
        @test InterestRates.discountfactor(compose_div_mult_curve, Date(2015,10,1)) ≈ InterestRates.discountfactor(curve_NS, Date(2015,10,1)) * InterestRates.discountfactor(curve_spline_rates, Date(2015,10,1)) / InterestRates.discountfactor(curve_ac360_cont_ff, Date(2015,10,1))

        let
            maturity = Date(2015,10,1)
            erf = InterestRates.ERF(curve_NS, Date(2015,10,1)) * InterestRates.ERF(curve_spline_rates, Date(2015,10,1)) / InterestRates.ERF(curve_ac360_cont_ff, Date(2015,10,1))
            yf = BusinessDays.bdayscount(BusinessDays.BRSettlement(), dt_curve, maturity) / 252
            @test  InterestRates.zero_rate(compose_div_mult_curve, maturity) ≈ erf^(1/yf) - 1
        end
    end
end

@testset "BufferedIRCurve" begin

    vert_x = [11, 15, 19, 23]
    vert_y = [0.10, 0.15, 0.20, 0.19]

    dt_curve = Date(2015,08,03)

    curve_ac360_cont_ff = InterestRates.IRCurve("dummy-cont-flatforward", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.FlatForward(), dt_curve,
        vert_x, vert_y)

    buffered_curve_ac360_cont_ff = InterestRates.BufferedIRCurve(curve_ac360_cont_ff)
    dt_curve = InterestRates.curve_get_date(buffered_curve_ac360_cont_ff)

    Threads.@threads for i in 1:10
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(11)) ≈ 0.1
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(11)) ≈ 0.1
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(15)) ≈ 0.15
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(15)) ≈ 0.15
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(19)) ≈ 0.20
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(19)) ≈ 0.20
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(23)) ≈ 0.19
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(23)) ≈ 0.19
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(16)) > 0.15
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(16)) > 0.15
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(17)) < 0.20
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(17)) < 0.20
        @test forward_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(11), dt_curve + Dates.Day(15)) ≈ 0.2875 # forward_rate calculation on vertices
        @test forward_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(11), dt_curve + Dates.Day(13)) ≈ 0.2875 # forward_rate calculation on interpolated maturity
        @test ERF(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(13)) ≈ 1.00466361875533 # ffwd interp on ERF
        @test ERF(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(13)) ≈ 1.00466361875533

        dt_maturity = dt_curve+Dates.Day(30)
        @test ERF_to_rate(buffered_curve_ac360_cont_ff, ERF(buffered_curve_ac360_cont_ff, dt_maturity), InterestRates.yearfraction(buffered_curve_ac360_cont_ff, dt_maturity)) ≈ zero_rate(buffered_curve_ac360_cont_ff, dt_maturity)
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(13)) ≈ 0.128846153846152 # ffwd interp as zero_rate
        @test ERF(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ 1.00158458746737
        @test forward_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ 0.1425000000000040
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(30)) ≈ 0.1789166666666680 # ffwd extrap after last vertice
        @test forward_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ forward_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(50), dt_curve + Dates.Day(51))
        @test forward_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ forward_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(50), dt_curve + Dates.Day(100))

        @test forward_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(11), dt_curve + Dates.Day(15)) ≈ 0.2875
        @test zero_rate(buffered_curve_ac360_cont_ff, dt_curve + Dates.Day(9)) ≈ 0.05833333333333 # ffwd extrap before first vertice

        @test discountfactor(buffered_curve_ac360_cont_ff, dt_curve) == 1
        @test isnan(ERF_to_rate(buffered_curve_ac360_cont_ff, 1.0, InterestRates.YearFraction(0.0)))
    end
end

@testset "YearFraction" begin

    @testset "YearFraction API" begin
        yf = InterestRates.YearFraction(1)
        @test iszero(InterestRates.YearFraction(0))
        @test iszero(InterestRates.YearFraction(0.0))
        @test zero(yf) == InterestRates.YearFraction(0)
        @test zero(yf) == InterestRates.YearFraction(0.0)
        @test iszero(zero(yf))

        InterestRates.yearfractionvalue(InterestRates.Actual360(), 10) == 10 / 360
        InterestRates.yearfractionvalue(InterestRates.Actual360(), Date(2020, 2, 1), Date(2020, 2, 11)) == 10 / 360
    end
    vert_x = [1, 11, 15, 19, 23, 2520]
    vert_y = [0.13, 0.14, 0.15, 0.20, 0.19, 0.25 ]

    dt_curve = Date(2015,08,03)

    curve_b252_ec_lin = InterestRates.IRCurve("dummy-linear", InterestRates.BDays252(BusinessDays.Brazil()),
        InterestRates.SimpleCompounding(), InterestRates.Linear(), dt_curve,
        vert_x, vert_y)

    half_a_day = InterestRates.YearFraction(1 // 504)
    one_day_and_a_half = InterestRates.YearFraction(3 // 504)
    a_float = InterestRates.YearFraction(1.75 / 252)
    an_int = InterestRates.YearFraction(1)
    yf_1_day = InterestRates.YearFraction(1/252)
    yf_2_days = InterestRates.YearFraction(2/252)

    @test InterestRates.days_to_maturity(curve_b252_ec_lin, an_int) == InterestRates.value(an_int) * 252
    @test InterestRates.days_to_maturity(curve_b252_ec_lin, a_float) == InterestRates.value(a_float) * 252
    @test InterestRates.days_to_maturity(curve_b252_ec_lin, half_a_day) == InterestRates.value(half_a_day) * 252
    @test InterestRates.days_to_maturity(curve_b252_ec_lin, one_day_and_a_half) == InterestRates.value(one_day_and_a_half) * 252

    dt_maturity_1_day = BusinessDays.advancebdays(BusinessDays.Brazil(), dt_curve, 1)
    dt_maturity_2_days = BusinessDays.advancebdays(BusinessDays.Brazil(), dt_curve, 2)
    dt_maturity_1_year = BusinessDays.advancebdays(BusinessDays.Brazil(), dt_curve, 252)

    @test InterestRates.yearfractionvalue(curve_b252_ec_lin, dt_maturity_2_days) == 2 / 252

    @testset "Linear" begin
        curve = curve_b252_ec_lin
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)

        let
            yf_diff = InterestRates.value(one_day_and_a_half) - InterestRates.value(half_a_day)
            df = discountfactor(curve, half_a_day, one_day_and_a_half)
            fwd_rate = ((1/df) - 1)/yf_diff
            @test forward_rate(curve, half_a_day, one_day_and_a_half) ≈ fwd_rate
        end
    end

    @testset "FlatForward" begin
        curve = InterestRates.IRCurve("dummy-cont-flatforward", InterestRates.BDays252(BusinessDays.Brazil()),
            InterestRates.ContinuousCompounding(), InterestRates.FlatForward(), dt_curve,
            vert_x, vert_y)

        @test discountfactor(curve, one_day_and_a_half) ≈ exp(-InterestRates.zero_rate(curve, one_day_and_a_half) * 3 / 504)
        @test discountfactor(curve, InterestRates.YearFraction(2 / 252)) ≈ exp(-InterestRates.zero_rate(curve, dt_maturity_2_days) * 2 / 252)
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)

        let
            yf_diff = InterestRates.value(one_day_and_a_half) - InterestRates.value(half_a_day)
            df = discountfactor(curve, half_a_day, one_day_and_a_half)
            fwd_rate = -log(df)/yf_diff
            @test forward_rate(curve, half_a_day, one_day_and_a_half) ≈ fwd_rate
        end
    end

    @testset "CubicSplineOnRates" begin
        curve = InterestRates.IRCurve("dummy-cont-flatforward", InterestRates.BDays252(BusinessDays.Brazil()),
            InterestRates.ExponentialCompounding(), InterestRates.CubicSplineOnRates(), dt_curve,
            vert_x, vert_y)

        @test discountfactor(curve, one_day_and_a_half) ≈ 1.0 / (( 1 + InterestRates.zero_rate(curve, one_day_and_a_half))^( 3 / 504))
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)
    end

    @testset "CubicSplineOnDiscountFactors" begin
        curve = InterestRates.IRCurve("dummy-cont-flatforward", InterestRates.BDays252(BusinessDays.Brazil()),
            InterestRates.ExponentialCompounding(), InterestRates.CubicSplineOnDiscountFactors(), dt_curve,
            vert_x, vert_y)

        @test discountfactor(curve, one_day_and_a_half) ≈ 1.0 / (( 1 + InterestRates.zero_rate(curve, one_day_and_a_half))^( 3 / 504))
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)
    end

    @testset "Svensson" begin
        curve = InterestRates.IRCurve("dummy-continuous-svensson", InterestRates.BDays252(BusinessDays.Brazil()),
            InterestRates.ExponentialCompounding(), InterestRates.Svensson(), dt_curve,
            [0.1, 0.2, 0.3, 0.4, 0.5, 0.8])

        @test discountfactor(curve, one_day_and_a_half) ≈ 1.0 / (( 1 + InterestRates.zero_rate(curve, one_day_and_a_half))^( 3 / 504))
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)
    end

    @testset "NelsonSiegel" begin
        curve = InterestRates.IRCurve("dummy-continuous-nelsonsiegel", InterestRates.BDays252(BusinessDays.Brazil()),
            InterestRates.ExponentialCompounding(), InterestRates.NelsonSiegel(), dt_curve,
            [0.1, 0.2, 0.3, 0.5])

        @test discountfactor(curve, one_day_and_a_half) ≈ 1.0 / (( 1 + InterestRates.zero_rate(curve, one_day_and_a_half))^( 3 / 504))
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)
    end

    @testset "StepFunction" begin
        curve = InterestRates.IRCurve("step-curve", InterestRates.BDays252(BusinessDays.Brazil()),
            InterestRates.ExponentialCompounding(), InterestRates.StepFunction(), dt_curve,
            vert_x, vert_y)

        @test discountfactor(curve, one_day_and_a_half) ≈ 1.0 / (( 1 + InterestRates.zero_rate(curve, one_day_and_a_half))^( 3 / 504))
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)
    end

    @testset "CompositeInterpolation" begin
        comp = InterestRates.CompositeInterpolation(InterestRates.StepFunction(), InterestRates.Linear(), InterestRates.StepFunction())

        curve = InterestRates.IRCurve("dummy-simple-linear", InterestRates.BDays252(BusinessDays.Brazil()),
            InterestRates.ExponentialCompounding(), comp, dt_curve,
            vert_x, vert_y)

        @test discountfactor(curve, one_day_and_a_half) ≈ 1.0 / (( 1 + InterestRates.zero_rate(curve, one_day_and_a_half))^( 3 / 504))
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)
    end

    @testset "BufferedIRCurve" begin
        curve = InterestRates.IRCurve("dummy-cont-flatforward", InterestRates.BDays252(BusinessDays.Brazil()),
            InterestRates.ContinuousCompounding(), InterestRates.FlatForward(), dt_curve,
            vert_x, vert_y)

        curve = InterestRates.BufferedIRCurve(curve)

        @test discountfactor(curve, one_day_and_a_half) ≈ exp(-InterestRates.zero_rate(curve, one_day_and_a_half) * 3 / 504)
        @test discountfactor(curve, InterestRates.YearFraction(2 / 252)) ≈ exp(-InterestRates.zero_rate(curve, dt_maturity_2_days) * 2 / 252)
        @test discountfactor(curve, half_a_day) > discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, dt_maturity_1_day) > discountfactor(curve, one_day_and_a_half)
        @test discountfactor(curve, one_day_and_a_half) > discountfactor(curve, a_float)
        @test discountfactor(curve, a_float) > discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, an_int) ≈ discountfactor(curve, dt_maturity_1_year)
        @test discountfactor(curve, yf_1_day) ≈ discountfactor(curve, dt_maturity_1_day)
        @test discountfactor(curve, yf_2_days) ≈ discountfactor(curve, dt_maturity_2_days)
        @test discountfactor(curve, yf_1_day, yf_2_days) ≈ discountfactor(curve, dt_maturity_1_day, dt_maturity_2_days)
        @test discountfactor(curve, half_a_day, one_day_and_a_half) ≈ discountfactor(curve, one_day_and_a_half) / discountfactor(curve, half_a_day)
    end
end

@testset "CurveMap" begin
    vert_x = [11, 15, 19, 23]
    vert_y = [0.09, 0.14, 0.19, 0.18]

    # parallel shock of 1%
    function map_parallel_1pct(rate, maturity)
        return rate + 0.01
    end

    dt_curve = Date(2015,08,03)

    curve_map = InterestRates.CurveMap("parallel-1pct", map_parallel_1pct, InterestRates.IRCurve("dummy-cont-flatforward", InterestRates.Actual360(),
        InterestRates.ContinuousCompounding(), InterestRates.FlatForward(), dt_curve,
        vert_x, vert_y))

    @test InterestRates.curve_get_name(curve_map) == "parallel-1pct"
    @test zero_rate(curve_map, dt_curve + Dates.Day(11)) ≈ 0.1
    @test zero_rate(curve_map, dt_curve + Dates.Day(15)) ≈ 0.15
    @test zero_rate(curve_map, dt_curve + Dates.Day(19)) ≈ 0.20
    @test zero_rate(curve_map, dt_curve + Dates.Day(23)) ≈ 0.19
    @test zero_rate(curve_map, dt_curve + Dates.Day(16)) > 0.15
    @test zero_rate(curve_map, dt_curve + Dates.Day(17)) < 0.20
    @test forward_rate(curve_map, dt_curve + Dates.Day(11), dt_curve + Dates.Day(15)) ≈ 0.2875 # forward_rate calculation on vertices
    @test forward_rate(curve_map, dt_curve + Dates.Day(11), dt_curve + Dates.Day(13)) ≈ 0.2875 # forward_rate calculation on interpolated maturity
    @test ERF(curve_map, dt_curve + Dates.Day(13)) ≈ 1.00466361875533 # ffwd interp on ERF
    @test discountfactor(curve_map, dt_curve + Dates.Day(20)) ≈ 0.9891083592630893

    dt_maturity = dt_curve+Dates.Day(30)
    @test ERF_to_rate(curve_map, ERF(curve_map, dt_maturity), InterestRates.yearfraction(curve_map, dt_maturity)) ≈ zero_rate(curve_map, dt_maturity)
    @test zero_rate(curve_map, dt_curve + Dates.Day(13)) ≈ 0.128846153846152 # ffwd interp as zero_rate
    @test ERF(curve_map, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ 1.00158458746737
    @test forward_rate(curve_map, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ 0.1425000000000040
    @test zero_rate(curve_map, dt_curve + Dates.Day(30)) ≈ 0.1789166666666680 # ffwd extrap after last vertice
    @test forward_rate(curve_map, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ forward_rate(curve_map, dt_curve + Dates.Day(50), dt_curve + Dates.Day(51))
    @test forward_rate(curve_map, dt_curve + Dates.Day(19), dt_curve + Dates.Day(23)) ≈ forward_rate(curve_map, dt_curve + Dates.Day(50), dt_curve + Dates.Day(100))

    @test forward_rate(curve_map, dt_curve + Dates.Day(11), dt_curve + Dates.Day(15)) ≈ 0.2875
    @test zero_rate(curve_map, dt_curve + Dates.Day(9)) ≈ 0.05833333333333 # ffwd extrap before first vertice

    @test discountfactor(curve_map, dt_curve) == 1
    @test isnan(ERF_to_rate(curve_map, 1.0, InterestRates.YearFraction(0.0)))
    @test isnullcurve(curve_map) == false
end

@testset "DailyDatesRange" begin
    @testset "bdays252" begin
        dd = InterestRates.DailyDatesRange(Date(2020, 4, 29), Date(2020, 5, 6), InterestRates.BDays252(BusinessDays.BRSettlement()))
        result_dates = [ Date(2020, 4, 29), Date(2020, 4, 30), Date(2020, 5, 4), Date(2020, 5, 5), Date(2020, 5, 6) ]
        @test firstindex(dd) == 1
        @test !isempty(dd)
        @test eltype(dd) == Date
        @test length(dd) == length(result_dates)
        @test lastindex(dd) == lastindex(result_dates)
        @test issorted(dd)
        @test InterestRates.yearfractionvalue(dd) == 1 / 252
        @test minimum(dd) == Date(2020, 4, 29)
        @test maximum(dd) == Date(2020, 5, 6)

        for dt in result_dates
            @test dt ∈ dd
        end

        @test !in(Date(2020, 4, 28), dd)
        @test !in(Date(2020, 5, 1), dd)
        @test !in(Date(2020, 5, 2), dd)
        @test !in(Date(2020, 5, 7), dd)

        let
            i = 1

            for dt in dd
                @test dt == result_dates[i]
                i += 1
            end

            @test i == length(result_dates) + 1
        end

        for i in eachindex(dd)
            @test dd[i] == result_dates[i]
        end

        for (i, dt) in enumerate(dd)
            @test dt == result_dates[i]
        end

        @test collect(dd) == result_dates

        reversed_dd = reverse(dd)
        @test isa(reversed_dd, InterestRates.DailyDatesRange)
        @test !issorted(reversed_dd)
        @test collect(reversed_dd) == reverse(result_dates)
        @test minimum(reversed_dd) == Date(2020, 4, 29)
        @test maximum(reversed_dd) == Date(2020, 5, 6)

        let
            i = length(result_dates)

            for dt in reversed_dd
                @test dt == result_dates[i]
                i -= 1
            end

            @test i == 0
        end
    end

    @testset "Actual360" begin
        dd = InterestRates.DailyDatesRange(Date(2020, 4, 29), Date(2020, 5, 6), InterestRates.Actual360())
        @test !isempty(dd)
        result_dates = collect(Date(2020, 4, 29):Dates.Day(1):Date(2020, 5, 6))
        @test collect(dd) == result_dates

        for dt in result_dates
            @test dt ∈ dd
        end

        @test !in(Date(2020, 4, 28), dd)
        @test !in(Date(2020, 5, 7), dd)
    end
end

@testset "Thirty360" begin

    dc = InterestRates.Thirty360()
    # usual rule: ((d2-d1) + (m2-m1)*30 + (y2-y1)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2011,12,28), Date(2012, 2,28)) == ((28-28) + (2-12)*30 + (2012-2011)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2011,12,28), Date(2012, 2,29)) == ((29-28) + (2-12)*30 + (2012-2011)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2011,12,28), Date(2012, 3, 1)) == ((1-28) + (3-12)*30 + (2012-2011)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2011,12,28), Date(2016, 2,28)) == ((28-28) + (2-12)*30 + (2016-2011)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2011,12,28), Date(2016, 2,29)) == ((29-28) + (2-12)*30 + (2016-2011)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2011,12,28), Date(2016, 3, 1)) == ((1-28) + (3-12)*30 + (2016-2011)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2012, 2,28), Date(2012, 3,28)) == ((28-28) + (3-2)*30 + (2012-2012)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2012, 2,29), Date(2012, 3,28)) == ((28-29) + (3-2)*30 + (2012-2012)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2012, 3, 1), Date(2012, 3,28)) == ((28-1) + (3-3)*30 + (2012-2012)*360)/360

    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,29), Date(2013, 8,29)) == ((29-29) + (8-5)*30 + (2013-2012)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,29), Date(2013, 8,30)) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,29), Date(2013, 8,31)) == ((31-29) + (8-5)*30 + (2013-2012)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,30), Date(2013, 8,29)) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,30), Date(2013, 8,30)) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360
    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,30), Date(2013, 8,31)) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,31), Date(2013, 8,29)) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,31), Date(2013, 8,30)) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test InterestRates.yearfractionvalue(dc, Date(2012, 5,31), Date(2013, 8,31)) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception

    # zeros
    @test InterestRates.yearfractionvalue(dc, Date(2011,12,28), Date(2011,12,28)) == 0
    @test InterestRates.yearfractionvalue(dc, Date(2011,12,31), Date(2011,12,31)) == 0
    @test InterestRates.yearfractionvalue(dc, Date(2012, 2,28), Date(2012, 2,28)) == 0
    @test InterestRates.yearfractionvalue(dc, Date(2012, 2,29), Date(2012, 2,29)) == 0

    # reflection
    @test InterestRates.yearfractionvalue(dc, Date(2012, 2,28), Date(2011,12,28)) == -InterestRates.yearfractionvalue(dc, Date(2011,12,28), Date(2012, 2,28))
    @test InterestRates.yearfractionvalue(dc, Date(2012, 3,28), Date(2012, 2,29)) == -InterestRates.yearfractionvalue(dc, Date(2012, 2,29), Date(2012, 3,28))

    # discount factor
    dt_curve = Date(2012,3,1)
    curve = InterestRates.IRCurve("dummy-thirty360", InterestRates.Thirty360(),
        InterestRates.SimpleCompounding(), InterestRates.StepFunction(), dt_curve,
        [1], [0.05])

    @test InterestRates.discountfactor(curve, Date(2012, 3,28)) == 1/(1+0.05 * ((28-1) + (3-3)*30 + (2012-2012)*360)/360)
    @test InterestRates.discountfactor(curve, Date(2012, 4,1)) == 1/(1+0.05 * 30/360)
    @test InterestRates.discountfactor(curve, Date(2012, 9,1)) == 1/(1+0.05 * 180/360)
    @test InterestRates.discountfactor(curve, Date(2013, 3,1)) == 1/(1+0.05 * 360/360)
end

@testset "Usage" begin
    include("usage.jl")
end

@testset "Benchmarks" begin
    include("perftests.jl")
end


text:
default_Δv_to_avoid_tellurics = RvSpectMLBase.max_bc # 29785.8905  # ~ 2pi AU/year in m/s

max_num_spectra = 10000  # arbitrary limit

#import RvSpectMLBase: make_clean_line_list_from_tellurics
#export filter_line_list
#export make_clean_line_list_from_tellurics
""" For generic functions on spectra that are to be overloaded for each instrument, see [RvSpectMLBase](https://rvspectml.github.io/RvSpectMLBase.jl/stable/)'s [src/instrumenets/common.jl](https://github.com/RvSpectML/RvSpectMLBase.jl/blob/main/src/instruments/common.jl)."""

#=
# This is now in RvSpectmLBase
# Declare functions that should be specialized for each instrument here, so they can be imported into their instrument's module.
"Returns minimum allowable order for the specified instrument"
function min_order end

"Returns maximum allowable order for the specified instrument"
function max_order end

"Returns minimum allowable pixel index for the specified instrument and order index"
function min_pixel_in_order end

"Returns maximum allowable pixel index for the specified instrument and order index"
function max_pixel_in_order end

"Returns minimum allowable pixel index for the specified instrument and any order"
function min_pixel end

"Returns maximum allowable pixel index for the specified instrument and any order"
function max_pixel end

"""Returns range of order indices to be used by default for the specified instrument"""
function orders_to_use_default end

"Returns minimum pixel index to be used by default for the specified instrument"
function min_col_default end

"Returns maximum pixel index to be read from header into metadata by default for the specified instrument"
function max_col_default end

"Returns array of symbols to be stored from metadata in header by default for the specified instrument"
function metadata_symbols_default end

"Returns array of strings to be read from header into metadata by default for the specified instrument"
function metadata_strings_default end

"Returns the maximum range of wavelengths (in solar system barycenter frame) to be used for given spectrum"
function get_λ_range end

"""" `filter_line_list`( df, inst )
Returns a dataframe filtered to only include lines appropriate for the specified instrument.
Inputs:
- df: Dataframe containing a column with key `:lambda` for wavelengths to be considered.
- inst: instrument whose properties will be used in choosing which lines to keep.
Optional Inputs:
- λmin: Overide the instrument's default minimum wavelength
- λmax: Overide the instrument's default maximum wavelength
"""
function filter_line_list end

"Returns the module containing code specific to the specified instrument"
function get_inst_module end

"Returns the default velocity width (in m/s) of a tophat CCF mask for the specified instrument"
function default_ccf_mask_v_width end

# Trait-based functions that provide defaults (can be overwritten by instrument-specific versions)
" Returns range of all allowable orders for the specified instrument"
function orders_all end

orders_all(inst::AbstractInstrument2D) = min_order(inst):max_order(inst)

" Returns range of all allowable pixels for the specified instrument"
function pixels_all end

pixels_all(inst::AbstractInstrument2D) = min_pixels_in_order(inst):max_pixel_in_order(inst)
pixels_all(inst::AbstractInstrument1D) = min_pixel(inst):max_pixel(inst)

" Returns maximum number of pixels in a spectrum for the specified instrument"
function max_pixels_in_spectra end

max_pixels_in_spectra(inst::AbstractInstrument1D) = length(pixels_all(inst))
max_pixels_in_spectra(inst::AbstractInstrument2D) = (max_order(inst)-min_order(inst)+1) * (max_pixel_in_order(inst)-min_pixel_in_order(inst)+1)

" Returns maximum number of pixels in a chunk from the specified instrument"
min_pixels_in_chunk(inst::AbstractInstrument) = 6

=#

max_stars_repo_name: RvSpectML/EchelleInstruments

max_issues_repo_name: RvSpectML/EchelleInstruments

max_forks_repo_name: RvSpectML/EchelleInstruments


text:
# Very simple initial sentinel structure for Graff elements in DFG.
# TODO: Need to flesh out further in next release.

export User, Robot, Session

abstract type AbstractCGNode
end

mutable struct User <: AbstractCGNode
    id::Symbol
    name::String
    description::String
    data::Dict{Symbol, String}
    createdTimestamp::String
    lastUpdatedTimestamp::String
    User(id::Symbol,
          name::String,
          description::String,
          data::Dict{Symbol, String},
          createdTimestamp::String=string(now(UTC)),
          lastUpdatedTimestamp::String=string(now(UTC))) =
          new(id, name, description, data, createdTimestamp, lastUpdatedTimestamp)
end

mutable struct Robot <: AbstractCGNode
    id::Symbol
    userId::Symbol
    name::String
    description::String
    data::Dict{Symbol, String}
    createdTimestamp::String
    lastUpdatedTimestamp::String
    Robot(id::Symbol,
          userId::Symbol,
          name::String,
          description::String,
          data::Dict{Symbol, String},
          createdTimestamp::String=string(now(UTC)),
          lastUpdatedTimestamp::String=string(now(UTC))) =
          new(id, userId, name, description, data, createdTimestamp, lastUpdatedTimestamp)
end

mutable struct Session <: AbstractCGNode
    id::Symbol
    robotId::Symbol
    userId::Symbol
    name::String
    description::String
    data::Dict{Symbol, String}
    createdTimestamp::String
    lastUpdatedTimestamp::String
    Session(id::Symbol,
          robotId::Symbol,
          userId::Symbol,
          name::String,
          description::String,
          data::Dict{Symbol, String},
          createdTimestamp::String=string(now(UTC)),
          lastUpdatedTimestamp::String=string(now(UTC))) =
          new(id, robotId,userId, name, description, data, createdTimestamp, lastUpdatedTimestamp)

end


text:
module MatrixAlgebra

export MElement

using LinearAlgebra
using SparseArrays: iswrsparse

struct MElement{T,N,P,M} <: AbstractArray{T,N}
    data::P
    MElement(a::P) where {T,N,P<:AbstractArray{T,N}} = new{T,N,P,mode(P)}(a)
end
mode(P::Type{<:AbstractArray}) = iswrsparse(P) ? :sparse : :dense

struct MFactorization{T,P} <: Factorization{T}
    data::P
    MFactorization(p::P) where {T,P<:Factorization{T}} = new{T,P}(p)
end

import Base: ==, +, -, *
import Base: size, getindex
import LinearAlgebra: lu, ldiv!

size(a::MElement) = size(a.data)
getindex(a::MElement, i...) = getindex(a.data, i...)

-(a::MElement) = MElement(-data.a)
==(a::MElement, b::MElement) = MElement(a.data == b.data)
+(a::MElement, b::MElement) = MElement(a.data + b.data)
-(a::MElement, b::MElement) = MElement(a.data - b.data)
*(a::MElement, b::MElement) = MElement(a.data * b.data)

lu(a::MElement{T,2}) where T = MFactorization(lu(a.data))
ldiv!(a::MFactorization, b) = MElement(ldiv!(a.data, b))

include("synchron.jl")
include("tridiagonalize.jl")
include("toeplitz.jl")
include("givens.jl")
include("zrst.jl")
include("determinant.jl")
include("eigenvalues.jl")
include("rational.jl")

end # module


text:
using Cairo, Gtk, Images, Colors

abstract type AbstractDrawParams end

struct CairoCtx <: AbstractCtx
    params::AbstractDrawParams
    viewer::Cairo.CairoSurfaceBase{UInt32}
end

struct RGBCtx <: AbstractCtx
    params::AbstractDrawParams
    viewer::Cairo.CairoSurfaceBase{UInt32}
end

struct GtkCtx <: AbstractCtx
    params::AbstractDrawParams
    canvas::GtkCanvas
    win::GtkWindowLeaf
end

struct NoCtx <: AbstractCtx
end

function render!(env::AbstractEnv, ctx::CairoCtx)
    drawcanvas!(env, CairoContext(ctx.viewer), ctx.params)
    display(ctx.viewer)
end

function render!(env::AbstractEnv, ctx::RGBCtx)
    drawcanvas!(env, CairoContext(ctx.viewer), ctx.params)
    ptr = ccall((:cairo_image_surface_get_data, Cairo._jl_libcairo), Ptr{UInt32}, (Ptr{Nothing},), ctx.viewer.ptr)
    arr = unsafe_wrap(Array, ptr, (ctx.params.screen_width, ctx.params.screen_height))
    rgb_arr = convert.(Float64, channelview(colorview(RGB{N0f8}, permutedims(reinterpret(RGB24, arr), [2, 1]))))
    return rgb_arr
end

function render!(env::AbstractEnv, ctx::GtkCtx)
    !visible(ctx.win) && visible(ctx.win, true)
    @guarded draw(ctx.canvas) do widget
        drawcanvas!(env, getgc(ctx.canvas), ctx.params)
    end
    reveal(ctx.canvas, true)
end

render!(env::AbstractEnv, ctx::NoCtx) = nothing


text:
struct OptimizationTraceRecord
    iteration::Int
    population
    value
    minimizer
    metadata::Dict{String,Any}
end

value(tr::OptimizationTraceRecord) = tr.value

function show(io::IO, t::OptimizationTraceRecord)
    print(io, lpad("$(t.iteration)", 6))
    print(io, "   ")
    print(io, lpad("$(t.value)", 14))
    for (key, value) in t.metadata
        print(io, "\n * $key: $value")
    end
    return
end

const OptimizationTrace = Vector{OptimizationTraceRecord}

function show(io::IO, tr::OptimizationTrace)
    print(io, "Iter     Function value\n")
    print(io, "------   --------------\n")
    for rec in tr
        show(io, rec)
        print(io, "\n")
    end
    return
end

abstract type AbstractOptimizationResult end

summary(or::AbstractOptimizationResult) = summary(or.method)

minimizer(r::AbstractOptimizationResult) = r.minimizer

minimum(r::AbstractOptimizationResult) = r.minimum

iterations(r::AbstractOptimizationResult) = r.iterations

iteration_limit_reached(r::AbstractOptimizationResult) = r.iteration_converged

trace(r::AbstractOptimizationResult) =
    length(r.trace) > 0 ? r.trace :
    error(
        "No trace in optimization results. To get a trace, run optimize() with store_trace = true.",
    )

f_calls(r::AbstractOptimizationResult) = r.f_calls

abstol(r::AbstractOptimizationResult) =
    error("`abstol` is not implemented for $(summary(r)).")

reltol(r::AbstractOptimizationResult) =
    error("`reltol` is not implemented for $(summary(r)).")

abschange(r::AbstractOptimizationResult) =
    error("`abschange` is not implemented for $(summary(r)).")
relchange(r::AbstractOptimizationResult) =
    error("`relchange` is not implemented for $(summary(r)).")

mutable struct OptimizationResult <: AbstractOptimizationResult
    method
    minimizer
    minimum
    iterations::Int
    iteration_converged::Bool
    converged::Bool
    metrics::ConvergenceMetrics
    f_calls::Int
    trace
    time_limit::Float64
    time_run::Float64
    is_moo::Bool
end

converged(r::OptimizationResult) = r.converged
time_limit(r::OptimizationResult) = r.time_limit
time_run(r::OptimizationResult) = r.time_run
is_moo(r::OptimizationResult) = r.is_moo

function show(io::IO, r::OptimizationResult)
    failure_string = "failure"
    if iteration_limit_reached(r)
        failure_string *= " (reached maximum number of iterations)"
    end
    if time_run(r) > time_limit(r)
        failure_string *= " (exceeded time limit of $(time_limit(r)))"
    end
    print(io, "\n")
    print(io, " * Status: ", converged(r) ? "success" : failure_string, "\n\n")
    print(io, " * Candidate solution\n")
    mzr = minimizer(r)
    if is_moo(r)
        pfsize = size(mzr)[1]
        pl = pfsize > 1 ? "s" : ""
        print(io, "    Pareto front: $(pfsize) element$pl\n")
    elseif mzr isa AbstractVector
        nx = length(mzr)
        str_x_elements = ["$_x" for _x in Iterators.take(mzr, min(nx, 3))]
        if nx >= 4
            push!(str_x_elements, " ...")
        end
        print(io, "    Minimizer:  [", join(str_x_elements, ", "), "]\n")
    else
        print(io, "    Minimizer:  $mzr\n")
    end
    !is_moo(r) && print(io, "    Minimum:    $(minimum(r))\n")
    print(io, "    Iterations:", rpad(" ", is_moo(r) ? 3 : 0), "$(iterations(r))\n")
    print(io, "\n")
    print(io, " * Found with\n")
    print(io, "    Algorithm: $(summary(r))\n")
    print(io, "\n")
    if length(r.metrics) > 0
        print(io, " * Convergence measures\n")
        maxdsclen = maximum(length(description(cm)) for cm in r.metrics)
        rpd = " "^4
        for cm in r.metrics
            sgn = converged(cm) ? "≤" : "≰"
            dsc = description(cm)
            lpd = " "^(maxdsclen + 1 - length(dsc))
            print(io, "$rpd$dsc$lpd= $(diff(cm)) $sgn $(tolerance(cm))\n")
        end
        print(io, "\n")
    end
    print(io, " * Work counters\n")
    tr = round(time_run(r); digits = 4)
    tl = isnan(time_limit(r)) ? Inf : round(time_limit(r); digits = 4)
    print(io, "    Seconds run:   $tr (vs limit $tl)\n")
    print(io, "    Iterations:    $(iterations(r))\n")
    print(io, "    f(x) calls:    $(f_calls(r))\n")
    return
end


text:
@testset "135.candy.jl" begin
    @test candy([1, 0, 2]) == 5
    @test candy([1, 2, 2]) == 4
    @test candy([1]) == 1
end


text:
using Cbc

@static if VERSION < v"0.7.0-beta2.203"
    include(joinpath(Pkg.dir("MathProgBase"),"test","mixintprog.jl"))
else
    import MathProgBase
    include(joinpath(dirname(pathof(MathProgBase)), "..", "test", "mixintprog.jl"))
end
include(joinpath(dirname(@__FILE__),"..","examples","knapsack.jl"))

mixintprogtest(CbcSolver(logLevel=1))

@static if VERSION < v"0.7.0-beta2.203"
    include(joinpath(Pkg.dir("MathProgBase"),"test","conicinterface.jl"))
else
    include(joinpath(dirname(pathof(MathProgBase)), "..", "test", "conicinterface.jl"))
end
solver = CbcSolver(logLevel=1)
MathProgBase.setparameters!(solver, Silent=true, TimeLimit=100.0)
println("Cbc should not display any output from these tests")
coniclineartest(solver)

include("MOIWrapper.jl")


text:
using ParametricInversion
using Test

const ϵ = 0.000001

@testset "simple invert" begin
  f(x, y, z) = x * y + z
  out = 5
  res = invertinvoke(f, Tuple{Float64, Float64, Float64}, out, rand(2))
  @test f(res...) ≈ out atol=ϵ
end

@testset "invert with constants" begin
  f(x, y) = x * 2.0 + y * 3.0
  out = 100.0
  res = invertinvoke(f, Tuple{Float64, Float64}, out, rand(2))
  @test f(res...) ≈ out atol=ϵ
end

@testset "multiple variable use" begin
  function g(x, y)
    a = x + y
    b = a + a
    c = a * a
    d = c + b
    e = y * a
    f = e + d
    return f
  end
  # ParametricInversion.invertinvoketransform(typeof(g), Tuple{Float64, Float64})
  invertinvoke(g, Tuple{Float64, Float64}, 3, rand(10))
end