### A Pluto.jl notebook ###
# v0.14.7

using Markdown
using InteractiveUtils

# ╔═╡ 08af84c0-2456-11eb-326c-2d675ca197af
using SpectralVis

# ╔═╡ 57a60da0-2461-11eb-07df-2b3ab48a6675
using Colors

# ╔═╡ 9ff8ea50-2461-11eb-3143-d1b38243f297
using Plots; plotly()

# ╔═╡ 14f2d870-245d-11eb-1fe3-779aa49070f1
using SSpline

# ╔═╡ f07cef7e-014a-4d59-8e31-4d394391adda
begin
	#import DarkMode
    #DarkMode.enable(theme="material-palenight", cm_config=Dict("tabSize" => 4))
	#DarkMode.enable()
    #DarkMode.Toolbox(theme="default")
	html"""<style> main {max-width: 900px;}"""
end

# ╔═╡ 78adafde-247e-11eb-12e1-ddea99d50711
md"# A quick tour of the `SpectralVis` and `SSpline` packages"

# ╔═╡ a8bb86de-29be-474d-94e6-1a03f9afe70f
md"## First, let’s get the basic stuff out of the way:"

# ╔═╡ f444ebf0-2460-11eb-2ed3-c5db8e304e95
md"We need to get a few packages to deal with spectra"

# ╔═╡ 1cb25910-2461-11eb-1f85-a9ed9362e326
md"**SpectralVis** contains (almost) everything you need for spectral calculations, spectral data, etc."

# ╔═╡ 5dacb780-2461-11eb-277d-e7226385a5ec
md"The **Colors** package is also needed"

# ╔═╡ 79763630-2461-11eb-0b0d-15b00898c512
md"We also would like to do some plots, so we also use the **Plots** package with the **PyPlot** backend."

# ╔═╡ 0d1268b0-2461-11eb-15a9-87ca552b10c5
md"Then we need the spectral spline package **SSpline**, which is a specialized spline package for spectral calculations. Apart from specialized functionality, it’s also faster than the **Interpolations** package."

# ╔═╡ 1afb7e20-245d-11eb-1ca8-9da92491cd50
md"Now we’re ready to have some fun!"

# ╔═╡ d04f4ee1-2609-4355-9846-5e155557d709
md"## Spectral environments"

# ╔═╡ 7d8d8c1e-ed75-472f-b08b-a93c29ab588c
md"""
The package `SpectralVis` automatically creates a *spectral environment* which defines the necessary parameters to work with spectra. These parameters are stored in the mutable struct `SPECENV` and can be requested and changed to suit the necessary requirements.

The fields contained in `SPECENV` are:

`λmin`: minimum wavelength, ideally identical with λmin of `cmf`, default: 390.0 nm

`Δλ`: wavelength stepwidth, default: 1.0 nm

`λmax`: maximum wavelength, ideally identical with λmax of `cmf`, default: 830.0 nm

`cmf`: Color matching function (CMF) of type `CMatch`, default: `CIE12_10` (CIE 2012 10° observer)

options: `CIE31` (CIE1931 2° observer), `CIE31_J` (CIE1931 2° observer with Judd-correction), `CIE31_JV` (Judd-Vos-correction, `CIE64` (CIE1964 10° observer), `CIE12_2` (CIE2012 2° observer), `CIE12_10` (CIE2012 10° observer)

`ex`: extrapolation type, with the options `:zero`, `:none`, `:boundary`, `:linear`, `:quadratic` (not yet implemented). Default: `:linear`
"""

# ╔═╡ f5760d98-77e3-4847-bb79-97f45b50b7da
env = SPECENV

# ╔═╡ 0881c790-2462-11eb-05e1-9113e664ec28
md"First, let’s create a light source! We have several options for these."

# ╔═╡ 2f7258b0-2462-11eb-31a2-8da7d9fb1dca
md"One option is to create a black body illuminant. Let’s say our light source is a black body radiator at a temperature of 6500K. We want to create an illuminant in the visual range of wavelength, say between 390 and 830 nm, with a resolution of 1 nm:"

# ╔═╡ 5216c132-2462-11eb-11ef-259ba512e80a
bb6500K = blackbody_illuminant(env,6500)

# ╔═╡ 60284748-a78f-4dfa-9927-18ad85bdf5d0
md"What is the type of the spectrum?"

# ╔═╡ ad2414b2-2462-11eb-3ea5-955a5a918a30
typeof(bb6500K)

# ╔═╡ 27bae2a1-2dc1-4a75-84ed-d329ce62abea
md"`LSpec` stands for Luminance Spectrum. We are dealing with a light source."

# ╔═╡ 437e5e6d-1469-4767-a0a9-8fed5ecf6250
md"Show the wavelengths at which this spectrum is sampled:"

# ╔═╡ d99b2330-2462-11eb-2bee-6b79c56cf0cf
bb6500K.λ

# ╔═╡ 7db83947-d5dd-4a3a-9f63-efe1da7eb26c
md"And the sampled luminance values:"

# ╔═╡ e34ef68e-2462-11eb-394c-5712ac9de6da
bb6500K.l

# ╔═╡ e8969630-2462-11eb-3c02-996a99d88691
md"So, the `LSpec` struct has two fields: λ and l.

`λ` contains the sampled wavelengths in *nm*, `l` contains the sampled *luminance* at the sampled wavelengths. For reflectance spectra of type `RSpec`, these are `λ` and `r` (reflectance)."

# ╔═╡ f24e2d8e-2477-11eb-3a27-dfebdfc43440
md"Let’s plot the spectrum of our light source:"

# ╔═╡ 0c6bf812-2478-11eb-14ea-57c6522324f3
plot(bb6500K.λ,bb6500K.l,labels=false)

# ╔═╡ 2c7f7c80-2478-11eb-1728-a1ab0b5ef106
md"Whoa! The luminance values are crazy high! That’s because of the units `[J/m³/s]`. It’s easy to mitigate that. Let’s normalize this spectrum to a peak value of 1.0. To normalize a spectrum, use the `normalize_spec` function as follows:"

# ╔═╡ 00a64130-2463-11eb-1ed2-29a671508315
bb65=normalize_spec(bb6500K)

# ╔═╡ 1e781300-2463-11eb-2b3e-ff33d0043b07
md"Let’s have a look at the spectrum in a graph:"

# ╔═╡ 369a82b0-2463-11eb-06c0-a79be68e16a6
plot(bb65.λ,bb65.l,labels=false)

# ╔═╡ 47f2352e-2463-11eb-3976-d77c1624e204
md"That looks much better! It’s convenient and common practice in color metrics to normalize illuminants to a peak value of 1."

# ╔═╡ 60fc2740-247a-11eb-031a-8bef9409f862
md"Another useful method is using a CIE Daylight generator, which creates daylight spectra according to the CIE daylight standard. This mimics the spectral distribution of natural daylight spectra. `D_series_illuminant` produces daylight spectra, like *D65* or *D50*, according to a so-called *correlated color temperature*, also abbreviated as *CCT*. To create an illuminant with a CCT of 6500K, similar to the blackbody radiator, use the `D_series_illuminant` function as follows:"

# ╔═╡ fdfa8220-2462-11eb-0cf8-c188ac491781
d6500=D_series_illuminant(6500)

# ╔═╡ 89f88560-2463-11eb-38be-eb669b15ab60
md"Before we plot this D65 spectrum, let’s normalize it as well. The standard CIE daylight function generates spectra that are normalized to a value 100.0 at a wavelength of 560 nm, which results in spectra with a spectral distribution that mimics the proper relative energy output of different D-series illuminants. For our purposes, let’s normalize the D65 spectrum:"

# ╔═╡ 97598240-2463-11eb-0ec9-0be99eea414d
d65=normalize_spec(d6500)

# ╔═╡ d0a02310-2463-11eb-2788-5dcb718cf9cb
md"Now we can plot both spectra for comparison:"

# ╔═╡ d811596e-2463-11eb-0de0-792feca7ffa0
begin
	plot(bb65.λ, bb65.l, linewidth=2, color= :blue, label="black body at 6500K")
	plot!(d65.λ, d65.l, linewidth=2, color= :gold2, label="D65 daylight generator")
end

# ╔═╡ e8a8a220-2463-11eb-2a70-cd5fbe573691
md"Voilá! Looking good!"

# ╔═╡ fe2f5850-2463-11eb-29a7-93f2db686897
md"But we’d like both spectra to have the *same wavelength range*. And both should also have the *same spectral resolution*. The *d65* spectrum has a resolution of only 5 nm. Let’s change that!

For demonstration purposes, we will first take a short detour to the `SSpline` package. Let’s use it to adapt the resolution and range of the *d65* spectrum.
Later, we’ll see a more convenient way to adapt spectra to arbitrary conditions, resolutions, etc.

Now, let’s do it the `SSpline` way first:"

# ╔═╡ 69e30fb0-2464-11eb-0066-d15aa8c969e3
md"## A quick look at the `SSpline` package"

# ╔═╡ 9b44a9b0-2464-11eb-138b-0d9775344d9f
md"First, we create a spline object. The Daylight illuminant generator creates linearly interpolated values, so we don’t need to bother with any fancy spline interpolations in this case. We only need a linear spline:"

# ╔═╡ c1a3a930-2464-11eb-1bef-996a6b7468f5
d65spl = linearspline(d65.λ,d65.l)

# ╔═╡ c77141b0-2464-11eb-1efa-a73f2a72b3b0
md"As a result, we get an object of type `LinearSpline`. It contains all the spline coefficients at the knots of the spline, and the knots themselves:"

# ╔═╡ c5490aa0-254d-11eb-383c-adb08ba2eedf
typeof(d65spl)

# ╔═╡ 07995c00-2465-11eb-125c-b5a167e0fc90
md"Now we want to interpolate this spline at certain values. For the black body illuminant we used the range `390:1:830`, and these are going to be our new `λ` values for the D65 illuminant as well:"

# ╔═╡ 385b81b0-2465-11eb-151f-878bb717e157
λ=collect(390.0:1.0:830.0)

# ╔═╡ a57b2320-247b-11eb-17d1-9fd1ecce6ed5
md"Now we create the new, interpolated values with the `interp` function. The function returns two vectors, one for the `λ` values, one for the luminance values:"

# ╔═╡ 492e25b0-2465-11eb-36eb-491d2c186e8b
lam,s=interp(d65spl,λ)

# ╔═╡ e54233b0-2465-11eb-1f8d-2f087aa4d31e
md"Now all that’s left is creating a new *Spectrum* type object containing these values. In our case we need to create a luminance spectrum because we want the spectrum to be a light source:"

# ╔═╡ 0c92e630-2466-11eb-20c4-9fe8bede9360
d65f=luminance_spec(lam,s)

# ╔═╡ 1d179140-2466-11eb-2d71-11e9622e355b
md"What does it look like in the graph?"

# ╔═╡ cb75ef1e-2466-11eb-26b4-d58a8167559c
begin
	plot(d65f.λ,d65f.l, linestyle=:dash, labels=false)
	plot!(bb65.λ,bb65.l,linestyle=:solid, labels=false)
end

# ╔═╡ ddff1cc0-2466-11eb-0709-1baba5e3fca6
md"Excellent!"

# ╔═╡ 0e233640-247e-11eb-193a-ed5ab087c968
md"## Back to Spectra"

# ╔═╡ f9023e30-2466-11eb-346f-8380306e6ab3
md"There are other ways to generate spectra. The function `led_spec` simulates the spectrum of a light emitting diode:"

# ╔═╡ b9028080-2473-11eb-3bc2-91cb08806a45
led=led_spec(390,830,1,455,30)

# ╔═╡ dde6edf0-2473-11eb-2671-2b1f152e2ced
md"Creates an LED spectrum in the range 390 to 830 nm, with a wavelength step width of 1 nm. The *peak wavelength* is at 455 nm, and the *half spectral width* (bandwidth at 50% of peak intensity) is 30 nm. Let’s have a look at the spectrum:"

# ╔═╡ 62d4c690-2474-11eb-2665-c724b32a74f4
plot!(led.λ,led.l,linestyle=:solid, labels=false)

# ╔═╡ 7bd22cf0-2474-11eb-0266-8bb6aeb08b46
md"And last, but not least, there is a method to create a special kind of spectrum, the so-called *block spectrum*. This type of spectrum is the theoretical extreme of spectra that can be produced. Physically, these do not exist, but they are very useful, if not essential, in researching e.g. the gamuts, which means the range of all theoretically producible colors of color spaces:"

# ╔═╡ f95f7100-2474-11eb-3072-f1d2fdb0ba57
bspec=block_spec(390,830,1,501,533,1.0,0)

# ╔═╡ 23b4ff10-2475-11eb-3324-176b6bc48b81
plot!(bspec.λ,bspec.r,color=:orange, labels=false)

# ╔═╡ 3c46d260-2475-11eb-0204-c3da17762579
md"As you can see, this kind of spectrum only has the values 0.0 or 1.0. This creates the theoretically most saturated colors of any given bandwidth. Block spectra exist as two different types. **Type 1**, which you can see above, creates the purest possible colors with a single center wavelength. Type 1 block spectra are zero everywhere except in the block defined by min und max values.

### Usage:

`block_spec(λmin,λmax,Δλ,λs,λe,str=1.0,mode=0)`

`λmin, λmax, Δλ`: range and resolution of spectrum

`λs,λe`: range of block peak

`str=1.0`: strength of block, default=1.0. Setting this to other values can be useful in certain cases.

`mode=0`: mode for single peak block spectrum, default = 0
"

# ╔═╡ b3ee6870-247d-11eb-0587-ef4e7989be87
md"Type 1 block spectra create the inverse of type 0 block spectra. They are 1 (or a defined maximum value) everywhere except between the defined min and max values:"

# ╔═╡ bbd38ff0-2475-11eb-30e6-939c1fae753f
bspec0=block_spec(390,830,1,410,570,1.0,1)

# ╔═╡ 0c572270-2476-11eb-0972-89e9f7f99f3d
plot!(bspec0.λ,bspec0.r,color=:black,linestyle=:dot, labels=false)

# ╔═╡ b2a5b300-2505-11eb-1ecc-098dda235b36
md"## Spectral environments, or how to adapt spectral properties"

# ╔═╡ e880d120-255b-11eb-12d4-d945762279a1
md"Let’s define a light source and a reflective surface. We use the spectrum of the Martian sky, as measured by the Opportunity rover, as light source:"

# ╔═╡ 10fbabc0-255c-11eb-0225-fb05d87dd7f2
light = mars_sky_opportunity()

# ╔═╡ 0e81b72c-1f09-4145-bd54-058ae1b7ded9
md"And our reflective surface is the spectrum of the blue patch of the calibration target on the Mars Exploration Rover:"

# ╔═╡ 23f09380-255c-11eb-1d49-89943d828c8c
blue = mer_caltarget(:blue)

