text:
### A Pluto.jl notebook ###
# v0.17.3

using Markdown
using InteractiveUtils

# ╔═╡ 864ec7b4-680b-4c07-b54c-521e4897d5a7
# hideall

using Genie, Genie.Router

# ╔═╡ a486afe6-316a-11ec-3c48-09b49e9aca8c
md"""
# Hello world with Genie

Here are a few examples to quickly get you started with building Genie web apps.

## Running Genie interactively at the REPL or in Jupyter

The simplest use case is to configure a routing function at the REPL and start the web server. That's all that's needed to run your code on the web:

### Example:
"""

# ╔═╡ 7f6498e9-44e5-498e-a81c-cca54d371438
md"""
Adding Genie package:

```julia
julia> using Pkg

julia> Pkg.add("Genie")
```
"""

# ╔═╡ 7222af83-c058-4e11-98dc-d995e93fb260
md"""

```julia
julia> using Genie, Genie.Router
```

"""

# ╔═╡ 41780b3c-e8dd-48ba-9329-bd301bb7e5d8
md"""

```julia
julia> route("/hello") do
            "Hello World"
       end
```
"""

# ╔═╡ 0b20eb67-4853-46d8-a439-1766ad5b48a9
# hideall

route("/hello") do
	"Hello World"
end

# ╔═╡ c3cfaeec-7cb9-4821-b708-cf07289f92af
md"""

```julia
julia> up() # start server
```
"""

# ╔═╡ a180d05a-1977-41ae-82f4-e061d500c43a
# hideall

up(); # to up the server

# ╔═╡ d6647691-1e14-45a5-b9f6-fa8e89d4e26f
md"""

```julia
julia> down() # stopping the server
```
"""

# ╔═╡ c5beb412-1b7a-45a1-abe6-3187758d2767
# hideall

down(); # to shutdown the running server

# ╔═╡ 21ac9bab-e8f0-4012-a649-08577715efda
md"""
The `route` function (available in the `Router` module) defines a mapping between a URL (`"/hello"`) and a Julia function which will be automatically invoked to send the response back to the client. In this case we're sending back the string "Hello World".

That's all! We have set up an app, a route, and started the web server. Open your favourite web browser and go to <http://127.0.0.1:8000/hello> to see the result.

---
**HEADS UP**

Keep in mind that Julia JIT-compiles. A function is automatically compiled the first time it is invoked. The function, in this case, is our route handler serving the request. This will make the first response slower as it also includes compilation time. But once the function is compiled, for all the subsequent requests, it will be super fast!

---
"""

# ╔═╡ 9f5a22bd-362a-428f-ba5f-4e1d07cf49be
md"""
## Developing a simple Genie script

Genie can also be used in custom scripts, for example when building micro-services with Julia. Let's create a simple Hello World micro-service.

Start by creating a new file to host our code -- let's call it `geniews.jl`
"""

# ╔═╡ 2280055c-1fb0-4519-8ba4-84e484307124
md"""

```julia
julia> touch("geniews.jl")
```
"""

# ╔═╡ c9ffdde1-8bf3-4759-a3c2-6139cec96019
# hideall

begin
	file_created = true;
	touch("geniews.jl");
end;

# ╔═╡ 82aaf57a-3390-43a9-8cd4-548c96c576a0
# hideall

begin
	file_created;
	@assert isfile("geniews.jl") == true;
end;

# ╔═╡ a5841694-02ea-42dc-a58a-7e7ae651bf79
# hideall

begin
	file_written = true;
	write("geniews.jl", """using Genie, Genie.Router
using Genie.Renderer, Genie.Renderer.Html, Genie.Renderer.Json

route("/hello.html") do
  html("Hello World")
end

route("/hello.json") do
  json("Hello World")
end

route("/hello.txt") do
   respond("Hello World", :text)
end

up(8002, async = true)""");
end;

# ╔═╡ 3aa6c93b-b9a9-4758-bd5c-2ab08e7047fc
# hideall

begin
	file_written
	include("geniews.jl");
end;

# ╔═╡ e078471a-5488-46a6-8ca2-06ea02e16a0e
md"""

Now, open it in the editor: and edit `geniews.jl`

```julia
julia> edit("geniews.jl")
```

"""

# ╔═╡ 534f2f99-9c49-4bf6-a7d5-686e43664327
md"""
Add the following code:

```julia
using Genie, Genie.Router
using Genie.Renderer, Genie.Renderer.Html, Genie.Renderer.Json

route("/hello.html") do
  html("Hello World")
end

route("/hello.json") do
  json("Hello World")
end

route("/hello.txt") do
   respond("Hello World", :text)
end

up(8001, async = true)
```
"""

# ╔═╡ 0476f46e-9a30-4f20-9fe6-0148487426b9
md"""

You can run the code by saying:

```julia
julia> include("geniews.jl")
```
"""

