# Julia wrapper for header: gswteos-10.h
# Automatically generated using Clang.jl

function gsw_add_barrier(input_data, lon, lat, long_grid, lat_grid, dlong_grid, dlat_grid, output_data)
    ccall((:gsw_add_barrier, libgswteos), Cvoid, (Ptr{Cdouble}, Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}), input_data, lon, lat, long_grid, lat_grid, dlong_grid, dlat_grid, output_data)
end

function gsw_add_mean(data_in, data_out)
    ccall((:gsw_add_mean, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}), data_in, data_out)
end

function gsw_adiabatic_lapse_rate_from_ct(sa, ct, p)
    ccall((:gsw_adiabatic_lapse_rate_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_adiabatic_lapse_rate_ice(t, p)
    ccall((:gsw_adiabatic_lapse_rate_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_alpha(sa, ct, p)
    ccall((:gsw_alpha, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_alpha_on_beta(sa, ct, p)
    ccall((:gsw_alpha_on_beta, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_alpha_wrt_t_exact(sa, t, p)
    ccall((:gsw_alpha_wrt_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_alpha_wrt_t_ice(t, p)
    ccall((:gsw_alpha_wrt_t_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_beta_const_t_exact(sa, t, p)
    ccall((:gsw_beta_const_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_beta(sa, ct, p)
    ccall((:gsw_beta, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_cabbeling(sa, ct, p)
    ccall((:gsw_cabbeling, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_c_from_sp(sp, t, p)
    ccall((:gsw_c_from_sp, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sp, t, p)
end

function gsw_chem_potential_water_ice(t, p)
    ccall((:gsw_chem_potential_water_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_chem_potential_water_t_exact(sa, t, p)
    ccall((:gsw_chem_potential_water_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_cp_ice(t, p)
    ccall((:gsw_cp_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_cp_t_exact(sa, t, p)
    ccall((:gsw_cp_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_ct_first_derivatives(sa, pt, ct_sa, ct_pt)
    ccall((:gsw_ct_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, pt, ct_sa, ct_pt)
end

function gsw_ct_first_derivatives_wrt_t_exact(sa, t, p, ct_sa_wrt_t, ct_t_wrt_t, ct_p_wrt_t)
    ccall((:gsw_ct_first_derivatives_wrt_t_exact, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, t, p, ct_sa_wrt_t, ct_t_wrt_t, ct_p_wrt_t)
end

function gsw_ct_freezing(sa, p, saturation_fraction)
    ccall((:gsw_ct_freezing, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, p, saturation_fraction)
end

function gsw_ct_freezing_first_derivatives(sa, p, saturation_fraction, ctfreezing_sa, ctfreezing_p)
    ccall((:gsw_ct_freezing_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, saturation_fraction, ctfreezing_sa, ctfreezing_p)
end

function gsw_ct_freezing_first_derivatives_poly(sa, p, saturation_fraction, ctfreezing_sa, ctfreezing_p)
    ccall((:gsw_ct_freezing_first_derivatives_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, saturation_fraction, ctfreezing_sa, ctfreezing_p)
end

function gsw_ct_freezing_poly(sa, p, saturation_fraction)
    ccall((:gsw_ct_freezing_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, p, saturation_fraction)
end

function gsw_ct_from_enthalpy(sa, h, p)
    ccall((:gsw_ct_from_enthalpy, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, h, p)
end

function gsw_ct_from_enthalpy_exact(sa, h, p)
    ccall((:gsw_ct_from_enthalpy_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, h, p)
end

function gsw_ct_from_entropy(sa, entropy)
    ccall((:gsw_ct_from_entropy, libgswteos), Cdouble, (Cdouble, Cdouble), sa, entropy)
end

function gsw_ct_from_pt(sa, pt)
    ccall((:gsw_ct_from_pt, libgswteos), Cdouble, (Cdouble, Cdouble), sa, pt)
end

function gsw_ct_from_rho(rho, sa, p, ct, ct_multiple)
    ccall((:gsw_ct_from_rho, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), rho, sa, p, ct, ct_multiple)
end

function gsw_ct_from_t(sa, t, p)
    ccall((:gsw_ct_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_ct_maxdensity(sa, p)
    ccall((:gsw_ct_maxdensity, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_ct_second_derivatives(sa, pt, ct_sa_sa, ct_sa_pt, ct_pt_pt)
    ccall((:gsw_ct_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, pt, ct_sa_sa, ct_sa_pt, ct_pt_pt)
end

function gsw_deltasa_atlas(p, lon, lat)
    ccall((:gsw_deltasa_atlas, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), p, lon, lat)
end

function gsw_deltasa_from_sp(sp, p, lon, lat)
    ccall((:gsw_deltasa_from_sp, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sp, p, lon, lat)
end

function gsw_dilution_coefficient_t_exact(sa, t, p)
    ccall((:gsw_dilution_coefficient_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_dynamic_enthalpy(sa, ct, p)
    ccall((:gsw_dynamic_enthalpy, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_enthalpy_ct_exact(sa, ct, p)
    ccall((:gsw_enthalpy_ct_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_enthalpy_diff(sa, ct, p_shallow, p_deep)
    ccall((:gsw_enthalpy_diff, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p_shallow, p_deep)
end

function gsw_enthalpy(sa, ct, p)
    ccall((:gsw_enthalpy, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_enthalpy_first_derivatives_ct_exact(sa, ct, p, h_sa, h_ct)
    ccall((:gsw_enthalpy_first_derivatives_ct_exact, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, h_sa, h_ct)
end

function gsw_enthalpy_first_derivatives(sa, ct, p, h_sa, h_ct)
    ccall((:gsw_enthalpy_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, h_sa, h_ct)
end

function gsw_enthalpy_ice(t, p)
    ccall((:gsw_enthalpy_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_enthalpy_second_derivatives_ct_exact(sa, ct, p, h_sa_sa, h_sa_ct, h_ct_ct)
    ccall((:gsw_enthalpy_second_derivatives_ct_exact, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, h_sa_sa, h_sa_ct, h_ct_ct)
end

function gsw_enthalpy_second_derivatives(sa, ct, p, h_sa_sa, h_sa_ct, h_ct_ct)
    ccall((:gsw_enthalpy_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, h_sa_sa, h_sa_ct, h_ct_ct)
end

function gsw_enthalpy_sso_0(p)
    ccall((:gsw_enthalpy_sso_0, libgswteos), Cdouble, (Cdouble,), p)
end

function gsw_enthalpy_t_exact(sa, t, p)
    ccall((:gsw_enthalpy_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_entropy_first_derivatives(sa, ct, eta_sa, eta_ct)
    ccall((:gsw_entropy_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, eta_sa, eta_ct)
end

function gsw_entropy_from_ct(sa, ct)
    ccall((:gsw_entropy_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_entropy_from_pt(sa, pt)
    ccall((:gsw_entropy_from_pt, libgswteos), Cdouble, (Cdouble, Cdouble), sa, pt)
end

function gsw_entropy_from_t(sa, t, p)
    ccall((:gsw_entropy_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_entropy_ice(t, p)
    ccall((:gsw_entropy_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_entropy_part(sa, t, p)
    ccall((:gsw_entropy_part, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_entropy_part_zerop(sa, pt0)
    ccall((:gsw_entropy_part_zerop, libgswteos), Cdouble, (Cdouble, Cdouble), sa, pt0)
end

function gsw_entropy_second_derivatives(sa, ct, eta_sa_sa, eta_sa_ct, eta_ct_ct)
    ccall((:gsw_entropy_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, eta_sa_sa, eta_sa_ct, eta_ct_ct)
end

function gsw_fdelta(p, lon, lat)
    ccall((:gsw_fdelta, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), p, lon, lat)
end

function gsw_frazil_properties(sa_bulk, h_bulk, p, sa_final, ct_final, w_ih_final)
    ccall((:gsw_frazil_properties, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa_bulk, h_bulk, p, sa_final, ct_final, w_ih_final)
end

function gsw_frazil_properties_potential(sa_bulk, h_pot_bulk, p, sa_final, ct_final, w_ih_final)
    ccall((:gsw_frazil_properties_potential, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa_bulk, h_pot_bulk, p, sa_final, ct_final, w_ih_final)
end

function gsw_frazil_properties_potential_poly(sa_bulk, h_pot_bulk, p, sa_final, ct_final, w_ih_final)
    ccall((:gsw_frazil_properties_potential_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa_bulk, h_pot_bulk, p, sa_final, ct_final, w_ih_final)
end

function gsw_frazil_ratios_adiabatic(sa, p, w_ih, dsa_dct_frazil, dsa_dp_frazil, dct_dp_frazil)
    ccall((:gsw_frazil_ratios_adiabatic, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, w_ih, dsa_dct_frazil, dsa_dp_frazil, dct_dp_frazil)
end

function gsw_frazil_ratios_adiabatic_poly(sa, p, w_ih, dsa_dct_frazil, dsa_dp_frazil, dct_dp_frazil)
    ccall((:gsw_frazil_ratios_adiabatic_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, w_ih, dsa_dct_frazil, dsa_dp_frazil, dct_dp_frazil)
end

function gsw_geo_strf_dyn_height(sa, ct, p, p_ref, n_levels, dyn_height)
    ccall((:gsw_geo_strf_dyn_height, libgswteos), Ptr{Cdouble}, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cdouble, Cint, Ptr{Cdouble}), sa, ct, p, p_ref, n_levels, dyn_height)
end

function gsw_geo_strf_dyn_height_1(sa, ct, p, p_ref, n_levels, dyn_height, max_dp_i, interp_method)
    ccall((:gsw_geo_strf_dyn_height_1, libgswteos), Cint, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cdouble, Cint, Ptr{Cdouble}, Cdouble, Cint), sa, ct, p, p_ref, n_levels, dyn_height, max_dp_i, interp_method)
end

function gsw_geo_strf_dyn_height_pc(sa, ct, delta_p, n_levels, geo_strf_dyn_height_pc, p_mid)
    ccall((:gsw_geo_strf_dyn_height_pc, libgswteos), Ptr{Cdouble}, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, delta_p, n_levels, geo_strf_dyn_height_pc, p_mid)
end

function gsw_gibbs_ice(nt, np, t, p)
    ccall((:gsw_gibbs_ice, libgswteos), Cdouble, (Cint, Cint, Cdouble, Cdouble), nt, np, t, p)
end

function gsw_gibbs_ice_part_t(t, p)
    ccall((:gsw_gibbs_ice_part_t, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_gibbs_ice_pt0(pt0)
    ccall((:gsw_gibbs_ice_pt0, libgswteos), Cdouble, (Cdouble,), pt0)
end

function gsw_gibbs_ice_pt0_pt0(pt0)
    ccall((:gsw_gibbs_ice_pt0_pt0, libgswteos), Cdouble, (Cdouble,), pt0)
end

function gsw_gibbs(ns, nt, np, sa, t, p)
    ccall((:gsw_gibbs, libgswteos), Cdouble, (Cint, Cint, Cint, Cdouble, Cdouble, Cdouble), ns, nt, np, sa, t, p)
end

function gsw_gibbs_pt0_pt0(sa, pt0)
    ccall((:gsw_gibbs_pt0_pt0, libgswteos), Cdouble, (Cdouble, Cdouble), sa, pt0)
end

function gsw_grav(lat, p)
    ccall((:gsw_grav, libgswteos), Cdouble, (Cdouble, Cdouble), lat, p)
end

function gsw_helmholtz_energy_ice(t, p)
    ccall((:gsw_helmholtz_energy_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_hill_ratio_at_sp2(t)
    ccall((:gsw_hill_ratio_at_sp2, libgswteos), Cdouble, (Cdouble,), t)
end

function gsw_ice_fraction_to_freeze_seawater(sa, ct, p, t_ih, sa_freeze, ct_freeze, w_ih)
    ccall((:gsw_ice_fraction_to_freeze_seawater, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, t_ih, sa_freeze, ct_freeze, w_ih)
end

function gsw_internal_energy(sa, ct, p)
    ccall((:gsw_internal_energy, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_internal_energy_ice(t, p)
    ccall((:gsw_internal_energy_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_ipv_vs_fnsquared_ratio(sa, ct, p, p_ref, nz, ipv_vs_fnsquared_ratio, p_mid)
    ccall((:gsw_ipv_vs_fnsquared_ratio, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cdouble, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, p_ref, nz, ipv_vs_fnsquared_ratio, p_mid)
end

function gsw_kappa_const_t_ice(t, p)
    ccall((:gsw_kappa_const_t_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_kappa(sa, ct, p)
    ccall((:gsw_kappa, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_kappa_ice(t, p)
    ccall((:gsw_kappa_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_kappa_t_exact(sa, t, p)
    ccall((:gsw_kappa_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_latentheat_evap_ct(sa, ct)
    ccall((:gsw_latentheat_evap_ct, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_latentheat_evap_t(sa, t)
    ccall((:gsw_latentheat_evap_t, libgswteos), Cdouble, (Cdouble, Cdouble), sa, t)
end

function gsw_latentheat_melting(sa, p)
    ccall((:gsw_latentheat_melting, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_linear_interp_sa_ct(sa, ct, p, np, p_i, npi, sa_i, ct_i)
    ccall((:gsw_linear_interp_sa_ct, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, np, p_i, npi, sa_i, ct_i)
end

function gsw_melting_ice_equilibrium_sa_ct_ratio(sa, p)
    ccall((:gsw_melting_ice_equilibrium_sa_ct_ratio, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_melting_ice_equilibrium_sa_ct_ratio_poly(sa, p)
    ccall((:gsw_melting_ice_equilibrium_sa_ct_ratio_poly, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_melting_ice_into_seawater(sa, ct, p, w_ih, t_ih, sa_final, ct_final, w_ih_final)
    ccall((:gsw_melting_ice_into_seawater, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, w_ih, t_ih, sa_final, ct_final, w_ih_final)
end

function gsw_melting_ice_sa_ct_ratio(sa, ct, p, t_ih)
    ccall((:gsw_melting_ice_sa_ct_ratio, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, t_ih)
end

function gsw_melting_ice_sa_ct_ratio_poly(sa, ct, p, t_ih)
    ccall((:gsw_melting_ice_sa_ct_ratio_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, t_ih)
end

function gsw_melting_seaice_equilibrium_sa_ct_ratio(sa, p)
    ccall((:gsw_melting_seaice_equilibrium_sa_ct_ratio, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_melting_seaice_equilibrium_sa_ct_ratio_poly(sa, p)
    ccall((:gsw_melting_seaice_equilibrium_sa_ct_ratio_poly, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_melting_seaice_into_seawater(sa, ct, p, w_seaice, sa_seaice, t_seaice, sa_final, ct_final)
    ccall((:gsw_melting_seaice_into_seawater, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, w_seaice, sa_seaice, t_seaice, sa_final, ct_final)
end

function gsw_melting_seaice_sa_ct_ratio(sa, ct, p, sa_seaice, t_seaice)
    ccall((:gsw_melting_seaice_sa_ct_ratio, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, sa_seaice, t_seaice)
end

function gsw_melting_seaice_sa_ct_ratio_poly(sa, ct, p, sa_seaice, t_seaice)
    ccall((:gsw_melting_seaice_sa_ct_ratio_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, sa_seaice, t_seaice)
end

function gsw_nsquared(sa, ct, p, lat, nz, n2, p_mid)
    ccall((:gsw_nsquared, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, lat, nz, n2, p_mid)
end

function gsw_o2sol(sa, ct, p, lon, lat)
    ccall((:gsw_o2sol, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, lon, lat)
end

function gsw_o2sol_sp_pt(sp, pt)
    ccall((:gsw_o2sol_sp_pt, libgswteos), Cdouble, (Cdouble, Cdouble), sp, pt)
end

function gsw_pot_enthalpy_from_pt_ice(pt0_ice)
    ccall((:gsw_pot_enthalpy_from_pt_ice, libgswteos), Cdouble, (Cdouble,), pt0_ice)
end

function gsw_pot_enthalpy_from_pt_ice_poly(pt0_ice)
    ccall((:gsw_pot_enthalpy_from_pt_ice_poly, libgswteos), Cdouble, (Cdouble,), pt0_ice)
end

function gsw_pot_enthalpy_ice_freezing(sa, p)
    ccall((:gsw_pot_enthalpy_ice_freezing, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_pot_enthalpy_ice_freezing_first_derivatives(sa, p, pot_enthalpy_ice_freezing_sa, pot_enthalpy_ice_freezing_p)
    ccall((:gsw_pot_enthalpy_ice_freezing_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, pot_enthalpy_ice_freezing_sa, pot_enthalpy_ice_freezing_p)
end

function gsw_pot_enthalpy_ice_freezing_first_derivatives_poly(sa, p, pot_enthalpy_ice_freezing_sa, pot_enthalpy_ice_freezing_p)
    ccall((:gsw_pot_enthalpy_ice_freezing_first_derivatives_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, pot_enthalpy_ice_freezing_sa, pot_enthalpy_ice_freezing_p)
end

function gsw_pot_enthalpy_ice_freezing_poly(sa, p)
    ccall((:gsw_pot_enthalpy_ice_freezing_poly, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_pot_rho_t_exact(sa, t, p, p_ref)
    ccall((:gsw_pot_rho_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, t, p, p_ref)
end

function gsw_pressure_coefficient_ice(t, p)
    ccall((:gsw_pressure_coefficient_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_pressure_freezing_ct(sa, ct, saturation_fraction)
    ccall((:gsw_pressure_freezing_ct, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, saturation_fraction)
end

function gsw_pt0_cold_ice_poly(pot_enthalpy_ice)
    ccall((:gsw_pt0_cold_ice_poly, libgswteos), Cdouble, (Cdouble,), pot_enthalpy_ice)
end

function gsw_pt0_from_t(sa, t, p)
    ccall((:gsw_pt0_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_pt0_from_t_ice(t, p)
    ccall((:gsw_pt0_from_t_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_pt_first_derivatives(sa, ct, pt_sa, pt_ct)
    ccall((:gsw_pt_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, pt_sa, pt_ct)
end

function gsw_pt_from_ct(sa, ct)
    ccall((:gsw_pt_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_pt_from_entropy(sa, entropy)
    ccall((:gsw_pt_from_entropy, libgswteos), Cdouble, (Cdouble, Cdouble), sa, entropy)
end

function gsw_pt_from_pot_enthalpy_ice(pot_enthalpy_ice)
    ccall((:gsw_pt_from_pot_enthalpy_ice, libgswteos), Cdouble, (Cdouble,), pot_enthalpy_ice)
end

function gsw_pt_from_pot_enthalpy_ice_poly_dh(pot_enthalpy_ice)
    ccall((:gsw_pt_from_pot_enthalpy_ice_poly_dh, libgswteos), Cdouble, (Cdouble,), pot_enthalpy_ice)
end

function gsw_pt_from_pot_enthalpy_ice_poly(pot_enthalpy_ice)
    ccall((:gsw_pt_from_pot_enthalpy_ice_poly, libgswteos), Cdouble, (Cdouble,), pot_enthalpy_ice)
end

function gsw_pt_from_t(sa, t, p, p_ref)
    ccall((:gsw_pt_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, t, p, p_ref)
end

function gsw_pt_from_t_ice(t, p, p_ref)
    ccall((:gsw_pt_from_t_ice, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), t, p, p_ref)
end

function gsw_pt_second_derivatives(sa, ct, pt_sa_sa, pt_sa_ct, pt_ct_ct)
    ccall((:gsw_pt_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, pt_sa_sa, pt_sa_ct, pt_ct_ct)
end

function gsw_rho_alpha_beta(sa, ct, p, rho, alpha, beta)
    ccall((:gsw_rho_alpha_beta, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, rho, alpha, beta)
end

function gsw_rho(sa, ct, p)
    ccall((:gsw_rho, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_rho_first_derivatives(sa, ct, p, drho_dsa, drho_dct, drho_dp)
    ccall((:gsw_rho_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, drho_dsa, drho_dct, drho_dp)
end

function gsw_rho_first_derivatives_wrt_enthalpy(sa, ct, p, rho_sa, rho_h)
    ccall((:gsw_rho_first_derivatives_wrt_enthalpy, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, rho_sa, rho_h)
end

function gsw_rho_ice(t, p)
    ccall((:gsw_rho_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_rho_second_derivatives(sa, ct, p, rho_sa_sa, rho_sa_ct, rho_ct_ct, rho_sa_p, rho_ct_p)
    ccall((:gsw_rho_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, rho_sa_sa, rho_sa_ct, rho_ct_ct, rho_sa_p, rho_ct_p)
end

function gsw_rho_second_derivatives_wrt_enthalpy(sa, ct, p, rho_sa_sa, rho_sa_h, rho_h_h)
    ccall((:gsw_rho_second_derivatives_wrt_enthalpy, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, rho_sa_sa, rho_sa_h, rho_h_h)
end

function gsw_rho_t_exact(sa, t, p)
    ccall((:gsw_rho_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_rr68_interp_sa_ct(sa, ct, p, mp, p_i, mp_i, sa_i, ct_i)
    ccall((:gsw_rr68_interp_sa_ct, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, mp, p_i, mp_i, sa_i, ct_i)
end

function gsw_saar(p, lon, lat)
    ccall((:gsw_saar, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), p, lon, lat)
end

function gsw_sa_freezing_estimate(p, saturation_fraction, ct, t)
    ccall((:gsw_sa_freezing_estimate, libgswteos), Cdouble, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), p, saturation_fraction, ct, t)
end

function gsw_sa_freezing_from_ct(ct, p, saturation_fraction)
    ccall((:gsw_sa_freezing_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), ct, p, saturation_fraction)
end

function gsw_sa_freezing_from_ct_poly(ct, p, saturation_fraction)
    ccall((:gsw_sa_freezing_from_ct_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), ct, p, saturation_fraction)
end

function gsw_sa_freezing_from_t(t, p, saturation_fraction)
    ccall((:gsw_sa_freezing_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), t, p, saturation_fraction)
end

function gsw_sa_freezing_from_t_poly(t, p, saturation_fraction)
    ccall((:gsw_sa_freezing_from_t_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), t, p, saturation_fraction)
end

function gsw_sa_from_rho(rho, ct, p)
    ccall((:gsw_sa_from_rho, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), rho, ct, p)
end

function gsw_sa_from_sp_baltic(sp, lon, lat)
    ccall((:gsw_sa_from_sp_baltic, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sp, lon, lat)
end

function gsw_sa_from_sp(sp, p, lon, lat)
    ccall((:gsw_sa_from_sp, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sp, p, lon, lat)
end

function gsw_sa_from_sstar(sstar, p, lon, lat)
    ccall((:gsw_sa_from_sstar, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sstar, p, lon, lat)
end

function gsw_sa_p_inrange(sa, p)
    ccall((:gsw_sa_p_inrange, libgswteos), Cint, (Cdouble, Cdouble), sa, p)
end

function gsw_seaice_fraction_to_freeze_seawater(sa, ct, p, sa_seaice, t_seaice, sa_freeze, ct_freeze, w_seaice)
    ccall((:gsw_seaice_fraction_to_freeze_seawater, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, sa_seaice, t_seaice, sa_freeze, ct_freeze, w_seaice)
end

function gsw_sigma0(sa, ct)
    ccall((:gsw_sigma0, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sigma1(sa, ct)
    ccall((:gsw_sigma1, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sigma2(sa, ct)
    ccall((:gsw_sigma2, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sigma3(sa, ct)
    ccall((:gsw_sigma3, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sigma4(sa, ct)
    ccall((:gsw_sigma4, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sound_speed(sa, ct, p)
    ccall((:gsw_sound_speed, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_sound_speed_ice(t, p)
    ccall((:gsw_sound_speed_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_sound_speed_t_exact(sa, t, p)
    ccall((:gsw_sound_speed_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_specvol_alpha_beta(sa, ct, p, specvol, alpha, beta)
    ccall((:gsw_specvol_alpha_beta, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, specvol, alpha, beta)
end

function gsw_specvol_anom_standard(sa, ct, p)
    ccall((:gsw_specvol_anom_standard, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_specvol(sa, ct, p)
    ccall((:gsw_specvol, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_specvol_first_derivatives(sa, ct, p, v_sa, v_ct, v_p)
    ccall((:gsw_specvol_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, v_sa, v_ct, v_p)
end

function gsw_specvol_first_derivatives_wrt_enthalpy(sa, ct, p, v_sa, v_h)
    ccall((:gsw_specvol_first_derivatives_wrt_enthalpy, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, v_sa, v_h)
end

function gsw_specvol_ice(t, p)
    ccall((:gsw_specvol_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_specvol_second_derivatives(sa, ct, p, v_sa_sa, v_sa_ct, v_ct_ct, v_sa_p, v_ct_p)
    ccall((:gsw_specvol_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, v_sa_sa, v_sa_ct, v_ct_ct, v_sa_p, v_ct_p)
end

function gsw_specvol_second_derivatives_wrt_enthalpy(sa, ct, p, v_sa_sa, v_sa_h, v_h_h)
    ccall((:gsw_specvol_second_derivatives_wrt_enthalpy, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, v_sa_sa, v_sa_h, v_h_h)
end

function gsw_specvol_sso_0(p)
    ccall((:gsw_specvol_sso_0, libgswteos), Cdouble, (Cdouble,), p)
end

function gsw_specvol_t_exact(sa, t, p)
    ccall((:gsw_specvol_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_sp_from_c(c, t, p)
    ccall((:gsw_sp_from_c, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), c, t, p)
end

function gsw_sp_from_sa_baltic(sa, lon, lat)
    ccall((:gsw_sp_from_sa_baltic, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, lon, lat)
end

function gsw_sp_from_sa(sa, p, lon, lat)
    ccall((:gsw_sp_from_sa, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, p, lon, lat)
end

function gsw_sp_from_sk(sk)
    ccall((:gsw_sp_from_sk, libgswteos), Cdouble, (Cdouble,), sk)
end

function gsw_sp_from_sr(sr)
    ccall((:gsw_sp_from_sr, libgswteos), Cdouble, (Cdouble,), sr)
end

function gsw_sp_from_sstar(sstar, p, lon, lat)
    ccall((:gsw_sp_from_sstar, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sstar, p, lon, lat)
end

function gsw_sp_salinometer(rt, t)
    ccall((:gsw_sp_salinometer, libgswteos), Cdouble, (Cdouble, Cdouble), rt, t)
end

function gsw_spiciness0(sa, ct)
    ccall((:gsw_spiciness0, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_spiciness1(sa, ct)
    ccall((:gsw_spiciness1, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_spiciness2(sa, ct)
    ccall((:gsw_spiciness2, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sr_from_sp(sp)
    ccall((:gsw_sr_from_sp, libgswteos), Cdouble, (Cdouble,), sp)
end

function gsw_sstar_from_sa(sa, p, lon, lat)
    ccall((:gsw_sstar_from_sa, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, p, lon, lat)
end

function gsw_sstar_from_sp(sp, p, lon, lat)
    ccall((:gsw_sstar_from_sp, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sp, p, lon, lat)
end

function gsw_t_deriv_chem_potential_water_t_exact(sa, t, p)
    ccall((:gsw_t_deriv_chem_potential_water_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_t_freezing(sa, p, saturation_fraction)
    ccall((:gsw_t_freezing, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, p, saturation_fraction)
end

function gsw_t_freezing_first_derivatives_poly(sa, p, saturation_fraction, tfreezing_sa, tfreezing_p)
    ccall((:gsw_t_freezing_first_derivatives_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, saturation_fraction, tfreezing_sa, tfreezing_p)
end

function gsw_t_freezing_first_derivatives(sa, p, saturation_fraction, tfreezing_sa, tfreezing_p)
    ccall((:gsw_t_freezing_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, saturation_fraction, tfreezing_sa, tfreezing_p)
end

function gsw_t_freezing_poly(sa, p, saturation_fraction)
    ccall((:gsw_t_freezing_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, p, saturation_fraction)
end

function gsw_t_from_ct(sa, ct, p)
    ccall((:gsw_t_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_t_from_pt0_ice(pt0_ice, p)
    ccall((:gsw_t_from_pt0_ice, libgswteos), Cdouble, (Cdouble, Cdouble), pt0_ice, p)
end

function gsw_thermobaric(sa, ct, p)
    ccall((:gsw_thermobaric, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_turner_rsubrho(sa, ct, p, nz, tu, rsubrho, p_mid)
    ccall((:gsw_turner_rsubrho, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, nz, tu, rsubrho, p_mid)
end

function gsw_util_indx(x, n, z)
    ccall((:gsw_util_indx, libgswteos), Cint, (Ptr{Cdouble}, Cint, Cdouble), x, n, z)
end

function gsw_util_interp1q_int(nx, x, iy, nxi, x_i, y_i)
    ccall((:gsw_util_interp1q_int, libgswteos), Ptr{Cdouble}, (Cint, Ptr{Cdouble}, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), nx, x, iy, nxi, x_i, y_i)
end

function gsw_util_linear_interp(nx, x, ny, y, nxi, x_i, y_i)
    ccall((:gsw_util_linear_interp, libgswteos), Ptr{Cdouble}, (Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), nx, x, ny, y, nxi, x_i, y_i)
end

function gsw_util_sort_real(rarray, nx, iarray)
    ccall((:gsw_util_sort_real, libgswteos), Cvoid, (Ptr{Cdouble}, Cint, Ptr{Cint}), rarray, nx, iarray)
end

function gsw_util_xinterp1(x, y, n, x0)
    ccall((:gsw_util_xinterp1, libgswteos), Cdouble, (Ptr{Cdouble}, Ptr{Cdouble}, Cint, Cdouble), x, y, n, x0)
end

function gsw_util_pchip_interp(x, y, n, xi, yi, ni)
    ccall((:gsw_util_pchip_interp, libgswteos), Cint, (Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Cint), x, y, n, xi, yi, ni)
end

function gsw_z_from_p(p, lat, geo_strf_dyn_height, sea_surface_geopotential)
    ccall((:gsw_z_from_p, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), p, lat, geo_strf_dyn_height, sea_surface_geopotential)
end

function gsw_p_from_z(z, lat, geo_strf_dyn_height, sea_surface_geopotential)
    ccall((:gsw_p_from_z, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), z, lat, geo_strf_dyn_height, sea_surface_geopotential)
end


# This file is a part of ValueShapes.jl, licensed under the MIT License (MIT).

"""
    varshape(f::Function)::AbstractValueShape

Get the value shape of the input/argument of an unary function `f`. `f`
should support call syntax

    f(x::T)

with `valshape(x) == varshape(f)` as well all

    f(x::AbstractVector{<:Real})

with `length(eachindex(x)) == vardof(f)` (see [`vardof`](@ref)).
"""
function varshape end
export varshape

"""
    vardof(f::Function)::Integer

Get the number of degrees of freedom of the input/argument of f.

Equivalent to `totalndof(varshape(f))` (see [`varshape`](@ref)).
"""
function vardof end
export vardof

vardof(f::Function) = totalndof(varshape(f))

struct FuncWithVarShape{F<:Function,VS<:AbstractValueShape} <: Function
    f::F
    varshape::VS
end

varshape(fws::FuncWithVarShape) = fws.varshape

Base.@propagate_inbounds (fws::FuncWithVarShape)(x::Any) = fws.f(x)

Base.@propagate_inbounds function (fws::FuncWithVarShape)(x::AbstractVector{<:Real})
    # ToDo: Check performance:
    x_shaped = stripscalar(varshape(fws)(x))
    fws.f(x_shaped)
end

import Base.>>
@inline >>(varshape::AbstractValueShape, f::Function) = FuncWithVarShape(f, varshape)


using MLStyle
using MLStyle.MatchCore: mangle
using MLStyle.Infras: @format

def_pattern(Main,
    predicate = (@λ begin
        :[$_ for $_ in $_] -> true
        _ -> false
    end),
    rewrite = (tag, case, mod) -> begin
        @match case begin
            :[$expr for $iter in $seq] => begin
                function (body)
                    iter_var = mangle(mod)
                    produced_elt_var = mangle(mod)
                    inner_test_var = mangle(mod)
                    seq_var = mangle(mod)
                    decons_elt = mk_pattern(iter_var, expr, mod)(iter)
                    decons_seq = mk_pattern(seq_var, seq, mod)(body)
                    @format [
                        seq_var, iter_var, inner_test_var, produced_elt_var,
                        body, tag, decons_elt, decons_seq, push!] quote
                        if tag isa Vector
                            let seq_var = [], inner_test_var = true
                                for iter_var in tag
                                    produced_elt_var = decons_elt
                                    if produced_elt_var === failed
                                        inner_test_var = false
                                        break
                                    end
                                    push!(seq_var, produced_elt_var)
                                end
                                if inner_test_var
                                    decons_seq
                                else
                                    failed
                                end
                            end
                        else
                            failed
                        end
                    end
                end
            end
        end
    end
)


"""
    AbstractMutation{T}

Abstract type defining a mutation operation on entities of type `T`.

Implementations are expected to be callable using an entity of type `T` as only input.

May also implement a callable accepting a `AbstractVector{<:T}` if it is useful to work on
all items to mutate at once.
"""
abstract type AbstractMutation{T} end

(m::AbstractMutation{T})(es::AbstractVector{<:T}) where T = m.(es)

"""
    AbstractCrossover{T}

Type alias for `AbstractMutation{Tuple{T,T}}` defining a crossover of two entities of type `T`.

Implementations are expected to be callable using a tuple of two type `T` as only input.
"""
const AbstractCrossover{T} = AbstractMutation{Tuple{T,T}}

"""
    DecoratingMutation{T}

Abstract type indicating that the type itself does not perform any mutation but wraps a type which might do.

Must either implement callable method for `AbstractVector{<:T}` or accept keyword arguments `next=wrapped(m)` and
`noop=identity` along with a single `T`.
"""
abstract type DecoratingMutation{T} <: AbstractMutation{T} end
wrapped(m::DecoratingMutation) = m.m

mutationleaves(m::DecoratingMutation) = (mutationleaves(wrapped(m))...,)
mutationleaves(tm::Tuple) = mapreduce(mutationleaves, (t1,t2) -> (t1...,t2...), tm)
mutationleaves(m) = tuple(m)

# Apart from being overengineered this helps protecting against forgetting to handle arrays in a DecoratingMutation
# The next/noop happens to work with most existing DecoratingMutations, but it is a bit arbitrary and in some cases
# one must implement both the per-element and the vector of elements versions.
function (m::DecoratingMutation{T})(es::AbstractVector{<:T}) where T
    cnt = Ref(1)
    fornext = Int[]
    next = function(e)
        push!(fornext, cnt[])
        cnt[] += 1
        e
    end
    noop = function(e)
        cnt[] += 1
        e
    end

    allres = m.(es; next, noop)
    mres = wrapped(m)(es[fornext])

    # Mutation might accidentally widen the type compared to allres and then we can't insert mres into allres.
    # Lets fix that if it happens
    RT = typejoin(eltype(allres), eltype(mres))
    res = RT === eltype(allres) ? allres :  convert(Vector{RT}, allres)

    res[fornext] = mres
    return res
end

"""
    MutationProbability{T} <: DecoratingMutation{T}
    MutationProbability(m::AbstractMutation{T}, p::Probability)
    MutationProbability(m::AbstractMutation{T}, p::Number)

Applies `m` with probability `p`.
"""
struct MutationProbability{T, P<:Probability} <: DecoratingMutation{T}
    m::AbstractMutation{T}
    p::P
end
MutationProbability(m::AbstractMutation{T}, p::Number) where T = MutationProbability(m, Probability(p))
(m::MutationProbability{T})(e::T; next=m.m, noop=identity) where T = apply(() -> next(e), m.p, () -> noop(e))

"""
    WeightedMutationProbability{T,F} <: DecoratingMutation{T}
    WeightedMutationProbability(m::AbstractMutation::T, pfun::F)

Applies `m` to an entity `e` with a probability `pfun(e)`.
"""
struct WeightedMutationProbability{T,F} <: DecoratingMutation{T}
    m::AbstractMutation{T}
    pfun::F
end
(m::WeightedMutationProbability{T})(e::T; next=m.m, noop=identity) where T = apply(() -> next(e), m.pfun(e), () -> noop(e))

"""
    HighUtilityMutationProbability(m::AbstractMutation{T}, pbase::Real, rng=rng_default; spread=0.5)

Return a `WeightedMutationProbability` which applies `m` to vertices with an (approximately) average probability of `pbase` and where high `neuronutility` compared to other vertices in same graph means higher probability.

Parameter `spread` can be used to control how much the difference in probability is between high and low utlity. High spread means high difference while low spread means low difference.
"""
HighUtilityMutationProbability(m::AbstractMutation{T}, pbase::Real, rng=rng_default;spread=0.5) where T <: AbstractVertex = WeightedMutationProbability(m, weighted_neuronutility_high(pbase, rng,spread=spread))

"""
    LowUtilityMutationProbability(m::AbstractMutation{T}, pbase::Real, rng=rng_default; spread=2)

Return a `WeightedMutationProbability` which applies `m` to vertices with an (approximately) average probability of `pbase` and where low `neuronutility` compared to other vertices in same graph means higher probability.

Parameter `spread` can be used to control how much the difference in probability is between high and low utlity. High spread means high difference while low spread means low difference.
"""
LowUtilityMutationProbability(m::AbstractMutation{T}, pbase::Real, rng=rng_default;spread=2) where T <: AbstractVertex = WeightedMutationProbability(m, weighted_neuronutility_low(pbase, rng, spread=spread))

weighted_neuronutility_high(pbase, rng=rng_default; spread=0.5) = function(v::AbstractVertex)
    ismissing(NaiveNASflux.neuronutility(v)) && return pbase
    return Probability(fixnan(pbase ^ normexp(v, spread), pbase), rng)
end

weighted_neuronutility_low(pbase, rng=rng_default;spread=2) = function(v::AbstractVertex)
    ismissing(NaiveNASflux.neuronutility(v)) && return pbase
    return Probability(fixnan(pbase ^ (1/normexp(v, 1/spread)), pbase), rng)
end

fixnan(x, rep) = isnan(x) ? rep : clamp(x, 0.0, 1.0)

# This is pretty hacky and arbitrary. Change to something better
function normexp(v::AbstractVertex, s)
    allvertices = filter(allow_mutation, all_in_graph(v))
    allvalues = map(vi -> NaiveNASflux.neuronutility(vi), allvertices)
    meanvalues = map(mean, skipmissing(allvalues))
    meanvalue = mean(meanvalues)
    maxvalue = maximum(meanvalues)
    utlity = mean(NaiveNASflux.neuronutility(v))
    # Basic idea: maxvalue - utlity means the (to be) exponent is <= 0 while the division seems to normalize so that
    # average of pbase ^ normexp across allvertices is near pbase (no proof!). The factor 2 is just to prevent
    # probability of vertex with maxvalue to be 1.
    return (2maxvalue^s - utlity^s) / (2maxvalue^s - meanvalue^s)
end

"""
    MutationChain{T} <: DecoratingMutation{T}
    MutationChain(m::AbstractMutation{T}...)

Chains multiple `AbstractMutation{T}`s after each other.

Input entities will be mutated by the first `AbstractMutation{T}` in the chain and the output will be fed into the next `AbstractMutation{T}` in the chain and so on. The output from the last `AbstractMutation{T}` is returned.
"""
struct MutationChain{T} <: DecoratingMutation{T}
    m::Tuple{Vararg{AbstractMutation{T}}}
end
MutationChain(m::AbstractMutation{T}...) where T = MutationChain(m)
# Identical, but can't use Union due to ambiguity
(m::MutationChain{T})(es::AbstractVector{<:T}) where T = foldl((ei, mi) -> mi(ei), m.m; init=es)
(m::MutationChain{T})(e::T) where T = foldl((ei, mi) -> mi(ei), m.m; init=e)

"""
    RecordMutation{T} <: DecoratingMutation{T}
    RecordMutation(m::AbstractMutation{T})

Records all mutated entities.

Intended use case is to be able to do parameter selection on mutated vertices.
"""
struct RecordMutation{T} <: DecoratingMutation{T}
    m::AbstractMutation{T}
    mutated::Vector{T}
end
RecordMutation(m::AbstractMutation{T}) where T = RecordMutation(m, T[])
function (m::RecordMutation{T})(e::T; next=m.m, noop=identity) where T
    em = next(e)
    push!(m.mutated, em)
    return em
end
function fetchmutated!(m::RecordMutation)
    mutated = copy(m.mutated)
    deleteat!(m.mutated, eachindex(m.mutated))
    return mutated
end

"""
    LogMutation{T} < :DecoratingMutation{T}
    LogMutation(strfun, m::AbstractMutation{T}; level = Logging.Info, nextlogfun=e -> PrefixLogger("   "))
    LogMutation(strfun, level::LogLevel, nextlogfun, m::AbstractMutation{T})

Logs all mutation operations.

Argument `strfun` maps the mutated entity to the logged string.

Calling `nextlogfun(e)` where `e` is the entity to mutate produces an `AbstractLogger` which will be used when applying `m(e)`.

By default, this is used to add a level of indentation to subsequent logging calls which makes logs of hierarchical mutations (e.g. mutate a CompGraph by applying mutations to some of its vertices) easier to read. Set `nextlogfun = e -> current_logger()` to remove this behaviour.
"""
struct LogMutation{T,F,L<:LogLevel,LF} <: DecoratingMutation{T}
    strfun::F
    level::L
    nextlogfun::LF
    m::AbstractMutation{T}
end
LogMutation(strfun, m::AbstractMutation{T}; level = Logging.Info, nextlogfun=e -> PrefixLogger("   ")) where T = LogMutation(strfun, level, nextlogfun, m)
function (m::LogMutation{T})(e::T; next=m.m, noop=identity) where T
    @logmsg m.level m.strfun(e)
    return with_logger(() -> next(e), m.nextlogfun(e))
end

"""
    MutationFilter{T} <: DecoratingMutation{T}
    MutationFilter(predicate, m)

Applies mutation `m` only for entities `e` for which `predicate(e)` returns true.
"""
struct MutationFilter{T,P} <: DecoratingMutation{T}
    predicate::P
    m::AbstractMutation{T}
end
function (m::MutationFilter{T})(e::T; next=m.m, noop=identity) where T
    m.predicate(e) && return next(e)
    return noop(e)
end

"""
    VertexMutation <: DecoratingMutation{CompGraph}
    VertexMutation(m::AbstractMutation{AbstractVertex}, s::AbstractVertexSelection)
    VertexMutation(m::AbstractMutation{AbstractVertex})

Applies a wrapped `AbstractMutation{AbstractVertex}` to each selected vertex in a `CompGraph`.

Vertices to select is determined by the configured `AbstractVertexSelection`.
"""
struct VertexMutation{S<:AbstractVertexSelection} <: DecoratingMutation{CompGraph}
    m::AbstractMutation{AbstractVertex}
    s::S
end
VertexMutation(m::AbstractMutation{AbstractVertex}) = VertexMutation(m, FilterMutationAllowed())
function (m::VertexMutation)(g::CompGraph)
    m.m(select(m.s, g, m))
    return g
end

"""
    NoutMutation <:AbstractMutation{AbstractVertex}
    NoutMutation(l1::Real,l2::Real, rng::AbstractRNG)
    NoutMutation(limit, rng::AbstractRNG=rng_default)
    NoutMutation(l1,l2)

Mutate the out size of a vertex or vector of vertices.

Size is changed by `x * nout(v)` rounded away from from zero where `x` is drawn from `U(minrel, maxrel)` where
`minrel` and `maxrel` are `l1` and `l2` if `l1 < l2` and `l2` and `l1` otherwise.
"""
struct NoutMutation{R<:Real, RNG<:AbstractRNG} <:AbstractMutation{AbstractVertex}
    minrel::R
    maxrel::R
    rng::RNG
    function NoutMutation(l1::R1, l2::R2, rng::RNG) where {R1, R2, RNG}
        R = promote_type(R1, R2)
        return l1 < l2 ? new{R, RNG}(promote(l1, l2)..., rng) : new{R, RNG}(promote(l2, l1)..., rng)
    end
end
NoutMutation(limit, rng::AbstractRNG=rng_default) = NoutMutation(0, limit, rng)
NoutMutation(l1,l2) = NoutMutation(l1,l2, rng_default)
(m::NoutMutation)(v::AbstractVertex) = first(m([v]))
function (m::NoutMutation)(vs::AbstractVector{<:AbstractVertex})

    Δs = Dict{AbstractVertex, Int}()
    shift = m.minrel
    scale = m.maxrel - m.minrel

    for v in vs
        terminputs = findterminating(v, inputs)

        # We are basically just searching for Immutable vertices here, allow_mutation(trait(v)) happens to do just that
        any(tv -> allow_mutation(trait(tv)), terminputs) || continue

        Δfloat = rand(m.rng) * scale + shift

        Δ = ceil(Int, abs(Δfloat) * nout(v)) *  sign(Δfloat)
        minsize = minimum(nout.(terminputs))
        # Or else we might increase the size despite Δ being negative which would be surprising to a user who has specified
        # strictly negative size changes
        minsize + Δ <= 0 && continue

        Δs[v] = Δ
    end

    if !isempty(Δs)
        failmsg = (args...) -> "Could not change nout of $(join(NaiveNASlib.nameorrepr.(keys(Δs)), ", ", " and ")) by $(join(values(Δs), ", ", " and ")). No change!"

        strategy = TimeOutAction(;base=ΔNoutRelaxed(Δs), fallback=LogΔSizeExec(failmsg, Logging.Warn, ΔSizeFailNoOp()))

        Δsize!(strategy)
    end
    return vs
end

"""
    AddVertexMutation <:AbstractMutation{AbstractVertex}
    AddVertexMutation(s::AbstractArchSpace, outselect::Function, WeightInit::AbstractWeightInit, rng::AbstractRNG)
    AddVertexMutation(s, outselect::Function=identity)
    AddVertexMutation(s, rng::AbstractRNG)
    AddVertexMutation(s, wi::AbstractWeightInit)

Insert a vertex from the wrapped `AbstractArchSpace` `s` after a given vertex `v`.

The function `outselect` takes an `AbstractVector{AbstractVertex}` representing the output of `v` and returns an `AbstractVector{AbstractVertex}` which shall be reconnected to the vertex `v'` returned by `s`. Defaults to `identity` meaning all outputs of `v` are reconnected to `v'`.
"""
struct AddVertexMutation{S<:AbstractArchSpace, F, WI<:AbstractWeightInit, RNG<:AbstractRNG} <:AbstractMutation{AbstractVertex}
    s::S
    outselect::F
    weightinit::WI
    rng::RNG
end
AddVertexMutation(s, outselect::Function=identity) = AddVertexMutation(s, outselect, IdentityWeightInit(), rng_default)
AddVertexMutation(s, rng::AbstractRNG) = AddVertexMutation(s, identity, IdentityWeightInit(), rng)
AddVertexMutation(s, wi::AbstractWeightInit) = AddVertexMutation(s, identity, wi, rng_default)

function (m::AddVertexMutation)(v::AbstractVertex)
    insert!(v, vi -> m.s(name(vi), vi, m.rng, outsize=nout(vi), wi=m.weightinit), m.outselect)
    return v
end

"""
    RemoveVertexMutation <:AbstractMutation{AbstractVertex}
    RemoveVertexMutation(s::RemoveStrategy)
    RemoveVertexMutation()

Remove the given vertex `v` using the configured `RemoveStrategy`.

Default size align strategy is `IncreaseSmaller -> DecreaseBigger -> AlignSizeBoth -> FailAlignSizeWarn -> FailAlignSizeRevert`.

Default reconnect strategy is `ConnectAll`.

Note: High likelyhood of large accuracy degradation after applying this mutation.
"""
struct RemoveVertexMutation{S<:RemoveStrategy} <:AbstractMutation{AbstractVertex}
    s::S
end
function RemoveVertexMutation()
    alignstrat = IncreaseSmaller(fallback=DecreaseBigger(fallback=AlignSizeBoth(fallback=FailAlignSizeWarn(msgfun = (vin,vout) -> "Can not remove vertex $(name(vin))! Could not align sizes of neighbours!"))))
    return RemoveVertexMutation(RemoveStrategy(CheckAligned(CheckNoSizeCycle(alignstrat, FailAlignSizeWarn(msgfun = (vin,vout) -> "Can not remove vertex $(name(vin))! Size cycle detected!")))))
end

function (m::RemoveVertexMutation)(v::AbstractVertex)
    remove!(v, m.s)
    return v
end

default_neuronselect(args...) = NaiveNASlib.defaultutility(args...)

"""
    AddEdgeMutation <: AbstractMutation{AbstractVertex}
    AddEdgeMutation(p; rng=rng_default, mergefun=default_mergefun(rng=rng), filtfun=no_shapechange, utilityfun=default_neuronselect)
    AddEdgeMutation(p::Probability; rng=rng_default, mergefun=default_mergefun(rng=rng), filtfun=no_shapechange, utilityfun=default_neuronselect)

Add an edge from a vertex `vi` to another vertex `vo` randomly selected from `vs = filtfun(vi)`.

Higher values of `p` will give more preference to earlier vertices of `vs`.

If `vo` is not capable of having multiple inputs (determined by `singleinput(v) == true`), `vm = mergefun(voi)` where `voi` is a randomly selected input to `vo` will be used instead of `vo` and `vo` will be added as the output of `vm`.

When selecting neurons/outputs after any eventual size change the output of `utilityfun(v)` will be used to determine the utlity of each output in vertex `v`. Note that `length(utilityfun(v)) == nout(v)` must hold.

Note: High likelyhood of large accuracy degradation after applying this mutation.
"""
struct AddEdgeMutation{F1, F2, F3, P<:Probability, RNG} <: AbstractMutation{AbstractVertex}
    mergefun::F1
    filtfun::F2
    utilityfun::F3
    p::P
    rng::RNG
end
AddEdgeMutation(p; rng=rng_default, mergefun=default_mergefun(rng=rng), filtfun=no_shapechange, utilityfun=default_neuronselect) = AddEdgeMutation(Probability(p, rng), rng=rng, mergefun=mergefun, filtfun=filtfun, utilityfun=utilityfun)
AddEdgeMutation(p::Probability; rng=rng_default, mergefun=default_mergefun(rng=rng), filtfun=no_shapechange, utilityfun=default_neuronselect) = AddEdgeMutation(mergefun, filtfun, utilityfun, p, rng)

default_mergefun(pconc = 0.5; rng=rng_default, traitfun = MutationShield ∘ RemoveIfSingleInput ∘ validated() ∘ default_logging(), layerfun = ActivationContribution) = function(vin)
    if rand(rng) > pconc
        return invariantvertex(layerfun(+), vin, traitdecoration=traitfun ∘ named(name(vin) * ".add"))
    end
    return concat(vin, traitfun = traitfun ∘ named(name(vin) * ".cat"), layerfun=layerfun)
end

function no_shapechange(vi)
    # all_in_graph is not sorted, and we want some kind of topoligical order here so that earlier indices are closer to vi
    allsorted = mapreduce(ancestors, vcat, filter(v -> isempty(outputs(v)), all_in_graph(vi))) |> unique

    # Vertices which have the same input as vi and are singleinput
    #   Reason is that this will cause a new vertex to be added between the target output vertex vo
    #   and the input vertex to vi (vii) and this is detected as a size cycle which causes
    #   try_add_edge to fail.
    inouts = filter(singleinput, mapreduce(outputs, vcat, inputs(vi); init=[]))
    # All vertices which are after vi in the topology
    vsafter = setdiff(allsorted, ancestors(vi), outputs(vi), inouts)

    vitrace = shapetrace(vi)
    viorder = allΔshapetypes(vitrace)
    viΔshape = squashshapes(vitrace; order=viorder)

    return filter(vsafter) do vafter
        all(inputs(vafter)) do v
        t = shapetrace(v)
        vΔshape = squashshapes(t; order=union(viorder, allΔshapetypes(t)))
        return viΔshape == vΔshape
        end
    end
end

function (m::AddEdgeMutation)(vi::AbstractVertex)
    # All vertices for which it is allowed to add vi as an input
    allverts = filter(allow_mutation, m.filtfun(vi))
    isempty(allverts) && return vi

    # Higher probability to select a vertex close to v is desired behaviour
    # One line less than a for loop => FP wins!!
    selfun(::Nothing, vc) = apply(m.p) ? vc : nothing
    selfun(vs, vd) = vs
    vo = foldl(selfun, allverts, init=nothing)
    vo = isnothing(vo) ? rand(m.rng, allverts) : vo

    try_add_edge(vi, vo, m.mergefun, m.utilityfun)
    return vi
end

function try_add_edge(vi, vo, mergefun, utilityfun=default_neuronselect)

    # Need to add a vertex which can handle multiple inputs if vo is single input only
    # For cleaning up added vertex if the whole operation fails
    cleanup_failed = () -> nothing
    if singleinput(vo)
        voi = inputs(vo)[1]
        # If the input to vo is capable of multi input we don't need to create a new vertex
        # We must also check that this input does not happen to be an input to vi as this would create a cycle in the graph
        if singleinput(voi) || voi in ancestors(vi)
            vm = mergefun(voi)
            # Insert vm between voi and vo, i.e voi -> vo turns into voi -> vm -> vo
            # vs -> [vo] means only add the new vertex between voi and vo as voi could have other outputs
            insert!(voi, vv -> vm, vs -> [vo])
            cleanup_failed = function()
                length(inputs(vm)) > 1 && return
                remove!(vm, RemoveStrategy(NoSizeChange()))
            end
            vo = vm # vm is the one we shall add an edge to
            @debug "Create new vertex for merging $(name(vo))"
        else
            vo = voi
        end
    end
    # This is mainly because FailAlignSizeRevert does not work when the same vertex is input more than once, but it also seems kinda redundant.
    vi in inputs(vo) && return
    @debug "Create edge between $(name(vi)) and $(name(vo))"
    create_edge!(vi, vo, strategy = create_edge_strat(vo, utilityfun))
    cleanup_failed()
end
# Need to override this one for strange types e.g. layers which support exactly 2 inputs or something.
singleinput(v) = isempty(inputs(v)) || length(inputs(v)) == 1

create_edge_strat(v::AbstractVertex, utilityfun) = create_edge_strat(trait(v), utilityfun)
create_edge_strat(d::DecoratingTrait, utilityfun) = create_edge_strat(base(d), utilityfun)
function create_edge_strat(::SizeInvariant, utilityfun)
    warnfailalign = FailAlignSizeWarn(msgfun = (vin,vout) -> "Could not align sizes of $(name(vin)) and $(name(vout))!")
    alignstrat = AlignSizeBoth(;mapstrat=WithUtilityFun(utilityfun), fallback = warnfailalign)
    # Tricky failure case: It is possible that CheckCreateEdgeNoSizeCycle does not detect any size cycle until after the edge has been created?
    sizecyclewarn = FailAlignSizeWarn(msgfun = (vin,vout) -> "Could not align sizes of $(name(vin)) and $(name(vout))! Size cycle detected!")

    return CheckCreateEdgeNoSizeCycle(ifok=alignstrat, ifnok=sizecyclewarn)
end
function create_edge_strat(::SizeStack, utilityfun)
    warnfailalign = FailAlignSizeWarn(msgfun = (vin,vout) -> "Could not align sizes of $(name(vin)) and $(name(vout))!")
    alignstrat = PostAlign(TruncateInIndsToValid(WithUtilityFun(utilityfun, AlignNinToNout(;fallback=ΔSizeFailNoOp()))), fallback=warnfailalign)

    sizecyclewarn = FailAlignSizeWarn(msgfun = (vin,vout) -> "Could not align sizes of $(name(vin)) and $(name(vout))! Size cycle detected!")
    return CheckCreateEdgeNoSizeCycle(ifok=alignstrat, ifnok=sizecyclewarn)
end

"""
    RemoveEdgeMutation <: AbstractMutation{AbstractVertex}
    RemoveEdgeMutation(;utilityfun=default_neuronselect, rng=rng_default)

Remove an edge from a vertex `vi` to another vertex `vo` randomly selected from `outputs(vi)`.

Vertex `vi` must have more than one output and vertex `vo` must have more than one output for the edge to be removed. Otherwise no change is made.

If there are multiple edges between `vi` and `vo` no change will be made due to NaiveNASlib not being able to revert a failed operation in this case..

When selecting neurons/outputs after any eventual size change the output of `utilityfun(v)` will be used to determine the utlity of each output in vertex `v`. Note that `length(utilityfun(v)) == nout(v)` must hold.

Note: High likelyhood of large accuracy degradation after applying this mutation.
"""
struct RemoveEdgeMutation{F, RNG<:AbstractRNG} <: AbstractMutation{AbstractVertex}
    utilityfun::F
    rng::RNG
end
RemoveEdgeMutation(;utilityfun=default_neuronselect, rng=rng_default) = RemoveEdgeMutation(utilityfun, rng)

function (m::RemoveEdgeMutation)(vi::AbstractVertex)
    length(outputs(vi)) < 2 && return vi

    allverts = filter(vo -> length(inputs(vo)) > 1, outputs(vi))

    isempty(allverts) && return vi

    vo = rand(m.rng, allverts)
    sum(inputs(vo) .== vi) > 1 && return vi# Not implemented in NaiveNASlib

    @debug "Remove edge between $(name(vi)) and $(name(vo))"
    remove_edge!(vi, vo, strategy=remove_edge_strat(vo, m.utilityfun))
    return vi
end

remove_edge_strat(v::AbstractVertex, utilityfun) = remove_edge_strat(trait(v), utilityfun)
remove_edge_strat(d::DecoratingTrait, utilityfun) = remove_edge_strat(base(d), utilityfun)
remove_edge_strat(::SizeInvariant, utilityfun) = NoSizeChange()
remove_edge_strat(t::SizeStack, utilityfun) = create_edge_strat(t, utilityfun)

"""
    KernelSizeMutation{N} <: AbstractMutation{AbstractVertex}
    KernelSizeMutation(Δsizespace::AbstractParSpace{N, Int}; maxsize, pad, rng)
    KernelSizeMutation2D(absΔ::Integer;maxsize, pad, rng)
    KernelSizeMutation(absΔ::Integer...;maxsize, pad, rng)

Mutate the size of filter kernels of convolutional layers.

Note: High likelyhood of large accuracy degradation after applying this mutation.

`KernelSizeMutation2D` is a convenience constructor for `KernelSizeMutation(absΔ, absΔ;...)`.
"""
struct KernelSizeMutation{N,F,P} <: AbstractMutation{AbstractVertex}
    Δsizespace::AbstractParSpace{N, Int}
    maxsize::F
    pad::P
    rng::AbstractRNG
end
KernelSizeMutation(Δsizespace::AbstractParSpace{N, Int}; maxsize = v -> ntuple(i->Inf,N), pad=SamePad(), rng=rng_default) where N = KernelSizeMutation(Δsizespace, maxsize, pad, rng)
KernelSizeMutation2D(absΔ::Integer;maxsize = v -> (Inf,Inf), pad=SamePad(), rng=rng_default) = KernelSizeMutation(absΔ, absΔ, maxsize = maxsize, pad=pad, rng=rng)
KernelSizeMutation(absΔ::Integer...;maxsize = v -> ntuple(i->Inf, length(absΔ)), pad=SamePad(), rng=rng_default) = KernelSizeMutation(ParSpace(UnitRange.(.-absΔ, absΔ));maxsize = maxsize, pad=pad, rng=rng)

function (m::KernelSizeMutation{N})(v::AbstractVertex) where N
    layertype(v) isa FluxConvolutional{N} || return
    l = layer(v)

    currsize = size(NaiveNASflux.weights(l))[1:N]
    Δsize = Int.(clamp.(m.Δsizespace(m.rng), 1 .- currsize, m.maxsize(v) .- currsize)) # ensure new size is > 0 and < maxsize
    # This will eventually boil down to Setfield doing its thing, and that won't be using any convenience constructors
    pad = Flux.calc_padding(typeof(l), m.pad, currsize .+ Δsize, dilation(l), stride(l))
    KernelSizeAligned(Δsize, pad)(v)
    return v
end
dilation(l) = l.dilation
stride(l) = l.stride

"""
    ActivationFunctionMutation{T,R} <: AbstractMutation{AbstractVertex} where {T <: AbstractParSpace{1}, R <: AbstractRNG}
    ActivationFunctionMutation(actspace::AbstractParSpace{1}, rng::AbstractRNG)
    ActivationFunctionMutation(acts...;rng=rng_default)
    ActivationFunctionMutation(acts::AbstractVector;rng=rng_default)

Mutate the activation function of layers which have an activation function.

Note: High likelyhood of large accuracy degradation after applying this mutation.
"""
struct ActivationFunctionMutation{T,RNG} <: AbstractMutation{AbstractVertex} where {T <: AbstractParSpace{1}, R <: AbstractRNG}
    actspace::T
    rng::RNG
end
ActivationFunctionMutation(acts...;rng=rng_default) = ActivationFunctionMutation(collect(acts), rng=rng)
ActivationFunctionMutation(acts::AbstractVector;rng=rng_default) = ActivationFunctionMutation(ParSpace(acts), rng)

function (m::ActivationFunctionMutation)(v::AbstractVertex)
    m(layertype(v), v)
    return v
end
function (m::ActivationFunctionMutation)(t, v) end
(m::ActivationFunctionMutation)(::Union{FluxDense, FluxConvolutional}, v) = NaiveNASflux.setlayer!(v, (σ = m.actspace(m.rng),))
(m::ActivationFunctionMutation)(::FluxParNorm, v) = NaiveNASflux.setlayer!(v, (λ = m.actspace(m.rng),))
function (m::ActivationFunctionMutation)(::FluxRnn, v)
    newcell = setproperties(layer(v).cell, (σ = m.actspace(m.rng),))
    NaiveNASflux.setlayer!(v, (cell = newcell,))
end

"""
    PostMutation{T} <: DecoratingMutation{T}
    PostMutation(actions, m::AbstractMutation{T})
    PostMutation(m::AbstractMutation{T}, actions...)

Performs a set of actions after a wrapped `AbstractMutation` is applied.

Actions will be invoked with arguments (m::PostMutation{T}, e::T) where m is the enclosing `PostMutation` and `e` is the mutated entity of type `T`.
"""
struct PostMutation{T,A} <: DecoratingMutation{T}
    actions::A
    m::AbstractMutation{T}
end
PostMutation(m::AbstractMutation{T}, actions...) where T = PostMutation(actions, m)
PostMutation(action::Function, m::AbstractMutation{T}) where T = PostMutation(m, action)
function (m::PostMutation{T})(e::T; next=m.m, noop=identity) where T
    eout = next(e)
    foreach(a -> a(m, eout), m.actions)
    return eout
end

"""
    RemoveZeroNout()
    RemoveZeroNout(fallback)

Search for vertices with zero output size and remove them and all of their input vertices if possible to do so witout removing an input or output vertex.

Removal is only possible if a vertex is inside a parallel path which will later be concatenated.
"""
struct RemoveZeroNout
    fallback
end
RemoveZeroNout() = RemoveZeroNout(IncreaseZeroNout())
struct IncreaseZeroNout end

(r::RemoveZeroNout)(m, e) = r(e)
function (r::RemoveZeroNout)(g::CompGraph)
    topoligical_order = vertices(g)
    for v in topoligical_order
        nout(v) == 0 || continue

        # Beware! This turned out to be a lot harder than I first thought.
        # I'm not sure the algorithm works (or realizes that it won't work) for all possible cases.

        # This obviously only works if v is inside a parallel path which will later be concatenated

        # To make sure it is, we look ahead in forward and backward direction.
        # If we don't see 1) an input vertex 2) a vertex without outputs (i.e an output vertex) we are good to go
        fseen, fpoints = findforkpoint(v, topoligical_order, inputs, outputs)
        isempty(fpoints) && continue

        bseen, bpoints = findforkpoint(v, topoligical_order, outputs, inputs)
        isempty(bpoints) && continue

        # Ok, fpoints are all vertices where forks with v in them join and bpoints are all vertices where forks with v in them begin

        # If we start removing all seen vertices which are input to an fpoint until we hit a bpoint we should have removed the fork with v in it, right?
        seen = union(fseen, bseen)
        to_rm = intersect(vcat(inputs.(fpoints)...), seen)
        foreach(fpoint -> remove_all_inputs(fpoint, seen, bpoints), to_rm)
    end
end

function findforkpoint(v::AbstractVertex, topoligical_order, f1=inputs, f2=outputs, seen = vcat(v, f1(v)), points = AbstractVertex[])

    if all(x -> x in seen, f1(v))

        # Check if we came across a vertex which previously was thought to be a forkpoint and remove it if so
        if v in points
            deleteat!(points, indexin([v], points))
        end
        # Always visit in reverse topoligical order to mitigate chasing down paths just because we haven't explored them yet
        nextverts = reverse(topoligical_order[unique(indexin(f2(v), topoligical_order))])
        push!(seen, filter(v2 -> !in(v2, seen), nextverts)...)
        foreach(v2 -> findforkpoint(v2, topoligical_order, f1, f2, seen, points), nextverts)
    elseif !(v in points)
        push!(points, v)
    end

    return seen, points
end

function remove_all_inputs(v, seen, stop)
    v in stop && return
    foreach(vrm -> remove_all_inputs(vrm, seen, stop), intersect(inputs(v), seen))
    remove!(v, RemoveStrategy(ConnectNone(), NoSizeChange()))
end

"""
    struct OptimizerMutation{F} <: AbstractMutation{FluxOptimizer}
    OptimizerMutation(optfun)
    OptimizerMutation(os::Union{Tuple, <:AbstractArray})

Mutatates optimizers not wrapped in `ShieldedOpt` through `optfun`.

Invoked recursively for `Flux.Optimiser`s.
"""
struct OptimizerMutation{F} <: AbstractMutation{FluxOptimizer}
    optfun::F
end
OptimizerMutation(os::Union{Tuple, <:AbstractArray}, rng=rng_default) = OptimizerMutation(o -> rand(rng, os)(learningrate(o)))

"""
    LearningRateMutation(rng=rng_default)

Return an `OptimizerMutation` which mutates the learning rate of optimizers.
"""
LearningRateMutation(rng=rng_default) = OptimizerMutation(o -> nudgelr(o, rng))

(m::OptimizerMutation)(opt::Flux.Optimiser) = Flux.Optimiser(m.(opt.os))
(m::OptimizerMutation)(o::ShieldedOpt) = o;
(m::OptimizerMutation)(o::FluxOptimizer) = m.optfun(o)

nudgelr(o, rng=rng_default) = sameopt(o, nudgelr(learningrate(o), rng))
nudgelr(lr::Number, rng=rng_default) = clamp(lr + (rand(rng) - 0.5) * lr * 0.3, 1e-6, 1.0)

learningrate(o::Flux.Optimiser) = prod(learningrate.(o.os))
learningrate(o::ShieldedOpt) = learningrate(o.opt)
learningrate(o) = o.eta

newlr(o, lrf = nudgelr) = sameopt(o, lrf(learningrate(o)))
sameopt(o, lr) = @set o.eta = lr

"""
    AddOptimizerMutation{F} <: AbstractMutation{FluxOptimizer}

Adds optimizer generated by `optgen(os)` to the set of optimizers where `os` is the existing set.

An attempt to merge optimizers of the same type is made using `mergeopts`.
"""
struct AddOptimizerMutation{F} <: AbstractMutation{FluxOptimizer}
    optgen::F
end
(m::AddOptimizerMutation)(o::ShieldedOpt) = o;
(m::AddOptimizerMutation)(o::FluxOptimizer) = m(Flux.Optimiser([o]))
function (m::AddOptimizerMutation)(opt::Flux.Optimiser)
    newopt = m.optgen(opt)
    return Flux.Optimiser(mergeopts(typeof(newopt), newopt, opt.os...))
end


"""
    rand(::Type{DQMC}, m::Model, nslices::Int)

Draw random configuration.
"""
Base.rand(::Type{DQMC}, m::Model, nslices::Int) = error("Model $(typeof(m)) doesn't implement `rand(::Type{DQMC}, m::Model, nslices::Int)`!")

"""
	hopping_matrix(mc::DQMC, m::Model)

Calculates the hopping matrix \$T_{i\\sigma, j\\sigma '}\$ where \$i, j\$ are site indices and \$\\sigma , \\sigma '\$
are flavor indices (e.g. spin indices). The hopping matrix should also contain
potential chemical potential terms on the diagonal.

A matrix element is the hopping amplitude for a hopping process: \$j,\\sigma ' \\rightarrow i,\\sigma\$.

Regarding the order of indices, if `T[i, σ, j, σ']` is your desired 4D hopping array, then `reshape(T, (n_sites * n_flavors, :))`
is the hopping matrix.
"""
hopping_matrix(mc::DQMC, m::Model) = error("Model has no implementation of `hopping_matrix(mc::DQMC, m::Model)`.")

"""
    interaction_matrix_exp!(mc::DQMC, m::Model, result::Matrix, conf, slice::Int, power::Float64=1.) -> nothing

Calculate the interaction matrix exponential `expV = exp(- power * delta_tau * V(slice))`
and store it in `result::Matrix`. Potential chemical potential terms should be
part of the `hopping_matrix` and not the interaction.

This is a performance critical method and one might consider efficient in-place (in `result`) construction.
"""
interaction_matrix_exp!(mc::DQMC, m::Model, result::Matrix, conf, slice::Int, power::Float64=1.) = error("Model has no implementation of `interaction_matrix_exp!(mc::DQMC, m::Model, result::Matrix, conf, slice::Int, power::Float64=1.)`.")

"""
    propose_local(mc::DQMC, m::Model, i::Int, conf) -> detratio, ΔE_boson, Δ

Propose a local move for lattice site `i` of current configuration `conf` . Returns the
Green's function determinant ratio, the boson energy difference `ΔE_boson = E_boson_new - E_boson`,
and potentially additional local move information `Δ` (will be forwarded to `accept_local!`).

See also [`accept_local!`](@ref).
"""
propose_local(mc::DQMC, m::Model, i::Int, conf) =
    error("Model has no implementation of `propose_local(mc::DQMC, m::Model, i::Int, conf)`!")

"""
    accept_local(mc::DQMC, m::Model, i::Int, slice::Int, conf, Δ, detratio, ΔE_boson)

Accept a local move for site `i` at imaginary time slice `slice` of current configuration `conf`.
Arguments `Δ`, `detratio` and `ΔE_boson` correspond to output of `propose_local` for that local move.

See also [`propose_local`](@ref).
"""
accept_local(mc::DQMC, m::Model, i::Int, slice::Int, conf, Δ, detratio, ΔE_boson) =
    error("Model has no implementation of `accept_local(mc::DQMC, m::Model, i::Int, slice::Int, conf, Δ, detratio, ΔE_boson)`!")


module Inspire

using Codex.Questionnaires: Items, get_scores
using DataFrames

export mike2scores

# The scoring is determined by guessing by me.
# I expect to have made a few mistakes, but overall the scoring was reasonably after knowing
# the number of items per predictor; thanks to
# https://repository.tudelft.nl/view/tno/uuid:98d97d5c-ff8e-4f26-9fba-f993bc585d39.
# For coping, the last 4 items seem to be reversed and added later by someone.
# Those are ignored.

coping_flexibility = Items(1:36, [])

emotional_stability = Items(
    [41, 44, 49, 50, 52, 54, 56],
    [42, 43, 45, 46, 47, 48, 51, 53, 55]
)

optimism = Items(
    [61, 63, 69],
    [58, 59, 67]
)

social_competence = Items(
    [57, 60, 62, 64, 65, 66, 68, 70],
    []
)

self_efficacy = Items(71:83, [])

self_reflection = Items(
    [88, 90, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103],
    [84, 85, 86, 87, 91, 92, 89]
)

function mike2scores(df::DataFrame)
    new_df = DataFrame(df)
    if !("completed_at" in names(new_df))
        # Cleaning up the 2018 data.
        new_df[:, :completed_at] = repeat([""], nrow(new_df))
        select!(new_df, :id, :completed_at, Not([:id, :completed_at]))
        new_df = filter(:id => x -> !contains(x, "unknown"), new_df)
    end

    new_df[:, :coping_flexibility] = get_scores(new_df, coping_flexibility)
    new_df[:, :emotional_stability] = get_scores(new_df, emotional_stability)
    new_df[:, :optimism] = get_scores(new_df, optimism)
    new_df[:, :social_competence] = get_scores(new_df, social_competence)
    new_df[:, :self_efficacy] = get_scores(new_df, self_efficacy)
    new_df[:, :self_reflection] = get_scores(new_df, self_reflection)

    scored = select(new_df, :id, :completed_at,
        :coping_flexibility, :emotional_stability, :optimism,
        :social_competence, :self_efficacy, :self_reflection
    )
    # This could have been done above, but I mistakenly used `disallowmissing`
    # instead and went from there.
    dropmissing!(scored)
    scored
end

end # module


module StochasticDelayDiffEq
#########################################################
#########################################################
#########################################################
#########################################################
# using DifferentialEquations
using Reexport
@reexport using StochasticDiffEq
# import StochasticDiffEq: calc_J, calc_J!, calc_tderivative!, calc_tderivative
using LinearAlgebra, StaticArrays
using Parameters, DataStructures
using Logging
using RecursiveArrayTools
using DiffEqBase: AbstractSDDEProblem, AbstractSDDEAlgorithm, AbstractRODESolution, AbstractRODEFunction, AbstractSDEIntegrator, AbstractSDDEIntegrator, DEIntegrator, DEAlgorithm, AbstractRODEAlgorithm, AbstractSDEAlgorithm

import DelayDiffEq: constant_extrapolant!, constant_extrapolant, AbstractMethodOfStepsAlgorithm, iscomposite, MethodOfSteps
using DiffEqNoiseProcess

import RandomNumbers: Xorshifts
using Random
import Base: convert

# #########################################################
# #########################################################
# #########################################################
# #########################################################

include("discontinuity_type.jl")
include("integrators/type.jl")
include("integrators/interface.jl")
include("integrators/utils.jl")
include("functionwrapper.jl")
include("history_function.jl")
include("utils.jl")
include("solve.jl")

end # module


#=
Modia main module.

* Developers: Hilding Elmqvist, Mogram AB, Toivo Henningsson, Lund and Martin Otter, DLR
* Copyright (c) 2016-2018: Hilding Elmqvist, Toivo Henningsson, Martin Otter
* License: MIT (expat)
=#

import ModiaMath

doc"""
Modia - Dynamic Modeling and Simulation in Julia

To define a model:

```julia
  @model FirstOrder begin
     x = Variable(start=1)   # start means x(0)
     T = Parameter(0.5)      # Time constant
     u = 2.0                 # Same as Parameter(2.0)
  @equations begin
     T*der(x) + x = u        # der() means time derivative
     end
  end;
```

To simulate a model:
```julia
  result = simulate(FirstOrder, 2);
  @show result["x"][end];
  ModiaMath.plot(result, "x")
```

To run examples:
```julia
  include("$(Modia.ModiaDir)/examples/runexamples.jl")
```

To run tests:
```julia
  include("$(Modia.ModiaDir)/test/runtests.jl")
```
For more information, see (https://github.com/ModiaSim/Modia.jl/blob/master/README.md)
"""
module Modia

const Version = "0.2.0-beta.1"
const Date = "2018-08-03"

#println(" \n\nWelcome to Modia - Dynamic MODeling and Simulation in julIA")
print(" \n\nWelcome to ")
print("Mod")
print_with_color(:red, "ia", bold=true)
print(" - ")
print_with_color(:light_black, "Dynamic ")
print("Mod")
print_with_color(:light_black, "eling and Simulation with Jul")
print_with_color(:red, "ia", bold=true)
println()

println("Version $Version ($Date)")
println("Type \"?Modia\" for help.\n\n")

const ModiaDir = Pkg.dir("Modia")
export ModiaDir

export @model, simulateModel, simulate, checkSimulation, simulateMultiModeModel
export Variable, Float, Boolean, Integ, Str, Parameter, Var, Par, undefined
export Variability, constant, parameter, discrete, continuous
export Property, general, symmetric, orthogonal, rotationGroup3D
export skew, skewCoords
export allInstances
export @component, addComponent!

include("language/ModiaLogging.jl")
include("language/Instantiation.jl")
include("language/Execution.jl")

include("symbolic/DAEquations/Synchronous.jl") # Before models/Electric, etc

include("models/models.jl")  # Before symbolic because MultiBody is used in BasicStructuralTransform

include("symbolic/symbolic.jl")

using .Instantiation

include("models/ModiaBase.jl")

end


module EvoTrees

export init_evotree, grow_evotree!, grow_tree, fit_evotree, predict,
    fit_evotree_gpu, predict_gpu,
    EvoTreeRegressor, EvoTreeCount, EvoTreeClassifier, EvoTreeGaussian,
    EvoTreeRModels, importance

using Statistics
using Base.Threads: @threads
using StatsBase: sample, quantile
using Random
using StaticArrays
using Distributions
using CategoricalArrays
using CUDA
import MLJModelInterface
import MLJModelInterface: fit, update
import MLJModelInterface: predict

include("models.jl")
include("structs.jl")
include("loss.jl")
include("eval.jl")
include("predict.jl")
include("find_split.jl")
include("fit.jl")
include("importance.jl")
include("MLJ.jl")

include("structs_gpu.jl")
include("loss_gpu.jl")
include("eval_gpu.jl")
include("predict_gpu.jl")
include("find_split_gpu.jl")
include("fit_gpu.jl")

end # module


export TD, FD
import Base: unsafe_convert
using Base: size_to_strides
using Knet.KnetArrays: DevArray
using AutoGrad: Value

using CUDA.CUDNN: handle,
    cudnnTensorDescriptor_t,
    cudnnCreateTensorDescriptor,
    cudnnSetTensor4dDescriptor,
    cudnnSetTensor4dDescriptorEx,
    cudnnGetTensor4dDescriptor,
    cudnnSetTensorNdDescriptor,
    cudnnSetTensorNdDescriptorEx,
    cudnnGetTensorNdDescriptor,
    cudnnGetTensorSizeInBytes,
    cudnnDestroyTensorDescriptor,

    cudnnFilterDescriptor_t,
    cudnnCreateFilterDescriptor,
    cudnnSetFilter4dDescriptor,
    cudnnGetFilter4dDescriptor,
    cudnnSetFilterNdDescriptor,
    cudnnGetFilterNdDescriptor,
    cudnnGetFilterSizeInBytes,
    cudnnDestroyFilterDescriptor,

    cudnnDataType_t,
        CUDNN_DATA_FLOAT,
        CUDNN_DATA_DOUBLE,
        CUDNN_DATA_HALF,
        CUDNN_DATA_INT8,
        CUDNN_DATA_INT32,
        CUDNN_DATA_INT8x4,
        CUDNN_DATA_UINT8,
        CUDNN_DATA_UINT8x4,
        CUDNN_DATA_INT8x32,

    cudnnTensorFormat_t,
        CUDNN_TENSOR_NCHW,
        CUDNN_TENSOR_NHWC,
        CUDNN_TENSOR_NCHW_VECT_C

# cudnn tensor/filter descriptors: need to use mutable structs in order to have finalizers.

mutable struct TensorDescriptor; ptr::cudnnTensorDescriptor_t; end
unsafe_convert(::Type{<:Ptr}, td::TensorDescriptor)=td.ptr
TD(a) = TD(eltype(a),size(a))
function TD(T::Type, dims::Dims{N}) where {N}
    ptr = cudnnTensorDescriptor_t[C_NULL]
    cudnnCreateTensorDescriptor(ptr)
    sz = Cint[reverse(dims)...]
    st = Cint[reverse(size_to_strides(1,dims...))...]
    cudnnSetTensorNdDescriptor(ptr[1], DT(T), N, sz, st)
    td = TensorDescriptor(ptr[1])
    finalizer(x->cudnnDestroyTensorDescriptor(x.ptr), td)
    return td
end

mutable struct FilterDescriptor; ptr::cudnnFilterDescriptor_t; end
unsafe_convert(::Type{<:Ptr}, fd::FilterDescriptor)=fd.ptr
FD(a) = FD(eltype(a),size(a),CUDNN_TENSOR_NCHW)
function FD(T::Type, dims::Dims{N},format::cudnnTensorFormat_t) where N
    ptr = cudnnFilterDescriptor_t[C_NULL]
    cudnnCreateFilterDescriptor(ptr)
    sz = Cint[reverse(dims)...]
    cudnnSetFilterNdDescriptor(ptr[1], DT(T), format, N, sz)
    fd = FilterDescriptor(ptr[1])
    finalizer(x->cudnnDestroyFilterDescriptor(x.ptr), fd)
    return fd
end

DT(::Type{T}) where T = get(DataTypes, T) do; error("CUDNN does not support $T"); end
DataTypes = Dict{Type,cudnnDataType_t}(
    Float32 => CUDNN_DATA_FLOAT,
    Float64 => CUDNN_DATA_DOUBLE,
    Float16 => CUDNN_DATA_HALF,
    Int8 => CUDNN_DATA_INT8,
    UInt8 => CUDNN_DATA_UINT8,
    Int32 => CUDNN_DATA_INT32,
    # The following are 32-bit elements each composed of 4 8-bit integers, only supported with CUDNN_TENSOR_NCHW_VECT_C (TODO)
    # CUDNN_DATA_INT8x4,
    # CUDNN_DATA_UINT8x4,
    # CUDNN_DATA_INT8x32,
)

nothing


module AdversarialDriving
    using POMDPs
    using POMDPPolicies
    using POMDPSimulators
    using AutomotiveSimulator
    using AutomotiveVisualization
    using Distributions
    using Parameters
    using Random
    using Reel
    using Cairo

    # TIDM model exports
    export  PEDESTRIAN_DEF, Noise, NoisyState, NoisyPedState, BlinkerState,
            update_veh_state, noisy_scene, noisy_entity, blinker, goals, noise,
            BlinkerVehicle, NoisyPedestrian, Disturbance, BlinkerVehicleControl,
            PedestrianControl, AdversarialPedestrian, TIDM, lane_belief, laneid,
            can_have_goal, any_collides, ego_collides, end_of_road, PolicyModel,
            Blindspot, in_blindspot, RenderableBlindspot
    include("driving_models.jl")

    # T-Intersection roadway exports
    export  roadway_lane,
            road_with_crosswalk, Crosswalk, DEFAULT_CROSSWALK_LANE,
            ped_roadway, crosswalk, ped_yields_way, ped_intersection_enter_loc, ped_intersection_exit_loc, ped_goals, ped_should_blink,
            T_intersection, Tint_goal_label, Tint_signal_right,
            Tint_roadway, Tint_yields_way, Tint_intersection_enter_loc, Tint_intersection_exit_loc, Tint_goals, Tint_should_blink,
            Tint_TIDM_template, ped_TIDM_template
    include("roadways.jl")

    # MDP
    export Agent, BlinkerVehicleAgent, NoisyPedestrianAgent, id,
           AdversarialDrivingMDP, action_probability, step_scene,
           agents, adversaries, model, sut, sutid, update_adversary!, DrivingMDP
    include("mdp.jl")

    # states
    export PEDESTRIAN_ENTITY_DIM, NoisyPedestrian_to_vec, vec_to_NoisyPedestrian_fn,
           BLINKERVEHICLE_ENTITY_DIM, BLINKERVEHICLE_EXPANDED_ENTITY_DIM, BlinkerVehicle_to_vec, vec_to_BlinkerVehicle, BlinkerVehicle_to_expanded_vec
    include("states.jl")

    #actions
    export PEDESTRIAN_DISTURBANCE_DIM, PedestrianControl_to_vec, vec_to_PedestrianControl,
           BLINKERVEHICLE_DISTURBANCE_DIM, BlinkerVehicleControl_to_vec, vec_to_BlinkerVehicleControl,
           BV_ACTIONS, BV_ACTION_PROB, get_actions, get_actionindex, DiscreteActionModel,
           combine_disturbance_models, combine_continuous, combine_discrete, get_bv_actions, get_rand_bv_actions
    include("actions.jl")

    #helpers
    export decompose_scene,
           decompose_indices,
           ez_Tint_vehicle,
           get_Tint_vehicle,
           get_rand_Tint_vehicle,
           up_left,
           left_straight,
           left_turnright,
           right_straight,
           right_turnleft,
           rand_up_left,
           rand_left,
           rand_right,
           ez_ped_vehicle,
           ez_pedestrian,
           get_ped_vehicle,
           get_rand_ped_vehicle,
           get_pedestrian,
           get_rand_pedestrian,
           scenes_to_gif,
           random_IDM,
           create_actions_1BV,
           continuous_policy
    include("helpers.jl")
end


# Since Zygote is at its early stage
# this patch contains some workaround of known issues
# it should be able to get removed once those issues are fixed

using Zygote, LuxurySparse, Yao, YaoBase, SparseArrays, BitBasis

# LuxurySparse constructor adjoints
Zygote.@adjoint (::Type{T})(perms, vals) where T <: PermMatrix = T(perms, vals), Δ -> nothing
Zygote.@adjoint (::Type{T})() where T <: IMatrix = T(), Δ -> nothing
Zygote.@adjoint Base.:(*)(A::Number, B::PermMatrix) = A * B, Δ->(sum(Δ .* B), A * Δ)
Zygote.@adjoint Base.:(*)(A::PermMatrix, B::Number) = A * B, Δ->(A * Δ, sum(Δ .* B))

Zygote.@adjoint SparseArrays.SparseMatrixCSC(A::PermMatrix) = SparseMatrixCSC(A), Δ->(Δ, )

# BitBasis constructor (no gradient needed)
Zygote.@adjoint BitBasis.onehot(::Type{T}, nbits::Int, x::Integer, nbatch::Int) where T = onehot(T, nbits, x, nbatch), Δ -> nothing

# state(r::ArrayReg)
Zygote.@adjoint function Base.getfield(r::ArrayReg, n::Symbol)
    getfield(r, n), Δ -> (ArrayReg(Δ), nothing)
end

# workaround of upstream
Zygote.@adjoint Base.:(-)(a, b) = a-b, Δ -> (Δ, -Δ)
Zygote.@adjoint Base.:(+)(a, b) = a+b, Δ -> (Δ, Δ)

# require mutation support
Zygote.@adjoint! function copyto!(xs::AbstractVector, ys::Tuple)
    xs_ = copy(xs)
    copyto!(xs, ys), function (dxs)
        copyto!(xs_, xs)
        return (nothing, Tuple(dxs))
    end
end


#=
This file is auto-generated. Do not edit.
=#
"""
    mutable struct TGTypeII <: TurbineGov
        R::Float64
        T1::Float64
        T2::Float64
        τ_min::Float64
        τ_max::Float64
        ext::Dict{String, Any}
        states::Vector{Symbol}
        n_states::Int64
        internal::InfrastructureSystemsInternal
    end

Parameters of a Turbine Governor Type II.

# Arguments
- `R::Float64`: Droop parameter, validation range: (0, nothing)
- `T1::Float64`: Transient gain time constant, validation range: (0, nothing)
- `T2::Float64`: Power fraction time constant, validation range: (0, nothing)
- `τ_min::Float64`: Min Power into the Governor, validation range: (0, nothing)
- `τ_max::Float64`: Max Power into the Governor, validation range: (0, nothing)
- `ext::Dict{String, Any}`
- `states::Vector{Symbol}`
- `n_states::Int64`
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct TGTypeII <: TurbineGov
    "Droop parameter"
    R::Float64
    "Transient gain time constant"
    T1::Float64
    "Power fraction time constant"
    T2::Float64
    "Min Power into the Governor"
    τ_min::Float64
    "Max Power into the Governor"
    τ_max::Float64
    ext::Dict{String, Any}
    states::Vector{Symbol}
    n_states::Int64
    "power system internal reference, do not modify"
    internal::InfrastructureSystemsInternal
end

function TGTypeII(R, T1, T2, τ_min, τ_max, ext=Dict{String, Any}(), )
    TGTypeII(R, T1, T2, τ_min, τ_max, ext, [:xg], 1, InfrastructureSystemsInternal(), )
end

function TGTypeII(; R, T1, T2, τ_min, τ_max, ext=Dict{String, Any}(), )
    TGTypeII(R, T1, T2, τ_min, τ_max, ext, )
end

# Constructor for demo purposes; non-functional.
function TGTypeII(::Nothing)
    TGTypeII(;
        R=0,
        T1=0,
        T2=0,
        τ_min=0,
        τ_max=0,
        ext=Dict{String, Any}(),
    )
end

"""Get TGTypeII R."""
get_R(value::TGTypeII) = value.R
"""Get TGTypeII T1."""
get_T1(value::TGTypeII) = value.T1
"""Get TGTypeII T2."""
get_T2(value::TGTypeII) = value.T2
"""Get TGTypeII τ_min."""
get_τ_min(value::TGTypeII) = value.τ_min
"""Get TGTypeII τ_max."""
get_τ_max(value::TGTypeII) = value.τ_max
"""Get TGTypeII ext."""
get_ext(value::TGTypeII) = value.ext
"""Get TGTypeII states."""
get_states(value::TGTypeII) = value.states
"""Get TGTypeII n_states."""
get_n_states(value::TGTypeII) = value.n_states
"""Get TGTypeII internal."""
get_internal(value::TGTypeII) = value.internal


# Planet, Reluplex and Reluval

using NeuralVerification
using Test

macro no_error(ex)
    quote
        try $(esc(ex))
            true
        catch
            false
        end
    end
end

at = @__DIR__

# Problem type - input:Hyperrectangle, output:Hyperrectangle
print("###### Problem type - input:Hyperrectangle, output:Hyperrectangle ######\n")
# Small MNIST Network

mnist_small = read_nnet("$at/../examples/networks/mnist_small.nnet")

# entry 23 in MNIST datset
input_center = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,121,254,136,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,230,253,248,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,118,253,253,225,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,61,253,253,253,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,206,253,253,186,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,211,253,253,239,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,253,253,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142,255,253,186,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,149,229,254,207,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,54,229,253,254,105,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,152,254,254,213,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,251,253,253,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,212,253,250,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,214,253,253,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,253,253,253,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,253,253,189,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,253,253,84,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,235,253,126,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,99,248,253,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,225,235,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
output_center = [-1311.1257826380004,4633.767704436501,-654.0718535670002,-1325.349417307,1175.2361184373997,-1897.8607293569007,-470.3405972940001,830.8337987382,-377.7467076115001,572.3674015264198]

in_epsilon = 1 #0-255
out_epsilon = 10 #logit domain

input_low = input_center .- in_epsilon
input_high = input_center .+ in_epsilon

output_low = output_center .- out_epsilon
output_high = output_center .+ out_epsilon

inputSet = Hyperrectangle(low=input_low, high=input_high)
outputSet = Hyperrectangle(low=output_low, high=output_high)

print("\n\n################## Small ##################\n")

problem_hyperrect_small = Problem(mnist_small, inputSet, outputSet)
solver_reluVal = ReluVal(max_iter = 2)
print("\nReluval - Small")
timed_result = @timed solve(solver_reluVal, problem_hyperrect_small)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

print("\nPlanet - Small")
optimizer = GLPKSolverMIP()
solver = Planet(optimizer)
timed_result = @timed solve(solver, problem_hyperrect_small)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

print("\nReluplex - Small")
solver=Reluplex()
timed_result = @timed solve(solver, problem_hyperrect_small)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

# Deep MNIST network
mnist_large = read_nnet("$at/../examples/networks/mnist_large.nnet")

# entry 23 in MNIST datset
input_center = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,121,254,136,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,230,253,248,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,118,253,253,225,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,61,253,253,253,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,206,253,253,186,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,211,253,253,239,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,253,253,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142,255,253,186,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,149,229,254,207,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,54,229,253,254,105,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,152,254,254,213,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,251,253,253,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,212,253,250,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,214,253,253,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,253,253,253,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,253,253,189,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,253,253,84,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,235,253,126,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,99,248,253,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,225,235,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
output_center = [131.8781755,134.8987015,141.6166255,158.34307,129.8803525,104.8286425,98.64196,133.6358395,131.1716215,105.10621]

in_epsilon = 1 #0-255
out_epsilon = 10 #logit domain

input_low = input_center .- in_epsilon
input_high = input_center .+ in_epsilon

output_low = output_center .- out_epsilon
output_high = output_center .+ out_epsilon

inputSet = Hyperrectangle(low=input_low, high=input_high)
outputSet = Hyperrectangle(low=output_low, high=output_high)

print("\n\n################## Deep ##################\n")
problem_hyperrect_deep = Problem(mnist_large, inputSet, outputSet)
solver_reluVal = ReluVal(max_iter = 2)
print("\nReluval - Deep")
timed_result = @timed solve(solver_reluVal, problem_hyperrect_deep)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

print("\nPlanet - Deep")
optimizer = GLPKSolverMIP()
solver = Planet(optimizer)
timed_result = @timed solve(solver, problem_hyperrect_deep)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

print("\nReluplex - Deep")
solver=Reluplex()
#timed_result = @timed solve(solver, problem_hyperrect_deep)
#t = timed_result[2]
#out = timed_result[1]
#print(" - Time: $t s - Output: $out")

# Wide MNIST network

mnist_wide = read_nnet("$at/../examples/networks/mnist-1-100.nnet")
input_center = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,121,254,136,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,230,253,248,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,118,253,253,225,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,61,253,253,253,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,206,253,253,186,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,211,253,253,239,69,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,253,253,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,142,255,253,186,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,149,229,254,207,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,54,229,253,254,105,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,152,254,254,213,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,251,253,253,26,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,212,253,250,149,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,214,253,253,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,253,253,253,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,253,253,189,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,253,253,84,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,235,253,126,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,99,248,253,119,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,225,235,49,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
output_center = [-1219.6781446362043,2713.019467032775,-3826.9579099907123,-3306.084501236369,2641.9250628243003,-1829.0475478735848,-2477.0765438475523,84.03007306734946,-2072.196780418711,145.0876643197917]

in_epsilon = 1 #0-255
out_epsilon = 10 #logit domain

input_low = input_center .- in_epsilon
input_high = input_center .+ in_epsilon

output_low = output_center .- out_epsilon
output_high = output_center .+ out_epsilon

inputSet = Hyperrectangle(low=input_low, high=input_high)
outputSet = Hyperrectangle(low=output_low, high=output_high)

print("\n\n################## Wide ##################\n")

problem_hyperrect_wide = Problem(mnist_wide, inputSet, outputSet)
solver_reluVal = ReluVal(max_iter = 2)
print("\nReluval - Wide")
timed_result = @timed solve(solver_reluVal, problem_hyperrect_wide)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

print("\nPlanet - Wide")
optimizer = GLPKSolverMIP()
solver = Planet(optimizer)
timed_result = @timed solve(solver, problem_hyperrect_wide)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

print("\nReluplex - Wide")
solver=Reluplex()
#timed_result = @timed solve(solver, problem_hyperrect_wide)
#t = timed_result[2]
#out = timed_result[1]
#print(" - Time: $t s - Output: $out")

print("\n\n################## Acas ##################\n")

acas_nnet = read_nnet("$at/../examples/networks/ACASXU_run2a_1_1_tiny_4.nnet")

b_upper = [0.58819589, 0.4999999 , -0.4999999, 0.52838384, 0.4]
b_lower = [0.21466922, 0.11140846, -0.4999999, 0.52838384, 0.4]

inputSet = Hyperrectangle(low=b_lower, high=b_upper)

out_lower = [-0.02088955, -0.01863226,  0.01521364, -0.01918539, 0.01563704]
out_upper = [-0.02077696, -0.01852366,  0.01523927, -0.01912529, 0.015789]

outputSet = Hyperrectangle(low=out_lower, high=out_upper)

# TODO: compute output region - need to do some forward passes in the network
# to get a sense of the output ranges.

problem_hyperrectangle_hyperrectangle_acas = Problem(acas_nnet, inputSet, outputSet)
solver_reluVal = ReluVal(max_iter = 2)
print("\nReluval - Acas")
timed_result = @timed solve(solver_reluVal, problem_hyperrectangle_hyperrectangle_acas)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

print("\nPlanet - Acas")
optimizer = GLPKSolverMIP()
solver = Planet(optimizer)
timed_result = @timed solve(solver, problem_hyperrectangle_hyperrectangle_acas)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")

print("\nReluplex - Acas")
solver=Reluplex()
timed_result = @timed solve(solver, problem_hyperrectangle_hyperrectangle_acas)
t = timed_result[2]
out = timed_result[1]
print(" - Time: $t s - Output: $out")