# ╔═╡ 3e1e3c80-255c-11eb-23dc-75a55d31786e
begin
	plot(blue.λ,blue.r,color=:blue,label="MER caltarget “blue”")
	plot!(light.λ,light.l,color=:orange,label="Mars Sky Opportunity measurement")
end

# ╔═╡ a9cb5ab0-8b16-11eb-3d10-dfc3953fbe2b
blue

# ╔═╡ f77e81a0-8a72-11eb-1e00-f9266532685f
light

# ╔═╡ 4c3765d0-255c-11eb-3e16-c98892838d0e
md"It’s obvious that both spectra have different ranges and also different and irregular sample points. The surface spectrum even reaches pretty far into the IR region. This can easily be mitigated by adapting both spectra to a set of rules defined by the *spectral environment*.
For that, we need to define a **spectral environment**, which contains all information about the overall valid traits of the spectra we want to work with. This is important to ensure and enable compatibility between spectra etc. which might have different wavelength ranges, resolutions, etc. The function to set up a spectral environment is:

`set_specenv(λmin,Δλ,λmax,extrapolation)`

possible values for extrapolation:

`:zero`, `:constant`, `:linear`, `:quadratic`"

# ╔═╡ 15adccb0-255d-11eb-2b83-c9564c07b62b
md"We want to work in a spectral range that satisfies the latest color matching functions defined by the CIE in 2012, which are defined in a range of 390 nm to 830 nm in steps of 1 nm. Furthermore, for the sake of simplicity, we define that all extrapolated values should be set to zero. Extrapolation is necessary if a spectrum is not defined up to the boundaries (390 / 830 nm)."

# ╔═╡ 1a26c720-255c-11eb-09fe-cb4cd9cafd46
senv=set_specenv();senv.ex=:boundary

# ╔═╡ a2061550-8a70-11eb-1d1c-1b884f18ae48
senv

# ╔═╡ a386f890-255d-11eb-0fae-2326d5d88d34
md"To adapt a spectrum to the environment, use the function

`adapt_spec(spec::Spectrum,specenv::SpecEnvironment,interporder...)`

Valid arguments for `interporder` (order of spline interpolation):

1st order interpolation:

`:linear` for linear splines

2nd order interpolation, not yet implemented:

`:quadratic` for quadratic splines

3rd order interpolation:

`:cubic, :natural` for natural cubic splines (zero curvature at boundaries)

`:cubic, :periodic` for periodic cubic splines (both endpoints must have same value)

`:cubic, :clamped, slope_start, slope_end` for clamped cubic splines with defined slopes at boundaries

`:cubic, :constrained` for cubic constrained splines"

# ╔═╡ 0c5a9a70-255e-11eb-26e0-8f1b434ed385
l=adapt_spec(light,senv,:cubic,:natural)

# ╔═╡ 42a8468e-255e-11eb-0495-5b80974193c4
b=adapt_spec(blue,senv,:cubic,:natural)

# ╔═╡ 59847aa0-255e-11eb-36a6-8f10abd28695
md"Now we can check and compare the original and the adapted versions of the splines. The boxes mark the original irregular spectral samples, the smaller circles mark the resampled spectra as defined in the environment. You can see that for the **blue** spectrum the lower range was extrapolated according to the cubic spline defined by the original knots. Here is a zoomed in version of the two spectra:"

# ╔═╡ cf17c5be-255d-11eb-0cb9-3703acb4ae96
begin
	scatter(blue.λ,blue.r,color=:blue,marker=:square,markersize=6,label="MER caltarget “blue”",legend=:topleft,xlim=(385,430),ylim=(0.1,0.55))
	scatter!(light.λ,light.l,color=:orange,marker=:square,markersize=6,label="Mars Sky Opportunity measurement")
	scatter!(b.λ,b.r,color=:lightblue,linestyle=:dash,markersize=2,label="MER caltarget resampled")
	scatter!(l.λ,l.l,color=:gold,linestyle=:dash,markersize=2,label="Mars Sky Opportunity resampled")
end

# ╔═╡ d5d44030-255f-11eb-1bf2-fbc628979e90
md"Let’s have a look at the opposite end:"

# ╔═╡ c8bd44a0-255f-11eb-06d0-c911969126bf
begin
	scatter(blue.λ,blue.r,color=:blue,marker=:cross,markersize=6,label="MER caltarget “blue”",legend=:bottomleft,xlim=(800,850),ylim=(-0.1,1.0))
	scatter!(light.λ,light.l,color=:orange,marker=:cross,markersize=6,label="Mars Sky Opportunity measurement")
	scatter!(b.λ,b.r,color=:lightblue,linestyle=:dash,markersize=2,label="MER caltarget spec_env")
	scatter!(l.λ,l.l,color=:yellow,linestyle=:dash,markersize=2,label="Mars Sky Opportunity spec_env")
end

# ╔═╡ 9016a8f8-561f-4f0c-bd5e-8e3437a36801
begin
	scatter(blue.λ,blue.r,color=:blue,marker=:square,markersize=3,label="MER caltarget “blue”",legend=:topleft,xlim=(380,840))
	scatter!(light.λ,light.l,color=:orange,marker=:square,markersize=3,label="Mars Sky Opportunity measurement")
	scatter!(b.λ,b.r,color=:lightblue,linestyle=:dash,markersize=1,label="MER caltarget resampled")
	scatter!(l.λ,l.l,color=:gold,linestyle=:dash,markersize=1,label="Mars Sky Opportunity resampled")
end

# ╔═╡ e74f5600-255b-11eb-3a98-718912d58c6c
md"# Spectral operators"

# ╔═╡ d3d88a20-2505-11eb-3fe8-a7183ff72a46
md"We’re actually interested the resulting color and spectrum with a given light source and reflectance. `SpectralVis` contains a few overloaded operators that make this kind of calculation easy. Let’s define a light source and a reflecting surface:"

# ╔═╡ 3d0f5aa0-2506-11eb-3072-3d7840704c25
lightsource = mars_sky_opportunity()

# ╔═╡ 1f18b440-85f3-4e23-bb8d-1c7889c1c7c7
surface = mer_caltarget(:green)

# ╔═╡ c5ff81a0-2506-11eb-305c-8721c7eb5987
green_spl = spline3(surface.λ,surface.r,:natural)

# ╔═╡ 1497e433-4a73-4074-8b7e-228d3f029b50
green_extrap = extrap(green_spl,390:830,:linear)

# ╔═╡ 0b7b056d-0f00-4f82-873f-53825874e76f
green_int = interp(green_extrap,390:1:830)

# ╔═╡ cd636a24-b558-4c33-adca-ece8652b1640
light_spl = spline3(lightsource.λ,lightsource.l,:natural)

# ╔═╡ ad8c32f7-0a8f-4ab0-a088-c688945f96be
light_extrap = extrap(light_spl,390:830,:linear)

# ╔═╡ 9cc68e94-7008-4af2-bd56-fdaa922de29e
light_int = interp(light_spl,390:830)

# ╔═╡ 924c6ba0-2508-11eb-25d9-8fe5faf11bad
begin
	plot(surface.λ,surface.r,color=:green,label="MER caltarget “green”")
	plot!(lightsource.λ,lightsource.l,color=:orange,label="Mars Sky Opportunity measurement")
	scatter!(green_int[1],green_int[2],color=:cyan,markersize=1)
	scatter!(light_int[1],light_int[2],color=:orange,markerkize=2)
end

# ╔═╡ 32c456b0-2ed2-11eb-2c69-f3989cde9b93
surface.λ

# ╔═╡ 3e9c69a0-2ed2-11eb-3dbf-e7a0ecb05d4c
surface.r

# ╔═╡ 67776940-8b22-11eb-0fa2-3de9f03d6679
lightsource.λ[end]

# ╔═╡ af8d15e0-8b22-11eb-31c9-2be22127eb1d
begin
	senv.λmax=840.0
	senv.ex=:boundary
	adapt_spec(lightsource,senv,:cubic,:natural)
end

# ╔═╡ 6126a56a-681f-4cce-b1f5-5cf6138d51bf
senv

# ╔═╡ 899288cb-714a-4399-a9ab-911b2e0c2c37
extrap(linearspline(lightsource.λ,lightsource.l),390.0:890.0,senv.ex)

# ╔═╡ 8a955ace-2e99-11eb-25dd-e9a7242b4f6c
begin
	lightspec=adapt_spec(lightsource,senv,:linear)
	greenspec=adapt_spec(surface,senv,:cubic,:natural)
end

# ╔═╡ 0f23dce0-89d5-11eb-0b0b-7193920bef8b
begin
	plot(lightspec.λ,lightspec.l,color=:red,style=:dashdot,thickness=3,label="Martian sky (light source)")
	plot!(greenspec.λ,greenspec.r,color=:green,label="MER caltarget green (refl)")
	#plot!(refl.λ,refl.r,color=:black,style=:dashdot,label="reflected light")
	#plot!(greenfilt.λ,greenfilt.t)
end

# ╔═╡ afcd7890-89d5-11eb-30db-4f57ca767fef
begin
	refl=*(greenspec,greenspec)
	#greenf=TSpec(greenspec.λ,greenspec.r,.05)
	#greenfilt=greenf*greenf
end

# ╔═╡ 2c64c8da-779e-4a2d-b1fd-cb739b316a17
begin
	→(s::SpectralVis.Spectrum,Δλ) = shift_spec(s,Δλ)
	←(s::SpectralVis.Spectrum,Δλ) = shift_spec(s,-Δλ)
end

# ╔═╡ 66307660-89da-11eb-0576-8d571268e6dd
plot(led.λ,led.l)

# ╔═╡ c5e0d2c5-1f93-4475-b8ae-8a40d35f3db5
begin
	d65s=→(d65,23.3)
	plot(d65s.λ,d65s.l)
	plot!(d65.λ,d65.l)
end

# ╔═╡ 983b7c23-29fb-452d-beba-2aec2b8abe22
cm=cmf(LMS2006_10)

# ╔═╡ 0213f1eb-4cbe-4aa1-b8c1-4a469836723a
begin
	long=cm.l
	mid=cm.m
	short=cm.s
end

# ╔═╡ 70626740-a681-4da3-956c-1ee4c4badcdb
begin
	plot(cm.λ,cm.l,yrange=(0.000001,1.1),color=:red)
	plot!(cm.λ,cm.m,color=:green)
	plot!(cm.λ,cm.s,color=:blue)
end

# ╔═╡ 7fd0091a-640d-4df0-aabe-646c5f8742e0
source=D_series_luminance(6500)

# ╔═╡ 37dac578-8fc2-4c04-87d6-81b4822fc7ed
Macbeth_color_chart

# ╔═╡ 5e6acbc7-f548-4b5b-8774-443a92e06db8
begin
	cc = env.cmf
	#dd = shift_colormatch(cc, :l, -20)
	plot(cc.λ, cc.l, color = :red)
	plot!(cc.λ, cc.m, color = :green)
	plot!(cc.λ, cc.s, color = :blue)
end