# ╔═╡ 67d40bac-fafd-47a2-ab7a-0d59bea58b42
md"""
We begun by defining 2 routes and we used the `html` and `json` rendering functions (available in the `Renderer.Html` and the `Renderer.Json` modules). These functions are responsible for outputting the data using the correct format and document type (with the correct MIME), in our case HTML data for `hello.html`, and JSON data for `hello.json`.

The third `route` serves text responses. As Genie does not provide a specialized method for sending `text/plain` responses, we use the generic `respond` function, indicating the desired MIME type. In our case `:text`, corresponding to `text/plain`. Other available MIME types shortcuts are `:xml`, `:markdown`, and `:javascript`. If you're looking for something else, you can always pass the full mime type as a string, ie `"text/csv"`.

The `up` function will launch the web server on port `8001`. This time, very important, we instructed it to start the server synchronously (that is, _blocking_ the execution of the script), by passing the `async = false` argument. This way we make sure that our script stays running. Otherwise, at the end of the script, it would normally exit, killing our server.

In order to launch the script, run `$ julia geniews.jl`.

## Batteries included

Genie readily makes available a rich set of features - you have already seen the rendering and the routing engines in action. But for instance, logging (to file and console) can also be easily triggered with one line of code, powerful caching can be enabled with a couple more lines, and so on.

The app already handles "404 Page Not Found" and "500 Internal Error" responses. If you try to access a URL which is not handled by the app, like say <http://127.0.0.1:8001/not_here>, you'll see Genie's default 404 page. The default error pages can be overwritten with custom ones and we'll see how to do this later on.
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Genie = "c43c736e-a2d1-11e8-161f-af95117fbd1e"

[compat]
Genie = "~3.3.0"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[ArgParse]]
deps = ["Logging", "TextWrap"]
git-tree-sha1 = "3102bce13da501c9104df33549f511cd25264d7d"
uuid = "c7e460c6-2fb9-53a9-8c5b-16f535851c63"
version = "1.1.4"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[CSTParser]]
deps = ["Tokenize"]
git-tree-sha1 = "b2667530e42347b10c10ba6623cfebc09ac5c7b6"
uuid = "00ebfdb7-1f24-5e51-bd34-a7502290713f"
version = "3.2.4"

[[CodeTracking]]
deps = ["InteractiveUtils", "UUIDs"]
git-tree-sha1 = "9aa8a5ebb6b5bf469a7e0e2b5202cf6f8c291104"
uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
version = "1.0.6"

[[CommonMark]]
deps = ["Crayons", "JSON", "URIs"]
git-tree-sha1 = "393ac9df4eb085c2ab12005fc496dae2e1da344e"
uuid = "a80b9123-70ca-4bc0-993e-6e3bcb318db6"
version = "0.8.3"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "31d0151f5716b655421d9d75b7fa74cc4e744df2"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.39.0"

[[Crayons]]
git-tree-sha1 = "3f71217b538d7aaee0b69ab47d9b7724ca8afa0d"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.0.4"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[EzXML]]
deps = ["Printf", "XML2_jll"]
git-tree-sha1 = "0fa3b52a04a4e210aeb1626def9c90df3ae65268"
uuid = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615"
version = "1.1.0"

[[FilePathsBase]]
deps = ["Dates", "Mmap", "Printf", "Test", "UUIDs"]
git-tree-sha1 = "7fb0eaac190a7a68a56d2407a6beff1142daf844"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.12"

[[FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"

[[GMP_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d"

[[Genie]]
deps = ["ArgParse", "Dates", "Distributed", "EzXML", "FilePathsBase", "HTTP", "HttpCommon", "Inflector", "JSON3", "JuliaFormatter", "Logging", "Markdown", "MbedTLS", "Millboard", "Nettle", "OrderedCollections", "Pkg", "REPL", "Reexport", "Revise", "SHA", "Serialization", "Sockets", "UUIDs", "Unicode", "YAML"]
git-tree-sha1 = "4f5526913b239ece648a73d4445c7978f1251935"
uuid = "c43c736e-a2d1-11e8-161f-af95117fbd1e"
version = "3.3.0"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "14eece7a3308b4d8be910e265c724a6ba51a9798"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.16"

[[HttpCommon]]
deps = ["Dates", "Nullables", "Test", "URIParser"]
git-tree-sha1 = "46313284237aa6ca67a6bce6d6fbd323d19cff59"
uuid = "77172c1b-203f-54ac-aa54-3f1198fe9f90"
version = "0.5.0"

[[Inflector]]
deps = ["Unicode"]
git-tree-sha1 = "8555b54ddf27806b070ce1d1cf623e1feb13750c"
uuid = "6d011eab-0732-4556-8808-e463c76bf3b6"
version = "1.0.1"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

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

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

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

[[JSON3]]
deps = ["Dates", "Mmap", "Parsers", "StructTypes", "UUIDs"]
git-tree-sha1 = "7d58534ffb62cd947950b3aa9b993e63307a6125"
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
version = "1.9.2"

[[JuliaFormatter]]
deps = ["CSTParser", "CommonMark", "DataStructures", "Pkg", "Tokenize"]
git-tree-sha1 = "10c95cebcfa37c1f510a726c90886db4745e1238"
uuid = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
version = "0.15.11"

[[JuliaInterpreter]]
deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"]
git-tree-sha1 = "e273807f38074f033d94207a201e6e827d8417db"
uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"
version = "0.8.21"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[LoweredCodeUtils]]
deps = ["JuliaInterpreter"]
git-tree-sha1 = "491a883c4fef1103077a7f648961adbf9c8dd933"
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
version = "2.1.2"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Millboard]]
git-tree-sha1 = "ea6a5b7e56e76d8051023faaa11d91d1d881dac3"
uuid = "39ec1447-df44-5f4c-beaa-866f30b4d3b2"
version = "0.2.5"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[Nettle]]
deps = ["Libdl", "Nettle_jll"]
git-tree-sha1 = "a68340b9edfd98d0ed96aee8137cb716ea3b6dea"
uuid = "49dea1ee-f6fa-5aa6-9a11-8816cee7d4b9"
version = "0.5.1"

[[Nettle_jll]]
deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "eca63e3847dad608cfa6a3329b95ef674c7160b4"
uuid = "4c82536e-c426-54e4-b420-14f461c4ed8b"
version = "3.7.2+0"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[Nullables]]
git-tree-sha1 = "8f87854cc8f3685a60689d8edecaa29d2251979b"
uuid = "4d1e1d77-625e-5b40-9113-a560ec7a8ecd"
version = "1.0.0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "f19e978f81eca5fd7620650d7dbea58f825802ee"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.1.0"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

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

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

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

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[Revise]]
deps = ["CodeTracking", "Distributed", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Pkg", "REPL", "Requires", "UUIDs", "Unicode"]
git-tree-sha1 = "41deb3df28ecf75307b6e492a738821b031f8425"
uuid = "295af30f-e4ad-537b-8983-00126c2a3abe"
version = "3.1.20"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

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

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[StringEncodings]]
deps = ["Libiconv_jll"]
git-tree-sha1 = "50ccd5ddb00d19392577902f0079267a72c5ab04"
uuid = "69024149-9ee7-55f6-a4c4-859efe599b68"
version = "0.3.5"

[[StructTypes]]
deps = ["Dates", "UUIDs"]
git-tree-sha1 = "d24a825a95a6d98c385001212dc9020d609f2d4f"
uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
version = "1.8.1"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[TextWrap]]
git-tree-sha1 = "9250ef9b01b66667380cf3275b3f7488d0e25faf"
uuid = "b718987f-49a8-5099-9789-dcd902bef87d"
version = "1.0.1"

[[Tokenize]]
git-tree-sha1 = "0952c9cee34988092d73a5708780b3917166a0dd"
uuid = "0796e94c-ce3b-5d07-9a54-7f471281c624"
version = "0.5.21"

[[URIParser]]
deps = ["Unicode"]
git-tree-sha1 = "53a9f49546b8d2dd2e688d216421d050c9a31d0d"
uuid = "30578b45-9adc-5946-b283-645ec420af67"
version = "0.4.1"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

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

[[XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"

[[YAML]]
deps = ["Base64", "Dates", "Printf", "StringEncodings"]
git-tree-sha1 = "3c6e8b9f5cdaaa21340f841653942e1a6b6561e5"
uuid = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
version = "0.4.7"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─a486afe6-316a-11ec-3c48-09b49e9aca8c
# ╟─7f6498e9-44e5-498e-a81c-cca54d371438
# ╟─7222af83-c058-4e11-98dc-d995e93fb260
# ╠═864ec7b4-680b-4c07-b54c-521e4897d5a7
# ╟─41780b3c-e8dd-48ba-9329-bd301bb7e5d8
# ╠═0b20eb67-4853-46d8-a439-1766ad5b48a9
# ╟─c3cfaeec-7cb9-4821-b708-cf07289f92af
# ╠═a180d05a-1977-41ae-82f4-e061d500c43a
# ╟─d6647691-1e14-45a5-b9f6-fa8e89d4e26f
# ╠═c5beb412-1b7a-45a1-abe6-3187758d2767
# ╟─21ac9bab-e8f0-4012-a649-08577715efda
# ╟─9f5a22bd-362a-428f-ba5f-4e1d07cf49be
# ╟─2280055c-1fb0-4519-8ba4-84e484307124
# ╠═c9ffdde1-8bf3-4759-a3c2-6139cec96019
# ╠═82aaf57a-3390-43a9-8cd4-548c96c576a0
# ╠═a5841694-02ea-42dc-a58a-7e7ae651bf79
# ╠═e078471a-5488-46a6-8ca2-06ea02e16a0e
# ╟─534f2f99-9c49-4bf6-a7d5-686e43664327
# ╟─0476f46e-9a30-4f20-9fe6-0148487426b9
# ╠═3aa6c93b-b9a9-4758-bd5c-2ab08e7047fc
# ╟─67d40bac-fafd-47a2-ab7a-0d59bea58b42
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


text:
using Limbdark
import Limbdark: Transit_Struct
include("occultquad.jl")
#include("../../../src/transit_poly_struct.jl")
include("../../../test/loglinspace.jl")

using Optim
using Printf

if VERSION >= v"0.7"
  using Statistics
end

function sqarea_triangle(a::T,b::T,c::T) where {T <: Real}
# How to compute (sixteen times the) area squared of triangle with
# high precision (Goldberg 1991).
# First, do a quick sort of three numbers:
if c > b
  tmp = c; c = b; b = tmp
end
if c > a
  tmp = a; a = c; c = tmp
end
if b > a
  tmp = b; b = a; a = tmp
end
area = (a+(b+c))*(c-(a-b))*(c+(a-b))*(a+(b-c))
return area
end

function area_overlap(R::T,r::T,b::T) where {T <: Real}
# Compute the light curve of a uniform source:
if b <= abs(R-r)
  area = pi*minimum([r,R])^2
else
  # Twice area of kite-shaped region connecting centers of circles & intersection points:
  kite_area2 = sqrt(abs(sqarea_triangle(R,b,r)))
  # Angle of section for occultor:
  kap0  = atan(kite_area2,(r-R)*(r+R)+b^2)
  # Angle of section for source:
  kap1 = atan(kite_area2,(R-r)*(r+R)+b^2)
  # Flux of visible uniform disk:
  area = R^2*kap1 + r^2*kap0 - 0.5*kite_area2
end
return area
end

# Compute non-linear limb-darkened light curve as a sum over uniform
# sources:

function occult_nonlinear(r::T,b::T,c1::T,c2::T,c3::T,c4::T) where {T <: Real}
#  Computes non-linear limb-darkening with numerical integration
#  using the same approach as Batman:

  function Iofmu(r::T) where {T <: Real}
  mu = sqrt(one(T)-r^2)
  muh = sqrt(mu)
  return one(T)-c1-c2-c3-c4+c1*muh+c2*mu+c3*muh*mu+c4*mu^2
  end

  x0 = maximum([b-r,zero(T)]); xn = minimum([b+r,one(T)])
  theta1 = asin(x0); theta2 = asin(xn);
  nint = 16; delta0 = one(T); delta1 = zero(T)
  tol = 1e-8; iter = 0; itmax = 20
  while abs(delta0-delta1) > tol*delta0 && iter <= itmax
    if VERSION >= v"0.7"
      xgrid = sin.(range(theta1,stop=theta2,length=nint))
    else
      xgrid = sin.(linspace(theta1,theta2,nint))
    end
    delta0 = delta1
    delta1 = zero(T)
    for i=2:nint
      sb = Iofmu(0.5*(xgrid[i]+xgrid[i-1]))
      da = area_overlap(xgrid[i],r,b)-area_overlap(xgrid[i-1],r,b)
      delta1 += sb*da
    end
    nint *=2; iter +=1
  end
return delta1
end

function transit_poly_int(r,b,c,ns)
if b < (1.0+r)
  s_1 = maximum([0.0,b-r]); s_2=minimum([1.0,b+r])
  ds=(s_2-s_1)/ns
  s = linearspace(s_1+.5*ds,s_2-.5*ds,ns)
  fobs = zero(r)
  for j=1:ns
    if s[j] < r-b
      dphi = 2pi
    else
      dphi = 2*acos((s[j]^2+b^2-r^2)/(2*b*s[j]))
    end
    z = sqrt(sqrt(1-s[j]^2))
    imu = 1.0-sum(c)
#    one(T)-c1-c2-c3-c4+c1*muh+c2*mu+c3*muh*mu+c4*mu^2
    for i=1:4
      imu += c[i]*z^i
    end
    fobs += s[j]*ds*dphi*imu
  end
  norm = 1.0-sum(c)
 #omega = (1.0-c1-c2-c3-c4)+0.8*c1+2.0*c2/3.0+4.0*c3/7.0+c4/2.0
  for i=1:4
    norm += 4c[i]/(4.0+i)
  end
  fobs /= norm
#  fobs = 1-fobs
  return fobs
else
  return 1.0
end
end

nx = 1024
if VERSION >= v"0.7"
 x = range(-1.2,stop=1.2,length=nx)
else
 x = linspace(-1.2,1.2,nx)
end
y = 0.0
b = sqrt.(x.^2 .+ y.^2)
c1 = 0.2; c2 = 0.2; c3 = 0.2; c4 = 0.2
r=0.1; flux1 = zeros(nx); flux2 = zeros(nx)
omega = (1.0-c1-c2-c3-c4)+0.8*c1+2.0*c2/3.0+4.0*c3/7.0+c4/2.0
ns = 5000
for i=1:nx
  if b[i] < 1.0+r
    flux1[i]=occult_nonlinear(r,b[i],c1,c2,c3,c4)/omega
    flux2[i]=transit_poly_int(r,b[i],[c1,c2,c3,c4],ns)
  end
end

using PyPlot
clf()
plot(x, 1.0 .- flux1/pi)
flux1 = 1.0 .- flux1/pi
plot(x, 1.0 .- flux2/pi)
flux2 = 1.0 .- flux2/pi
# The following two lines are used to check occult_nonlinear
# when c1 = c3 = 0, which yields the quadratic case:
#u2 = -c4; u1 = c2-2*u2
#fquad = occultquad.(b,u1,u2,r)
#plot(x,fquad,linestyle="--")
savefig("occult_nonlinear.pdf", bbox_inches="tight")

# Now, optimize a fit with polynomial limb-darkening:

function optimize_fit!(r::T,b::Array{T,1},fobs::Array{T,1},fmod::Array{T,1},u_n::Array{T,1}) where {T <: Real}
  nb = length(b)   # Number of points in "light curve"
  nu = length(u_n) # Number of limb-darkening coefficients
  # Optimizes a polynomial limb-darkening model to "data":
  # Compute a limb-darkening model with the impact parameter grid b
  # Initialize:
  trans = transit_init(r,b[1],u_n,true)

  # Compute model:
  function transit_model(u)
    fmodel = zeros(length(b))
    trans = transit_init(r,b[1],u,true)
    for i=1:nb
      trans.b = b[i]
      fmodel[i] = transit_poly!(trans)
    end
    return fmodel
  end

  # Compute "chi-square":
  function transit_fit(u)
    chisquare = 0.0
    trans = transit_init(r,b[1],u,true)
    for i=1:nb
      trans.b = b[i]
      chisquare += (fobs[i]-transit_poly!(trans))^2
    end
    return chisquare
  end

  # Compute gradient of chi-square with respect to limb-darkening coefficients:
  function transit_gradient!(G,u)
    fill!(G,0.0)
    trans = transit_init(r,b[1],u,true)
    for i=1:nb
      trans.b = b[i]
      fmodel = transit_poly!(trans)
      for j = 1:trans.n
        G[j] -= 2*(fobs[i]-fmodel)*trans.dfdu[j]
      end
    end
    return
  end

  # Now, optimize limb-darkening coefficients of the fit:
  result = optimize(transit_fit, transit_gradient! , u_n, LBFGS())
  # Then, compute the model again:
  u_n .= result.minimizer
  println("optimized limb-darkening params: ")
  for i = 1:length(u_n)
    @printf("%.16f, ", u_n[i])
  end
  println("")
return transit_model(u_n)
end

function optimize_fit_linear!(r::T,b::Array{T,1},fobs::Array{T,1},fmod::Array{T,1},u_n::Array{T,1}) where {T <: Real}
  nb = length(b)   # Number of points in "light curve"
  nu = length(u_n) # Number of limb-darkening coefficients
  # Optimizes a polynomial limb-darkening model to "data":
  # Compute a limb-darkening model with the impact parameter grid b
  # Initialize:
  trans = transit_init(r,b[1],u_n,true)
  # Compute model & gradient at each point:
  smat = zeros(T,nu+1,nu+1); fdots = zeros(T,nu+1)
  for i=1:nb
    trans.b = b[i]
    fcur = transit_poly!(trans)
    for j=1:nu+1
      fdots[j] += fobs[i]*trans.sn[j]
      for k=1:nu+1
        smat[j,k] += trans.sn[j]*trans.sn[k]
      end
    end
  end
  # Now, invert to find coefficients of:  f_obs = \sum_j \alpha_j s_j:
  alpha = \(smat,fdots)
  fill!(fmod,zero(T))
  for i=1:nb
    trans.b = b[i]
    fcur = transit_poly!(trans)
    for j=1:nu+1
      fmod[i] += alpha[j]*trans.sn[j]
    end
  end
return fmod
end

clf()
#plot(b,flux)
fig,axes = subplots(2,1, figsize=(8,8))
ax = axes[2]
dev = zeros(6)
flux = flux1
fmod = copy(flux)
#u_n = [.8]
#fmod = optimize_fit!(r,b,flux,fmod,u_n)
#plot(b,flux-fmod,linestyle="--",label="linear")
u_n = [0.1,0.8]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="quadratic")
println("quad: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
u_n = [0.1,0.1,0.1]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
#ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="cubic")
println("cubic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
u_n = [0.01,0.01,0.01,0.8]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="quartic")
println("quartic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
u_n = [0.01,0.01,0.01,0.01,0.8]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
#ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="quintic")
println("quintic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
u_n = [0.01,0.01,0.01,0.01,0.01,0.8]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
ax[:semilogy](b,abs.((flux-fmod)),linestyle="--",label="sextic")
println("sextic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
#u_n = [0.01,0.01,0.01,0.01,0.01,0.01,0.8]
#u_n = push!(u_n,-0.01)
#fmod = optimize_fit!(r,b,flux,fmod,u_n)
#ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="septic")
#println("septic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
#u_n = [0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]
#fmod = optimize_fit!(r,b,flux,fmod,u_n)
#plot(b,flux-fmod,linestyle="--",label="octic")
#println("octic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
#u_n = [.1,.1,.1,.1,.1,.1,.1,.1,.1]
#fmod = optimize_fit!(r,b,flux,fmod,u_n)
#plot(b,flux-fmod,linestyle="--",label="nonic")
#println("nonic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
ax[:legend](loc = "upper left",fontsize=10)
ax[:set_ylabel]("Deviation of fit", fontsize=16)
ax[:set_xlabel](L"$b$", fontsize=18)
ax[:axis]([0,1.2,-10,10])
ax = axes[1]

ax[:plot](b,flux,linewidth=2,label="non-linear")
ax[:plot](b,fmod,linestyle="--",label="sextic",linewidth=2)
ax[:set_title](L"$c_1 = c_2=c_3=c_4=0.2$")
ax[:legend](loc="upper left", fontsize=10)
ax[:axis]([0,1.2,0.984,1.001])
ax[:set_ylabel]("Relative flux", fontsize=16)

show()

savefig("occult_nonlinear_poly.pdf",bbox_inches="tight")

ext: jl

max_issues_repo_name: rodluger/limbdark


text:
"""
$(DocStringExtensions.README)
"""
module GalacticOptim

using DocStringExtensions
using Reexport
@reexport using DiffEqBase
@reexport using SciMLBase
using Requires
using DiffResults, ForwardDiff, Zygote, ReverseDiff, Tracker, FiniteDiff
@reexport using Optim, Flux
using Logging, ProgressLogging, Printf, ConsoleProgressMonitor, TerminalLoggers, LoggingExtras
using ArrayInterface, Base.Iterators

using ForwardDiff: DEFAULT_CHUNK_THRESHOLD
import SciMLBase: OptimizationProblem, OptimizationFunction, AbstractADType, __solve

import ModelingToolkit
import ModelingToolkit: AutoModelingToolkit
export AutoModelingToolkit

include("solve.jl")
include("function.jl")

export solve

export BBO, CMAEvolutionStrategyOpt

end # module


text:
using Documenter
using Automa

# run(`julia actions.jl`)
# run(`julia preconditions.jl`)

makedocs(
    format = :html,
    sitename = "Automa.jl",
    modules = [Automa],
    pages = [
        "Home" => "index.md",
        "References" => "references.md"
    ]
)

deploydocs(
    repo = "github.com/BioJulia/Automa.jl.git",
    julia = "0.6",
    target = "build",
    deps = nothing,
    make = nothing
)


text:
using POMDPs
using ContinuousPOMDPTreeSearchExperiments
using POMDPToolbox
using BasicPOMCP
using POMCPOW
using ParticleFilters
using CPUTime
using DataFrames
using ARDESPOT
using Distributions
using LaserTag
using DiscreteValueIteration

@everywhere begin
    using LaserTag
    using POMDPs
    using ParticleFilters
    using POMDPToolbox
    using ContinuousPOMDPTreeSearchExperiments
end

function gen_sims(x::Vector{Float64}, n, k, seed)
    c = max(0.0, x[1])
    k_obs = x[2]
    @assert k_obs >= 1.0
    inv_alpha_obs = x[3]
    @assert inv_alpha_obs >= 0.1

    problems, ropols = gen_problems_and_ropols(n, seed)

    sims = []

    for i in 1:n
        rng = MersenneTwister(0)
        ro = ropols[i]
        solver = POMCPOWSolver(tree_queries=10_000_000,
                               criterion=MaxUCB(c),
                               final_criterion=MaxTries(),
                               max_depth=90,
                               max_time=1.0,
                               k_observation=k_obs,
                               alpha_observation=1/inv_alpha_obs,
                               estimate_value=FOValue(ro),
                               enable_action_pw=false,
                               check_repeat_obs=true,
                               # default_action=ReportWhenUsed(TagAction(false, 0.0)),
                               # default_action=TagAction(false, 0.0),
                               rng=rng
                              )

        pomdp = problems[i]
        planner = solve(solver, pomdp)
        filter = ObsAdaptiveParticleFilter(deepcopy(pomdp),
                                           LowVarianceResampler(10_000),
                                           0.05,
                                           MersenneTwister(i+10000*k))

        srand(planner, i+40000*k)
        sim = Sim(deepcopy(pomdp),
                  deepcopy(planner),
                  filter,
                  rng=MersenneTwister(i+50_000*k),
                  max_steps=100,
                  metadata=Dict(:i=>i, :k=>k)
                 )
        push!(sims, sim)
    end

    return sims
end

function gen_problems_and_ropols(n, seed)
    rng = MersenneTwister(seed)
    solver = ValueIterationSolver()
    tuples = pmap(1:n) do i
        problem = gen_lasertag(rng=rng)
        pol = solve(solver, problem)
        return (problem, pol)
    end
    problems = collect(first(t) for t in tuples)
    ropols = collect(last(t) for t in tuples)
    return problems, ropols
end

start_mean = [80.0, 4.0, 15.0]
start_cov = diagm([40.0^2, 10.0^2, 10.0^2])
d = MvNormal(start_mean, start_cov)
rng = MersenneTwister(15)
K = 60  # 200
n = 100 # 40
m = 15  # 50
max_iters = 100

for i in 1:max_iters
    sims = []
    params = Vector{Vector{Float64}}(K)
    print("creating $K simulation sets")
    for k in 1:K
        p = rand(d)
        p[2] = max(1.0, p[2])
        p[3] = max(0.1, p[3])
        params[k] = p
        k_sims = gen_sims(p, n, k, i)
        print(".")
        append!(sims, k_sims)
    end
    println()
    results = run_parallel(sims)
    # results = run(sims)
    combined = by(results, :k) do df
        DataFrame(mean_reward=mean(df[:reward]))
    end
    @show mean(combined[:mean_reward])
    order = sortperm(combined[:mean_reward])
    elite = params[combined[:k][order[K-m:end]]]
    elite_matrix = Matrix{Float64}(length(start_mean), m)
    for k in 1:m
        elite_matrix[:,k] = elite[k]
    end
    try
        d = fit(typeof(d), elite_matrix)
    catch ex
        if ex isa Base.LinAlg.PosDefException
            println("pos def exception")
            d = fit(typeof(d), elite_matrix += 0.01*randn(size(elite_matrix)))
        else
            rethrow(ex)
        end
    end
    println("iteration $i")
    @show mean(d)
    @show ev = eigvals(cov(d))
    for j in 1:length(ev)
        @show eigvecs(cov(d))[:,j]
    end
end


text:
function quinn_fernandes(signal, seed)
	eps = 1.0e-6

	n = length(signal)
	a = 2.0*cos(seed)
	b = 0.1
	signal = vcat([0,0], signal)
	xi = zeros(size(signal))

	it = 0
	while (abs(a-b) > eps)
		it = it+1
		if (it > 1000)
			b = NaN
			break
		end
		a = 2*b - a
		for i=3:(n-1)
			xi[i] = signal[i] + a*xi[i-1] - xi[i-2]
		end
		b = sum([(xi[i]+xi[i-2])*xi[i-1] for i=3:(n-1)])/sum([xi[i-1]^2 for i=3:(n-1)])
	end

	acos(0.5*b)
end


text:
"""
Abstract type to encapsulate all solver types that manages other solvers.
"""
abstract type MetaSolver <: AbstractSolver end

meta_id(s) = s.meta_local_id[1]

make_id(meta, id, ::Val{:sub}) = (meta, id)

function _check_subs(s::MetaSolver)
    if is_sat(s)
        for (id, ss) in enumerate(s.subs)
            has_solution(ss) && return id
        end
    else
        for (id, ss) in enumerate(s.subs)
            bs = is_empty(s.pool) ? nothing : best_value(s)
            bss = is_empty(ss.pool) ? nothing : best_value(ss)
            isnothing(bs) && (isnothing(bss) ? continue : return id)
            isnothing(bss) ? continue : (bss < bs && return id)
        end
    end
    return 0
end

function solve_for_loop!(s::MetaSolver, stop, sat, iter, st)
    @threads for id in 1:min(nthreads(), get_option(s, "threads"))
        if id == 1
            add_time!(s, 3) # only used by MainSolver
            remote_dispatch!(s) # only used by MainSolver
            add_time!(s, 4) # only used by MainSolver
            solve_while_loop!(s, stop, sat, iter, st)
            atomic_or!(stop, true)
        else
            solve!(s.subs[id - 1], stop)
        end
    end
end


text:
using LightGraphs
using MetaGraphs

"""
    disconnect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol)

Remove any parameter connections for a given parameter `param_name` in a given component
`comp_name` of model `md`.
"""
function disconnect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol)
    # println("disconnect_param!($comp_name, $param_name)")
    filter!(x -> !(x.dst_comp_name == comp_name && x.dst_par_name == param_name), internal_param_conns(md))
    filter!(x -> !(x.comp_name == comp_name && x.param_name == param_name), external_param_conns(md))
end

# Default string, string unit check function
function verify_units(one::AbstractString, two::AbstractString)
    # True if and only if they match
    return one == two
end

function _check_labels(md::ModelDef, comp_def::ComponentDef, param_name::Symbol, ext_param::ArrayModelParameter)
    param_def = parameter(comp_def, param_name)

    t1 = eltype(ext_param.values)
    t2 = eltype(datatype(param_def))
    if !(t1 <: t2)
        error("Mismatched datatype of parameter connection: Component: $(comp_def.comp_id) ($t1), Parameter: $param_name ($t2)")
    end

    comp_dims  = dimensions(param_def)
    param_dims = dimensions(ext_param)

    if ! isempty(param_dims) && size(param_dims) != size(comp_dims)
        d1 = size(comp_dims)
        d2 = size(param_dims)
        error("Mismatched dimensions of parameter connection: Component: $(comp_def.comp_id) ($d1), Parameter: $param_name ($d2)")
    end

    # Don't check sizes for ConnectorComps since they won't match.
    if name(comp_def) in (:ConnectorCompVector, :ConnectorCompMatrix)
        return nothing
    end

    # index_values = indexvalues(md)

    for (i, dim) in enumerate(comp_dims)
        if isa(dim, Symbol)
            param_length = size(ext_param.values)[i]
            if dim == :time
                t = dimensions(md)[:time]
                first = first_period(md, comp_def)
                last = last_period(md, comp_def)
                comp_length = t[last] - t[first] + 1
            else
                comp_length = dim_count(md, dim)
            end
            if param_length != comp_length
                error("Mismatched data size for a parameter connection: dimension :$dim in $(comp_def.comp_id) has $comp_length elements; external parameter :$param_name has $param_length elements.")
            end
        end
    end
end

"""
    connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol)

Connect a parameter `param_name` in the component `comp_name` of model `md` to
the external parameter `ext_param_name`.
"""
function connect_param!(md::ModelDef, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol)
    comp_def = compdef(md, comp_name)
    ext_param = external_param(md, ext_param_name)

    if isa(ext_param, ArrayModelParameter)
        _check_labels(md, comp_def, param_name, ext_param)
    end

    disconnect_param!(md, comp_name, param_name)

    conn = ExternalParameterConnection(comp_name, param_name, ext_param_name)
    add_external_param_conn(md, conn)

    return nothing
end

"""
    connect_param!(md::ModelDef, dst_comp_name::Symbol, dst_par_name::Symbol,
        src_comp_name::Symbol, src_var_name::Symbol backup::Union{Nothing, Array}=nothing;
        ignoreunits::Bool=false, offset::Int=0)