# ╔═╡ Cell order:
# ╠═f07cef7e-014a-4d59-8e31-4d394391adda
# ╟─78adafde-247e-11eb-12e1-ddea99d50711
# ╟─a8bb86de-29be-474d-94e6-1a03f9afe70f
# ╟─f444ebf0-2460-11eb-2ed3-c5db8e304e95
# ╟─1cb25910-2461-11eb-1f85-a9ed9362e326
# ╠═08af84c0-2456-11eb-326c-2d675ca197af
# ╟─5dacb780-2461-11eb-277d-e7226385a5ec
# ╠═57a60da0-2461-11eb-07df-2b3ab48a6675
# ╟─79763630-2461-11eb-0b0d-15b00898c512
# ╠═9ff8ea50-2461-11eb-3143-d1b38243f297
# ╟─0d1268b0-2461-11eb-15a9-87ca552b10c5
# ╠═14f2d870-245d-11eb-1fe3-779aa49070f1
# ╟─1afb7e20-245d-11eb-1ca8-9da92491cd50
# ╟─d04f4ee1-2609-4355-9846-5e155557d709
# ╟─7d8d8c1e-ed75-472f-b08b-a93c29ab588c
# ╠═f5760d98-77e3-4847-bb79-97f45b50b7da
# ╟─0881c790-2462-11eb-05e1-9113e664ec28
# ╟─2f7258b0-2462-11eb-31a2-8da7d9fb1dca
# ╠═5216c132-2462-11eb-11ef-259ba512e80a
# ╟─60284748-a78f-4dfa-9927-18ad85bdf5d0
# ╠═ad2414b2-2462-11eb-3ea5-955a5a918a30
# ╟─27bae2a1-2dc1-4a75-84ed-d329ce62abea
# ╟─437e5e6d-1469-4767-a0a9-8fed5ecf6250
# ╠═d99b2330-2462-11eb-2bee-6b79c56cf0cf
# ╟─7db83947-d5dd-4a3a-9f63-efe1da7eb26c
# ╠═e34ef68e-2462-11eb-394c-5712ac9de6da
# ╟─e8969630-2462-11eb-3c02-996a99d88691
# ╟─f24e2d8e-2477-11eb-3a27-dfebdfc43440
# ╠═0c6bf812-2478-11eb-14ea-57c6522324f3
# ╟─2c7f7c80-2478-11eb-1728-a1ab0b5ef106
# ╠═00a64130-2463-11eb-1ed2-29a671508315
# ╟─1e781300-2463-11eb-2b3e-ff33d0043b07
# ╠═369a82b0-2463-11eb-06c0-a79be68e16a6
# ╟─47f2352e-2463-11eb-3976-d77c1624e204
# ╟─60fc2740-247a-11eb-031a-8bef9409f862
# ╠═fdfa8220-2462-11eb-0cf8-c188ac491781
# ╟─89f88560-2463-11eb-38be-eb669b15ab60
# ╠═97598240-2463-11eb-0ec9-0be99eea414d
# ╟─d0a02310-2463-11eb-2788-5dcb718cf9cb
# ╠═d811596e-2463-11eb-0de0-792feca7ffa0
# ╟─e8a8a220-2463-11eb-2a70-cd5fbe573691
# ╟─fe2f5850-2463-11eb-29a7-93f2db686897
# ╟─69e30fb0-2464-11eb-0066-d15aa8c969e3
# ╟─9b44a9b0-2464-11eb-138b-0d9775344d9f
# ╠═c1a3a930-2464-11eb-1bef-996a6b7468f5
# ╟─c77141b0-2464-11eb-1efa-a73f2a72b3b0
# ╠═c5490aa0-254d-11eb-383c-adb08ba2eedf
# ╟─07995c00-2465-11eb-125c-b5a167e0fc90
# ╠═385b81b0-2465-11eb-151f-878bb717e157
# ╟─a57b2320-247b-11eb-17d1-9fd1ecce6ed5
# ╠═492e25b0-2465-11eb-36eb-491d2c186e8b
# ╟─e54233b0-2465-11eb-1f8d-2f087aa4d31e
# ╠═0c92e630-2466-11eb-20c4-9fe8bede9360
# ╟─1d179140-2466-11eb-2d71-11e9622e355b
# ╠═cb75ef1e-2466-11eb-26b4-d58a8167559c
# ╟─ddff1cc0-2466-11eb-0709-1baba5e3fca6
# ╟─0e233640-247e-11eb-193a-ed5ab087c968
# ╟─f9023e30-2466-11eb-346f-8380306e6ab3
# ╠═b9028080-2473-11eb-3bc2-91cb08806a45
# ╟─dde6edf0-2473-11eb-2671-2b1f152e2ced
# ╠═62d4c690-2474-11eb-2665-c724b32a74f4
# ╟─7bd22cf0-2474-11eb-0266-8bb6aeb08b46
# ╠═f95f7100-2474-11eb-3072-f1d2fdb0ba57
# ╠═23b4ff10-2475-11eb-3324-176b6bc48b81
# ╟─3c46d260-2475-11eb-0204-c3da17762579
# ╟─b3ee6870-247d-11eb-0587-ef4e7989be87
# ╠═bbd38ff0-2475-11eb-30e6-939c1fae753f
# ╠═0c572270-2476-11eb-0972-89e9f7f99f3d
# ╟─b2a5b300-2505-11eb-1ecc-098dda235b36
# ╠═e880d120-255b-11eb-12d4-d945762279a1
# ╠═10fbabc0-255c-11eb-0225-fb05d87dd7f2
# ╟─0e81b72c-1f09-4145-bd54-058ae1b7ded9
# ╠═23f09380-255c-11eb-1d49-89943d828c8c
# ╠═3e1e3c80-255c-11eb-23dc-75a55d31786e
# ╠═a9cb5ab0-8b16-11eb-3d10-dfc3953fbe2b
# ╠═f77e81a0-8a72-11eb-1e00-f9266532685f
# ╟─4c3765d0-255c-11eb-3e16-c98892838d0e
# ╟─15adccb0-255d-11eb-2b83-c9564c07b62b
# ╠═1a26c720-255c-11eb-09fe-cb4cd9cafd46
# ╠═a2061550-8a70-11eb-1d1c-1b884f18ae48
# ╟─a386f890-255d-11eb-0fae-2326d5d88d34
# ╠═0c5a9a70-255e-11eb-26e0-8f1b434ed385
# ╠═42a8468e-255e-11eb-0495-5b80974193c4
# ╟─59847aa0-255e-11eb-36a6-8f10abd28695
# ╠═cf17c5be-255d-11eb-0cb9-3703acb4ae96
# ╟─d5d44030-255f-11eb-1bf2-fbc628979e90
# ╠═c8bd44a0-255f-11eb-06d0-c911969126bf
# ╠═9016a8f8-561f-4f0c-bd5e-8e3437a36801
# ╟─e74f5600-255b-11eb-3a98-718912d58c6c
# ╟─d3d88a20-2505-11eb-3fe8-a7183ff72a46
# ╠═3d0f5aa0-2506-11eb-3072-3d7840704c25
# ╠═1f18b440-85f3-4e23-bb8d-1c7889c1c7c7
# ╠═c5ff81a0-2506-11eb-305c-8721c7eb5987
# ╠═1497e433-4a73-4074-8b7e-228d3f029b50
# ╠═0b7b056d-0f00-4f82-873f-53825874e76f
# ╠═cd636a24-b558-4c33-adca-ece8652b1640
# ╠═ad8c32f7-0a8f-4ab0-a088-c688945f96be
# ╠═9cc68e94-7008-4af2-bd56-fdaa922de29e
# ╠═924c6ba0-2508-11eb-25d9-8fe5faf11bad
# ╠═32c456b0-2ed2-11eb-2c69-f3989cde9b93
# ╠═3e9c69a0-2ed2-11eb-3dbf-e7a0ecb05d4c
# ╠═67776940-8b22-11eb-0fa2-3de9f03d6679
# ╠═af8d15e0-8b22-11eb-31c9-2be22127eb1d
# ╠═6126a56a-681f-4cce-b1f5-5cf6138d51bf
# ╠═899288cb-714a-4399-a9ab-911b2e0c2c37
# ╠═8a955ace-2e99-11eb-25dd-e9a7242b4f6c
# ╠═0f23dce0-89d5-11eb-0b0b-7193920bef8b
# ╠═afcd7890-89d5-11eb-30db-4f57ca767fef
# ╠═2c64c8da-779e-4a2d-b1fd-cb739b316a17
# ╠═66307660-89da-11eb-0576-8d571268e6dd
# ╠═c5e0d2c5-1f93-4475-b8ae-8a40d35f3db5
# ╠═983b7c23-29fb-452d-beba-2aec2b8abe22
# ╠═0213f1eb-4cbe-4aa1-b8c1-4a469836723a
# ╠═70626740-a681-4da3-956c-1ee4c4badcdb
# ╠═7fd0091a-640d-4df0-aabe-646c5f8742e0
# ╠═37dac578-8fc2-4c04-87d6-81b4822fc7ed
# ╠═5e6acbc7-f548-4b5b-8774-443a92e06db8


# ---
# title: 437. Path Sum III
# id: problem437
# author: Indigo
# date: 2021-06-21
# difficulty: Medium
# categories: Tree
# link: <https://leetcode.com/problems/path-sum-iii/description/>
# hidden: true
# ---
#
# You are given a binary tree in which each node contains an integer value.
#
# Find the number of paths that sum to a given value.
#
# The path does not need to start or end at the root or a leaf, but it must go
# downwards (traveling only from parent nodes to child nodes).
#
# The tree has no more than 1,000 nodes and the values are in the range
# -1,000,000 to 1,000,000.
#
# **Example:**
#
#
#
#     root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
#
#           10
#          /  \
#         **5**   **-3**
#        **/** **\**    **\**
#       **3**   **2**   **11**
#      / \   **\**
#     3  -2   **1**
#
#     Return 3. The paths that sum to 8 are:
#
#     1.  5 -> 3
#     2.  5 -> 2 -> 1
#     3. -3 -> 11
#
#
#
## @lc code=start
using LeetCode

function tree_path_sum(root::TreeNode{Int}, sm::Int)::Int
    res = 0
    tsum(::Nothing, sm::Int) = nothing
    function tsum(root::TreeNode{Int}, sm::Int)::Nothing
        sm -= root.val
        sm == 0 && (res += 1)
        tsum(root.left, sm)
        tsum(root.right, sm)
    end
    tsum(root, sm)
    res += tree_path_sum(root.left, sm)
    res += tree_path_sum(root.right, sm)
end

tree_path_sum(root::Nothing, sum::Int) = 0
## @lc code=end


module MaternQ
using SparseArrays: SparseMatrixCSC
using LinearAlgebra: Symmetric
using OffsetArrays
export Qmatrix_matern2d

include("matern_values.jl")

""" Directly construct a SparseMatrixCSC with a 2d nearest neighbour structure used by
the sparse Matern precision matrix.
"""
function matern_grid2_CSC(nrow, ncol, nu::Int, f)
    colptr = fill(0, nrow*ncol+1)
    # Upper bound
    nnz = cld((nu*2+3)^2,2)*nrow*ncol
    ii = 1
    col = 1 # result matrix column index
    rowval = Vector{Int}(undef, nnz)
    nzval = Vector{Float64}(undef, nnz)
    li = LinearIndices((1:nrow, 1:ncol))
    for j = 1:ncol
        for i = 1:nrow
            colptr[col] = ii
            col += 1
            for l = -(nu+1):(nu+1)
                for k = -(nu+1):(nu+1)
                    if checkindex(Bool, 1:nrow, i+k) && checkindex(Bool, 1:ncol, j+l) && abs(k) + abs(l) <= nu + 1
                        @assert ii <= nnz
                        I = CartesianIndex(i, j)
                        J = CartesianIndex(i+k, j+l)
                        @inbounds nzval[ii] = f(I, J)
                        @inbounds rowval[ii] = li[J]
                        ii += 1
                    end
                end
            end
        end
    end
    colptr[end] = ii
    nnz = ii-1
    resize!(nzval, nnz)
    resize!(rowval, nnz)
    SparseMatrixCSC(ncol*nrow, ncol*nrow, colptr, rowval, nzval)
end
import GaussianRandomFields
dense_cache_matrix_size = (0,0)
dense_cache_valid = falses(1000)
dense_cache = Vector{Matrix{Float64}}(undef, 1000)
""" Constuct a dense precision matrix by inverting a dense covariance matrix
"""
function Qmatrix_matern2d_dense_cached(size, prec, range, nu::Int)
    global dense_cache_matrix_size, dense_cache, dense_cache_valid
    rangei = round(Int, range*100)
    range = rangei/100.0
    if dense_cache_matrix_size != size
        fill!(dense_cache_valid, false)
        dense_cache_matrix_size = size
    end
    if dense_cache_valid[rangei]
        Q = dense_cache[rangei]
    else
        covf = GaussianRandomFields.CovarianceFunction(2, GaussianRandomFields.Matern(range/4, nu, σ=1.0))
        C = GaussianRandomFields.apply(covf, 1:size[1], 1:size[2])
        Q = inv(C)
        dense_cache[rangei] = Q
        dense_cache_valid[rangei] = true
    end
    Symmetric(Q * prec)
end
function Qmatrix_matern2d_dense(size, prec, range, nu::Int)
    covf = GaussianRandomFields.CovarianceFunction(2, GaussianRandomFields.Matern(range/4, nu))
    C = GaussianRandomFields.apply(covf, 1:size[1], 1:size[2])
    Symmetric(inv(C) * prec)
end

dense = Ref(false)
function Qmatrix_matern2d(size, prec, range, nu::Int)
    dense[] && return Qmatrix_matern2d_dense_cached(size, prec, range, nu)
    nrow, ncol = size
    Q = matern_grid2_CSC(nrow, ncol, nu, Q_matern2d_fun(prec, range, nu))
    Symmetric(Q)
end

end # module


"""

MFGnet.jl -  A Machine Learning Framework for Solving High-Dimensional Mean Field Game and Mean Field Control Problems

A detailed description of the approach implemented here can be found in:

@article{ROLNWF2019,
  title = {A Machine Learning Framework for Solving High-Dimensional Mean Field Game and Mean Field Control Problems},
  year = {2019},
  journal = {arXiv preprint arXiv:1912.01825},
  author = {L. Ruthotto, S. Osher, W. Li, L. Nurbekyan, S. Wu Fung},
  pages = {15 pages}
}
"""
module MFGnet
    using LinearAlgebra
    using Flux, Zygote
    using Printf

    include("F.jl")
    include("G.jl")
    include("layers.jl")
    include("singleLayer.jl")
    include("ResNN.jl")
    include("NN.jl")
    include("odefun.jl")
    include("timeStepping.jl")
    include("linInter1D.jl")
    include("utils.jl")
    include("MFG.jl")
    include("param2vec.jl")
    include("Gaussians.jl")
    include("bfgs.jl")

end  # module MFGnet


using Test

using MathOptInterface
const MOI = MathOptInterface
const MOIT = MathOptInterface.Test
const MOIU = MathOptInterface.Utilities
const MOIB = MathOptInterface.Bridges

include("../utilities.jl")

mock = MOIU.MockOptimizer(MOIU.Model{Float64}())
config = MOIT.TestConfig()