Bind the parameter `dst_par_name` of one component `dst_comp_name` of model `md`
to a variable `src_var_name` in another component `src_comp_name` of the same model
using `backup` to provide default values and the `ignoreunits` flag to indicate the need
to check match units between the two.  The `offset` argument indicates the offset between the destination
and the source ie. the value would be `1` if the destination component parameter
should only be calculated for the second timestep and beyond.
"""
function connect_param!(md::ModelDef,
                           dst_comp_name::Symbol, dst_par_name::Symbol,
                           src_comp_name::Symbol, src_var_name::Symbol,
                           backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0)

    # remove any existing connections for this dst parameter
    disconnect_param!(md, dst_comp_name, dst_par_name)

    dst_comp_def = compdef(md, dst_comp_name)
    src_comp_def = compdef(md, src_comp_name)

    if backup !== nothing
        # If value is a NamedArray, we can check if the labels match
        if isa(backup, NamedArray)
            dims = dimnames(backup)
            check_parameter_dimensions(md, backup, dims, dst_par_name)
        else
            dims = nothing
        end

        # Check that the backup data is the right size
        if size(backup) != datum_size(md, dst_comp_def, dst_par_name)
            error("Cannot connect parameter; the provided backup data is the wrong size. Expected size $(datum_size(md, dst_comp_def, dst_par_name)) but got $(size(backup)).")
        end

        # some other check for second dimension??
        dst_param = parameter(dst_comp_def, dst_par_name)
        dst_dims  = dimensions(dst_param)

        backup = convert(Array{number_type(md)}, backup) # converts number type and, if it's a NamedArray, it's converted to Array
        first = first_period(md, dst_comp_def)
        T = eltype(backup)

        dim_count = length(dst_dims)

        if dim_count == 0
            values = backup
        else

            if isuniform(md)
                # use the first from the comp_def not the ModelDef
                _, stepsize = first_and_step(md)
                values = TimestepArray{FixedTimestep{first, stepsize}, T, dim_count}(backup)
            else
                times = time_labels(md)
                # use the first from the comp_def
                first_index = findfirst(isequal(first), times)
                values = TimestepArray{VariableTimestep{(times[first_index:end]...,)}, T, dim_count}(backup)
            end

        end

        set_external_array_param!(md, dst_par_name, values, dst_dims)
        backup_param_name = dst_par_name

    else
        # If backup not provided, make sure the source component covers the span of the destination component
        src_first, src_last = first_period(md, src_comp_def), last_period(md, src_comp_def)
        dst_first, dst_last = first_period(md, dst_comp_def), last_period(md, dst_comp_def)
        if dst_first < src_first || dst_last > src_last
            error("Cannot connect parameter; $src_comp_name only runs from $src_first to $src_last, whereas $dst_comp_name runs from $dst_first to $dst_last. Backup data must be provided for missing years. Try calling:
    `connect_param!(m, comp_name, par_name, comp_name, var_name, backup_data)`")
        end

        backup_param_name = nothing
    end

    # Check the units, if provided
    if ! ignoreunits && ! verify_units(variable_unit(md, src_comp_name, src_var_name),
                                       parameter_unit(md, dst_comp_name, dst_par_name))
        error("Units of $src_comp_name.$src_var_name do not match $dst_comp_name.$dst_par_name.")
    end

    # println("connect($src_comp_name.$src_var_name => $dst_comp_name.$dst_par_name)")
    conn = InternalParameterConnection(src_comp_name, src_var_name, dst_comp_name, dst_par_name, ignoreunits, backup_param_name, offset=offset)
    add_internal_param_conn(md, conn)

    return nothing
end

"""
    connect_param!(md::ModelDef, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol},
        backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0)

Bind the parameter `dst[2]` of one component `dst[1]` of model `md`
to a variable `src[2]` in another component `src[1]` of the same model
using `backup` to provide default values and the `ignoreunits` flag to indicate the need
to check match units between the two.  The `offset` argument indicates the offset
between the destination and the source ie. the value would be `1` if the destination
component parameter should only be calculated for the second timestep and beyond.
"""
function connect_param!(md::ModelDef, dst::Pair{Symbol, Symbol}, src::Pair{Symbol, Symbol},
                           backup::Union{Nothing, Array}=nothing; ignoreunits::Bool=false, offset::Int=0)
    connect_param!(md, dst[1], dst[2], src[1], src[2], backup; ignoreunits=ignoreunits, offset=offset)
end

"""
    connected_params(md::ModelDef, comp_name::Symbol)

Return list of parameters that have been set for component `comp_name` in model `md`.
"""
function connected_params(md::ModelDef, comp_name::Symbol)
    ext_set_params = map(x->x.param_name,   external_param_conns(md, comp_name))
    int_set_params = map(x->x.dst_par_name, internal_param_conns(md, comp_name))

    return union(ext_set_params, int_set_params)
end

"""
    unconnected_params(md::ModelDef)

Return a list of tuples (componentname, parametername) of parameters
that have not been connected to a value in the model `md`.
"""
function unconnected_params(md::ModelDef)
    unconnected = Vector{Tuple{Symbol,Symbol}}()

    for comp_def in compdefs(md)
        comp_name = name(comp_def)
        params = parameter_names(comp_def)
        connected = connected_params(md, comp_name)
        append!(unconnected, map(x->(comp_name, x), setdiff(params, connected)))
    end

    return unconnected
end

"""
    set_leftover_params!(m::Model, parameters::Dict)

Set all of the parameters in model `m` that don't have a value and are not connected
to some other component to a value from a dictionary `parameters`. This method assumes
the dictionary keys are strings that match the names of unset parameters in the model.
"""
function set_leftover_params!(md::ModelDef, parameters::Dict{T, Any}) where T
    parameters = Dict(k => v for (k, v) in parameters)
    leftovers = unconnected_params(md)
    external_params = md.external_params

    for (comp_name, param_name) in leftovers
        # check whether we need to set the external parameter
        if ! haskey(md.external_params, param_name)
            value = parameters[string(param_name)]
            param_dims = parameter_dimensions(md, comp_name, param_name)

            set_external_param!(md, param_name, value; param_dims = param_dims)

        end
        connect_param!(md, comp_name, param_name, param_name)
    end
    nothing
end

internal_param_conns(md::ModelDef) = md.internal_param_conns

external_param_conns(md::ModelDef) = md.external_param_conns

# Find internal param conns to a given destination component
function internal_param_conns(md::ModelDef, dst_comp_name::Symbol)
    return filter(x->x.dst_comp_name == dst_comp_name, internal_param_conns(md))
end

# Find external param conns for a given comp
function external_param_conns(md::ModelDef, comp_name::Symbol)
    return filter(x -> x.comp_name == comp_name, external_param_conns(md))
end

function external_param(md::ModelDef, name::Symbol)
    try
        return md.external_params[name]
    catch err
        if err isa KeyError
            error("$name not found in external parameter list")
        else
            rethrow(err)
        end
    end
end

function add_internal_param_conn(md::ModelDef, conn::InternalParameterConnection)
    push!(md.internal_param_conns, conn)
end

function add_external_param_conn(md::ModelDef, conn::ExternalParameterConnection)
    push!(md.external_param_conns, conn)
end

function set_external_param!(md::ModelDef, name::Symbol, value::ModelParameter)
    md.external_params[name] = value
end

function set_external_param!(md::ModelDef, name::Symbol, value::Number; param_dims::Union{Nothing,Array{Symbol}} = nothing)
    set_external_scalar_param!(md, name, value)
end

function set_external_param!(md::ModelDef, name::Symbol, value::Union{AbstractArray, AbstractRange, Tuple};
                             param_dims::Union{Nothing,Array{Symbol}} = nothing)
    if param_dims[1] == :time
        value = convert(Array{md.number_type}, value)
        num_dims = length(param_dims)
        values = get_timestep_array(md, eltype(value), num_dims, value)
    else
        values = value
    end

    set_external_array_param!(md, name, values, param_dims)
end

"""
    set_external_array_param!(md::ModelDef, name::Symbol, value::TimestepVector, dims)

Add a one dimensional time-indexed array parameter indicated by `name` and
`value` to the model `md`.  In this case `dims` must be `[:time]`.
"""
function set_external_array_param!(md::ModelDef, name::Symbol, value::TimestepVector, dims)
    # println("set_external_array_param!: dims=$dims, setting dims to [:time]")
    param = ArrayModelParameter(value, [:time])  # must be :time
    set_external_param!(md, name, param)
end

"""
    set_external_array_param!(md::ModelDef, name::Symbol, value::TimestepMatrix, dims)

Add a multi-dimensional time-indexed array parameter `name` with value
`value` to the model `md`.  In this case `dims` must be `[:time]`.
"""
function set_external_array_param!(md::ModelDef, name::Symbol, value::TimestepArray, dims)
    param = ArrayModelParameter(value, dims === nothing ? Vector{Symbol}() : dims)
    set_external_param!(md, name, param)
end

"""
    set_external_array_param!(m::Model, name::Symbol, value::AbstractArray, dims)

Add an array type parameter `name` with value `value` and `dims` dimensions to the model 'm'.
"""
function set_external_array_param!(md::ModelDef, name::Symbol, value::AbstractArray, dims)
    numtype = md.number_type

    if !(typeof(value) <: Array{numtype})
        numtype = number_type(md)
        # Need to force a conversion (simple convert may alias in v0.6)
        value = Array{numtype}(undef, value)
    end
    param = ArrayModelParameter(value, dims === nothing ? Vector{Symbol}() : dims)
    set_external_param!(md, name, param)
end

"""
    set_external_scalar_param!(md::ModelDef, name::Symbol, value::Any)

Add a scalar type parameter `name` with the value `value` to the model `md`.
"""
function set_external_scalar_param!(md::ModelDef, name::Symbol, value::Any)
    p = ScalarModelParameter(value)
    set_external_param!(md, name, p)
end

"""
    update_param!(md::ModelDef, name::Symbol, value; update_timesteps = false)

Update the `value` of an external model parameter in ModelDef `md`, referenced
by `name`. Optional boolean argument `update_timesteps` with default value
`false` indicates whether to update the time keys associated with the parameter
values to match the model's time index.
"""
function update_param!(md::ModelDef, name::Symbol, value; update_timesteps = false)
    _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; raise_error = true)
end

function _update_param!(md::ModelDef, name::Symbol, value, update_timesteps; raise_error = true)
    ext_params = md.external_params
    if ! haskey(ext_params, name)
        error("Cannot update parameter; $name not found in model's external parameters.")
    end

    param = ext_params[name]

    if param isa ScalarModelParameter
        if update_timesteps && raise_error
            error("Cannot update timesteps; parameter $name is a scalar parameter.")
        end
        _update_scalar_param!(param, value)
    else
        _update_array_param!(md, name, value, update_timesteps, raise_error)
    end

end

function _update_scalar_param!(param::ScalarModelParameter, value)
    if ! (value isa typeof(param.value))
        try
            value = convert(typeof(param.value), value)
        catch e
            error("Cannot update parameter $name; expected type $(typeof(param.value)) but got $(typeof(value)).")
        end
    end
    param.value = value
    nothing
end

function _update_array_param!(md::ModelDef, name, value, update_timesteps, raise_error)
    # Get original parameter
    param = md.external_params[name]

    # Check type of provided parameter
    if !(typeof(value) <: AbstractArray)
        error("Cannot update array parameter $name with a value of type $(typeof(value)).")
    elseif !(eltype(value) <: eltype(param.values))
        try
            value = convert(Array{eltype(param.values)}, value)
        catch e
            error("Cannot update parameter $name; expected array of type $(eltype(param.values)) but got $(eltype(value)).")
        end
    end

    # Check size of provided parameter
    if update_timesteps && param.values isa TimestepArray
        expected_size = ([length(dim_keys(md, d)) for d in param.dimensions]...,)
    else
        expected_size = size(param.values)
    end
    if size(value) != expected_size
        error("Cannot update parameter $name; expected array of size $expected_size but got array of size $(size(value)).")
    end

    if update_timesteps
        if param.values isa TimestepArray
            T = eltype(value)
            N = length(size(value))
            new_timestep_array = get_timestep_array(md, T, N, value)
            md.external_params[name] = ArrayModelParameter(new_timestep_array, param.dimensions)
        elseif raise_error
            error("Cannot update timesteps; parameter $name is not a TimestepArray.")
        else
            param.values = value
        end
    else
        if param.values isa TimestepArray
            param.values.data = value
        else
            param.values = value
        end
    end
    nothing
end

"""
    update_params!(md::ModelDef, parameters::Dict{T, Any}; update_timesteps = false) where T

For each (k, v) in the provided `parameters` dictionary, update_param!
is called to update the external parameter by name k to value v, with optional
Boolean argument update_timesteps. Each key k must be a symbol or convert to a
symbol matching the name of an external parameter that already exists in the
model definition.
"""
function update_params!(md::ModelDef, parameters::Dict; update_timesteps = false)
    parameters = Dict(Symbol(k) => v for (k, v) in parameters)
    for (param_name, value) in parameters
        _update_param!(md, param_name, value, update_timesteps; raise_error = false)
    end
    nothing
end

function add_connector_comps(md::ModelDef)
     conns = md.internal_param_conns        # we modify this, so we don't use functional API

    for comp_def in compdefs(md)
        comp_name = name(comp_def)

        # first need to see if we need to add any connector components for this component
        internal_conns  = filter(x -> x.dst_comp_name == comp_name, conns)
        need_conn_comps = filter(x -> x.backup !== nothing, internal_conns)

        # println("Need connectors comps: $need_conn_comps")

        for (i, conn) in enumerate(need_conn_comps)
            push!(md.backups, conn.backup)

            num_dims = length(size(external_param(md, conn.backup).values))

            if ! (num_dims in (1, 2))
                error("Connector components for parameters with > 2 dimensions are not implemented.")
            end

            # Fetch the definition of the appropriate connector commponent
            conn_name = num_dims == 1 ? :ConnectorCompVector : :ConnectorCompMatrix
            conn_comp_def = compdef(conn_name)
            conn_comp_name = connector_comp_name(i)

            # Add the connector component before the user-defined component that required it
            # println("add_connector_comps: add_comp!(md, $(conn_comp_def.comp_id), $conn_comp_name, before=$comp_name)")
            add_comp!(md, conn_comp_def, conn_comp_name, before=comp_name)

            # add a connection between src_component and the ConnectorComp
            push!(conns, InternalParameterConnection(conn.src_comp_name, conn.src_var_name,
                                                     conn_comp_name, :input1,
                                                     conn.ignoreunits))

            # add a connection between ConnectorComp and dst_component
            push!(conns, InternalParameterConnection(conn_comp_name, :output,
                                                     conn.dst_comp_name, conn.dst_par_name,
                                                     conn.ignoreunits))

            # add a connection between ConnectorComp and the external backup data
            push!(md.external_param_conns, ExternalParameterConnection(conn_comp_name, :input2, conn.backup))

            src_comp_def = compdef(md, conn.src_comp_name)
            set_param!(md, conn_comp_name, :first, first_period(md, src_comp_def))
            set_param!(md, conn_comp_name, :last, last_period(md, src_comp_def))

        end
    end

    # Save the sorted component order for processing
    # md.sorted_comps = _topological_sort(md)

    return nothing
end

#
# Support for automatic ordering of components
#

"""
    dependencies(md::ModelDef, comp_name::Symbol)

Return the set of component names that `comp_name` in `md` depends one, i.e.,
sources for which `comp_name` is the destination of an internal connection.
"""
function dependencies(md::ModelDef, comp_name::Symbol)
    conns = internal_param_conns(md)
    # For the purposes of the DAG, we don't treat dependencies on [t-1] as an ordering constraint
    deps = Set(c.src_comp_name for c in conns if (c.dst_comp_name == comp_name && c.offset == 0))
    return deps
end

"""
    comp_graph(md::ModelDef)

Return a MetaGraph containing a directed (LightGraph) graph of the components of
ModelDef `md`. Each vertex has a :name property with its component name.
"""
function comp_graph(md::ModelDef)
    comp_names = collect(compkeys(md))
    graph = MetaDiGraph()

    for comp_name in comp_names
        add_vertex!(graph, :name, comp_name)
    end

    set_indexing_prop!(graph, :name)

    for comp_name in comp_names
        for dep_name in dependencies(md, comp_name)
            src = graph[dep_name,  :name]
            dst = graph[comp_name, :name]
            add_edge!(graph, src, dst)
        end
    end

    #TODO:  for now we can allow cycles since we aren't using the offset
    # if is_cyclic(graph)
    #     error("Component graph contains a cycle")
    # end

    return graph
end

"""
    _topological_sort(md::ModelDef)