@testset "GeoMean" begin
    bridged_mock = MOIB.Constraint.GeoMean{Float64}(mock)

    MOIT.basic_constraint_tests(
        bridged_mock, config,
        include = [(F, S)
                   for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64},
                             MOI.VectorQuadraticFunction{Float64}]
                   for S in [MOI.GeometricMeanCone]])

    @testset "geomean1test" begin
        mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [ones(4); 2; √2; √2])
        MOIT.geomean1vtest(bridged_mock, config)
        MOIT.geomean1ftest(bridged_mock, config)

        @testset "Test mock model" begin
            var_names = ["t", "x", "y", "z", "x21", "x11", "x12"]
            MOI.set(mock, MOI.VariableName(), MOI.get(mock, MOI.ListOfVariableIndices()), var_names)
            lessthan = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
            @test length(lessthan) == 2
            MOI.set.(mock, MOI.ConstraintName(), lessthan, ["lessthan1", "lessthan2"])
            rsoc = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone}())
            @test length(rsoc) == 3
            MOI.set.(mock, MOI.ConstraintName(), rsoc, ["rsoc21", "rsoc11", "rsoc12"])

            s = """
            variables: t, x, y, z, x11, x12, x21
            lessthan1: t + -0.5 * x21 in MathOptInterface.LessThan(0.0)
            lessthan2: x + y + z in MathOptInterface.LessThan(3.0)
            rsoc11: [1.0x, y, x11] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc12: [z, 0.5 * x21, x12] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc21: [1.0x11, x12, x21] in MathOptInterface.RotatedSecondOrderCone(3)
            maxobjective: t
            """
            model = MOIU.Model{Float64}()
            MOIU.loadfromstring!(model, s)
            MOIU.test_models_equal(mock, model, var_names, ["lessthan1", "lessthan2", "rsoc11", "rsoc12", "rsoc21"])
        end

        @testset "Test bridged model" begin
            var_names = ["t", "x", "y", "z"]
            MOI.set(bridged_mock, MOI.VariableName(), MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names)
            lessthan = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
            @test length(lessthan) == 1
            MOI.set(bridged_mock, MOI.ConstraintName(), lessthan[1], "lessthan")
            geomean = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}())
            @test length(geomean) == 1
            MOI.set(bridged_mock, MOI.ConstraintName(), geomean[1], "geomean")

            s = """
            variables: t, x, y, z
            lessthan: x + y + z in MathOptInterface.LessThan(3.0)
            geomean: [1.0t, x, y, z] in MathOptInterface.GeometricMeanCone(4)
            maxobjective: t
            """
            model = MOIU.Model{Float64}()
            MOIU.loadfromstring!(model, s)
            MOIU.test_models_equal(bridged_mock, model, var_names, ["lessthan", "geomean"])
        end

        # Dual is not yet implemented for GeoMean bridge
        ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}()))
        test_delete_bridge(bridged_mock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0),
                                                (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 1),
                                                (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, 0)))
    end

    @testset "geomean2test" begin
        mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, vcat(ones(10), 4.0, fill(2 * √2, 2), fill(2, 4), fill(√2, 8)))
        MOIT.geomean2vtest(bridged_mock, config)
        MOIT.geomean2ftest(bridged_mock, config)

        @testset "Test mock model" begin
            x_names = ["x$(i)" for i in 1:9]
            l1_names = ["x1$(i)" for i in 1:8]
            l2_names = ["x2$(i)" for i in 1:4]
            l3_names = ["x3$(i)" for i in 1:2]
            var_names = vcat("t", x_names, "x41", l3_names, l2_names, l1_names)
            MOI.set(mock, MOI.VariableName(), MOI.get(mock, MOI.ListOfVariableIndices()), var_names)
            lessthan = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
            @test length(lessthan) == 1
            MOI.set(mock, MOI.ConstraintName(), lessthan[1], "lessthan")
            equalto = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}}())
            @test length(equalto) == 9
            equalto_names = ["equalto$(i)" for i in 1:9]
            MOI.set.(mock, MOI.ConstraintName(), equalto, equalto_names)
            rsoc = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone}())
            @test length(rsoc) == 15
            rsoc_names = vcat("rsoc41", ["rsoc3$(i)" for i in 1:2], ["rsoc2$(i)" for i in 1:4], ["rsoc1$(i)" for i in 1:8])
            MOI.set.(mock, MOI.ConstraintName(), rsoc, rsoc_names)

            varnames_str = vcat("t", [", " * v for v in var_names[2:end]])
            s = """
            variables: $(varnames_str...)
            lessthan: t + -0.25 * x41 in MathOptInterface.LessThan(0.0)
            equalto1: 1.0x1 in MathOptInterface.EqualTo(1.0)
            equalto2: 1.0x2 in MathOptInterface.EqualTo(1.0)
            equalto3: 1.0x3 in MathOptInterface.EqualTo(1.0)
            equalto4: 1.0x4 in MathOptInterface.EqualTo(1.0)
            equalto5: 1.0x5 in MathOptInterface.EqualTo(1.0)
            equalto6: 1.0x6 in MathOptInterface.EqualTo(1.0)
            equalto7: 1.0x7 in MathOptInterface.EqualTo(1.0)
            equalto8: 1.0x8 in MathOptInterface.EqualTo(1.0)
            equalto9: 1.0x9 in MathOptInterface.EqualTo(1.0)
            rsoc41: [1.0x31, x32, x41] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc31: [1.0x21, x22, x31] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc32: [1.0x23, x24, x32] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc21: [1.0x11, x12, x21] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc22: [1.0x13, x14, x22] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc23: [1.0x15, x16, x23] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc24: [1.0x17, x18, x24] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc11: [1.0x1, x2, x11] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc12: [1.0x3, x4, x12] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc13: [1.0x5, x6, x13] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc14: [1.0x7, x8, x14] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc15: [1.0x9, 0.25 * x41, x15] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc16: [0.25 * x41, 0.25 * x41, x16] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc17: [0.25 * x41, 0.25 * x41, x17] in MathOptInterface.RotatedSecondOrderCone(3)
            rsoc18: [0.25 * x41, 0.25 * x41, x18] in MathOptInterface.RotatedSecondOrderCone(3)
            maxobjective: t
            """
            model = MOIU.Model{Float64}()
            MOIU.loadfromstring!(model, s)
            MOIU.test_models_equal(mock, model, var_names, vcat("lessthan", equalto_names, rsoc_names))
        end

        @testset "Test bridged model" begin
            var_names = vcat("t", ["x$(i)" for i in 1:9])
            MOI.set(bridged_mock, MOI.VariableName(), MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names)
            equalto = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}}())
            @test length(equalto) == 9
            equalto_names = ["equalto$(i)" for i in 1:9]
            MOI.set.(bridged_mock, MOI.ConstraintName(), equalto, equalto_names)
            geomean = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}())
            @test length(geomean) == 1
            MOI.set(bridged_mock, MOI.ConstraintName(), geomean[1], "geomean")

            s = """
            variables: t, x1, x2, x3, x4, x5, x6, x7, x8, x9
            equalto1: 1.0x1 in MathOptInterface.EqualTo(1.0)
            equalto2: 1.0x2 in MathOptInterface.EqualTo(1.0)
            equalto3: 1.0x3 in MathOptInterface.EqualTo(1.0)
            equalto4: 1.0x4 in MathOptInterface.EqualTo(1.0)
            equalto5: 1.0x5 in MathOptInterface.EqualTo(1.0)
            equalto6: 1.0x6 in MathOptInterface.EqualTo(1.0)
            equalto7: 1.0x7 in MathOptInterface.EqualTo(1.0)
            equalto8: 1.0x8 in MathOptInterface.EqualTo(1.0)
            equalto9: 1.0x9 in MathOptInterface.EqualTo(1.0)
            geomean: [1.0t, x1, x2, x3, x4, x5, x6, x7, x8, x9] in MathOptInterface.GeometricMeanCone(10)
            maxobjective: t
            """
            model = MOIU.Model{Float64}()
            MOIU.loadfromstring!(model, s)
            MOIU.test_models_equal(bridged_mock, model, var_names, vcat(equalto_names, "geomean"))
        end

        # Dual is not yet implemented for GeoMean bridge
        ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}()))
        test_delete_bridge(bridged_mock, ci, 10, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0),
                                                (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 0),
                                                (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, 0)))
    end

    @testset "geomean3test" begin
        mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, 2 * ones(2))
        MOIT.geomean3vtest(bridged_mock, config)
        MOIT.geomean3ftest(bridged_mock, config)

        @testset "Test mock model" begin
            MOI.set(mock, MOI.VariableName(), MOI.get(mock, MOI.ListOfVariableIndices()), ["t", "x"])
            lessthan = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
            @test length(lessthan) == 2
            MOI.set.(mock, MOI.ConstraintName(), lessthan, ["lessthan1", "lessthan2"])
            rsoc = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone}())
            @test isempty(rsoc)
            nonneg = MOI.get(mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}())
            @test length(nonneg) == 1
            MOI.set(mock, MOI.ConstraintName(), nonneg[1], "nonneg")

            s = """
            variables: t, x
            lessthan1: t + -1.0x in MathOptInterface.LessThan(0.0)
            lessthan2: 1.0x in MathOptInterface.LessThan(2.0)
            nonneg: [1.0x] in MathOptInterface.Nonnegatives(1)
            maxobjective: 2.0 * t
            """
            model = MOIU.Model{Float64}()
            MOIU.loadfromstring!(model, s)
            MOIU.test_models_equal(mock, model, ["t", "x"], ["lessthan1", "lessthan2", "nonneg"])
        end

        @testset "Test bridged model" begin
            MOI.set(bridged_mock, MOI.VariableName(), MOI.get(bridged_mock, MOI.ListOfVariableIndices()), ["t", "x"])
            lessthan = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
            @test length(lessthan) == 1
            MOI.set(bridged_mock, MOI.ConstraintName(), lessthan[1], "lessthan")
            geomean = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}())
            @test length(geomean) == 1
            MOI.set(bridged_mock, MOI.ConstraintName(), geomean[1], "geomean")

            s = """
            variables: t, x
            lessthan: 1.0x in MathOptInterface.LessThan(2.0)
            geomean: [1.0t, x] in MathOptInterface.GeometricMeanCone(2)
            maxobjective: 2.0 * t
            """
            model = MOIU.Model{Float64}()
            MOIU.loadfromstring!(model, s)
            MOIU.test_models_equal(bridged_mock, model, ["t", "x"], ["lessthan", "geomean"])
        end

        # Dual is not yet implemented for GeoMean bridge
        ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}()))
        test_delete_bridge(bridged_mock, ci, 2, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0),
                                                (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 1),
                                                (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives, 0)))
    end

end


include("SortSparse.jl")
include("KoLesky.jl")
include("Cholesky.jl")
include("CovFuncs.jl")
using LinearAlgebra
using RecursiveArrayTools
using Distributions
using Distances
using Random
using Profile
using JLD
using MAT
using IterativeSolvers
using BlockArrays
Random.seed!(123)

# Creating uniform random data
NTrain = 30000
d =  2
NTest = 50
N = NTest + NTrain
x = rand(d, N)
setSplit = randperm(N)
trainInds = setSplit[1:NTrain]
testInds = setSplit[(NTrain + 1) : end]
xTrain = x[ :, trainInds ]
xTest = x[:, testInds ]
x = hcat(xTrain, xTest)

# # Creating gridded data
# n = 20
# h = 1 / (n - 1)
# xTrain = Matrix(VectorOfArray([[i * h; j * h] for i in 1:n, j in 1:n][:]))
# NTrain = size(xTrain,2)
# NTest = 50
# xTest = rand(2, NTest)
# N = NTrain + NTest
# x = hcat(xTrain, xTest) # .+ 0.5 * (1/sqrt(N)) * randn(2, N)

ρ = 3.0
λ = 1.5

# reordering points and forming skeletons
@time colptr, rowval, P, revP, distances = sortSparseRev(xTrain, xTest, ρ, 1)
x = x[:, P]

LTest = SparseMatrixCSC(N, N, colptr, rowval, ones(length(rowval)))
LTest22 = LTest[(NTest + 1) : end, (NTest + 1) : end]

xTest = x[:, 1:NTest]
xTrain = x[:, (NTest + 1) : end]
skeletons = construct_skeletons(colptr, rowval, distances, λ)

l = 1.1
σ = vcat(zeros(NTest), fill(1.0, NTrain))
# σ = vcat(zeros(NTest), 0.1 * exp.(randn(NTrain)) .+ 1e-4)
σ = PseudoBlockArray(σ, [NTest, NTrain])
# covfunc = r -> exp(-r/l)
# covfunc = r -> matern12!(r,l)
function covfunc!(r)
  matern12!(r,l)
end

# Creating
Θ = covfunc!(pairwise(Euclidean(), x, dims=2))
L_Θ = cholesky(Matrix(Θ))
y = L_Θ.L * randn(N)
Θ = Θ + Diagonal(σ)

# Creating data
Θ = PseudoBlockArray(Θ, [NTest, NTrain], [NTest, NTrain])
y = PseudoBlockArray(y, [NTest, NTrain])
y[Block(2)] .+= Diagonal(sqrt.(σ[Block(2)])) * randn(NTrain)

L = assembleL(skeletons, x, covfunc!)
LNaive = assembleL(skeletons, x, covfunc!, σ)
L22 = L[(NTest + 1) : end, (NTest + 1) : end]
A22 = L22 * L22' + Diagonal(1 ./ max.(σ[Block(2)], 1e-30))
# U22 = sparse(triu(A22))
U22 = squareSparse(L22)+ Diagonal(1 ./ max.(σ[Block(2)], 1e-30))

icholU_high_level!(U22)
# U22 = UpperTriangular(U22)
U22Exct = cholesky(Matrix(A22)).U

# The exact posterior mean
μFull = Θ[Block(1,2)] * (Θ[Block(2,2)] \ y[Block(2)])
# display(μFull)

# The vecchia posterior mean
# z = (Diagonal(σ[Block(2)]) \ (  U22 \ (U22' \ (L22 * (L22' * y[Block(2)])))))
noisecov = NoiseCov(L22, U22, σ[Block(2)])
# z = (L22 * (L22' * y[Block(2)]))
# z = cg(A22 , z, Pl = IChol(U22',U22), verbose=true, maxiter=10, tol=1e-16)
# z = (Diagonal(σ[Block(2)]))\ z

z = noisecov \ (y[Block(2)])