Build a directed acyclic graph referencing the positions of the components in
the OrderedDict of model `md`, tracing dependencies to create the DAG.
Perform a topological sort on the graph for the given model and return a vector
of component names in the order that will ensure dependencies are processed
prior to dependent components.
"""
function _topological_sort(md::ModelDef)
    graph = comp_graph(md)
    ordered = topological_sort_by_dfs(graph)
    names = map(i -> graph[i, :name], ordered)
    return names
end


text:
function main()

    io = open("argon.txt", "w");
    io_lp = open("argon_low_pressure.txt", "w");

    B1 = 20332.29e-8
    B2 = 34458.31e-8
    C1 = 206.12e-6*(1e-6)^2
    C2 = 8.066e-3*(1e-6)^2
    p0 = 1
    k0(λ) = 2π/λ
    a = 14.9e-6
    T = 273+20
    T0 = 273
    λ0 = 800e-9
    δ(λ) = T0 / T * (B1 * λ^2/(λ^2 - C1) + B2 * λ^2 / (λ^2 - C2))

    n(λ, p, u) = sqrt(1 + δ(λ)*p/p0 - (u / k0(λ) / a)^2 )

    λs = range(400e-9, 1000e-9, length=2000)

    e11 = 2.405
    e12 = 5.52
    e13 = 8.6573
    e23 = 10.1735
    e33 = 11.6198
    e43 = 13.0152

    for λ in λs
        p = 5.1
        p2 = 0.1
        res11 = (n(λ, p, e11) - n(λ/3, p, e11)) * 10^4
        res11_lp = (n(λ, p2, e11) - n(λ/3, p2, e11)) * 10^4
        res12 = (n(λ, p, e11) - n(λ/3, p, e12)) * 10^4
        res13 = (n(λ, p, e11) - n(λ/3, p, e13)) * 10^4
        res23 = (n(λ, p, e11) - n(λ/3, p, e23)) * 10^4
        res33 = (n(λ, p, e11) - n(λ/3, p, e33)) * 10^4
        res43 = (n(λ, p, e11) - n(λ/3, p, e43)) * 10^4
        write(io, "$(λ*10^9) \t $res11 \t $res12 \t $res13 \t $res23 \t $res33 \t $res43 \n")
        write(io_lp, "$(λ*10^9) \t $res11_lp\n")
    end
    close(io)
    close(io_lp)
end
main()


text:
Base.promote_rule(::Type{PT}, ::Type{PS}) where {PT<:APL, PS<:APL} = promote_type(polynomialtype(PT), polynomialtype(PS))
Base.promote_rule(::Type{PT}, ::Type{PT}) where {PT<:APL} = PT

promote_rule_constant(::Type{T}, ::Type{RationalPoly{NT, DT}}) where {T, NT, DT} = RationalPoly{promote_type(T, NT), promote_type(DT, termtype(DT))}

Base.promote_rule(::Type{PT}, ::Type{T}) where {T, PT<:APL} = promote_rule_constant(T, PT)
Base.promote_rule(::Type{T}, ::Type{PT}) where {T, PT<:APL} = promote_rule_constant(T, PT)
Base.promote_rule(::Type{T}, ::Type{RT}) where {T, RT<:RationalPoly} = promote_rule_constant(T, RT)
Base.promote_rule(::Type{RT}, ::Type{T}) where {T, RT<:RationalPoly} = promote_rule_constant(T, RT)

promote_rule_rational(::Type{PT}, ::Type{RationalPoly{S, T}}) where {PT<:APL, S, T} = RationalPoly{promote_type(PT, S), promote_type(T, termtype(T))}
promote_rule_rational(::Type{RationalPoly{S, T}}, ::Type{RationalPoly{U, V}}) where {S, T, U, V} = RationalPoly{promote_type(S, U), promote_type(T, V)}

Base.promote_rule(::Type{RS}, ::Type{RT}) where {RS<:RationalPoly, RT<:RationalPoly} = promote_rule_rational(RS, RT)
Base.promote_rule(::Type{PT}, ::Type{RT}) where {PT<:APL, RT<:RationalPoly} = promote_rule_rational(PT, RT)
Base.promote_rule(::Type{RT}, ::Type{PT}) where {PT<:APL, RT<:RationalPoly} = promote_rule_rational(PT, RT)

#promote_rule(::Type{Term{C, U}}, ::Type{RationalPoly{C, S, T}}) where {C, S, T, U} = RationalPoly{C, promote_type(U, S), T}
#promote_rule(::Type{RationalPoly{C, S, T}}, ::Type{Term{C, U}}) where {C, S, T, U} = RationalPoly{C, promote_type(U, S), T}
#promote_rule(::Type{Polynomial{C, U}}, ::Type{RationalPoly{C, S, T}}) where {C, S, T, U} = RationalPoly{C, promote_type(U, S), T}
#promote_rule(::Type{RationalPoly{C, S, T}}, ::Type{Polynomial{C, U}}) where {C, S, T, U} = RationalPoly{C, promote_type(U, S), T}

function MA.promote_operation(
    op::Union{typeof(+), typeof(-)}, PT::Type{<:APL{S}},
    QT::Type{<:APL{T}}) where {S, T}

    U = MA.promote_operation(op, S, T)
    return polynomialtype(promote_type(monomialtype(PT), monomialtype(QT)), U)
end
function MA.promote_operation(::typeof(*), MT1::Type{<:AbstractMonomialLike},
    MT2::Type{<:AbstractMonomialLike})
    return typeof(constantmonomial(MT1) * constantmonomial(MT2))
end
function MA.promote_operation(::typeof(*), TT::Type{<:AbstractTermLike{S}},
                              ST::Type{<:AbstractTermLike{T}}) where {S, T}
    U = MA.promote_operation(*, S, T)
    return termtype(promote_type(monomialtype(TT), monomialtype(ST)), U)
end
function MA.promote_operation(::typeof(*), PT::Type{<:APL{S}},
                              QT::Type{<:APL{T}}) where {S, T}
    U = MA.promote_operation(MA.add_mul, S, T)
    return polynomialtype(promote_type(monomialtype(PT), monomialtype(QT)), U)
end


text:
module TestS3
using AWS
using AWS.S3
using Base.Test

function check_resp(resp, resp_obj_type=Any)
    info("response: ", resp.obj)
    @test 200 <= resp.http_code <= 206
    @test isa(resp, AWS.S3.S3Response)
    @test isa(resp.obj, resp_obj_type)
end

function test_bucket_ops(env)
    bkt = "1-test-temp-bkt"

    info("list all buckets")
    resp = S3.list_all_buckets(env)
    check_resp(resp, AWS.S3.ListAllMyBucketsResult)
    @test isa(resp.obj.buckets, Vector{AWS.S3.Bucket})

    info("create a bucket")
    acl = S3.S3_ACL()
    acl.acl = "public-read"
    resp = S3.create_bkt(env, bkt, acl=acl, config=AWS.S3.CreateBucketConfiguration(env.region))
    check_resp(resp, String)

    po = S3.PutObjectOptions()
    po.acl = acl

    info("add a file")
    resp = S3.put_object(env, bkt, "file1", "Hello ", options=po)
    check_resp(resp, String)

    info("add another file")
    resp = S3.put_object(env, bkt, "file2", "World ")
    check_resp(resp, String)

    info("add another file")
    resp = S3.put_object(env, bkt, "file3", "Hello World ")
    check_resp(resp, String)

    info("list bucket")
    resp = S3.get_bkt(env, bkt)
    check_resp(resp, AWS.S3.ListBucketResult)
    @test isa(resp.obj.contents, Vector{AWS.S3.Contents})
    @test length(resp.obj.contents) == 3

    info("get file 1")
    resp = S3.get_object(env, bkt, "file1")
    check_resp(resp, Vector{UInt8})
    @test String(resp.obj) == "Hello "

    info("get file 2")
    resp = S3.get_object(env, bkt, "file2")
    check_resp(resp, Vector{UInt8})
    @test String(resp.obj) == "World "

    # sleep to allow some window to run manual debug commands
    #info("Sleep for 10 secs ....")
    #sleep(10)

    info("list objects")
    (bkt,objs) = S3.list_objects(env, bkt, "fi")
    @test collect(objs) == ["file1", "file2", "file3"]

    info("list objects with details")
    (bkt,objs) = S3.list_objects_details(env, bkt, "fi")
    @test [o.key for o in objs] == ["file1", "file2", "file3"]

    info("delete file 1")
    resp = S3.del_object(env, bkt, "file1")
    check_resp(resp, String)

    info("delete file 2 using the multi api")
    resp = S3.del_object_multi(env, bkt, S3.DeleteObjectsType([S3.ObjectType("file2"), S3.ObjectType("file3")]))
    check_resp(resp, AWS.S3.DeleteResult)
    @test isa(resp.obj.deleted, Vector{AWS.S3.Deleted})
    @test length(resp.obj.deleted) == 2

    info("delete bucket")
    resp = S3.del_bkt(env, bkt)
    check_resp(resp, String)
end

function test_endpoint(env)
    info("custom s3 endpoint")
    env2 = AWSEnv(env, ep="http://localhost/")
    try
        S3.list_all_buckets(env2)
        error("expected connect failure")
    catch ex
        @test isa(ex, Base.UVError)
        @test ex.prefix == "connect"
    end
end

function runtests(env, config)
    info("testing s3 bucket ops...")
    test_endpoint(env)
    test_bucket_ops(env)
end

end # module TestS3


text:
# ---
# title: 1600. Throne Inheritance
# id: problem1600
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Tree, Design
# link: <https://leetcode.com/problems/throne-inheritance/description/>
# hidden: true
# ---
#
# A kingdom consists of a king, his children, his grandchildren, and so on.
# Every once in a while, someone in the family dies or a child is born.
#
# The kingdom has a well-defined order of inheritance that consists of the king
# as the first member. Let's define the recursive function `Successor(x,
# curOrder)`, which given a person `x` and the inheritance order so far, returns
# who should be the next person after `x` in the order of inheritance.
#
#
#
#     Successor(x, curOrder):
#         if x has no children or all of x's children are in curOrder:
#             if x is the king return null
#             else return Successor(x's parent, curOrder)
#         else return x's oldest child who's not in curOrder
#
#
# For example, assume we have a kingdom that consists of the king, his children
# Alice and Bob (Alice is older than Bob), and finally Alice's son Jack.
#
#   1. In the beginning, `curOrder` will be `["king"]`.
#   2. Calling `Successor(king, curOrder)` will return Alice, so we append to `curOrder` to get `["king", "Alice"]`.
#   3. Calling `Successor(Alice, curOrder)` will return Jack, so we append to `curOrder` to get `["king", "Alice", "Jack"]`.
#   4. Calling `Successor(Jack, curOrder)` will return Bob, so we append to `curOrder` to get `["king", "Alice", "Jack", "Bob"]`.
#   5. Calling `Successor(Bob, curOrder)` will return `null`. Thus the order of inheritance will be `["king", "Alice", "Jack", "Bob"]`.
#
# Using the above function, we can always obtain a unique order of inheritance.
#
# Implement the `ThroneInheritance` class:
#
#   * `ThroneInheritance(string kingName)` Initializes an object of the `ThroneInheritance` class. The name of the king is given as part of the constructor.
#   * `void birth(string parentName, string childName)` Indicates that `parentName` gave birth to `childName`.
#   * `void death(string name)` Indicates the death of `name`. The death of the person doesn't affect the `Successor` function nor the current inheritance order. You can treat it as just marking the person as dead.
#   * `string[] getInheritanceOrder()` Returns a list representing the current order of inheritance **excluding** dead people.
#
#
#
# **Example 1:**
#
#
#
#     **Input**
#     ["ThroneInheritance", "birth", "birth", "birth", "birth", "birth", "birth", "getInheritanceOrder", "death", "getInheritanceOrder"]
#     [["king"], ["king", "andy"], ["king", "bob"], ["king", "catherine"], ["andy", "matthew"], ["bob", "alex"], ["bob", "asha"], [null], ["bob"], [null]]
#     **Output**
#     [null, null, null, null, null, null, null, ["king", "andy", "matthew", "bob", "alex", "asha", "catherine"], null, ["king", "andy", "matthew", "alex", "asha", "catherine"]]
#
#     **Explanation**
#     ThroneInheritance t= new ThroneInheritance("king"); // order: **king**
#     t.birth("king", "andy"); // order: king > **andy**
#     t.birth("king", "bob"); // order: king > andy > **bob**
#     t.birth("king", "catherine"); // order: king > andy > bob > **catherine**
#     t.birth("andy", "matthew"); // order: king > andy > **matthew** > bob > catherine
#     t.birth("bob", "alex"); // order: king > andy > matthew > bob > **alex** > catherine
#     t.birth("bob", "asha"); // order: king > andy > matthew > bob > alex > **asha** > catherine
#     t.getInheritanceOrder(); // return ["king", "andy", "matthew", "bob", "alex", "asha", "catherine"]
#     t.death("bob"); // order: king > andy > matthew > **~~bob~~** > alex > asha > catherine
#     t.getInheritanceOrder(); // return ["king", "andy", "matthew", "alex", "asha", "catherine"]
#
#
#
#
# **Constraints:**
#
#   * `1 <= kingName.length, parentName.length, childName.length, name.length <= 15`
#   * `kingName`, `parentName`, `childName`, and `name` consist of lowercase English letters only.
#   * All arguments `childName` and `kingName` are **distinct**.
#   * All `name` arguments of `death` will be passed to either the constructor or as `childName` to `birth` first.
#   * For each call to `birth(parentName, childName)`, it is guaranteed that `parentName` is alive.
#   * At most `105` calls will be made to `birth` and `death`.
#   * At most `10` calls will be made to `getInheritanceOrder`.
#
#
## @lc code=start
using LeetCode

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


text:
module MaxwellPDEs

export Medium , epsilon , mu , LorentzianSusceptibility, DrudeSusceptibility, PoleResidueSusceptibility
export DispersionModel , NKTable , PoleResidueDispersionModel , load_nk
# define sim objects using the Structure type
export Structure
# Source Types
export PlaneWaveSource, ModeSource, UniformSource , GaussianBeamSource
# Excitation Types
export Harmonic, Gaussian
# Boundary Conditions
export Bloch,Periodic,PEC, PMC, PMLStable , Absorber
# Symmetry Conditions
export Even, Odd
# Contains all data need to define a simulation
export Simulation

export EField,HField,Ex,Ey,Ez,Hx,Hy,Hz

using Parameters, LinearAlgebra , RecipesBase , DelimitedFiles
using Reexport
@reexport using Meshes

include("UtilTypes.jl")
include("Medium.jl")
include("Structure.jl")
include("SourceTypes.jl")
include("MonitorTypes.jl")
include("BoundaryConditionTypes.jl")
include("Symmetry.jl")
include("Simulation.jl")

end


text:
"""
    readsiggen(file_path::String; T::Type)

Reads a [SigGen](https://github.com/radforddc/icpc_siggen) configuration file (ending in `.config`) in 'file_path'
and returns a dictionary of all parameters. Non-existing parameteres are set to `0`.

## Arguments
* `file_path::String`: File path leading to the [SigGen](https://github.com/radforddc/icpc_siggen) configuration file.

## Keywords
* `T::Type`: Type of the parameters in the output dictionary. Default is `Float64`.
"""
function readsiggen(file_path::String; T::Type=Float64)

    detector_name = split(basename(file_path), ".config")[1]

    config = Dict("name"       => detector_name,
        "verbosity_level"      => 0,
        "xtal_length"          => 0,
        "xtal_radius"          => 0,
        "top_bullet_radius"    => 0, # not implemented in later conversions
        "bottom_bullet_radius" => 0, # not implemented in later conversions
        "pc_length"            => 0,
        "pc_radius"            => 0,
        "bulletize_PC"         => 0,
        "wrap_around_radius"   => 0,
        "ditch_depth"          => 0,
        "ditch_thickness"      => 0,
        "bottom_taper_length"  => 0,
        "hole_length"          => 0,
        "hole_radius"          => 0,
        "outer_taper_length"   => 0,
        "inner_taper_length"   => 0,
        "outer_taper_width"    => 0,
        "inner_taper_width"    => 0,
        "taper_angle"          => 0,
        "taper_length"         => 0,
        "Li_thickness"         => 0,
        "energy"               => 0,
        "xtal_grid"            => 0,
        "impurity_z0"          => 0,
        "impurity_gradient"    => 0,
        "impurity_surface"     => 0,
        "xtal_HV"              => 0,
        "max_iterations"       => 0,
        "write_field"          => 0,
        "write_WP"             => 0,
        "drift_name"           => "",
        "field_name"           => "",
        "wp_name"              => "",
        "xtal_temp"            => 0,
        "preamp_tau"           => 0,
        "time_steps_calc"      => 0,
        "step_time_calc"       => 0,
        "step_time_out"        => 0,
        "charge_cloud_size"    => 0,
        "use_diffusion"        => 0,
        "surface_drift_vel_factor" => 0);

    file = open(file_path) do f
        temp          = readlines(f)
        config_file   = Dict()
        for line in temp
            if line  != "" && string(line[1]) != "#"
                line  = strip(split(line, "#")[1])
                name  = ""
                value = ""
                if length(split(line, "\t")) > 1
                    name  = split(line, "\t")[1]
                    value = strip(split(line, "\t")[2])
                else
                    name  = split(line, " ")[1]
                    value = strip(split(line, " ")[end])
                end

                config_file[name] = value

            end
        end
        return config_file
    end

    config_keys = keys(config)
    file_keys   = keys(file)

    for key in file_keys
        if key in config_keys
            if key == "wp_name" || key == "drift_name" || key == "field_name"
                config[key] = string(file[key])
            elseif key == "taper_angle" && parse(Float64, file["taper_angle"]) != 0
                config["outer_taper_width"] = tan(deg2rad(parse(T, file[key]))) * parse(T, file["outer_taper_length"])
                config["inner_taper_width"] = tan(deg2rad(parse(T, file[key]))) * parse(T, file["inner_taper_length"])
                config[key]                 = parse(T, file[key])
            else
                config[key] = parse(T, file[key])
            end
        end
    end
    return config
end

"""
    siggentodict(config::Dict; units::Dict)

Converts the dictionary containing the parameters from a [SigGen](https://github.com/radforddc/icpc_siggen) configuration file
to a dictionary that can be understood by SolidStateDetectors.jl.

## Arguments
* `config::Dict`: Dictionary containing [SigGen](https://github.com/radforddc/icpc_siggen) parameters (output of `readsiggen()``).

## Keywords
* `units::Dict`: Units used in [SigGen](https://github.com/radforddc/icpc_siggen) configuration file (set to `"mm"`, `"deg"`, `"V"` and `"K"`).
    The dictionary needs the fields `"length"`, `"angle"`, `"potential"` and `"temperature"`.
"""
function siggentodict(config::Dict;
        units::Dict = Dict("length" => "mm","angle" => "deg","potential" => "V","temperature" => "K"))

    #>----------------Settings START-------------------------------------------<

    #>----------------Setting the z and r limits-------------------------------<
    z_limit      = ceil(config["xtal_length"]; sigdigits=1);
    if z_limit - config["xtal_length"] <= 0
        z_limit += 10
    end
    r_limit      = ceil(config["xtal_radius"]; sigdigits=1);
    if r_limit - config["xtal_radius"] <= 0
        r_limit += 10
    end

    #>----------------Setting the Axes & Grid dictionaries---------------------<
    axes         = Dict("r"           => Dict("to"         => r_limit,
                                              "boundaries" => "inf"),
                        "phi"         => Dict("from"       => 0,
                                              "to"         => 0,
                                              "boundaries" => "periodic"),
                        "z"           => Dict("from"       => -10,
                                              "to"         => z_limit,
                                              "boundaries" => Dict("left" => "inf", "right" => "inf"))
                        )

    grid         = Dict("coordinates" => "cylindrical",
                        "axes"        => axes);

    #>----------------Settings END---------------------------------------------<
    #<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><
    #>----------------Volume START---------------------------------------------<

    #>----------------Main Volume -> Initial Cylinder--------------------------<
    # r and L are given by the input file. Cylindrical symmetry is set to default
    main_vol     = Dict("translate"   => Dict("z"    => config["xtal_length"]/2,
                        "tube"        => Dict(
                        "name"        => "Initial Cylinder",
                        "r"           => Dict("from" => 0.0, "to" => config["xtal_radius"]),
                        "phi"         => Dict("from" => 0.0, "to" => 360.0),
                        "h"           => config["xtal_length"])));

    #>----------------Upper Cone / Taper on top--------------------------------<
    # If not existing, everything will be set to 0
    upper_cone   = Dict("translate"   => Dict("z"      => config["xtal_length"] -
                                            config["outer_taper_length"]/2,
                        "cone"        => Dict(
                        "name"        => "Upper Cone",
                        "r"           => Dict("bottom" => Dict("from" => config["xtal_radius"],
                                                               "to"   => ceil(config["xtal_radius"] + 1.0)),
                                              "top"    => Dict("from" => config["xtal_radius"] -
                                                                            config["outer_taper_width"],
                                                               "to"   => ceil(config["xtal_radius"] + 1.0))),
                        "phi"         => Dict("from"   => 0.0, "to" => 360.0),
                        "h"           => config["outer_taper_length"])));

    #>----------------Lower Cone / Taper on bottom-----------------------------<
    # 45-degree taper at bottom of ORTEC-type crystal
    lower_cone   = Dict("translate"   => Dict("z"      => 0.4*config["taper_length"],
                        "cone"        => Dict(
                        "name"        => "Lower Cone",
                        "r"           => Dict("top"    => Dict("from" => config["xtal_radius"],
                                                               "to"   => ceil(config["xtal_radius"] + 1.0)),
                                              "bottom" => Dict("from" => config["xtal_radius"] -
                                                                            1.2 * config["taper_length"],
                                                               "to"   => ceil(config["xtal_radius"] + 1.0))),
                        "phi"         => Dict("from"   => 0.0, "to" => 360.0),
                        "h"           => 1.2*config["taper_length"])));

    #>----------------Ditch around the bottom contact--------------------------<
    # If not existing, everything will be set to 0
    ditch        = Dict("translate"   => Dict("z"    => config["ditch_depth"]/2,
                        "tube"        => Dict(
                        "name"        => "Ditch",
                        "r"           => Dict("from" => config["wrap_around_radius"] -
                                                            config["ditch_thickness"],
                                              "to"   => config["wrap_around_radius"]),
                        "phi"         => Dict("from" => 0.0, "to" => 360.0),
                        "h"           => config["ditch_depth"])));

    #>----------------Borehole on bottom as contact----------------------------<
    # If not existing, everything will be set to 0
    borehole = if config["bulletize_PC"] == 1
        if config["pc_length"] < config["pc_radius"]
            Dict( "union"   => [
            Dict( "tube"    => Dict(
                  "r"       => config["pc_radius"] - config["pc_length"],
                  "h"       => 1.2*config["pc_length"],
                  "origin"  => Dict("z" => 0.4*config["pc_length"]))),
            Dict( "torus"   => Dict(
                  "r_torus" => config["pc_radius"] - config["pc_length"],
                  "r_tube"  => config["pc_length"]))])
        elseif config["pc_length"] > config["pc_radius"]
            Dict( "union"  => [
            Dict( "tube"   => Dict(
                  "r"      => config["pc_radius"],
                  "h"      => 1.2*config["pc_length"] - config["pc_radius"],
                  "origin" => Dict("z" => (0.8*config["pc_length"] - config["pc_radius"])/2))),
            Dict( "sphere" => Dict(
                  "r"      => config["pc_radius"],
                  "origin" => Dict("z" => config["pc_length"] - config["pc_radius"])))])
        else
            Dict( "sphere" => Dict(
                       "r" => config["pc_radius"]))
        end
    else # not bulletized
            Dict( "tube"   => Dict(
                  "name"   => "Borehole",
                  "r"      => Dict("from" => 0.0, "to" => config["pc_radius"]),
                  "h"      => config["pc_length"],
                  "origin" => Dict("z" => config["pc_length"]/2)));
    end

    #>----------------Hole on top of the detector------------------------------<
    # If not existing, everything will be set to 0
    hole = if config["inner_taper_width"] != 0 && config["inner_taper_length"] != 0
        Dict("translate"   => Dict("z"      => config["xtal_length"] -
                               config["inner_taper_length"]/2,
             "cone"        => Dict(
             "name"        => "Inner Cone",
             "r"           => Dict("top"    => Dict("from" => 0,
                                                   "to"   => config["hole_radius"] +
                                                                config["inner_taper_width"]),
                                  "bottom" => Dict("from" => 0,
                                                   "to"   => config["hole_radius"])),
             "phi"         => Dict("from"   => 0.0, "to" => 360.0),
             "h"           => config["inner_taper_length"])));

    else
        Dict("translate"   => Dict("z"    => config["xtal_length"] -
                                               0.8*config["hole_length"]/2,
             "tube"        => Dict(
             "name"        => "Top Hole",
             "r"           => Dict("from" => 0.0, "to" => config["hole_radius"]),
             "phi"         => Dict("from" => 0.0, "to" => 360.0),
             "h"           => 1.2*config["hole_length"])));
    end

    #>----------------Subtract volumes-----------------------------------------<
    geometry = main_vol;
    if config["outer_taper_length"] != 0 || config["outer_taper_width"] != 0
        geometry = Dict("difference" => [geometry, upper_cone])
    end
    if config["inner_taper_length"] != 0 || config["inner_taper_width"] != 0 || config["hole_radius"] != 0 || config["hole_length"] != 0
        geometry = Dict("difference" => [geometry, hole])
    end
    if config["taper_length"] != 0
        geometry = Dict("difference" => [geometry, lower_cone])
    end
    if config["ditch_thickness"] != 0
        geometry = Dict("difference" => [geometry, ditch])
    end
    if config["bulletize_PC"] == 1 || (config["pc_radius"] != 0 && config["pc_length"] >= 0.1) # below this, it is the thickness of the layer
        geometry = Dict("difference" => [geometry, borehole])
    end

    #>----------------Volume END-----------------------------------------------<
    #<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><
    #>----------------Contacts START-------------------------------------------<

    #>----------------Area/volume of point contact-----------------------------<
    #>----------------Contact 1------------------------------------------------<
    # Depending on whether the contact is a borehole or not

    geometry_1 = if config["bulletize_PC"] == 1.0
        if config["pc_length"] < config["pc_radius"]
            Dict( "union"      => [
            Dict( "tube"       => Dict(
                  "r"          => config["pc_radius"] - config["pc_length"],
                  "h"          => 0,
                  "origin"     => Dict("z" => config["pc_length"]))),
            Dict( "torus"      => Dict(
                  "r_torus"    => config["pc_radius"] - config["pc_length"],
                  "r_tube"     => Dict("from" => config["pc_length"],
                                       "to"   => config["pc_length"]),
                  "theta"      => Dict("from" => 0.0, "to" => 90.0)))])
        elseif config["pc_length"] > config["pc_radius"]
            Dict( "union"      => [
            Dict( "tube"       => Dict(
                  "r"          => Dict("from" => config["pc_radius"], "to" => config["pc_radius"]),
                  "h"          => config["pc_length"] - config["pc_radius"],
                  "origin"     => Dict("z" => (config["pc_length"] - config["pc_radius"])/2))),
            Dict( "difference" => [
            Dict( "sphere"     => Dict(
                  "r"          => Dict("from" => config["pc_radius"],
                                       "to"   => config["pc_radius"]),
                  "origin"     => Dict("z" => config["pc_length"] - config["pc_radius"]))),
            Dict( "tube"       => Dict(
                  "r"          => 1.2*config["pc_radius"],
                  "h"          => 1.2*config["pc_radius"],
                  "origin"     => Dict("z" => config["pc_length"] - 1.6*config["pc_radius"])))])])
        else
            Dict( "difference" => [
            Dict( "sphere"     => Dict(
                  "r"          => Dict("from" => config["pc_radius"],
                                       "to"   => config["pc_radius"]))),
            Dict( "tube"       => Dict(
                  "r"          => 1.2*config["pc_radius"],
                  "h"          => 1.2*config["pc_radius"],
                  "origin"     => Dict("z" => -0.6 * config["pc_radius"])))])
        end
    elseif config["pc_radius"] != 0 && config["pc_length"] >= 5 # below this, it is the thickness of the layer
            Dict( "union"      => [
            # borehole_wall
            Dict( "tube"       => Dict(
                  "r"          => Dict("from" => config["pc_radius"],
                                       "to"   => config["pc_radius"]),
                  "h"          => config["pc_length"],
                  "origin"     => Dict("z" => config["pc_length"]/2))),
            # borehole_top
            Dict( "tube"       => Dict(
                  "r"          => Dict("from" => 0.0,
                                       "to"   => config["pc_radius"]),
                  "h"          => 0.0,
                  "origin"     => Dict("z" => config["pc_length"]))),
            # borehole_around
            Dict( "tube"       => Dict(
                  "r"          => Dict("from" => config["pc_radius"],
                                       "to"   => config["wrap_around_radius"] - config["ditch_thickness"]),
                  "h"          => 0.0))])
    elseif config["pc_radius"] != 0 && config["pc_length"] < 5
            Dict( "tube"       => Dict(
                  "r"          => config["pc_radius"],
                  "h"          => config["pc_length"],
                  "origin"     => Dict("z" => config["pc_length"]/2)))
    end

    #>----------------Define contact 1-----------------------------------------<

    contact_1 = Dict("material"    => "HPGe",
                     "id"          => 1,
                     "potential"   => 0.0,
                     "geometry"    => geometry_1);

    #>----------------Contact 2------------------------------------------------<
    geometry_2 = Dict[]

    if config["wrap_around_radius"] != 0.0
        bottom = Dict("tube" => Dict(
                      "r"    => Dict("from" => config["wrap_around_radius"],
                                       "to" => config["xtal_radius"] - config["taper_length"]),
                      "phi"  => Dict("from" => 0.0,
                                       "to" => 360.0),
                      "h"    => 0.0))
        push!(geometry_2, bottom)
    end

    wall   = Dict("translate" => Dict("z" => config["xtal_length"]/2 + config["taper_length"]/2 - config["outer_taper_length"]/2,
                  "tube" => Dict(
                  "r"    => Dict("from" => config["xtal_radius"],
                                 "to"   => config["xtal_radius"]),
                  "phi"  => Dict("from" => 0.0,
                                   "to" => 360.0),
                  "h"    => config["xtal_length"] - config["taper_length"] - config["outer_taper_length"])))
    push!(geometry_2, wall)

    top    = Dict("translate" => Dict("z" => config["xtal_length"],
                  "tube" => Dict(
                  "r"    => Dict("from" => config["hole_radius"] + config["inner_taper_width"],
                                   "to" => config["xtal_radius"] - config["outer_taper_width"]),
                  "phi"  => Dict("from" => 0.0,
                                   "to" => 360.0),
                  "h"    => 0.0)))
    push!(geometry_2, top)
    if config["hole_radius"] != 0 && config["hole_length"] != 0 && config["inner_taper_width"] == 0 && config["inner_taper_length"] == 0
        hole   = Dict("translate" => Dict("z" => config["xtal_length"] - config["hole_length"]/2,
                      "tube" => Dict(
                      "r"    => Dict("from" => config["hole_radius"],
                                       "to" => config["hole_radius"]),
                      "phi"  => Dict("from" => 0.0,
                                       "to" => 360.0),
                      "h"    => config["hole_length"])))
        inner_hole_bottom =  Dict("translate" => Dict("z" => config["xtal_length"] - config["hole_length"],
                      "tube" => Dict(
                      "r"    => Dict("from" => 0.0,
                                       "to" => config["hole_radius"]),
                      "phi"  => Dict("from" => 0.0,
                                       "to" => 360.0),
                      "h"    => 0.0)))
        push!(geometry_2, hole)
        push!(geometry_2, inner_hole_bottom)
    end
    if config["hole_radius"] != 0 && config["hole_length"] != 0 && config["inner_taper_width"] != 0 && config["inner_taper_length"] != 0
        inner_taper = Dict("translate" => Dict("z" => config["xtal_length"] - config["inner_taper_length"]/2,
                      "cone" => Dict(
                      "r"    => Dict("top"    => Dict("from" => config["hole_radius"] + config["inner_taper_width"],
                                                      "to"   => config["hole_radius"] + config["inner_taper_width"]),
                                     "bottom" => Dict("from" => config["hole_radius"],
                                                      "to"   => config["hole_radius"])),
                      "phi"  => Dict("from" => 0.0, "to" => 360.0),
                      "h"    => config["inner_taper_length"])))
        inner_hole_bottom = Dict("translate" => Dict("z" => config["xtal_length"] - config["hole_length"],
                      "tube" => Dict(
                      "r"    => Dict("from" => 0.0, "to" => config["hole_radius"]),
                      "phi"  => Dict("from" => 0.0, "to" => 360.0),
                      "h"    => 0.0)))
        push!(geometry_2, inner_taper)
        push!(geometry_2, inner_hole_bottom)
    end
    if config["outer_taper_width"] != 0 && config["outer_taper_length"] != 0
        outer_taper = Dict("translate" => Dict("z" => config["xtal_length"] - config["outer_taper_length"]/2,
                      "cone" => Dict(
                      "r"    => Dict("top"    => Dict("from" => config["xtal_radius"] - config["outer_taper_width"],
                                                      "to"   => config["xtal_radius"] - config["outer_taper_width"]),
                                     "bottom" => Dict("from" => config["xtal_radius"],
                                                      "to"   => config["xtal_radius"])),
                      "phi"  => Dict("from"   => 0.0, "to" => 360.0),
                      "h"    => config["outer_taper_length"])))
        push!(geometry_2, outer_taper)
    end
    if config["taper_length"] != 0
        taper = Dict("translate" => Dict("z" => config["taper_length"]/2,
                      "cone" => Dict(
                      "r"    => Dict("top"    => Dict("from" => config["xtal_radius"],
                                                      "to"   => config["xtal_radius"]),
                                     "bottom" => Dict("from" => config["xtal_radius"] - config["taper_length"],
                                                      "to"   => config["xtal_radius"] - config["taper_length"])),
                      "phi"   => Dict("from"   => 0.0, "to" => 360.0),
                      "h"     => config["taper_length"])))
        push!(geometry_2, taper)
    end

    #>----------------Define contact 2-----------------------------------------<

    contact_2 = Dict("material"    => "HPGe",
                     "id"          => 2,
                     "potential"   => config["xtal_HV"],
                     "geometry"    => Dict("union" => geometry_2));

    #>----------------Contacts END---------------------------------------------<
    #<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><
    #>----------------Final configuration--------------------------------------<

    impurity_z0            = config["impurity_z0"]*1e10
    impurity_gradient      = config["impurity_gradient"]*1e10
    if units["length"]     == "mm"
        impurity_z0        /= 1e3
        impurity_gradient  /= 1e4
    elseif units["length"] == "m"
        impurity_z0        *= 1e6
        impurity_gradient  *= 1e8
    end

    objects = Dict("semiconductor" => Dict(
                    "material"    => "HPGe",
                    "temperature" => config["xtal_temp"],
                    "impurity_density" => Dict(
                                "name"     => "cylindrical",
                                "r"        => Dict("init"     => 0.0,
                                                   "gradient" => 0.0),
                                "z"        => Dict("init"     => impurity_z0,
                                                   "gradient" => impurity_gradient)),
                    "geometry"    => geometry),
                    "contacts" => [ contact_1, contact_2 ]);

    conf = Dict("name"    => config["name"],
                "units"   => units,
                "grid"    => grid,
                "medium"  => "vacuum",
                "detectors" => [objects]);

    return conf
end


text:
"""
    Molecules.translate!(atoms::Molecule, r::Vector{T})

Translate atoms into position r.
"""
function translate!(atoms::Molecule, r::Vector{T}) where T
    for a in atoms
        for i = 1:3
            a.xyz[i] -= r[i]
        end
    end
end

"""
    Molecules.translate(atoms::Molecule)

Return a copy of atoms translated into position r.
"""
function translate(atoms::Molecule, r::Vector{T}) where T
    newatoms = deepcopy(atoms)
    translate!(newatoms, r)
    return newatoms
end

"""
    Molecules.Cn(v::Vector, n::Int)

Returns an n rotation matrix about vector v
"""
function Cn(v, n::Integer)
    θ = 2*π / n
    return rotation_matrix(v, θ)
end

"""
    Molecules.rotation_matrix(v, θ::AbstractFloat)

Returns a rotation matrix by θ about a rotation axis v
"""
function rotation_matrix(v, θ::AbstractFloat)
    cosθ = cos(θ)
    sinθ = sin(θ)
    a = [1,2,3]
    O = zeros(eltype(v), (3,3))
    O .+= 1 - cosθ
    for i = 1:3, j = 1:3
        if i == j
            O[i,i] *= v[i]^2
            O[i,i] += cosθ
        else
            O[i,j] *= v[i]*v[j]
            b = [i,j]
            C = [i for i in a if i ∉ b][1]
            if i < j
                O[i,j] += (-1)^(i+j) * v[C]*sinθ
            else
                O[i,j] += (-1)^(i+j-1) * v[C]*sinθ
            end
        end
    end
    return O
end

"""
    Molecules.reflection_matrix(v::Vector)

Returns a reflection matrix through the plane with normal vector v
"""
function reflection_matrix(v)
    O = zeros(eltype(v), (3,3))
    for i = 1:3, j = i:3
        if i == j
            O[i,i] = 1 - 2*v[i]^2
        else
            O[i,j] = -2 * v[i] * v[j]
            O[j,i] = O[i,j]
        end
    end
    return O
end

function σ(v)
    return reflection_matrix(v)
end

"""
    Molecules.Sn(v::Vector, n::int)

Returns Sn improper rotation about vector v
"""
function Sn(v, n)
    return Cn(v, n) * σ(v)
end

"""
    Molecules.inversion_matrix()

Returns a diagonal matrix with -1 along the diagonal
"""
function inversion_matrix()
    a = zeros(Int8, (3,3))
    for i = 1:3
        a[i,i] = -1
    end
    return a
end

function i()
    return inversion_matrix()
end

"""
    Molecules.transform!(A::Molecule, O::Array)

Transforms xyz coordinates of each atom in A by some operation O
"""
function transform!(atoms::Molecule, O::Array)
    for a in atoms
        for i = 1:3
            a.xyz[i] = sum(a.xyz[j]*O[i,j] for j = 1:3)
        end
    end
end

"""
    Molecules.transform(A::Molecule, O::Array)

Transforms xyz coordinates of each atom in A by some operation O
Returns new list of atoms with transformed xyz coordinates
"""
function transform(atoms::Molecule, O::Array)
    newatoms = deepcopy(atoms)
    transform!(newatoms, O)
    return newatoms
end

"""
    Molecules.issame(A::Molecule, B::Molecule)

Checks to see if two arrays of atoms are equivalent under permutation
Returns true or false
"""
function isequivalent(A::Molecule, B::Molecule)
    h = []
    len = size(A,1)
    for i = 1:len
        for j = 1:len
            if A[i].mass == B[j].mass
                if isapprox(A[i].xyz, B[j].xyz, rtol=1E-5)
                    push!(h, i)
                    break
                end
            end
        end
    end
    return size(h,1) == len
end


text:
using Compose
@doc """Line\n
-------------------------------\n
The circle object creates a drawable scalable vector graphic circle.\n
Circle(x,y,diameter,fillin)\n
pairs:: NTuple[Int64/float64] Vector2s determining line position.\n
color:: Fill color, takes Julia Symbol type.\n
line_example = Line([(-1,-1), (-1,1), (1,1)],:blue)"""
function Line(pairs, color=:black, width=2)
    tree() = introspect(composition)
    show() = composition
    composition = compose(context(), line(pairs), stroke(string(color)))
    color = string("\"",string(color),"\"")
    pairstring = ""
    for i in pairs
        s = string(i) * ","
        pairstring = pairstring * s
    end
    update(pairs) = string("line([",string(join(pairstring)),"]), stroke(", color, "), linewidth(",width,"mm),")
    (var)->(show;composition;tree;update;color;pairs)
end
@doc """Circle\n
-------------------------------\n
The circle object creates a drawable scalable vector graphic circle.\n
Circle(x,y,diameter,fillin)\n
x:: The X position of the Circle.\n
y:: The Y position of the Circle.\n
diameter:: The diameter of the circle.\n
fillin:: Fill color, takes Julia Symbol type.\n
big = Circle(.5,.5,.01,:orange)"""
function Circle(x,y,diameter,fillin=:orange,stroke=:black)
    composition = compose(context(), circle(x,y,diameter), fill(string(fillin)))
    color = string("\"",string(fillin),"\"")
    stroke = string("\"",string(stroke),"\"")
    tag = string("circle(",string(x),",",string(y),',',diameter,"), fill(", color , "),")
    tree() = introspect(composition)
    show() = composition
    x = x
    y = y
    update(x,y) =  string("circle(",string(x),",",string(y),',',diameter,"), fill(", color ,"), stroke(",stroke, "),")
    (var)->(show;composition;tree;tag;x;y;update;diameter)
end
function Rectangle(x,y,width,height,fillin=:blue)
    composition = compose(context(), rectangle(x,y,width,height), fill(["yellow,blue"]))
    color = string("\"",string(fillin),"\"")
    tag = string("rectangle(",string(x),",",string(y),',',width,',',height, "), fill(", color , "),")
    tree() = introspect(composition)
    show() = composition
    x = x
    y = y
    update(x,y) =  string("rectangle(",string(x),",",string(y),',',width,',',height, "), fill(", color , "),")
    (var)->(show;composition;tree;tag;x;y;update;diameter)
end
function Label(label,x,y,stroke="",size=5,fill=:gray)
    if stroke != ""
        stroke = string("stroke(","\"",string(stroke),"\"","),")
    end
    fill = string("\"", string(fill), "\"")
    label = string("\"",string(label),"\"")
    tag = string("text(",x, ",", y, ",", label, ",hcenter, vcenter),",
           stroke," fontsize(", size, "), fill(", fill, "),")
    expression = string("compose(context(), ",tag,")")
    exp = Meta.parse(expression)
    show() = eval(exp)
    update() = tag
    (var)->(show;expression;tag;exp)
end
#---------------------------------------------------


text:
module ExploratoryDataAnalysis

using StatsBase
using DataFrames
using CategoricalArrays
using FreqTables
using NamedArrays

export infovalue
export cramerv
export mutualinfo
export eda
export ranks

include("eda.jl")
include("ranks.jl")

end