μVechia = (L' \ (L \ (vcat(zero(y[Block(1)]), z))))[1:NTest]
# display(μVechia)
@show mean(abs.(μFull - μVechia))
@show median(abs.(μFull - μVechia))

# The vecchia posterior mean
# z = (Diagonal(σ[Block(2)]) \ ( U22Exct \ (U22Exct' \ (L22 * (L22' * y[Block(2)])))))
z = (L22 * (L22' * y[Block(2)]))
z = cg(A22 , z, Pl = IChol(U22Exct',U22Exct), verbose=true, maxiter=10)
z = (Diagonal(σ[Block(2)]))\ z

μVechiaExct = (L' \ (L \ (vcat(zero(y[Block(1)]), z))))[1:NTest]
# display(μVechiaExct)
@show mean(abs.(μFull - μVechiaExct))
@show median(abs.(μFull - μVechiaExct))

# The naive application of vecchia
μNaive = -LNaive[1:NTest, 1:NTest]' \ (LNaive[(NTest + 1) : end, 1 : NTest]' * y[Block(2)])
# display(μNaive)
@show mean(abs.(μFull - μNaive))
@show median(abs.(μFull - μNaive))

# # Direct application of Vecchia
# # μ = Θ[Block(1,2)] * (L * (L' * y[Block(2)]))
#
# # Version with Cholesky factor
# μ = Θ[Block(1,2)] * (σ \ (  U \ (U' \ (L * (L' * y[Block(2)])))))
#
# display(μTrue)
# display(μ)
# display(y[Block(1)])
#
# @show norm(μTrue - μ) / norm(μTrue)
#
#


"""
    equal_weights(returns::AbstractMatrix)::AbstractVector
```math
    w_i = 1/N \\quad  i \\in [1, \\hdots, N]
```
External links
* DeMiguel, V., Garlappi, L., & Uppal, R. (2009).
  Optimal versus naive diversification: How inefficient is the 1/N portfolio strategy?.
  The review of Financial studies, 22(5), 1915-1953.
  doi: [10.1093/hhm075](https://doi.org/10.1093/rfs/hhm075)
"""
function equal_weights(returns::AbstractMatrix)::AbstractVector
    """
    Input: Returns over a specific period.
    Output: The weights of equal weights portfolio allocation.
    """
    num_assets = size(returns)[2]
    weights = fill(1/num_assets, (num_assets))
    return weights
end

"""
long_only_minimum_variance
    ```math
    \\textrm{minimize}_{\\frac{\\vec{w}^T \\vec{\\sigma}}{\\sqrt{\\vec{w}^T \\Sigma \\vec{w}}}, \\\\
    \\textrm{subject to} \\quad \\vec{1}^T \\vec{w} = 1 \\quad \\vec{w} \\geq 0.
```
External links
* DeMiguel, V., Garlappi, L., & Uppal, R. (2009).
  Optimal versus naive diversification: How inefficient is the 1/N portfolio strategy?.
  The review of Financial studies, 22(5), 1915-1953.
  doi: [10.1093/hhm075](https://doi.org/10.1093/rfs/hhm075)
"""
function long_only_minimum_variance(returns::AbstractMatrix)::AbstractVector
    cov_matrix = cov(returns)
    portfolio = Model(Ipopt.Optimizer)
    set_silent(portfolio) # Suppress the output
    num_assets = size(cov_matrix)[1]
    @variable(portfolio, x[1:num_assets] >= 0)
    @objective(portfolio, Min, x' * cov_matrix * x)
    @constraint(portfolio, sum(x[i] for i = 1:num_assets) == 1)
    optimize!(portfolio)
    weights = value.(x)
    return weights
end
"""
    random_weights
```math
f(x) = \\frac{1}{\\pi \\sqrt{(x - a) (b - x)}}, \\quad x \\in [a, b]
```
"""
function random_weights(returns::AbstractMatrix)::AbstractVector
    """
    Input: Returns over a specific period.
    Output: Randomized weights of a portfolio.
    """
    num_assets = size(returns)[2]
    weights = rand(num_assets)
    norm = sum(weights)
    weights = weights ./ norm
    return weights
end
"""
    mdp
```math
    \\textrm{maximize}_{\\vec{w}} \\quad  \\frac{\\vec{w}^T \\vec{\\sigma}}{\\sqrt{\\vec{w}^T \\Sigma \\vec{w}}}, \\\\
    \\textrm{subject to} \\quad \\vec{1}^T \\vec{w} = 1.
```
External links
* Choueifaty, Y., Froidure, T., & Reynier, J. (2013).
  Properties of the most diversified portfolio.
  Journal of investment strategies, 2(2), 49-70.
  doi: [10.2139/ssrn.1895459](https://dx.doi.org/10.2139/ssrn.1895459)
"""
function mdp(returns::AbstractMatrix)::AbstractVector
    cov_matrix = cov(returns)
    var_vector = var(returns)
    portfolio = Model(Ipopt.Optimizer)
    set_silent(portfolio) # Suppress the output
    num_assets = size(cov_matrix)[1]
    @variable(portfolio, x[1:num_assets] >= 0)
    @objective(portfolio, Min, 0.5 * x' * cov_matrix * x)
    @constraint(portfolio, sum(x[i] * var_vector[i] for i = 1:num_assets) == 1)
    optimize!(portfolio)
    weights = value.(x)
    weights = weights ./ sum(weights)
    return weights
end

function inverse_variance(returns::AbstractMatrix)::AbstractVector
    return 0
end
function inverse_volatility(returns::AbstractMatrix)::AbstractVector
    return 0
end


using TrafficAssignment
using Base.Test

ta_data = load_ta_network("Anaheim")
ta_data = load_ta_network("Barcelona")
ta_data = load_ta_network("Chicago Sketch")
ta_data = load_ta_network("Winnipeg")

data_time = time()
ta_data = load_ta_network("Sioux Falls")

println("Data Loading Completed, time:", time() - data_time, " seconds")

link_flow, link_travel_time, objective = ta_frank_wolfe(ta_data, method=:bfw, step=:exact, log=:on, tol=1e-3, max_iter_no=5)

link_flow, link_travel_time, objective = ta_frank_wolfe(ta_data, method=:cfw, step=:newton, log=:on, tol=1e-3, max_iter_no=5)

link_flow, link_travel_time, objective = ta_frank_wolfe(ta_data, method=:fw, step=:exact, log=:on, tol=1e-3, max_iter_no=5)
@assert abs( objective - 4.963799502172654e6 ) < 1e6


import DSP: filt, filtfilt, resample

export fir, removedc, removedc!, demon
export upconvert, downconvert, rrcosfir, rcosfir
export mseq, gmseq, circconv, goertzel, pll
export sfilt, sfiltfilt, sresample, mfilter
export istft, whiten

"""
$(SIGNATURES)
Designs a `n`-tap FIR filter with a passband from `f1` to `f2` using the
specified `method`. If frame rate `fs` is not specified, `f1` and `f2` are given
in normalized units (1.0 being Nyquist). If `f1` is 0, the designed filter is
a lowpass filter, and if `f2` is `nothing` then it is a highpass filter.

This method is a convenience wrapper around `DSP.digitalfilter`.

# Examples:
```julia-repl
julia> lpf = fir(127, 0, 10kHz; fs=44.1kHz)   # design a lowpass filter
127-element Array{Float64,1}:
  ⋮

julia> hpf = fir(127, 10kHz; fs=44.1kHz)      # design a highpass filter
127-element Array{Float64,1}:
  ⋮

julia> bpf = fir(127, 1kHz, 5kHz; fs=44.1kHz) # design a bandpass filter
127-element Array{Float64,1}:
  ⋮
```
"""
function fir(n, f1, f2=nothing; fs=2.0, method=FIRWindow(hanning(n)))
  fs = inHz(fs)
  if f1 == 0
    f = Lowpass(inHz(f2); fs=fs)
  elseif f2 == nothing || inHz(f2) == fs/2
    f = Highpass(inHz(f1); fs=fs)
  else
    f = Bandpass(inHz(f1), inHz(f2); fs=fs)
  end
  return digitalfilter(f, method)
end

"""
$(SIGNATURES)
DC removal filter. Parameter `α` controls the cutoff frequency. Implementation
based on Lyons 2011 (3rd ed) real-time DC removal filter in Fig. 13-62(d).

See also: [`removedc`](@ref)
"""
function removedc!(s; α=0.95)
  for k = 1:size(s,2)
    for j = 2:size(s,1)
      s[j,k] += α*s[j-1,k]
    end
    s[2:end,k] .+= -s[1:end-1,k]
  end
  s *= sqrt(α)
  return s
end

"""
$(SIGNATURES)
DC removal filter. Parameter `α` controls the cutoff frequency. Implementation
based on Lyons 2011 (3rd ed) real-time DC removal filter in Fig. 13-62(d).

See also: [`removedc!`](@ref)
"""
removedc(s; α=0.95) = removedc!(copy(s); α=α)

"""
$(SIGNATURES)
Estimates DEMON spectrum. The output is highpass filtered with a `cutoff`
frequency and downsampled. Supported downsampling methods are `:rms` (default),
`:mean` and `:fir`.
"""
function demon(x; fs=framerate(x), downsample=250, method=:rms, cutoff=1.0)
  local y
  fs /= downsample
  for k = 1:size(x,2)
    if downsample == 1
      y1 = abs.(hilbert(x[:,k]))
    elseif method == :rms
      y1 = sqrt.(mean.(Periodograms.arraysplit(abs2.(hilbert(x[:,k])), downsample, 0)))
    elseif method == :mean
      y1 = mean.(Periodograms.arraysplit(abs.(hilbert(x[:,k])), downsample, 0))
    elseif method == :fir
      aaf = fir(127, 0, 0.48fs; fs=fs)
      y1 = filtfilt(aaf, abs.(hilbert(x[:,k])))[1:downsample:end]
    else
      throw(ArgumentError("Unknown method"))
    end
    if k == 1
      y = zeros(length(y1), size(x,2))
    end
    y[:,k] .= y1
  end
  maxflen = length(y)÷4
  mod(maxflen, 2) == 0 && (maxflen += 1)
  hpf = fir(min(127, maxflen), cutoff; fs=fs)
  signal(filtfilt(hpf, y), fs)
end

function demon(x::AbstractVector{T}; fs=framerate(x), downsample=250, method=:rms, cutoff=1.0) where T
  y = @view samples(x)[:,1:1]
  z = demon(y, fs=fs, downsample=downsample, method=method, cutoff=cutoff)
  signal(dropdims(samples(z), dims=2), framerate(z))
end

"""
$(SIGNATURES)
Converts baseband signal with `sps` symbols per passband sample to a real
passband signal centered around carrier frequency `fc`.
"""
function upconvert(s::AbstractVector, sps, fc, pulseshape=rrcosfir(0.25, sps); fs=framerate(s))
  pad = cld(length(pulseshape), 2*sps) - 1
  s = vcat(zeros(pad), analytic(s), zeros(pad))
  s = signal(resample(s, sps, pulseshape), sps*fs)
  √2 * real.(s .* cis.(2π * inHz(fc) * domain(s)))
end

function upconvert(s::AbstractMatrix, sps, fc, pulseshape=rrcosfir(0.25, sps); fs=framerate(s))
  out = Any[]
  for j in 1:nchannels(s)
    push!(out, upconvert(s[:,j], sps, fc, pulseshape; fs=fs))
  end
  hcat(out...)
end

"""
$(SIGNATURES)
Converts passband signal centered around carrier frequency `fc` to baseband,
and downsamples it by a factor of `sps`. If the `pulseshape` is specified to
be `nothing`, downsampling is performed without filtering.
"""
function downconvert(s::AbstractVector, sps, fc, pulseshape=rrcosfir(0.25, sps); fs=framerate(s))
  s = signal(analytic(s), fs)
  s .*= cis.(-2π * inHz(fc) * domain(s))
  sps == 1 && return signal(s, fs)
  pulseshape == nothing && return signal(s[1:sps:end,:], fs/sps)
  signal(resample(s, 1//sps, pulseshape), fs/sps)
end

function downconvert(s::AbstractMatrix, sps, fc, pulseshape=rrcosfir(0.25, sps); fs=framerate(s))
  out = Any[]
  for j in 1:nchannels(s)
    push!(out, downconvert(s[:,j], sps, fc, pulseshape; fs=fs))
  end
  hcat(out...)
end

"""
$(SIGNATURES)
Root-raised cosine filter.
"""
function rrcosfir(β, sps, span = β < 0.68 ? 33-floor(Int, 44β) : 4)
  # default span based on http://www.commsys.isy.liu.se/TSKS04/lectures/3/MichaelZoltowski_SquareRootRaisedCosine.pdf
  delay = fld(span*sps, 2)
  t = collect(-delay:delay)/sps
  h = Array{Float64}(undef, size(t))
  for i ∈ 1:length(t)
    if t[i] == 0
      h[i] = (1 + β*(4/π - 1))/sps
    elseif abs(t[i]) == 1/(4β)
      h[i] = β/(√2*sps) * ((1+2/pi)*sin(π/(4β)) + (1-2/pi)*cos(π/(4β)))
    else
      h[i] = (sin(π*t[i]*(1-β)) + 4β*t[i]*cos(π*t[i]*(1+β))) / (π*t[i]*(1 - (4β*t[i])^2)) / sps
    end
  end
  h / √sum(h.^2)
end

"""
$(SIGNATURES)
Raised cosine filter.
"""
function rcosfir(β, sps, span = β < 0.68 ? 33-floor(Int, 44β) : 4)
  # default span based on http://www.commsys.isy.liu.se/TSKS04/lectures/3/MichaelZoltowski_SquareRootRaisedCosine.pdf
  # since the span is for rrcosfir, for rcosfir, it is very conservative
  delay = fld(span*sps, 2)
  t = collect(-delay:delay)/sps
  h = Array{Float64}(undef, size(t))
  for i ∈ 1:length(t)
    if abs(t[i]) == 1/(2β)
      h[i] = π/(4sps) * sinc(1/(2β))
    else
      h[i] = sinc(t[i]) * cos(π * β * t[i]) / (1-(2β * t[i])^2) / sps
    end
  end
  h / √sum(h.^2)
end

"""
$(SIGNATURES)
Generates an m-sequence of length `2^m-1` or tap specification `m`.

m-sequences are sequences of `+1/-1` values with near-perfect discrete periodic
auto-correlation properties. All non-zero lag periodic auto-correlations
are -1. The zero-lag autocorrelation is `2^m-1`, where `m` is the shift register
length.

This function currently supports shift register lengths between 2 and 30.

# Examples:
```julia-repl
julia> x = mseq(3)                  # generate regular m-sequence
7-element Array{Float64,1}:
  1.0
  1.0
  1.0
 -1.0
  1.0
 -1.0
 -1.0

julia> x = mseq((1,3))              # generate m-sequence with specification (1,3)
7-element Array{Float64,1}:
  1.0
  1.0
  1.0
 -1.0
  1.0
 -1.0
 -1.0
```
"""
function mseq(m, θ=π/2)
  knownspecs = Dict(  # known m-sequences are specified as base 1 taps
       2 => (1,2),          3 => (1,3),          4 => (1,4),          5 => (2,5),
       6 => (1,6),          7 => (1,7),          8 => (1,2,7,8),      9 => (4,9),
      10 => (3,10),        11 => (9,11),        12 => (6,8,11,12),   13 => (9,10,12,13),
      14 => (4,8,13,14),   15 => (14,15),       16 => (4,13,15,16),  17 => (14,17),
      18 => (11,18),       19 => (14,17,18,19), 20 => (17,20),       21 => (19,21),
      22 => (21,22),       23 => (18,23),       24 => (17,22,23,24), 25 => (22,25),
      26 => (20,24,25,26), 27 => (22,25,26,27), 28 => (25,28),       29 => (27,29),
      30 => (7,28,29,30)
  )
  if m ∈ keys(knownspecs)
    spec = collect(knownspecs[m])
  else
    spec = collect(m)
    m = maximum(spec)
  end
  n = 2^m - 1
  reg = ones(UInt8, m)
  x = zeros(Float64, n)
  for j ∈ 1:n
    b = ⊻(reg[spec]...)
    reg = circshift(reg, 1)
    x[j] = 2.0*reg[1] - 1.0
    reg[1] = b
  end
  return x
end

"""
$(SIGNATURES)
Generates an generalized m-sequence of length `2^m-1` or tap specification `m`.

Generalized m-sequences are related to m-sequences but have an additional parameter
`θ`. When `θ = π/2`, generalized m-sequences become normal m-sequences. When
`θ < π/2`, generalized m-sequences contain a DC-component that leads to an exalted
carrier after modulation. When `θ` is `atan(√(2^m-1))`, the m-sequence
is considered to be _period matched_. Period matched m-sequences are complex sequences
with perfect discrete periodic auto-correlation properties, i.e., all non-zero lag
periodic auto-correlations are zero. The zero-lag autocorrelation is `2^m-1`,
where `m` is the shift register length.

This function currently supports shift register lengths between 2 and 30.

# Examples:
```julia-repl
julia> x = gmseq(3)         # generate period matched m-sequence
7-element Array{Complex{Float64},1}:
 0.3535533905932738 + 0.9354143466934853im
 0.3535533905932738 + 0.9354143466934853im
 0.3535533905932738 + 0.9354143466934853im
 0.3535533905932738 - 0.9354143466934853im
 0.3535533905932738 + 0.9354143466934853im
 0.3535533905932738 - 0.9354143466934853im
 0.3535533905932738 - 0.9354143466934853im

julia> x = gmseq(3, π/4)    # generate m-sequence with exalted carrier
7-element Array{Complex{Float64},1}:
 0.7071067811865476 + 0.7071067811865475im
 0.7071067811865476 + 0.7071067811865475im
 0.7071067811865476 + 0.7071067811865475im
 0.7071067811865476 - 0.7071067811865475im
 0.7071067811865476 + 0.7071067811865475im
 0.7071067811865476 - 0.7071067811865475im
 0.7071067811865476 - 0.7071067811865475im
```
"""
function gmseq(m, θ=atan(√(2^maximum(m)-1)))
  x = mseq(m) .+ 0im
  cos(θ) .+ 1im * sin(θ) .* x
end

"""
$(SIGNATURES)
Computes the circular convolution of `x` and `y`. Both vectors must be the same
length.
"""
function circconv(x::AbstractVector, y::AbstractVector=x)
  if length(x) != length(y)
    throw(ArgumentError("x and y must be of equal length"))
  end
  n = length(x)
  z = similar(x)
  for j ∈ 1:n
    z[j] = circshift(x, j-1)' * y
  end
  return z
end

"""
$(SIGNATURES)
Detects frequency `f` in input signal using the Goertzel algorithm.

The detection metric returned by this function is the complex output
of the Goertzel filter at the end of the input block. Typically, you
would want to compare the magnitude of this output with a threshold to
detect a frequency.

When a block size `n` is specified, the Goertzel algorithm in applied to
blocks of data from the original time series.
"""
function goertzel(x::AbstractVector, f, n; fs=framerate(x))
  signal(map(x1 -> goertzel(x1, f; fs=fs), partition(x, n)), fs/n)
end

function goertzel(x::AbstractVector, f; fs=framerate(x))
  n = length(x)
  m = inHz(f)/(inHz(fs)/n)
  w1 = 0
  w2 = 0
  for j ∈ 1:n
    w0 = 2 * cos(2π * m/n) * w1 - w2 + x[j]
    w2 = w1
    w1 = w0
  end
  w0 = 2 * cos(2π * m/n) * w1 - w2
  w0 - cis(-2π * m/n) * w1
end

function goertzel(x::AbstractMatrix, f, n; fs=framerate(x))
  count = cld(size(x,1), n)
  out = Array{ComplexF64}(undef, (count, nchannels(x)))
  for j ∈ 1:nchannels(x)
    out[:,j] = goertzel(x[:,j], f, n; fs=fs)
  end
  signal(out, fs/n)
end

function goertzel(x::AbstractMatrix, f; fs=framerate(x))
  out = Array{ComplexF64}(undef, nchannels(x))
  for j ∈ 1:nchannels(x)
    out[j] = goertzel(x[:,j], f; fs=fs)
  end
  out
end

"""
$(SIGNATURES)
Phased-lock loop to track dominant carrier frequency in input signal.
"""
function pll(x::AbstractVecOrMat, bandwidth=1e-3; fs=framerate(x))
  β = √bandwidth
  n = nchannels(x)
  ϕ = zeros(1,n)
  ω = zeros(1,n)
  y = similar(x, ComplexF64)
  for j ∈ 1:nframes(x)
    y[j,:] = cis.(ϕ)
    Δϕ = angle.(x[j,:] .* conj.(y[j,:])) .* abs.(x[j,:])
    ω .+= bandwidth * Δϕ
    ϕ .+= β*Δϕ .+ ω
  end
  signal(y, fs)
end

"""
$(SIGNATURES)
Same as [`filt`](https://docs.juliadsp.org/stable/filters/#DSP.filt),
but retains sampling rate information.
"""
sfilt(f, x, args...) = signal(filt(f, samples(x), args...), framerate(x))
sfilt(b, a, x, args...) = signal(filt(b, a, samples(x), args...), framerate(x))

"""
$(SIGNATURES)
Same as [`filtfilt`](https://docs.juliadsp.org/stable/filters/#DSP.Filters.filtfilt),
but retains sampling rate information.
"""
sfiltfilt(coef, x) = signal(filtfilt(coef, samples(x)), framerate(x))

"""
$(SIGNATURES)
Same as [`resample`](https://docs.juliadsp.org/stable/filters/#DSP.Filters.resample),
but correctly handles sampling rate conversion.
"""
sresample(x, rate, args...) = signal(resample(samples(x), rate, args...), rate * framerate(x))

"""
$(SIGNATURES)
Matched filter looking for reference signal `r` in signal `s`.
"""
function mfilter(r, s)
  f = conj.(reverse(samples(r)))
  n = length(r) - 1
  sfilt(f, padded(s, (0, n)))[n+1:end]
end

"""
$(SIGNATURES)
Compute the inverse short time Fourier transform (ISTFT) of STFT coefficients `X` which is based
on segments with `nfft` samples with overlap of `noverlap` samples. Refer to `DSP.Periodograms.spectrogram`
for description of the parameters.

For perfect reconstruction, the parameters `nfft`, `noverlap` and `window` in `stft` and
`istft` have to be the same, and the windowing must obey the constraint of "nonzero overlap add" (NOLA).
Implementation based on Zhivomirov 2019 and `istft` in `scipy`.

# Examples:
```julia-repl
julia> x = randn(1024)
1024-element Array{Float64,1}:
 -0.7903319156212055
 -0.564789077302601
  0.8621044972211616
  0.9351928359709288
  ⋮
  2.6158861993992533
  1.2980813993011973
 -0.010592954871694647
julia> X = stft(x, 64, 0)
33×31 Array{Complex{Float64},2}:
  ⋮
julia> x̂ = istft(X; nfft=64, noverlap=0)
1024-element Array{Float64,1}:
 -0.7903319156212054
 -0.5647890773026012
  0.8621044972211612
  0.9351928359709288
  ⋮
  2.6158861993992537
  1.2980813993011973
 -0.010592954871694371
```
"""
function istft(X::AbstractMatrix{Complex{T}};
               nfft::Int,
               noverlap::Int,
               onesided::Bool=true,
               window::Union{Function,AbstractVector,Nothing}=nothing) where {T<:AbstractFloat}
  # H. Zhivomirov, TEM Journal, Vol. 8, No. 1, pp. 56-64, 2019.
  (window === nothing) && (window = rect)
  win, norm2 = Periodograms.compute_window(window, nfft)

  nstep = nfft - noverlap
  nseg = size(X, 2)
  outputlength = nfft + (nseg-1) * nstep

  iX = onesided ? irfft(X, nfft, 1) : ifft(X, 1)
  iX .*= win
  x = zeros(eltype(iX), outputlength)
  normw = zeros(T, outputlength)
  for i = 1:nseg
      x[1+(i-1)*nstep:nfft+(i-1)*nstep] += iX[:,i]
      normw[1+(i-1)*nstep:nfft+(i-1)*nstep] += win .^ 2
  end

  (sum(normw[nfft÷2:end-nfft÷2] .> 1e-10) != length(normw[nfft÷2:end-nfft÷2])) && (
      @warn "NOLA condition failed, STFT may not be invertible")
  x .*= nstep/norm2
end

"""
$(SIGNATURES)
Spectral whitening of input signal `x` in the frequency domain. The parameters `nfft`,
`noverlap` and `window` are required for the computation of STFT coefficients of `x`.
Refer to `DSP.Periodograms.spectrogram` for description of the parameters. `γ` is a
scaling or degree-of-flattening factor. The algorithm is based on Lee 1986.
"""
function whiten(x::AbstractVector;
                nfft::Int,
                noverlap::Int,
                window::Union{Function,AbstractVector,Nothing}=nothing,
                γ=1)
  # M. W. Lee, Open-File Report 86-108, 1986.
  xstft = stft(x, nfft, noverlap; window=window)
  mag = abs.(xstft)
  logmag = log.(mag .+ eps(eltype(mag)))
  logmag .-= γ * mean(logmag; dims=2)
  istft(exp.(logmag) .* exp.(im .* angle.(xstft)); nfft=nfft, noverlap=noverlap, window=window)
end


@testset "basics" begin

	bc = rand(2,3)
	bcde = rand(2,3,4,5);

	@reduce C[c] := sum(b) bc[b,c]
	@test C == vec(sum(bc, dims=1))

	@reduce B[b] := prod(c:3) bc[b,c]
	@test B == vec(prod(bc, dims=2))

	using Statistics

	@reduce W[b⊗d] := Statistics.mean(c,e) bcde[b,c,d,e]
	V = vec(mean(bcde, dims=(2,4)))
	@test V == W

	@reduce Z[e,b,β] := Statistics.std(c,α) bcde[b,c,α⊗β,e]  (α in 1:2)
	@test size(Z) == (5,2,2)

	@reduce A[_,e,_,b] := sum(c) bcde[b,c,3,e]
	@test size(A) == (1,5,1,2)
	B = sum(bcde[:,:,3,:], dims=2)
	@test A[1,3,1,2] == B[2,1,3]
	C = similar(A);
	@reduce C[_,e,_,b] = sum(c) bcde[b,c,3,e]
	@test A ≈ C

	@reduce D[b][c] := sum(d,e) bcde[b,c,d,e] + 1
	@reduce E[b][c] := sum(e,d) bcde[b,c,d,e] + 1
	@reduce F[c][b] := sum(d:4,e:5) bcde[b,c,d,e] + 1 # doesn't warn
    @reduce F′[c][b] := sum(d,e) bcde[b,c,d,e] + 1  d in 1:4, e in 1:5

	@test D[1][2] ≈ sum(bcde, dims=(3,4))[1,2, 1,1] + 4*5
	@test D[1][2] ≈ E[1][2] ≈ F[2][1]
    @test F′ ≈ F

end
@testset "scalar" begin

	bc = rand(2,3)

	@reduce S[] := sum(i,j) bc[j,i]
	T = @reduce sum(i,j) bc[j,i]
	U = @reduce sum(j,i) bc[j,i]
	@test S[] ≈ sum(bc) ≈ T ≈ U

end
@testset "inference" begin

    # inference for a⊗b⊗c had an (Any[]...) dots problem at first
    B = randn(8,24);
    @reduce A[b,c, y,z] := sum(a:2, x:2) B[a⊗b⊗c, x⊗y⊗z]  b in 1:2, y in 1:3
    @test size(A) == (2,2, 3,4)

    C = similar(A)
    @reduce C[b,c, y,z] = sum(a, x) B[a⊗b⊗c, x⊗y⊗z]
    @test C ≈ A

    # with a:2 given, it doesn't try product inference, just leaves a :, which is OK.
    @reduce C[b,c, y,z] = sum(a:2, x) B[a⊗b⊗c, x⊗y⊗z]
    @test C ≈ A

end
@testset "recursion" begin

    # from readme
    A = rand(4);
    B = randn(4,4);
    R = @reduce sum(i) A[i] * log( @reduce _[i] := sum(j) A[j] * exp(B[i,j]) )
    @reduce inner[i] := sum(j) A[j] * exp(B[i,j])
    S = @reduce sum(i) A[i] * log(inner[i])
    @test S == R

    R2 = @reduce sum(i) A[i] * log( @reduce inner[i] = sum(j) A[j] * exp(B[i,j]) )
    @test S == R2

    # scalar result inside
    tot = @reduce sum(i) A[i]
    @cast N[i] := A[i] / tot
    @cast M[i] := A[i] / @reduce sum(i) A[i]
    @test N == M

    # this needs size of the result of inner macro, failed at first:
    A = rand(2,3); B = rand(3,4); C = rand(4,5);
    @reduce W[i,_,l] := sum(j) A[i,j] * (@matmul _[j,l] := sum(k) B[j,k] * C[k,l])
    @test W ≈ reshape(A * B * C, 2,1,5)

end


### Routines for constructing smooth functions that generate field data ###

import Base: *, -, +, show

abstract type AbstractSpatialField end
abstract type AbstractGeneratedField end

#include("motionprofiles.jl")
using SpaceTimeFields
import SpaceTimeFields: Abstract1DProfile, radius, strength

## Empty spatial field

"""
    EmptySpatialField()

Create a blank spatial field. This is primarily useful for initializing a
sum of spatial fields.

# Example
```jldoctest
julia> g = EmptySpatialField()
EmptySpatialField()

julia> g(2,3)
0.0
```
"""
struct EmptySpatialField <: AbstractSpatialField end
(g::EmptySpatialField)(a...) = Float64(0)

## Spatial Gaussian field ##

"""
    SpatialGaussian(σx,σy,x0,y0,A[,derivdir=0])

Set up a spatial field in the form of a Gaussian centered at `x0,y0` with
radii `σx` and `σy` in the respective directions and amplitude `A`. If the
optional parameter `deriv` is set to 1 or 2, then it returns the first
derivative of a Gaussian in that direction (`x` or `y`, respectively).

`SpatialGaussian(σx,σy,x0,y0,A,u,v[,derivdir=0])` generates a Gaussian
that convects at velocity `(u,v)`. It can be evaluated with an additional
argument for time.
"""
struct SpatialGaussian{CT,GX,GY} <: AbstractSpatialField
  gx :: GX
  gy :: GY
  A :: Float64
  u :: Float64
  v :: Float64
  SpatialGaussian(gx::Abstract1DProfile,gy::Abstract1DProfile,A,u,v) = new{true,typeof(gx),typeof(gy)}(gx,gy,A,u,v)
  SpatialGaussian(gx::Abstract1DProfile,gy::Abstract1DProfile,A) = new{false,typeof(gx),typeof(gy)}(gx,gy,A,0.0,0.0)
end

SpatialGaussian(σx::Real,σy::Real,x0::Real,y0::Real,A::Real;deriv::Int=0) =
                _spatialdgaussian(σx,σy,x0,y0,A,Val(deriv))
SpatialGaussian(σx::Real,σy::Real,x0::Real,y0::Real,A::Real,u::Real,v::Real;deriv::Int=0) =
                _spatialdgaussian(σx,σy,x0,y0,A,u,v,Val(deriv))

_spatialdgaussian(σx,σy,x0,y0,A,::Val{0}) = SpatialGaussian(Gaussian(σx,A) >> x0,
                                                            Gaussian(σy,1) >> y0,A)
_spatialdgaussian(σx,σy,x0,y0,A,::Val{1}) = SpatialGaussian(DGaussian(σx,A) >> x0,
                                                            Gaussian(σy,1) >> y0,A)
_spatialdgaussian(σx,σy,x0,y0,A,::Val{2}) = SpatialGaussian(Gaussian(σx,A) >> x0,
                                                            DGaussian(σy,1) >> y0,A)

_spatialdgaussian(σx,σy,x0,y0,A,u,v,::Val{0}) = SpatialGaussian(Gaussian(σx,A) >> x0,
                                                                Gaussian(σy,1) >> y0,A,u,v)
_spatialdgaussian(σx,σy,x0,y0,A,u,v,::Val{1}) = SpatialGaussian(DGaussian(σx,A) >> x0,
                                                                Gaussian(σy,1) >> y0,A,u,v)
_spatialdgaussian(σx,σy,x0,y0,A,u,v,::Val{2}) = SpatialGaussian(Gaussian(σx,A) >> x0,
                                                                DGaussian(σy,1) >> y0,A,u,v)

SpatialGaussian(σ,x0,y0,A;deriv::Int=0) = SpatialGaussian(σ,σ,x0,y0,A,deriv=deriv)

(g::SpatialGaussian{GX,GY})(x,y) where {CT,GX,GY} = g.gx(x)*g.gy(y)
# ignore the time argument if it is called with this...
(g::SpatialGaussian{false,GX,GY})(x,y,t) where {GX,GY} = g(x,y)
(g::SpatialGaussian{true,GX,GY})(x,y,t) where {GX,GY} = g.gx(x-g.u*t)*g.gy(y-g.v*t)

## Scaling spatial fields

struct ScaledField{N <: Real, P <: AbstractSpatialField} <: AbstractSpatialField
    s::N
    p::P
end
function show(io::IO, p::ScaledField)
    print(io, "$(p.s) × ($(p.p))")
end
s::Number * p::AbstractSpatialField = ScaledField(s, p)
-(p::AbstractSpatialField) = ScaledField(-1, p)

(p::ScaledField)(x,y) = p.s*p.p(x,y)
(p::ScaledField)(x,y,t) = p.s*p.p(x,y,t)

## Adding spatial fields together.

struct AddedFields{T <: Tuple} <: AbstractSpatialField
    ps::T
end
function show(io::IO, Σp::AddedFields)
    println(io, "AddedFields:")
    for p in Σp.ps
        println(io, "  $p")
    end
end

"""
    p₁::AbstractSpatialField + p₂::AbstractSpatialField

Add the fields so that `(p₁ + p₂)(x,y) = p₁(x,y) + p₂(x,y)`.

"""
+(p::AbstractSpatialField, Σp::AddedFields) = AddedFields((p, Σp.ps...))
+(Σp::AddedFields, p::AbstractSpatialField) = AddedFields((Σp.ps..., p))
function +(Σp₁::AddedFields, Σp₂::AddedFields)
    AddedFields((Σp₁..., Σp₂...))
end

-(p₁::AbstractSpatialField, p₂::AbstractSpatialField) = p₁ + (-p₂)

+(p::AbstractSpatialField...) = AddedFields(p)

# Evaluate at x,y , assuming that t = 0 for any time-varying member
function (Σp::AddedFields)(x,y)
    f = 0.0
    for p in Σp.ps
        f += p(x,y,0.0)
    end
    f
end

# Evaluate at x,y,t, ignoring t for any constant member
function (Σp::AddedFields)(x,y,t)
    f = 0.0
    for p in Σp.ps
        f += p(x,y,t)
    end
    f
end

## For generating an actual instance of a spatial field
"""
    GeneratedField(d::GridData,field::AbstractSpatialField...,grid::PhysicalGrid)

Create an instance of a spatial field function `field` on scalar grid data `d`,
based on a grid `grid`. After creating the instance `g = GeneratedField(d,field,grid)`,
then the resulting grid data can be accessed by typing `g()`. For vector grid data,
a separate `field` must be supplied for each component.

If the fields are time dependent, then you can also evaluate `g(t)` at the
desired time. The time argument is ignored if the fields are static.
"""
struct GeneratedField{T <: GridData}
    fielddata :: T
    fieldfcns :: Vector{AbstractSpatialField}
    grid :: PhysicalGrid
end

function GeneratedField(d::ScalarGridData,field::AbstractSpatialField,g::PhysicalGrid)
    #xg, yg = coordinates(d,g)
    #tmp = _generatedfield(xg,yg,field,d,0.0)
    tmp = similar(d)
    _generatedfield!(tmp,field,g,0.0)
    GeneratedField{typeof(tmp)}(tmp,AbstractSpatialField[field],g)
end

function GeneratedField(d::CollectedGridData,fields::Vector{AbstractSpatialField},g::PhysicalGrid)
  @assert length(fields) == _numberofcomponents(typeof(d))
  tmp = similar(d)
  _generatedfield!(tmp,fields,g,0.0)
  GeneratedField{typeof(tmp)}(tmp,fields,g)
end

function _generatedfield!(d::ScalarGridData,field::AbstractSpatialField,g::PhysicalGrid,t::Real)
    xg, yg = coordinates(d,g)
    d .= _generatedfield(xg,yg,field,d,t)
    return d
end

_generatedfield!(d::ScalarGridData,fields::Vector{AbstractSpatialField},g::PhysicalGrid,t::Real) =
    _generatedfield!(d,fields[1],g,t)

function _generatedfield!(d::CollectedGridData,fields::Vector{AbstractSpatialField},g::PhysicalGrid,t::Real)
  cnt = 0
  for (i,fname) in enumerate(propertynames(d))
    if typeof(getfield(d,fname)) <: GridData
        cnt += 1
        x, y = coordinates(getfield(d,fname),g)
        getfield(d,fname) .= _generatedfield(x,y,fields[cnt],getfield(d,fname),t)
    end
  end
  return d
end

GeneratedField(d::VectorGridData,
      fieldu::AbstractSpatialField,fieldv::AbstractSpatialField,
      g::PhysicalGrid) = GeneratedField(d,AbstractSpatialField[fieldu,fieldv],g)

_generatedfield(xg,yg,field::AbstractSpatialField,d::ScalarGridData,t) =
          typeof(d)(_evaluatefield(field,xg,yg,t))

_evaluatefield(field,xg,yg,t) = field.(xg*ones(1,length(yg)),ones(length(xg))*yg',t)

datatype(f::GeneratedField{T}) where {T} = T
grid(f::GeneratedField{T}) where {T} = f.grid

(f::GeneratedField{T})() where {T} = f.fielddata

function (f::GeneratedField)(t::Real) where {T<:ScalarGridData}
    _generatedfield!(f.fielddata,f.fieldfcns,f.grid,t)
end

## For generating a transient form of a spatial field
"""
    ModulatedField(g::GeneratedField,modfcn::Abstract1DProfile)

Create a time-modulated form of a generated spatial field, useful for
introducing a forcing field onto the grid. The supplied field `g`
is modulated by a function `modfcn` with a specified profle shape. The
resulting object can be evaluated with a single argument (time) and returns a
`GridData` type object of the same type contained in `g`.
"""
struct ModulatedField
  gfield :: GeneratedField
  modfcn :: Abstract1DProfile
end

(f::ModulatedField)(t) = f.modfcn(t)*f.gfield()

datatype(f::ModulatedField) = datatype(f.gfield)

"""
    PulseField(g::GeneratedField,t0,σ)

Create a transient form of a generated spatial field, useful for
introducing a short-lived forcing field onto the grid. The supplied field `g`
is modulated by a Gaussian pulse centered at time `t0` with width `σ`. This pulse's
maximum is unity, so that the amplitude of the overall field is set by `g`. The
resulting object can be evaluated with a single argument (time) and returns a
`ScalarGridData` type object of the same type contained in `g`.
"""
#=
struct PulseField
  gfield :: GeneratedField
  modfcn :: Abstract1DProfile
end
=#

PulseField(gfield::GeneratedField{T},t0::Real,σt::Real) where {T <: GridData} =
            ModulatedField(gfield,Gaussian(σt,sqrt(π*σt^2)) >> t0)

#(f::PulseField)(t) = f.modfcn(t)*f.gfield()

#datatype(f::PulseField) = datatype(f.gfield)

## Allow all spatial fields to accept a vector argument

#for T in InteractiveUtils.subtypes(AbstractSpatialField)
#    @eval (f::$T)(x::Vector{R}) where {R <: Float64} = f(x...)
#end


# ---
# title: 1343. Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold
# id: problem1343
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Array
# link: <https://leetcode.com/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/description/>
# hidden: true
# ---
#
# Given an array of integers `arr` and two integers `k` and `threshold`.
#
# Return _the number of sub-arrays_ of size `k` and average greater than or
# equal to `threshold`.
#
#
#
# **Example 1:**
#
#
#
#     Input: arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4
#     Output: 3
#     Explanation: Sub-arrays [2,5,5],[5,5,5] and [5,5,8] have averages 4, 5 and 6 respectively. All other sub-arrays of size 3 have averages less than 4 (the threshold).
#
#
# **Example 2:**
#
#
#
#     Input: arr = [1,1,1,1,1], k = 1, threshold = 0
#     Output: 5
#
#
# **Example 3:**
#
#
#
#     Input: arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5
#     Output: 6
#     Explanation: The first 6 sub-arrays of size 3 have averages greater than 5. Note that averages are not integers.
#
#
# **Example 4:**
#
#
#
#     Input: arr = [7,7,7,7,7,7,7], k = 7, threshold = 7
#     Output: 1
#
#
# **Example 5:**
#
#
#
#     Input: arr = [4,4,4,4], k = 4, threshold = 1
#     Output: 1
#
#
#
#
# **Constraints:**
#
#   * `1 <= arr.length <= 10^5`
#   * `1 <= arr[i] <= 10^4`
#   * `1 <= k <= arr.length`
#   * `0 <= threshold <= 10^4`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


include("get_div_grad.jl")

function test_cgs()
  cgs_tol = 1.0e-6

  # Symmetric and positive definite systems (cubic spline matrix).
  n = 10
  A = spdiagm(-1 => ones(n-1), 0 => 4*ones(n), 1 => ones(n-1))
  b = A * [1:n;]
  (x, stats) = cgs(A, b)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("CGS: Relative residual: %8.1e\n", resid)
  @test(resid ≤ cgs_tol)
  @test(stats.solved)

  # Symmetric indefinite variant.
  A = A - 3 * I
  b = A * [1:n;]
  (x, stats) = cgs(A, b)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("CGS: Relative residual: %8.1e\n", resid)
  @test(resid ≤ cgs_tol)
  @test(stats.solved)

  # Nonsymmetric and positive definite systems.
  A = [i == j ? 10.0 : i < j ? 1.0 : -1.0 for i=1:n, j=1:n]
  b = A * [1:n;]
  (x, stats) = cgs(A, b)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("CGS: Relative residual: %8.1e\n", resid)
  @test(resid ≤ cgs_tol)
  @test(stats.solved)

  # Nonsymmetric indefinite variant.
  A = [i == j ? 10*(-1.0)^(i*j) : i < j ? 1.0 : -1.0 for i=1:n, j=1:n]
  b = A * [1:n;]
  (x, stats) = cgs(A, b)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("CGS: Relative residual: %8.1e\n", resid)
  @test(resid ≤ cgs_tol)
  @test(stats.solved)

  # Code coverage.
  (x, stats) = cgs(sparse(A), b)
  show(stats)

  # Sparse Laplacian.
  A = get_div_grad(16, 16, 16)
  b = ones(size(A, 1))
  (x, stats) = cgs(A, b)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("CGS: Relative residual: %8.1e\n", resid)
  @test(resid ≤ cgs_tol)
  @test(stats.solved)

  # Test b == 0
  (x, stats) = cgs(A, zeros(size(A,1)))
  @test x == zeros(size(A,1))
  @test stats.status == "x = 0 is a zero-residual solution"

  # Test integer values
  A = spdiagm(-1 => ones(Int, n-1), 0 => 4*ones(Int, n), 1 => ones(Int, n-1))
  b = A * [1:n;]
  (x, stats) = cgs(A, b)
  @test stats.solved

  # Test with Jacobi (or diagonal) preconditioner
  A = ones(10,10) + 9 * I
  b = 10 * [1:10;]
  M = 1/10 * opEye(10)
  (x, stats) = cgs(A, b, M=M)
  show(stats)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("CGS: Relative residual: %8.1e\n", resid)
  @test(resid ≤ cgs_tol)
  @test(stats.solved)
end

test_cgs()


@doc doc"""
    Euclidean{T<:Tuple} <: Manifold

Euclidean vector space $\mathbb R^n$.

# Constructor

    Euclidean(n)

generates the $n$-dimensional vector space $\mathbb R^n$.

    Euclidean(n₁,n₂,...,nᵢ)

generates the $n_1n_2\cdot\ldots n_i$-dimensional vector space $\mathbb R^{n_1, n_2, \ldots, n_i}$, whose
elements are interpreted as $n_1 \times,n_2\times\cdots\times n_i$ arrays, e.g. for
two parameters as matrices.
"""
struct Euclidean{T<:Tuple} <: Manifold where {T} end
Euclidean(n::Vararg{Int,N}) where N = Euclidean{Tuple{n...}}()

"""
    representation_size(M::Euclidean{T})

returns the array dimensions required to represent an element on the
[`Euclidean`](@ref) manifold `M`, i.e. the vector of all array dimensions.
"""
@generated representation_size(::Euclidean{T}) where {T} = Tuple(T.parameters...)

"""
    manifold_dimension(M::Euclidean{T})

returns the manifold dimension of the [`Euclidean`](@ref) manifold `M`, i.e.
the product of all array dimensions.
"""
@generated manifold_dimension(::Euclidean{T}) where {T} = *(T.parameters...)

"""
    EuclideanMetric <: RiemannianMetric

a general type for any manifold that employs the Euclidean Metric, for example
the [`Euclidean`](@ref) manifold itself, or the [`Sphere`](@ref), where every
tangent space (as a plane in the embedding) uses this metric (in the embedding).
"""
struct EuclideanMetric <: RiemannianMetric end
is_default_metric(::Euclidean{T},::EuclideanMetric) where {T} = Val(true)

local_metric(::MetricManifold{<:Manifold,EuclideanMetric}, x) = Diagonal(ones(SVector{size(x, 1),eltype(x)}))

inverse_local_metric(M::MetricManifold{<:Manifold,EuclideanMetric}, x) = local_metric(M, x)

det_local_metric(M::MetricManifold{<:Manifold,EuclideanMetric}, x) = one(eltype(x))

log_local_metric_density(M::MetricManifold{<:Manifold,EuclideanMetric}, x) = zero(eltype(x))

@inline inner(::Euclidean, x, v, w) = dot(v, w)
@inline inner(::MetricManifold{<:Manifold,EuclideanMetric}, x, v, w) = dot(v, w)

"""
    distance(M::Euclidean,x,y)

compute the Euclidean distance between two points on the [`Euclidean`](@ref)
manifold `M`, i.e. for vectors it's just the norm of the difference, for matrices
and higher order arrays, the matrix and ternsor Frobenius norm, respectively.
"""
distance(::Euclidean, x, y) = norm(x-y)
"""
    norm(M::Euclidean,x,v)

compute the norm of a tangent vector `v` at `x` on the [`Euclidean`](@ref)
manifold `M`, i.e. since every tangent space can be identified with `M` itself
in this case, just the (Frobenius) norm of `v`.
"""
norm(::Euclidean, x, v) = norm(v)
norm(::MetricManifold{<:Manifold,EuclideanMetric}, x, v) = norm(v)

"""
    exp!(M::Euclidean,y, x, v)

compute the exponential map on the [`Euclidean`](@ref) manifold `M` from `x`
in direction `v` in place, i.e. in `y`, which in this case is just
````math
    y = x + v
````
"""
exp!(M::Euclidean, y, x, v) = (y .= x .+ v)
"""
    log!(M::Euclidean, v, x, y)

compute the logarithmic map on the [`Euclidean`](@ref) manifold `M` from `x`
tpo `y`, stored in the direction `v` in place, i.e. in `v`, which in this case is just
````math
    v = y - x
````
"""
log!(M::Euclidean, v, x, y) = (v .= y .- x)
"""
    zero_tangent_vector!(M::Euclidean, v, x)

compute a zero vector in the tangent space of `x` on the [`Euclidean`](@ref)
manifold `M`, whcih here is just a zero filled array the same size as the
in place parameter `v` which is assumed to have the same `size` as `x`.
"""
function zero_tangent_vector!(M::Euclidean, v, x)
    fill!(v, 0)
    return v
end

"""
    project_point!(M::Euclidean, x)

project an arbitrary point `x` onto the [`Euclidean`](@ref) manifold `M`, which
is of course just the identity map.
"""
project_point!(M::Euclidean, x) = x

"""
    project_tangent!(M::Euclidean, w, x, v)

project an arbitrary vector `v` into the tangent space of a point `x` on the
[`Euclidean`](@ref) manifold `M`, which is just the identity, since any tangent
space of `M` can be identified with all of `M`.
"""
function project_tangent!(M::Euclidean, w, x, v)
    w .= v
    return w
end
"""
    vector_transport_to!(M::Euclidean, vto, x, v, y, ::ParallelTransport)

parallel transport the vector `v` from the tangent space at `x` to the
tangent space at `y` on the [`Euclidean`](@ref) manifold `M`,
i.e. the in place `w` is just set to `v`.
"""
function vector_transport_to!(M::Euclidean, vto, x, v, y, ::ParallelTransport)
    vto .= v
    return vto
end
"""
    flat!(M::Euclidean, v, x, w)

since cotangent and tangent vectors can directly be identified in the [`Euclidean`](@ref)
case, this yields just the identity for a tangent vector `w` in the tangent space
of `x` on `M`. The result is returned also in place in `v`.
"""
function flat!(M::Euclidean, v::FVector{CotangentSpaceType}, x, w::FVector{TangentSpaceType})
    copyto!(v.data, w.data)
    return v
end
"""
    sharp!(M::Euclidean, v, x, w)

since cotangent and tangent vectors can directly be identified in the [`Euclidean`](@ref)
case, this yields just the identity for a cotangent vector `w` in the tangent space
of `x` on `M`. The result is returned also in place in `v`.
"""
function sharp!(M::Euclidean, v::FVector{TangentSpaceType}, x, w::FVector{CotangentSpaceType})
    copyto!(v.data, w.data)
    return v
end

"""
    projected_distribution(M::Euclidean, d, [x])

Wraps standard distribution `d` into a manifold-valued distribution. Generated
points will be of similar type to `x`. By default, the type is not changed.
"""
function projected_distribution(M::Euclidean, d, x)
    return ProjectedPointDistribution(M, d, project_point!, x)
end

function projected_distribution(M::Euclidean, d)
    return ProjectedPointDistribution(M, d, project_point!, rand(d))
end

"""
    normal_tvector_distribution(S::Euclidean, x, σ)

Normal distribution in ambient space with standard deviation `σ`
projected to tangent space at `x`.
"""
function normal_tvector_distribution(M::Euclidean{Tuple{N}}, x, σ) where N
    d = Distributions.MvNormal(zero(x), σ)
    return ProjectedFVectorDistribution(TangentBundleFibers(M), x, d, project_vector!, x)
end


module ImageReconstruction

using Base.Threads
using Interpolations
using FFTW
using ImageTransformations
export radon, iradon, sinogram

"""
    radon(I::AbstractMatrix, θ::AbstractRange, t::AbstractRange)

Radon transform of a image `I` producing a sinogram from view angles `θ` in
radians and detector sampling `t`.
"""
function radon(I::AbstractMatrix, θ::AbstractRange, t::AbstractRange)
    P = zeros(eltype(I), length(t), length(θ))
    ax1, ax2 = axes(I)

    nax1, nax2 = length(ax1), length(ax2)
    for j in ax2, i in ax1
        x = j - nax2 / 2 + 0.5
        y = i - nax1 / 2 + 0.5
        @inbounds for (k, θₖ) in enumerate(θ)
            t′ = x * cos(θₖ) + y * sin(θₖ)

            a = convert(Int, round((t′ - minimum(t)) / step(t) + 1))

            (a < 1 || a > length(t)) && continue
            α = abs(t′ - t[a])

            I′ = I[i, j]
            P[a, k] += (1 - α) * I′

            (a > length(t) + 1) && continue
            P[a+1, k] += α * I′
        end
    end

    P
end

function _ramp_spatial(N::Int, τ, Npad::Int = N)
    @assert Npad ≥ N
    N2 = N ÷ 2
    hval(n) = n == 0 ? 1 / (4*τ^2) : - mod(n, 2)/(π * n * τ)^2
    [i ≤ N ? hval(i - N2 - 1) : 0. for i = 1:Npad]
end

"""
	iradon(P::AbstractMatrix, θ::AbstractRange, t::AbstractRange)

Inverse radon transform of a sinogram `P` with view angles `θ` in radians and
detector sampling `t` producing an image on a 128x128 matrix.
"""
function iradon(P::AbstractMatrix, θ::AbstractRange, t::AbstractRange)
    pixels = 128

    N = length(t)
    K = length(θ)
    Npad = nextpow(2, 2 * N - 1)
    τ = step(t)
    ramp = fft(_ramp_spatial(N, τ, Npad))
    i = N ÷ 2 + 1
    j = i + N - 1

    T = eltype(P)
    I = [zeros(T, pixels, pixels) for _ = 1:nthreads()]
    P′ = [Vector{Complex{T}}(undef, Npad) for _ = 1:nthreads()]
    Q = [Vector{T}(undef, N) for _ = 1:nthreads()]

    l = SpinLock()
    @inbounds @threads for (k, θₖ) in collect(enumerate(θ))
        id = threadid()
        Pid, Qid, Iid = P′[id], Q[id], I[id]

        # filter projection
        Pid[1:N] .= P[:, k]
        Pid[N+1:end] .= 0

        # Need to prevent multiple thread execution during fft/ifft.
        # double free occurs otherwise.
        # https://github.com/JuliaMath/FFTW.jl/issues/134
        lock(l)
        fft!(Pid)
        Pid .*= ramp
        ifft!(Pid)
        unlock(l)
        Qid .= τ .* real.(@view Pid[i:j])

        Qₖ = LinearInterpolation(t, Qid)

        # backproject
        for c in CartesianIndices(first(I))
            x = c.I[2] - pixels ÷ 2 + 0.5
            y = c.I[1] - pixels ÷ 2 + 0.5
            x^2 + y^2 ≥ pixels^2 / 4 && continue
            t′ = x * cos(θₖ) + y * sin(θₖ)
            Iid[c] += Qₖ(t′)
        end
    end
    sum(I) .* π ./ K
end

"""
    sinogram(I::AbstractMatrix, n::AbstractRange)

Generate a sinogram `I` with `n` views  and producing an image on a 128x128 matrix.
"""
function sinogram(I::AbstractMatrix, n::Int64)

    angles = LinRange(1, 180, n)

    #Add padding (zeros) to avoid data loss while rotating
    image_size = 128
    image_diagonal = sqrt(image_size^2 + image_size^2)
    padding_amount = ceil(Int64,image_diagonal - image_size) + 2
    padded_image = zeros(image_size+padding_amount, image_size+padding_amount)
    padded_image[ceil(Int,padding_amount/2):(ceil(Int,padding_amount/2)+image_size-1),
                 ceil(Int,padding_amount/2):(ceil(Int,padding_amount/2)+image_size-1)] = I

    # Rotate the image for each angle and sum up the columns of each projected image to create sinogram
    #n = length(angles)
    sinogram_image = zeros(size(padded_image,2), n)
    for i = 1:n
        #temp_image = imrotate(padded_image, 90-angles(i), 'bilinear', 'crop')
        temp_image = imrotate(padded_image, 2*(90-angles[i])/pi)
        sinogram_image[:,i] .= sum(temp_image)
    end
    return sinogram_image
end

end # module


#miscellaneous helper functions and constants
Q95 = sqrt(2.)*erfinv(0.95)

range(x) = (minimum(x), maximum(x))

#95% normal quantile

roundv(v::Vector, i) = map(i -> round(i, 2), v)
roundv(v , i) = round(v,i)

eps2 = sqrt(eps())

intervalgaussian(U, a, b) = broadcast(intervalgaussian, U, a, b)
function intervalgaussian (U::Real,a::Real, b::Real)
	a, b = min(a,b), max(a,b)
	c =  erf(a/sqrt(2.))
	d =  erf(b/sqrt(2.))
	sqrt(2.)*erfinv(c + U.*(d-c))
end

function scalar(x)
assert(length(x) == 1)
x[1]
end

function norma!(x)
 minx = min(x)
 x = (x - minx) / (max(x)-minx)
 x
end

function cut(x, a, b)
	warn("cut(): better use clamp()")
	clamp(x, a,b)
end

#compute normal confidence interval for Monte Carlo estimate, precision aware rounding

function mc(M)
  m = mean(M)
  ste = std(M)/sqrt(length(M))
  round([m, Q95*ste], int(2-log(10,Q95*ste)))
end

#for sequential estimates, it suffices to provide the running sum Z, running sum of squares Z2 and the number of observations k

va(k, Z, Z2) =  Z2/k-(Z/k)^2
ste(k, Z, Z2) = sqrt(Z2/k^2-(Z/k)^2/k)

function mc2(k, Z, Z2, normalci = true)
	m = Z/k

	if normalci F = Q95 else F = 1. end

	try
		ste = sqrt(Z2/k-m.^2)/sqrt(k)

		res = (m, F*ste)

		return res
	catch
		return (m, NaN.*m)
	end

end

function selfadjmc(k, X, X2)
	m = X[1]/X[2]
	v = (va(k, X[1], X2[1,1]) - 2m*(X2[1,2]/k - X[1]*X[2]/k^2) + m^2*va(k, X[2], X2[2,2]))/k
	try
		ste = sqrt(v)
		res = [m, ste]
		return res
	catch
		return [m, NaN.*m]
	end

end


struct Simplex{T<:Number, U<:Complex}
  vertices::AbstractVector{Vertex{T,U}}
  function Simplex{T,U}(vertices::AbstractVector{Vertex{T,U}}
      ) where {T<:Number, U<:Complex}
    sort!(vertices, by=v->angle(value(v)))
    return new{T,U}(vertices)
  end
end

import Base: length, iterate, push!, iterate, getindex
import Base: eachindex, sort!, setindex!, hash, isequal
Base.length(s::Simplex) = length(s.vertices)
Base.iterate(s::Simplex) = iterate(s.vertices)
Base.iterate(s::Simplex, counter) = iterate(s.vertices, counter)
Base.getindex(s::Simplex, index) = s.vertices[index]
Base.sort!(s::Simplex; kwargs...) = sort!(s.vertices; kwargs...)
function Base.setindex!(s::Simplex, entry, index)
  s.vertices[index] = entry
  sortbyangle!(s)
  return nothing
end
Base.hash(s::Simplex) = hash(s, hash(:Simplex))
Base.hash(s::Simplex, h::UInt64) = hash(hash.(s), h)
Base.isequal(a::Simplex, b::Simplex) = all(isequal.(a, b))

sortbyangle!(s::Simplex) = sort!(s, by=v->angle(value(v)))
issortedbyangle(s::Simplex) = issorted(s, by=v->angle(value(v)))

dimensionality(s::Simplex) = length(s) - 1

function getvertex(s::Simplex, i::Int)
  @assert 1 <= i <= length(s)
  @assert issortedbyangle(s)
  return s[i]
end

function findabsvaluevertex(s::Simplex, op::T) where {T<:Function}
  _, index = op(map(v -> abs(value(v)), s))
  return s[index]
end

minabsvaluevertex(s::Simplex) = findabsvaluevertex(s, findmin)
maxabsvaluevertex(s::Simplex) = findabsvaluevertex(s, findmax)

function centroidignorevertex(f::T, s::Simplex, vertextoignore::Vertex
    ) where {T<:Function, U<:Function}
  g(v) = isequal(v, vertextoignore) ? zero(position(v)) : position(v)
  x = mapreduce(g, +, s) ./ (length(s) - 1)
  return Vertex(x, f(x))
end

centroid(s::Simplex) = mapreduce(position, +, s) ./ length(s)
function closestomiddlevertex(s::Simplex)
  mid = centroid(s)
  _, index = findmin(map(v->sum((position(v) - mid).^2), s))
  return s[index]
end

function swap!(s::Simplex, this::Vertex, forthat::Vertex)
  index = findfirst(x->isequal(x, this), s.vertices)
  s[index] = forthat
  @assert issortedbyangle(s)
  return nothing
end

function assessconvergence(simplex, config::NamedTuple)
  if abs(value(minabsvaluevertex(simplex))) <= config[:stopvalroot]
    return true, :STOPVAL_ROOT_REACHED
  elseif abs(value(maxabsvaluevertex(simplex))) >= config[:stopvalpole]
    return true, :STOPVAL_POLE_REACHED
  end

  toprocess = Set{Int}(1)
  processed = Set{Int}()
  while !isempty(toprocess)
    vi = pop!(toprocess)
    v = getvertex(simplex, vi)
    connectedto = Set{Int}()
    for (qi, q) ∈ enumerate(simplex)
      thisxtol = true
      for (i, (pv, pq)) ∈ enumerate(zip(position(v), position(q)))
        thisxtol &= isapprox(pv, pq, rtol=config[:xtol_rel][i],
                                     atol=config[:xtol_abs][i])
      end
      thisxtol && push!(connectedto, qi)
      thisxtol && for i in connectedto if i ∉ processed push!(toprocess, i) end end
    end
    push!(processed, vi)
  end
  allxtol = all(i ∈ processed for i ∈ 1:length(simplex))
  allxtol && return true, :XTOL_REACHED

  allftol = true
  for (vi, v) ∈ enumerate(simplex)
    for qi ∈ vi+1:length(simplex)
      q = getvertex(simplex, qi)
      allftol &= all(isapprox(value(v), value(q), rtol=config[:ftol_rel],
                              atol=config[:ftol_abs]))
    end
  end
  allftol && return true, :FTOL_REACHED

  return false, :CONTINUE
end

function _πtoπ(ϕ)
  ϕ < -π && return _πtoπ(ϕ + 2π)
  ϕ > π && return _πtoπ(ϕ - 2π)
  return ϕ
end

function windingangle(s::Simplex)
  return sum(_πtoπ.(angle.(value.(circshift(s.vertices, -1))) .-
                    angle.(value.(s.vertices))))
end

function windingnumber(s::Simplex)
  radians = windingangle(s)
  return isfinite(radians) ? Int64(round(radians / 2π)) : Int64(0)
end


function Kolmogorov_smirnov_test(dat::AbstractArray,d::ContinuousUnivariateDistribution,xmin::Number,xmax::Int64 = round(Int,1e5))
  data = sort(dat)
  n = float(length(data))
  max_indx = findlast(x -> x <= xmax, data)
  min_indx = findfirst(x -> x >= xmin, data)
  data = data[min_indx:max_indx]

  act_cdf = collect(0:length(data)-1) / n
  thr_cdf = cdf(d,float(data))
  D = maximum(abs(act_cdf-thr_cdf))
  return D
end

function Kolmogorov_smirnov_test(dat::AbstractArray,d::DiscreteUnivariateDistribution,xmin::Int64,xmax::Int64 = round(Int,1e5))
  alpha, xmin = params(d)
  data = round(Int,sort(dat))
  n = float(length(data))
  max_indx = findlast(x -> x <= xmax, data)
  min_indx = findfirst(x -> x >= xmin, data)
  data = data[min_indx:max_indx]

  thr_cdf = cdf(d,collect(xmin:data[end])+1)
  occurence = counts(data)[Int(xmin) - (data[1] - 1):end]
  act_cdf = occurence/sum(occurence)
  act_cdf = cumsum(act_cdf)
  D = maximum(abs(act_cdf-thr_cdf))
  return D
end

#helper function
function create_histogram(x::AbstractArray)
  h = zeros(typeof(x[1]),0)
  max = 0
  d = 0
  for i = 1:length(x)
    if max < x[i]
      for j=1:(x[i]-max)
        push!(h,0)
      end
      h[x[i]] =1
      max = x[i]
    else
      h[x[i]] +=1
    end
  end
  return h
end