diff --git a/Manifest.toml b/Manifest.toml index b3e446d..bbd6d55 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -10,21 +10,10 @@ uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" version = "0.5.0" [[BinaryProvider]] -deps = ["Libdl", "Logging", "SHA"] -git-tree-sha1 = "ecdec412a9abc8db54c0efc5548c64dfce072058" +deps = ["Libdl", "SHA"] +git-tree-sha1 = "5b08ed6036d9d3f0ee6369410b830f8873d4024c" uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232" -version = "0.5.10" - -[[Bzip2_jll]] -deps = ["Libdl", "Pkg"] -git-tree-sha1 = "3663bfffede2ef41358b6fc2e1d8a6d50b3c3904" -uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.6+2" - -[[CEnum]] -git-tree-sha1 = "1b77a77c3b28e0b3f413f7567c9bb8dd9bdccd14" -uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" -version = "0.3.0" +version = "0.5.8" [[Calculus]] deps = ["LinearAlgebra"] @@ -34,45 +23,33 @@ version = "0.5.1" [[Cbc]] deps = ["BinaryProvider", "Libdl", "MathOptInterface", "MathProgBase", "SparseArrays", "Test"] -git-tree-sha1 = "62d80f448b5d77b3f0a59cecf6197aad2a3aa280" +git-tree-sha1 = "0d51c2d66fc22e5e3fc64b6092ba0f0b3839c8c1" uuid = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" -version = "0.6.7" +version = "0.6.6" [[Clp]] -deps = ["BinaryProvider", "CEnum", "Clp_jll", "Libdl", "MathOptInterface", "SparseArrays"] -git-tree-sha1 = "cb38198edf176b720461273fd4a6c678ef9b2ec5" +deps = ["BinaryProvider", "Libdl", "LinearAlgebra", "MathOptInterface", "MathProgBase", "SparseArrays"] +git-tree-sha1 = "0872354eaeb05a86e400f45abaac1dc93da7bf76" uuid = "e2554f3b-3117-50c0-817c-e040a3ddf72d" -version = "0.8.0" - -[[Clp_jll]] -deps = ["CoinUtils_jll", "CompilerSupportLibraries_jll", "Libdl", "OpenBLAS32_jll", "Osi_jll", "Pkg"] -git-tree-sha1 = "70fe9e52fd95fa37f645e3d30f08f436cc5b1457" -uuid = "06985876-5285-5a41-9fcb-8948a742cc53" -version = "1.17.6+5" +version = "0.7.1" [[CodeTracking]] deps = ["InteractiveUtils", "UUIDs"] -git-tree-sha1 = "cab4da992adc0a64f63fa30d2db2fd8bec40cab4" +git-tree-sha1 = "0becdab7e6fbbcb7b88d8de5b72e5bb2f28239f3" uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" -version = "0.5.11" +version = "0.5.8" [[CodecBzip2]] -deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"] -git-tree-sha1 = "2e62a725210ce3c3c2e1a3080190e7ca491f18d7" +deps = ["BinaryProvider", "Libdl", "TranscodingStreams"] +git-tree-sha1 = "5db086e510c11b4c87d05067627eadb2dc079995" uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd" -version = "0.7.2" +version = "0.6.0" [[CodecZlib]] -deps = ["TranscodingStreams", "Zlib_jll"] -git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da" +deps = ["BinaryProvider", "Libdl", "TranscodingStreams"] +git-tree-sha1 = "05916673a2627dd91b4969ff8ba6941bc85a960e" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" -version = "0.7.0" - -[[CoinUtils_jll]] -deps = ["CompilerSupportLibraries_jll", "Libdl", "OpenBLAS32_jll", "Pkg"] -git-tree-sha1 = "ee1f06ab89337b7f194c29377ab174e752cdf60d" -uuid = "be027038-0da8-5614-b30d-e42594cb92df" -version = "2.11.3+3" +version = "0.6.0" [[CommonSubexpressions]] deps = ["Test"] @@ -82,21 +59,21 @@ version = "0.2.0" [[CompilerSupportLibraries_jll]] deps = ["Libdl", "Pkg"] -git-tree-sha1 = "7c4f882c41faa72118841185afc58a2eb00ef612" +git-tree-sha1 = "b57c5d019367c90f234a7bc7e24ff0a84971da5d" uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "0.3.3+0" +version = "0.2.0+1" [[CoordinateTransformations]] -deps = ["LinearAlgebra", "StaticArrays"] -git-tree-sha1 = "c230b1d94db9fdd073168830437e64b9db627fcb" +deps = ["LinearAlgebra", "Rotations", "StaticArrays"] +git-tree-sha1 = "71333ea3f841bca6c1aa2863f11758eb9b37bfbc" uuid = "150eb455-5306-5404-9cee-2592286d6298" -version = "0.6.0" +version = "0.5.1" [[DataStructures]] deps = ["InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "af6d9c86e191c917c2276fbede1137e8ea20157f" +git-tree-sha1 = "5a431d46abf2ef2a4d5d00bd0ae61f651cf854c8" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.17.17" +version = "0.17.10" [[Dates]] deps = ["Printf"] @@ -123,9 +100,9 @@ uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" [[ForwardDiff]] deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "NaNMath", "Random", "SpecialFunctions", "StaticArrays"] -git-tree-sha1 = "869540e4367122fbffaace383a5bdc34d6e5e5ac" +git-tree-sha1 = "88b082d492be6b63f967b6c96b352e25ced1a34c" uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "0.10.10" +version = "0.10.9" [[Geodesy]] deps = ["CoordinateTransformations", "Dates", "LinearAlgebra", "StaticArrays", "Test"] @@ -135,9 +112,9 @@ version = "0.5.0" [[HTTP]] deps = ["Base64", "Dates", "IniFile", "MbedTLS", "Sockets"] -git-tree-sha1 = "ec87d5e2acbe1693789efbbe14f5ea7525758f71" +git-tree-sha1 = "8d9bdd55c9d0d6ddf08f8b5229f90b7f274b6777" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "0.8.15" +version = "0.8.12" [[IniFile]] deps = ["Test"] @@ -163,18 +140,17 @@ version = "0.2.0" [[JuMP]] deps = ["Calculus", "DataStructures", "ForwardDiff", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "Random", "SparseArrays", "Statistics"] -git-tree-sha1 = "84c1cf8bec4729b8b2ef4dfc4e1db1b892ad0d30" +git-tree-sha1 = "8e87337fd19b6717fd9d5324bfab99848e363d9f" uuid = "4076af6c-e467-56ae-b986-b466b2749572" -version = "0.21.2" +version = "0.21.1" [[JuliaInterpreter]] deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] -git-tree-sha1 = "adfa56c6a1066d3baadb5d2a070d0f966d880a6d" +git-tree-sha1 = "2eadbbde5534346cbb837c3a75b377cba477a06d" uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" -version = "0.7.17" +version = "0.7.13" [[LibGit2]] -deps = ["Printf"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" [[Libdl]] @@ -189,9 +165,9 @@ uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" [[LoweredCodeUtils]] deps = ["JuliaInterpreter"] -git-tree-sha1 = "225f0035f01c24858c0884f38bb519e22b0a5150" +git-tree-sha1 = "1c41621653250b2824b6e664ac5bd805558aeff9" uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" -version = "0.4.5" +version = "0.4.3" [[Markdown]] deps = ["Base64"] @@ -199,9 +175,9 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[MathOptInterface]] deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "JSON", "JSONSchema", "LinearAlgebra", "MutableArithmetics", "OrderedCollections", "SparseArrays", "Test", "Unicode"] -git-tree-sha1 = "27f2ef85879b8f1d144266ab44f076ba0dfbd8a1" +git-tree-sha1 = "f0d60e42d8b64dd1b511e2dc13e0b72ba1dfc9cf" uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -version = "0.9.13" +version = "0.9.12" [[MathProgBase]] deps = ["LinearAlgebra", "SparseArrays"] @@ -211,36 +187,30 @@ version = "0.7.8" [[MbedTLS]] deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"] -git-tree-sha1 = "426a6978b03a97ceb7ead77775a1da066343ec6e" +git-tree-sha1 = "a9e2221f06b42f56052f43ad7edecb01d0ef5ab4" uuid = "739be429-bea8-5141-9913-cc70e7f3736d" -version = "1.0.2" +version = "1.0.1" [[MbedTLS_jll]] deps = ["Libdl", "Pkg"] -git-tree-sha1 = "c83f5a1d038f034ad0549f9ee4d5fac3fb429e33" +git-tree-sha1 = "066a4467008745eed36dad973ceb66405785a621" uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.16.0+2" +version = "2.16.0+1" [[Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" [[MutableArithmetics]] deps = ["LinearAlgebra", "SparseArrays", "Test"] -git-tree-sha1 = "e1edd618a8f39d16f8595dd622a63b25f759cf8a" +git-tree-sha1 = "020d4f22e1151e0613edf91a56535379564c1ce8" uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" -version = "0.2.9" +version = "0.2.7" [[NaNMath]] git-tree-sha1 = "928b8ca9b2791081dc71a51c55347c27c618760f" uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" version = "0.3.3" -[[OpenBLAS32_jll]] -deps = ["CompilerSupportLibraries_jll", "Libdl", "Pkg"] -git-tree-sha1 = "793b33911239d2651c356c823492b58d6490d36a" -uuid = "656ef2d0-ae68-5445-9ca0-591084a874a2" -version = "0.3.9+4" - [[OpenSpecFun_jll]] deps = ["CompilerSupportLibraries_jll", "Libdl", "Pkg"] git-tree-sha1 = "d51c416559217d974a1113522d5919235ae67a87" @@ -248,24 +218,19 @@ uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" version = "0.5.3+3" [[OrderedCollections]] -git-tree-sha1 = "12ce190210d278e12644bcadf5b21cbdcf225cd3" +deps = ["Random", "Serialization", "Test"] +git-tree-sha1 = "c4c13474d23c60d20a67b217f1d7f22a40edf8f1" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.2.0" - -[[Osi_jll]] -deps = ["CoinUtils_jll", "CompilerSupportLibraries_jll", "Libdl", "OpenBLAS32_jll", "Pkg"] -git-tree-sha1 = "bd436a97280df40938e66ae8d18e57aceb072856" -uuid = "7da25872-d9ce-5375-a4d3-7a845f58efdd" -version = "0.108.5+3" +version = "1.1.0" [[Parsers]] deps = ["Dates", "Test"] -git-tree-sha1 = "f0abb338b4d00306500056a3fd44c221b8473ef2" +git-tree-sha1 = "0c16b3179190d3046c073440d94172cfc3bb0553" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "1.0.4" +version = "0.3.12" [[Pkg]] -deps = ["Dates", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] +deps = ["Dates", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Test", "UUIDs"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" [[Printf]] @@ -274,9 +239,9 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" [[ProgressBars]] deps = ["Printf"] -git-tree-sha1 = "fec529e15cccf342087de2ccda1b0b9064cbbb53" +git-tree-sha1 = "e66732bbdaad368cfc642cef1f639df5812dc818" uuid = "49802e3a-d2f1-5c88-81d8-b72133a6f568" -version = "0.7.1" +version = "0.6.0" [[REPL]] deps = ["InteractiveUtils", "Markdown", "Sockets"] @@ -288,9 +253,15 @@ uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[Revise]] deps = ["CodeTracking", "Distributed", "FileWatching", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Pkg", "REPL", "UUIDs", "Unicode"] -git-tree-sha1 = "adb8b66d5e53151628a9bcf51049ed70c8fa7626" +git-tree-sha1 = "3c04c929f8720c2fabb12534cd102a2356a7705c" uuid = "295af30f-e4ad-537b-8983-00126c2a3abe" -version = "2.7.0" +version = "2.5.4" + +[[Rotations]] +deps = ["LinearAlgebra", "StaticArrays", "Statistics"] +git-tree-sha1 = "d5f83867093db7319a9366d55f29280ecae9bcda" +uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc" +version = "0.13.0" [[SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" @@ -307,15 +278,15 @@ uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [[SpecialFunctions]] deps = ["OpenSpecFun_jll"] -git-tree-sha1 = "d8d8b8a9f4119829410ecd706da4cc8594a1e020" +git-tree-sha1 = "e19b98acb182567bcb7b75bb5d9eedf3a3b5ec6c" uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "0.10.3" +version = "0.10.0" [[StaticArrays]] deps = ["LinearAlgebra", "Random", "Statistics"] -git-tree-sha1 = "5c06c0aeb81bef54aed4b3f446847905eb6cbda0" +git-tree-sha1 = "5a3bcb6233adabde68ebc97be66e95dcb787424c" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "0.12.3" +version = "0.12.1" [[Statistics]] deps = ["LinearAlgebra", "SparseArrays"] @@ -337,9 +308,3 @@ uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [[Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" - -[[Zlib_jll]] -deps = ["Libdl", "Pkg"] -git-tree-sha1 = "a2e0d558f6031002e380a90613b199e37a8565bf" -uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.11+10" diff --git a/Project.toml b/Project.toml index 053106d..816001e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "RELOG" uuid = "a2afcdf7-cf04-4913-85f9-c0d81ddf2008" authors = ["Alinson S Xavier "] -version = "0.1.0" +version = "0.2.0" [deps] Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" @@ -24,4 +24,4 @@ JSON = "0.21" JSONSchema = "0.2" JuMP = "0.21" MathOptInterface = "0.9" -ProgressBars = "0.7" +ProgressBars = "0.6" diff --git a/docs/index.html b/docs/index.html index d06df47..ad385ce 100644 --- a/docs/index.html +++ b/docs/index.html @@ -275,6 +275,6 @@ POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/model/index.html b/docs/model/index.html index 62b4683..e8c291b 100644 --- a/docs/model/index.html +++ b/docs/model/index.html @@ -154,21 +154,21 @@ -time periods -Number of time periods in the simulation. +Time horizon (years) +Number of years in the simulation.

Example

{
-    "parameters": {
-        "time periods": 2
+    "Parameters": {
+        "Time horizon (years)": 2
     }
 }
 

Products

-

The products section describes all products and subproducts in the simulation. The field instance["products"] is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys:

+

The products section describes all products and subproducts in the simulation. The field instance["Products"] is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys:

@@ -178,11 +178,11 @@ - - + + - + @@ -197,57 +197,57 @@ - - + + - - + + - - + +
transportation costThe cost (in dollars per km per tonnes) to transport this product. Must be a timeseries.Transportation cost ($/km/tonne)The cost to transport this product. Must be a timeseries.
initial amountsInitial amounts A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted. Must be a timeseries.
latitudeThe latitude of the location, in degrees.Latitude (deg)The latitude of the location.
longitudeThe longitude of the location, in degrees.Longitude (deg)The longitude of the location.
amountThe amount (in tonnes) of the product initially available at the location. Must be a timeseries.Amount (tonne)The amount of the product initially available at the location. Must be a timeseries.

Example

{
-    "products": {
+    "Products": {
         "P1": {
-            "transportation cost": [0.015, 0.015],
-            "initial amounts": {
+            "Transportation cost ($/km/tonne)": [0.015, 0.015],
+            "Initial amounts": {
                 "C1": {
-                    "latitude": 7.0,
-                    "longitude": 7.0,
-                    "amount": [934.56, 934.56]
+                    "Latitude (deg)": 7.0,
+                    "Longitude (deg)": 7.0,
+                    "Amount (tonne)": [934.56, 934.56]
                 },
                 "C2": {
-                    "latitude": 7.0,
-                    "longitude": 19.0,
-                    "amount": [198.95, 198.95]
+                    "Latitude (deg)": 7.0,
+                    "Longitude (deg)": 19.0,
+                    "Amount (tonne)": [198.95, 198.95]
                 },
                 "C3": {
-                    "latitude": 84.0,
-                    "longitude": 76.0,
-                    "amount": [212.97, 212.97]
+                    "Latitude (deg)": 84.0,
+                    "Longitude (deg)": 76.0,
+                    "Amount (tonne)": [212.97, 212.97]
                 }
             }
         },
         "P2": {
-            "transportation cost": [0.02, 0.02]
+            "Transportation cost ($/km/tonne)": [0.02, 0.02]
         },
         "P3": {
-            "transportation cost": [0.0125, 0.0125]
+            "Transportation cost ($/km/tonne)": [0.0125, 0.0125]
         },
         "P4": {
-            "transportation cost": [0.0175, 0.0175]
+            "Transportation cost ($/km/tonne)": [0.0175, 0.0175]
         }
     }
 }
 

Processing Plants

-

The plants section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field instance["plants"] is a dictionary mapping the name of the plant to a dictionary with the following keys:

+

The plants section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field instance["Plants"] is a dictionary mapping the name of the plant to a dictionary with the following keys:

@@ -257,15 +257,15 @@ - + - + - + @@ -280,19 +280,19 @@ - + - + - + - + @@ -307,12 +307,12 @@ - - + + - - + +
inputInput The name of the product that this plant takes as input. Only one input is accepted per plant.
outputsOutputs (tonne) A dictionary specifying how many tonnes of each product is produced for each tonnes of input. For example, if the plant outputs 0.5 tonnes of P2 and 0.25 tonnes of P3 for each tonnes of P1 provided, then this entry should be {"P2": 0.5, "P3": 0.25}. If the plant does not output anything, this key may be omitted.
locationsLocations A dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below).
latitudeLatitude (deg) The latitude of the location, in degrees.
longitudeLongitude (deg) The longitude of the location, in degrees.
disposalDisposal A dictionary describing what products can be disposed locally at the plant.
capacitiesCapacities (tonne) A dictionary describing what plant sizes are allowed, and their characteristics.
costThe cost (in dollars per tonnes) to dispose of the product. Must be a timeseries.Cost ($/tonne)The cost to dispose of the product. Must be a timeseries.
limitThe maximum amount (in tonnes) that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries.Limit (tonne)The maximum amount that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries.
@@ -326,48 +326,48 @@ -opening cost -The cost (in dollars) to open a plant of this size. +Opening cost ($) +The cost to open a plant of this size. -fixed operating cost -The cost (in dollars) to keep the plant open, even if the plant doesn't process anything. Must be a timeseries. +Fixed operating cost ($) +The cost to keep the plant open, even if the plant doesn't process anything. Must be a timeseries. -variable operating cost -The cost (in dollars per tonnes) that the plant incurs to process each tonne of input. Must be a timeseries. +Variable operating cost ($/tonne) +The cost that the plant incurs to process each tonne of input. Must be a timeseries.

Example

{
-    "plants": {
+    "Plants": {
         "F1": {
-            "input": "P1",
-            "outputs": {
+            "Input": "P1",
+            "Outputs (tonne)": {
                 "P2": 0.2,
                 "P3": 0.5
             },
-            "locations": {
+            "Locations": {
                 "L1": {
-                    "latitude": 0.0,
-                    "longitude": 0.0,
-                    "disposal": {
+                    "Latitude (deg)": 0.0,
+                    "Longitude (deg)": 0.0,
+                    "Disposal": {
                         "P2": {
-                            "cost": [-10.0, -12.0],
-                            "limit": [1.0, 1.0]
+                            "Cost ($/tonne)": [-10.0, -12.0],
+                            "Limit (tonne)": [1.0, 1.0]
                         }
                     },
-                    "capacities": {
+                    "Capacities (tonne)": {
                         "100": {
-                            "opening cost": [500, 530],
-                            "fixed operating cost": [300.0, 310.0],
-                            "variable operating cost": [5.0, 5.2]
+                            "Opening cost ($)": [500, 530],
+                            "Fixed operating cost ($)": [300.0, 310.0],
+                            "Variable operating cost ($/tonne)": [5.0, 5.2]
                         },
                         "500": {
-                            "opening cost": [750, 760],
-                            "fixed operating cost": [400.0, 450.0],
-                            "variable operating cost": [5.0, 5.2]
+                            "Opening cost ($)": [750, 760],
+                            "Fixed operating cost ($)": [400.0, 450.0],
+                            "Variable operating cost ($/tonne)": [5.0, 5.2]
                         }
                     }
                 }
diff --git a/docs/search/lunr.js b/docs/search/lunr.js
index c353765..c218cc8 100644
--- a/docs/search/lunr.js
+++ b/docs/search/lunr.js
@@ -1,6 +1,6 @@
 /**
- * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.8
- * Copyright (C) 2019 Oliver Nightingale
+ * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.1.6
+ * Copyright (C) 2018 Oliver Nightingale
  * @license MIT
  */
 
@@ -54,15 +54,14 @@ var lunr = function (config) {
   return builder.build()
 }
 
-lunr.version = "2.3.8"
+lunr.version = "2.1.6"
 /*!
  * lunr.utils
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
  * A namespace containing utils for the rest of the lunr library
- * @namespace lunr.utils
  */
 lunr.utils = {}
 
@@ -70,8 +69,7 @@ lunr.utils = {}
  * Print a warning message to the console.
  *
  * @param {String} message The message to be printed.
- * @memberOf lunr.utils
- * @function
+ * @memberOf Utils
  */
 lunr.utils.warn = (function (global) {
   /* eslint-disable no-console */
@@ -92,7 +90,7 @@ lunr.utils.warn = (function (global) {
  *
  * @param {Any} obj The object to convert to a string.
  * @return {String} string representation of the passed object.
- * @memberOf lunr.utils
+ * @memberOf Utils
  */
 lunr.utils.asString = function (obj) {
   if (obj === void 0 || obj === null) {
@@ -101,52 +99,6 @@ lunr.utils.asString = function (obj) {
     return obj.toString()
   }
 }
-
-/**
- * Clones an object.
- *
- * Will create a copy of an existing object such that any mutations
- * on the copy cannot affect the original.
- *
- * Only shallow objects are supported, passing a nested object to this
- * function will cause a TypeError.
- *
- * Objects with primitives, and arrays of primitives are supported.
- *
- * @param {Object} obj The object to clone.
- * @return {Object} a clone of the passed object.
- * @throws {TypeError} when a nested object is passed.
- * @memberOf Utils
- */
-lunr.utils.clone = function (obj) {
-  if (obj === null || obj === undefined) {
-    return obj
-  }
-
-  var clone = Object.create(null),
-      keys = Object.keys(obj)
-
-  for (var i = 0; i < keys.length; i++) {
-    var key = keys[i],
-        val = obj[key]
-
-    if (Array.isArray(val)) {
-      clone[key] = val.slice()
-      continue
-    }
-
-    if (typeof val === 'string' ||
-        typeof val === 'number' ||
-        typeof val === 'boolean') {
-      clone[key] = val
-      continue
-    }
-
-    throw new TypeError("clone is not deep and does not support nested objects")
-  }
-
-  return clone
-}
 lunr.FieldRef = function (docRef, fieldName, stringValue) {
   this.docRef = docRef
   this.fieldName = fieldName
@@ -175,139 +127,6 @@ lunr.FieldRef.prototype.toString = function () {
 
   return this._stringValue
 }
-/*!
- * lunr.Set
- * Copyright (C) 2019 Oliver Nightingale
- */
-
-/**
- * A lunr set.
- *
- * @constructor
- */
-lunr.Set = function (elements) {
-  this.elements = Object.create(null)
-
-  if (elements) {
-    this.length = elements.length
-
-    for (var i = 0; i < this.length; i++) {
-      this.elements[elements[i]] = true
-    }
-  } else {
-    this.length = 0
-  }
-}
-
-/**
- * A complete set that contains all elements.
- *
- * @static
- * @readonly
- * @type {lunr.Set}
- */
-lunr.Set.complete = {
-  intersect: function (other) {
-    return other
-  },
-
-  union: function (other) {
-    return other
-  },
-
-  contains: function () {
-    return true
-  }
-}
-
-/**
- * An empty set that contains no elements.
- *
- * @static
- * @readonly
- * @type {lunr.Set}
- */
-lunr.Set.empty = {
-  intersect: function () {
-    return this
-  },
-
-  union: function (other) {
-    return other
-  },
-
-  contains: function () {
-    return false
-  }
-}
-
-/**
- * Returns true if this set contains the specified object.
- *
- * @param {object} object - Object whose presence in this set is to be tested.
- * @returns {boolean} - True if this set contains the specified object.
- */
-lunr.Set.prototype.contains = function (object) {
-  return !!this.elements[object]
-}
-
-/**
- * Returns a new set containing only the elements that are present in both
- * this set and the specified set.
- *
- * @param {lunr.Set} other - set to intersect with this set.
- * @returns {lunr.Set} a new set that is the intersection of this and the specified set.
- */
-
-lunr.Set.prototype.intersect = function (other) {
-  var a, b, elements, intersection = []
-
-  if (other === lunr.Set.complete) {
-    return this
-  }
-
-  if (other === lunr.Set.empty) {
-    return other
-  }
-
-  if (this.length < other.length) {
-    a = this
-    b = other
-  } else {
-    a = other
-    b = this
-  }
-
-  elements = Object.keys(a.elements)
-
-  for (var i = 0; i < elements.length; i++) {
-    var element = elements[i]
-    if (element in b.elements) {
-      intersection.push(element)
-    }
-  }
-
-  return new lunr.Set (intersection)
-}
-
-/**
- * Returns a new set combining the elements of this and the specified set.
- *
- * @param {lunr.Set} other - set to union with this set.
- * @return {lunr.Set} a new set that is the union of this and the specified set.
- */
-
-lunr.Set.prototype.union = function (other) {
-  if (other === lunr.Set.complete) {
-    return lunr.Set.complete
-  }
-
-  if (other === lunr.Set.empty) {
-    return this
-  }
-
-  return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))
-}
 /**
  * A function to calculate the inverse document frequency for
  * a posting. This is shared between the builder and the index
@@ -389,7 +208,7 @@ lunr.Token.prototype.clone = function (fn) {
 }
 /*!
  * lunr.tokenizer
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
@@ -401,30 +220,22 @@ lunr.Token.prototype.clone = function (fn) {
  * then will split this string on the character in `lunr.tokenizer.separator`.
  * Arrays will have their elements converted to strings and wrapped in a lunr.Token.
  *
- * Optional metadata can be passed to the tokenizer, this metadata will be cloned and
- * added as metadata to every token that is created from the object to be tokenized.
- *
  * @static
  * @param {?(string|object|object[])} obj - The object to convert into tokens
- * @param {?object} metadata - Optional metadata to associate with every token
  * @returns {lunr.Token[]}
- * @see {@link lunr.Pipeline}
  */
-lunr.tokenizer = function (obj, metadata) {
+lunr.tokenizer = function (obj) {
   if (obj == null || obj == undefined) {
     return []
   }
 
   if (Array.isArray(obj)) {
     return obj.map(function (t) {
-      return new lunr.Token(
-        lunr.utils.asString(t).toLowerCase(),
-        lunr.utils.clone(metadata)
-      )
+      return new lunr.Token(lunr.utils.asString(t).toLowerCase())
     })
   }
 
-  var str = obj.toString().toLowerCase(),
+  var str = obj.toString().trim().toLowerCase(),
       len = str.length,
       tokens = []
 
@@ -435,15 +246,11 @@ lunr.tokenizer = function (obj, metadata) {
     if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {
 
       if (sliceLength > 0) {
-        var tokenMetadata = lunr.utils.clone(metadata) || {}
-        tokenMetadata["position"] = [sliceStart, sliceLength]
-        tokenMetadata["index"] = tokens.length
-
         tokens.push(
-          new lunr.Token (
-            str.slice(sliceStart, sliceEnd),
-            tokenMetadata
-          )
+          new lunr.Token (str.slice(sliceStart, sliceEnd), {
+            position: [sliceStart, sliceLength],
+            index: tokens.length
+          })
         )
       }
 
@@ -465,7 +272,7 @@ lunr.tokenizer = function (obj, metadata) {
 lunr.tokenizer.separator = /[\s\-]+/
 /*!
  * lunr.Pipeline
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
@@ -509,8 +316,8 @@ lunr.Pipeline.registeredFunctions = Object.create(null)
  * or mutate (or add) metadata for a given token.
  *
  * A pipeline function can indicate that the passed token should be discarded by returning
- * null, undefined or an empty string. This token will not be passed to any downstream pipeline
- * functions and will not be added to the index.
+ * null. This token will not be passed to any downstream pipeline functions and will not be
+ * added to the index.
  *
  * Multiple tokens can be returned by returning an array of tokens. Each token will be passed
  * to any downstream pipeline functions and all will returned tokens will be added to the index.
@@ -673,9 +480,9 @@ lunr.Pipeline.prototype.run = function (tokens) {
     for (var j = 0; j < tokens.length; j++) {
       var result = fn(tokens[j], j, tokens)
 
-      if (result === null || result === void 0 || result === '') continue
+      if (result === void 0 || result === '') continue
 
-      if (Array.isArray(result)) {
+      if (result instanceof Array) {
         for (var k = 0; k < result.length; k++) {
           memo.push(result[k])
         }
@@ -696,12 +503,10 @@ lunr.Pipeline.prototype.run = function (tokens) {
  * token and mapping the resulting tokens back to strings.
  *
  * @param {string} str - The string to pass through the pipeline.
- * @param {?object} metadata - Optional metadata to associate with the token
- * passed to the pipeline.
  * @returns {string[]}
  */
-lunr.Pipeline.prototype.runString = function (str, metadata) {
-  var token = new lunr.Token (str, metadata)
+lunr.Pipeline.prototype.runString = function (str) {
+  var token = new lunr.Token (str)
 
   return this.run([token]).map(function (t) {
     return t.toString()
@@ -732,7 +537,7 @@ lunr.Pipeline.prototype.toJSON = function () {
 }
 /*!
  * lunr.Vector
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
@@ -893,14 +698,15 @@ lunr.Vector.prototype.dot = function (otherVector) {
 }
 
 /**
- * Calculates the similarity between this vector and another vector.
+ * Calculates the cosine similarity between this vector and another
+ * vector.
  *
  * @param {lunr.Vector} otherVector - The other vector to calculate the
  * similarity with.
  * @returns {Number}
  */
 lunr.Vector.prototype.similarity = function (otherVector) {
-  return this.dot(otherVector) / this.magnitude() || 0
+  return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude())
 }
 
 /**
@@ -929,7 +735,7 @@ lunr.Vector.prototype.toJSON = function () {
 /* eslint-disable */
 /*!
  * lunr.stemmer
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
  */
 
@@ -942,7 +748,6 @@ lunr.Vector.prototype.toJSON = function () {
  * @param {lunr.Token} token - The string to stem
  * @returns {lunr.Token}
  * @see {@link lunr.Pipeline}
- * @function
  */
 lunr.stemmer = (function(){
   var step2list = {
@@ -1151,7 +956,7 @@ lunr.stemmer = (function(){
 lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
 /*!
  * lunr.stopWordFilter
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
@@ -1161,7 +966,6 @@ lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
  * The built in lunr.stopWordFilter is built using this generator and can be used
  * to generate custom stopWordFilters for applications or non English languages.
  *
- * @function
  * @param {Array} token The token to pass through the filter
  * @returns {lunr.PipelineFunction}
  * @see lunr.Pipeline
@@ -1185,7 +989,6 @@ lunr.generateStopWordFilter = function (stopWords) {
  * This is intended to be used in the Pipeline. If the token does not pass the
  * filter then undefined will be returned.
  *
- * @function
  * @implements {lunr.PipelineFunction}
  * @params {lunr.Token} token - A token to check for being a stop word.
  * @returns {lunr.Token}
@@ -1316,7 +1119,7 @@ lunr.stopWordFilter = lunr.generateStopWordFilter([
 lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
 /*!
  * lunr.trimmer
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
@@ -1343,7 +1146,7 @@ lunr.trimmer = function (token) {
 lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
 /*!
  * lunr.TokenSet
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
@@ -1460,58 +1263,50 @@ lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
 
       if (frame.str.length == 1) {
         noEditNode.final = true
+      } else {
+        stack.push({
+          node: noEditNode,
+          editsRemaining: frame.editsRemaining,
+          str: frame.str.slice(1)
+        })
       }
-
-      stack.push({
-        node: noEditNode,
-        editsRemaining: frame.editsRemaining,
-        str: frame.str.slice(1)
-      })
-    }
-
-    if (frame.editsRemaining == 0) {
-      continue
-    }
-
-    // insertion
-    if ("*" in frame.node.edges) {
-      var insertionNode = frame.node.edges["*"]
-    } else {
-      var insertionNode = new lunr.TokenSet
-      frame.node.edges["*"] = insertionNode
     }
 
-    if (frame.str.length == 0) {
-      insertionNode.final = true
-    }
-
-    stack.push({
-      node: insertionNode,
-      editsRemaining: frame.editsRemaining - 1,
-      str: frame.str
-    })
-
     // deletion
     // can only do a deletion if we have enough edits remaining
     // and if there are characters left to delete in the string
-    if (frame.str.length > 1) {
-      stack.push({
-        node: frame.node,
-        editsRemaining: frame.editsRemaining - 1,
-        str: frame.str.slice(1)
-      })
+    if (frame.editsRemaining > 0 && frame.str.length > 1) {
+      var char = frame.str.charAt(1),
+          deletionNode
+
+      if (char in frame.node.edges) {
+        deletionNode = frame.node.edges[char]
+      } else {
+        deletionNode = new lunr.TokenSet
+        frame.node.edges[char] = deletionNode
+      }
+
+      if (frame.str.length <= 2) {
+        deletionNode.final = true
+      } else {
+        stack.push({
+          node: deletionNode,
+          editsRemaining: frame.editsRemaining - 1,
+          str: frame.str.slice(2)
+        })
+      }
     }
 
     // deletion
     // just removing the last character from the str
-    if (frame.str.length == 1) {
+    if (frame.editsRemaining > 0 && frame.str.length == 1) {
       frame.node.final = true
     }
 
     // substitution
     // can only do a substitution if we have enough edits remaining
     // and if there are characters left to substitute
-    if (frame.str.length >= 1) {
+    if (frame.editsRemaining > 0 && frame.str.length >= 1) {
       if ("*" in frame.node.edges) {
         var substitutionNode = frame.node.edges["*"]
       } else {
@@ -1521,19 +1316,40 @@ lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
 
       if (frame.str.length == 1) {
         substitutionNode.final = true
+      } else {
+        stack.push({
+          node: substitutionNode,
+          editsRemaining: frame.editsRemaining - 1,
+          str: frame.str.slice(1)
+        })
       }
+    }
 
-      stack.push({
-        node: substitutionNode,
-        editsRemaining: frame.editsRemaining - 1,
-        str: frame.str.slice(1)
-      })
+    // insertion
+    // can only do insertion if there are edits remaining
+    if (frame.editsRemaining > 0) {
+      if ("*" in frame.node.edges) {
+        var insertionNode = frame.node.edges["*"]
+      } else {
+        var insertionNode = new lunr.TokenSet
+        frame.node.edges["*"] = insertionNode
+      }
+
+      if (frame.str.length == 0) {
+        insertionNode.final = true
+      } else {
+        stack.push({
+          node: insertionNode,
+          editsRemaining: frame.editsRemaining - 1,
+          str: frame.str
+        })
+      }
     }
 
     // transposition
     // can only do a transposition if there are edits remaining
     // and there are enough characters to transpose
-    if (frame.str.length > 1) {
+    if (frame.editsRemaining > 0 && frame.str.length > 1) {
       var charA = frame.str.charAt(0),
           charB = frame.str.charAt(1),
           transposeNode
@@ -1547,13 +1363,13 @@ lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
 
       if (frame.str.length == 1) {
         transposeNode.final = true
+      } else {
+        stack.push({
+          node: transposeNode,
+          editsRemaining: frame.editsRemaining - 1,
+          str: charA + frame.str.slice(2)
+        })
       }
-
-      stack.push({
-        node: transposeNode,
-        editsRemaining: frame.editsRemaining - 1,
-        str: charA + frame.str.slice(2)
-      })
     }
   }
 
@@ -1572,13 +1388,14 @@ lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
  */
 lunr.TokenSet.fromString = function (str) {
   var node = new lunr.TokenSet,
-      root = node
+      root = node,
+      wildcardFound = false
 
   /*
    * Iterates through all characters within the passed string
    * appending a node for each character.
    *
-   * When a wildcard character is found then a self
+   * As soon as a wildcard character is found then a self
    * referencing edge is introduced to continually match
    * any number of any characters.
    */
@@ -1587,6 +1404,7 @@ lunr.TokenSet.fromString = function (str) {
         final = (i == len - 1)
 
     if (char == "*") {
+      wildcardFound = true
       node.edges[char] = node
       node.final = final
 
@@ -1596,6 +1414,11 @@ lunr.TokenSet.fromString = function (str) {
 
       node.edges[char] = next
       node = next
+
+      // TODO: is this needed anymore?
+      if (wildcardFound) {
+        node.edges["*"] = root
+      }
     }
   }
 
@@ -1606,10 +1429,6 @@ lunr.TokenSet.fromString = function (str) {
  * Converts this TokenSet into an array of strings
  * contained within the TokenSet.
  *
- * This is not intended to be used on a TokenSet that
- * contains wildcards, in these cases the results are
- * undefined and are likely to cause an infinite loop.
- *
  * @returns {string[]}
  */
 lunr.TokenSet.prototype.toArray = function () {
@@ -1626,11 +1445,6 @@ lunr.TokenSet.prototype.toArray = function () {
         len = edges.length
 
     if (frame.node.final) {
-      /* In Safari, at this point the prefix is sometimes corrupted, see:
-       * https://github.com/olivernn/lunr.js/issues/279 Calling any
-       * String.prototype method forces Safari to "cast" this string to what
-       * it's supposed to be, fixing the bug. */
-      frame.prefix.charAt(0)
       words.push(frame.prefix)
     }
 
@@ -1827,7 +1641,7 @@ lunr.TokenSet.Builder.prototype.minimize = function (downTo) {
 }
 /*!
  * lunr.Index
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
@@ -1841,7 +1655,7 @@ lunr.TokenSet.Builder.prototype.minimize = function (downTo) {
  * @constructor
  * @param {Object} attrs - The attributes of the built search index.
  * @param {Object} attrs.invertedIndex - An index of term/field to document reference.
- * @param {Object} attrs.fieldVectors - Field vectors
+ * @param {Object} attrs.documentVectors - Document vectors keyed by document reference.
  * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.
  * @param {string[]} attrs.fields - The names of indexed document fields.
  * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.
@@ -1887,12 +1701,6 @@ lunr.Index = function (attrs) {
  * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.
  * Avoid large values for edit distance to improve query performance.
  *
- * Each term also supports a presence modifier. By default a term's presence in document is optional, however
- * this can be changed to either required or prohibited. For a term's presence to be required in a document the
- * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and
- * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not
- * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.
- *
  * To escape special characters the backslash character '\' can be used, this allows searches to include
  * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead
  * of attempting to apply a boost of 2 to the search term "foo".
@@ -1908,16 +1716,13 @@ lunr.Index = function (attrs) {
  * hello^10
  * @example term with an edit distance of 2
  * hello~2
- * @example terms with presence modifiers
- * -foo +bar baz
  */
 
 /**
  * Performs a search against the index using lunr query syntax.
  *
  * Results will be returned sorted by their score, the most relevant results
- * will be returned first.  For details on how the score is calculated, please see
- * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.
+ * will be returned first.
  *
  * For more programmatic querying use lunr.Index#query.
  *
@@ -1968,18 +1773,7 @@ lunr.Index.prototype.query = function (fn) {
   var query = new lunr.Query(this.fields),
       matchingFields = Object.create(null),
       queryVectors = Object.create(null),
-      termFieldCache = Object.create(null),
-      requiredMatches = Object.create(null),
-      prohibitedMatches = Object.create(null)
-
-  /*
-   * To support field level boosts a query vector is created per
-   * field. An empty vector is eagerly created to support negated
-   * queries.
-   */
-  for (var i = 0; i < this.fields.length; i++) {
-    queryVectors[this.fields[i]] = new lunr.Vector
-  }
+      termFieldCache = Object.create(null)
 
   fn.call(query, query)
 
@@ -1993,13 +1787,10 @@ lunr.Index.prototype.query = function (fn) {
      * for a single query term.
      */
     var clause = query.clauses[i],
-        terms = null,
-        clauseMatches = lunr.Set.complete
+        terms = null
 
     if (clause.usePipeline) {
-      terms = this.pipeline.runString(clause.term, {
-        fields: clause.fields
-      })
+      terms = this.pipeline.runString(clause.term)
     } else {
       terms = [clause.term]
     }
@@ -2023,21 +1814,6 @@ lunr.Index.prototype.query = function (fn) {
       var termTokenSet = lunr.TokenSet.fromClause(clause),
           expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()
 
-      /*
-       * If a term marked as required does not exist in the tokenSet it is
-       * impossible for the search to return any matches. We set all the field
-       * scoped required matches set to empty and stop examining any further
-       * clauses.
-       */
-      if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {
-        for (var k = 0; k < clause.fields.length; k++) {
-          var field = clause.fields[k]
-          requiredMatches[field] = lunr.Set.empty
-        }
-
-        break
-      }
-
       for (var j = 0; j < expandedTerms.length; j++) {
         /*
          * For each term get the posting and termIndex, this is required for
@@ -2059,50 +1835,26 @@ lunr.Index.prototype.query = function (fn) {
           var field = clause.fields[k],
               fieldPosting = posting[field],
               matchingDocumentRefs = Object.keys(fieldPosting),
-              termField = expandedTerm + "/" + field,
-              matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)
+              termField = expandedTerm + "/" + field
 
           /*
-           * if the presence of this term is required ensure that the matching
-           * documents are added to the set of required matches for this clause.
+           * To support field level boosts a query vector is created per
+           * field. This vector is populated using the termIndex found for
+           * the term and a unit value with the appropriate boost applied.
            *
+           * If the query vector for this field does not exist yet it needs
+           * to be created.
            */
-          if (clause.presence == lunr.Query.presence.REQUIRED) {
-            clauseMatches = clauseMatches.union(matchingDocumentsSet)
-
-            if (requiredMatches[field] === undefined) {
-              requiredMatches[field] = lunr.Set.complete
-            }
-          }
-
-          /*
-           * if the presence of this term is prohibited ensure that the matching
-           * documents are added to the set of prohibited matches for this field,
-           * creating that set if it does not yet exist.
-           */
-          if (clause.presence == lunr.Query.presence.PROHIBITED) {
-            if (prohibitedMatches[field] === undefined) {
-              prohibitedMatches[field] = lunr.Set.empty
-            }
-
-            prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)
-
-            /*
-             * Prohibited matches should not be part of the query vector used for
-             * similarity scoring and no metadata should be extracted so we continue
-             * to the next field
-             */
-            continue
+          if (queryVectors[field] === undefined) {
+            queryVectors[field] = new lunr.Vector
           }
 
           /*
-           * The query field vector is populated using the termIndex found for
-           * the term and a unit value with the appropriate boost applied.
            * Using upsert because there could already be an entry in the vector
            * for the term we are working with. In that case we just add the scores
            * together.
            */
-          queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })
+          queryVectors[field].upsert(termIndex, 1 * clause.boost, function (a, b) { return a + b })
 
           /**
            * If we've already seen this term, field combo then we've already collected
@@ -2136,65 +1888,12 @@ lunr.Index.prototype.query = function (fn) {
         }
       }
     }
-
-    /**
-     * If the presence was required we need to update the requiredMatches field sets.
-     * We do this after all fields for the term have collected their matches because
-     * the clause terms presence is required in _any_ of the fields not _all_ of the
-     * fields.
-     */
-    if (clause.presence === lunr.Query.presence.REQUIRED) {
-      for (var k = 0; k < clause.fields.length; k++) {
-        var field = clause.fields[k]
-        requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)
-      }
-    }
-  }
-
-  /**
-   * Need to combine the field scoped required and prohibited
-   * matching documents into a global set of required and prohibited
-   * matches
-   */
-  var allRequiredMatches = lunr.Set.complete,
-      allProhibitedMatches = lunr.Set.empty
-
-  for (var i = 0; i < this.fields.length; i++) {
-    var field = this.fields[i]
-
-    if (requiredMatches[field]) {
-      allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])
-    }
-
-    if (prohibitedMatches[field]) {
-      allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])
-    }
   }
 
   var matchingFieldRefs = Object.keys(matchingFields),
       results = [],
       matches = Object.create(null)
 
-  /*
-   * If the query is negated (contains only prohibited terms)
-   * we need to get _all_ fieldRefs currently existing in the
-   * index. This is only done when we know that the query is
-   * entirely prohibited terms to avoid any cost of getting all
-   * fieldRefs unnecessarily.
-   *
-   * Additionally, blank MatchData must be created to correctly
-   * populate the results.
-   */
-  if (query.isNegated()) {
-    matchingFieldRefs = Object.keys(this.fieldVectors)
-
-    for (var i = 0; i < matchingFieldRefs.length; i++) {
-      var matchingFieldRef = matchingFieldRefs[i]
-      var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)
-      matchingFields[matchingFieldRef] = new lunr.MatchData
-    }
-  }
-
   for (var i = 0; i < matchingFieldRefs.length; i++) {
     /*
      * Currently we have document fields that match the query, but we
@@ -2205,17 +1904,8 @@ lunr.Index.prototype.query = function (fn) {
      * above, and combined into a final document score using addition.
      */
     var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),
-        docRef = fieldRef.docRef
-
-    if (!allRequiredMatches.contains(docRef)) {
-      continue
-    }
-
-    if (allProhibitedMatches.contains(docRef)) {
-      continue
-    }
-
-    var fieldVector = this.fieldVectors[fieldRef],
+        docRef = fieldRef.docRef,
+        fieldVector = this.fieldVectors[fieldRef],
         score = queryVectors[fieldRef.fieldName].similarity(fieldVector),
         docMatch
 
@@ -2280,7 +1970,7 @@ lunr.Index.load = function (serializedIndex) {
   var attrs = {},
       fieldVectors = {},
       serializedVectors = serializedIndex.fieldVectors,
-      invertedIndex = Object.create(null),
+      invertedIndex = {},
       serializedInvertedIndex = serializedIndex.invertedIndex,
       tokenSetBuilder = new lunr.TokenSet.Builder,
       pipeline = lunr.Pipeline.load(serializedIndex.pipeline)
@@ -2319,7 +2009,7 @@ lunr.Index.load = function (serializedIndex) {
 }
 /*!
  * lunr.Builder
- * Copyright (C) 2019 Oliver Nightingale
+ * Copyright (C) 2018 Oliver Nightingale
  */
 
 /**
@@ -2348,8 +2038,7 @@ lunr.Index.load = function (serializedIndex) {
  */
 lunr.Builder = function () {
   this._ref = "id"
-  this._fields = Object.create(null)
-  this._documents = Object.create(null)
+  this._fields = []
   this.invertedIndex = Object.create(null)
   this.fieldTermFrequencies = {}
   this.fieldLengths = {}
@@ -2379,20 +2068,6 @@ lunr.Builder.prototype.ref = function (ref) {
   this._ref = ref
 }
 
-/**
- * A function that is used to extract a field from a document.
- *
- * Lunr expects a field to be at the top level of a document, if however the field
- * is deeply nested within a document an extractor function can be used to extract
- * the right field for indexing.
- *
- * @callback fieldExtractor
- * @param {object} doc - The document being added to the index.
- * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.
- * @example Extracting a nested field
- * function (doc) { return doc.nested.field }
- */
-
 /**
  * Adds a field to the list of document fields that will be indexed. Every document being
  * indexed should have this field. Null values for this field in indexed documents will
@@ -2401,22 +2076,10 @@ lunr.Builder.prototype.ref = function (ref) {
  * All fields should be added before adding documents to the index. Adding fields after
  * a document has been indexed will have no effect on already indexed documents.
  *
- * Fields can be boosted at build time. This allows terms within that field to have more
- * importance when ranking search results. Use a field boost to specify that matches within
- * one field are more important than other fields.
- *
- * @param {string} fieldName - The name of a field to index in all documents.
- * @param {object} attributes - Optional attributes associated with this field.
- * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.
- * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.
- * @throws {RangeError} fieldName cannot contain unsupported characters '/'
+ * @param {string} field - The name of a field to index in all documents.
  */
-lunr.Builder.prototype.field = function (fieldName, attributes) {
-  if (/\//.test(fieldName)) {
-    throw new RangeError ("Field '" + fieldName + "' contains illegal character '/'")
-  }
-
-  this._fields[fieldName] = attributes || {}
+lunr.Builder.prototype.field = function (field) {
+  this._fields.push(field)
 }
 
 /**
@@ -2458,27 +2121,17 @@ lunr.Builder.prototype.k1 = function (number) {
  * it should have all fields defined for indexing, though null or undefined values will not
  * cause errors.
  *
- * Entire documents can be boosted at build time. Applying a boost to a document indicates that
- * this document should rank higher in search results than other documents.
- *
  * @param {object} doc - The document to add to the index.
- * @param {object} attributes - Optional attributes associated with this document.
- * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.
  */
-lunr.Builder.prototype.add = function (doc, attributes) {
-  var docRef = doc[this._ref],
-      fields = Object.keys(this._fields)
+lunr.Builder.prototype.add = function (doc) {
+  var docRef = doc[this._ref]
 
-  this._documents[docRef] = attributes || {}
   this.documentCount += 1
 
-  for (var i = 0; i < fields.length; i++) {
-    var fieldName = fields[i],
-        extractor = this._fields[fieldName].extractor,
-        field = extractor ? extractor(doc) : doc[fieldName],
-        tokens = this.tokenizer(field, {
-          fields: [fieldName]
-        }),
+  for (var i = 0; i < this._fields.length; i++) {
+    var fieldName = this._fields[i],
+        field = doc[fieldName],
+        tokens = this.tokenizer(field),
         terms = this.pipeline.run(tokens),
         fieldRef = new lunr.FieldRef (docRef, fieldName),
         fieldTerms = Object.create(null)
@@ -2506,8 +2159,8 @@ lunr.Builder.prototype.add = function (doc, attributes) {
         posting["_index"] = this.termIndex
         this.termIndex += 1
 
-        for (var k = 0; k < fields.length; k++) {
-          posting[fields[k]] = Object.create(null)
+        for (var k = 0; k < this._fields.length; k++) {
+          posting[this._fields[k]] = Object.create(null)
         }
 
         this.invertedIndex[term] = posting
@@ -2558,11 +2211,9 @@ lunr.Builder.prototype.calculateAverageFieldLengths = function () {
     accumulator[field] += this.fieldLengths[fieldRef]
   }
 
-  var fields = Object.keys(this._fields)
-
-  for (var i = 0; i < fields.length; i++) {
-    var fieldName = fields[i]
-    accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]
+  for (var i = 0; i < this._fields.length; i++) {
+    var field = this._fields[i]
+    accumulator[field] = accumulator[field] / documentsWithField[field]
   }
 
   this.averageFieldLength = accumulator
@@ -2581,17 +2232,13 @@ lunr.Builder.prototype.createFieldVectors = function () {
 
   for (var i = 0; i < fieldRefsLength; i++) {
     var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
-        fieldName = fieldRef.fieldName,
+        field = fieldRef.fieldName,
         fieldLength = this.fieldLengths[fieldRef],
         fieldVector = new lunr.Vector,
         termFrequencies = this.fieldTermFrequencies[fieldRef],
         terms = Object.keys(termFrequencies),
         termsLength = terms.length
 
-
-    var fieldBoost = this._fields[fieldName].boost || 1,
-        docBoost = this._documents[fieldRef.docRef].boost || 1
-
     for (var j = 0; j < termsLength; j++) {
       var term = terms[j],
           tf = termFrequencies[term],
@@ -2605,9 +2252,7 @@ lunr.Builder.prototype.createFieldVectors = function () {
         idf = termIdfCache[term]
       }
 
-      score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)
-      score *= fieldBoost
-      score *= docBoost
+      score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[field])) + tf)
       scoreWithPrecision = Math.round(score * 1000) / 1000
       // Converts 1.23456789 to 1.234.
       // Reducing the precision so that the vectors take up less
@@ -2653,7 +2298,7 @@ lunr.Builder.prototype.build = function () {
     invertedIndex: this.invertedIndex,
     fieldVectors: this.fieldVectors,
     tokenSet: this.tokenSet,
-    fields: Object.keys(this._fields),
+    fields: this._fields,
     pipeline: this.searchPipeline
   })
 }
@@ -2691,7 +2336,7 @@ lunr.Builder.prototype.use = function (fn) {
  */
 lunr.MatchData = function (term, field, metadata) {
   var clonedMetadata = Object.create(null),
-      metadataKeys = Object.keys(metadata || {})
+      metadataKeys = Object.keys(metadata)
 
   // Cloning the metadata to prevent the original
   // being mutated during match data combination.
@@ -2704,11 +2349,8 @@ lunr.MatchData = function (term, field, metadata) {
   }
 
   this.metadata = Object.create(null)
-
-  if (term !== undefined) {
-    this.metadata[term] = Object.create(null)
-    this.metadata[term][field] = clonedMetadata
-  }
+  this.metadata[term] = Object.create(null)
+  this.metadata[term][field] = clonedMetadata
 }
 
 /**
@@ -2823,42 +2465,11 @@ lunr.Query = function (allFields) {
  *   wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING
  * })
  */
-
 lunr.Query.wildcard = new String ("*")
 lunr.Query.wildcard.NONE = 0
 lunr.Query.wildcard.LEADING = 1
 lunr.Query.wildcard.TRAILING = 2
 
-/**
- * Constants for indicating what kind of presence a term must have in matching documents.
- *
- * @constant
- * @enum {number}
- * @see lunr.Query~Clause
- * @see lunr.Query#clause
- * @see lunr.Query#term
- * @example query term with required presence
- * query.term('foo', { presence: lunr.Query.presence.REQUIRED })
- */
-lunr.Query.presence = {
-  /**
-   * Term's presence in a document is optional, this is the default value.
-   */
-  OPTIONAL: 1,
-
-  /**
-   * Term's presence in a document is required, documents that do not contain
-   * this term will not be returned.
-   */
-  REQUIRED: 2,
-
-  /**
-   * Term's presence in a document is prohibited, documents that do contain
-   * this term will not be returned.
-   */
-  PROHIBITED: 3
-}
-
 /**
  * A single clause in a {@link lunr.Query} contains a term and details on how to
  * match that term against a {@link lunr.Index}.
@@ -2868,8 +2479,7 @@ lunr.Query.presence = {
  * @property {number} [boost=1] - Any boost that should be applied when matching this clause.
  * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.
  * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.
- * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.
- * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.
+ * @property {number} [wildcard=0] - Whether the term should have wildcards appended or prepended.
  */
 
 /**
@@ -2907,44 +2517,17 @@ lunr.Query.prototype.clause = function (clause) {
     clause.term = "" + clause.term + "*"
   }
 
-  if (!('presence' in clause)) {
-    clause.presence = lunr.Query.presence.OPTIONAL
-  }
-
   this.clauses.push(clause)
 
   return this
 }
 
-/**
- * A negated query is one in which every clause has a presence of
- * prohibited. These queries require some special processing to return
- * the expected results.
- *
- * @returns boolean
- */
-lunr.Query.prototype.isNegated = function () {
-  for (var i = 0; i < this.clauses.length; i++) {
-    if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {
-      return false
-    }
-  }
-
-  return true
-}
-
 /**
  * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}
  * to the list of clauses that make up this query.
  *
- * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion
- * to a token or token-like string should be done before calling this method.
- *
- * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an
- * array, each term in the array will share the same options.
- *
- * @param {object|object[]} term - The term(s) to add to the query.
- * @param {object} [options] - Any additional properties to add to the query clause.
+ * @param {string} term - The term to add to the query.
+ * @param {Object} [options] - Any additional properties to add to the query clause.
  * @returns {lunr.Query}
  * @see lunr.Query#clause
  * @see lunr.Query~Clause
@@ -2956,17 +2539,10 @@ lunr.Query.prototype.isNegated = function () {
  *   boost: 10,
  *   wildcard: lunr.Query.wildcard.TRAILING
  * })
- * @example using lunr.tokenizer to convert a string to tokens before using them as terms
- * query.term(lunr.tokenizer("foo bar"))
  */
 lunr.Query.prototype.term = function (term, options) {
-  if (Array.isArray(term)) {
-    term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)
-    return this
-  }
-
   var clause = options || {}
-  clause.term = term.toString()
+  clause.term = term
 
   this.clause(clause)
 
@@ -3078,7 +2654,6 @@ lunr.QueryLexer.FIELD = 'FIELD'
 lunr.QueryLexer.TERM = 'TERM'
 lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'
 lunr.QueryLexer.BOOST = 'BOOST'
-lunr.QueryLexer.PRESENCE = 'PRESENCE'
 
 lunr.QueryLexer.lexField = function (lexer) {
   lexer.backup()
@@ -3167,22 +2742,6 @@ lunr.QueryLexer.lexText = function (lexer) {
       return lunr.QueryLexer.lexBoost
     }
 
-    // "+" indicates term presence is required
-    // checking for length to ensure that only
-    // leading "+" are considered
-    if (char == "+" && lexer.width() === 1) {
-      lexer.emit(lunr.QueryLexer.PRESENCE)
-      return lunr.QueryLexer.lexText
-    }
-
-    // "-" indicates term presence is prohibited
-    // checking for length to ensure that only
-    // leading "-" are considered
-    if (char == "-" && lexer.width() === 1) {
-      lexer.emit(lunr.QueryLexer.PRESENCE)
-      return lunr.QueryLexer.lexText
-    }
-
     if (char.match(lunr.QueryLexer.termSeparator)) {
       return lunr.QueryLexer.lexTerm
     }
@@ -3200,7 +2759,7 @@ lunr.QueryParser.prototype.parse = function () {
   this.lexer.run()
   this.lexemes = this.lexer.lexemes
 
-  var state = lunr.QueryParser.parseClause
+  var state = lunr.QueryParser.parseFieldOrTerm
 
   while (state) {
     state = state(this)
@@ -3225,7 +2784,7 @@ lunr.QueryParser.prototype.nextClause = function () {
   this.currentClause = {}
 }
 
-lunr.QueryParser.parseClause = function (parser) {
+lunr.QueryParser.parseFieldOrTerm = function (parser) {
   var lexeme = parser.peekLexeme()
 
   if (lexeme == undefined) {
@@ -3233,8 +2792,6 @@ lunr.QueryParser.parseClause = function (parser) {
   }
 
   switch (lexeme.type) {
-    case lunr.QueryLexer.PRESENCE:
-      return lunr.QueryParser.parsePresence
     case lunr.QueryLexer.FIELD:
       return lunr.QueryParser.parseField
     case lunr.QueryLexer.TERM:
@@ -3250,43 +2807,6 @@ lunr.QueryParser.parseClause = function (parser) {
   }
 }
 
-lunr.QueryParser.parsePresence = function (parser) {
-  var lexeme = parser.consumeLexeme()
-
-  if (lexeme == undefined) {
-    return
-  }
-
-  switch (lexeme.str) {
-    case "-":
-      parser.currentClause.presence = lunr.Query.presence.PROHIBITED
-      break
-    case "+":
-      parser.currentClause.presence = lunr.Query.presence.REQUIRED
-      break
-    default:
-      var errorMessage = "unrecognised presence operator'" + lexeme.str + "'"
-      throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
-  }
-
-  var nextLexeme = parser.peekLexeme()
-
-  if (nextLexeme == undefined) {
-    var errorMessage = "expecting term or field, found nothing"
-    throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
-  }
-
-  switch (nextLexeme.type) {
-    case lunr.QueryLexer.FIELD:
-      return lunr.QueryParser.parseField
-    case lunr.QueryLexer.TERM:
-      return lunr.QueryParser.parseTerm
-    default:
-      var errorMessage = "expecting term or field, found '" + nextLexeme.type + "'"
-      throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
-  }
-}
-
 lunr.QueryParser.parseField = function (parser) {
   var lexeme = parser.consumeLexeme()
 
@@ -3350,9 +2870,6 @@ lunr.QueryParser.parseTerm = function (parser) {
       return lunr.QueryParser.parseEditDistance
     case lunr.QueryLexer.BOOST:
       return lunr.QueryParser.parseBoost
-    case lunr.QueryLexer.PRESENCE:
-      parser.nextClause()
-      return lunr.QueryParser.parsePresence
     default:
       var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
       throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
@@ -3393,9 +2910,6 @@ lunr.QueryParser.parseEditDistance = function (parser) {
       return lunr.QueryParser.parseEditDistance
     case lunr.QueryLexer.BOOST:
       return lunr.QueryParser.parseBoost
-    case lunr.QueryLexer.PRESENCE:
-      parser.nextClause()
-      return lunr.QueryParser.parsePresence
     default:
       var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
       throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
@@ -3436,9 +2950,6 @@ lunr.QueryParser.parseBoost = function (parser) {
       return lunr.QueryParser.parseEditDistance
     case lunr.QueryLexer.BOOST:
       return lunr.QueryParser.parseBoost
-    case lunr.QueryLexer.PRESENCE:
-      parser.nextClause()
-      return lunr.QueryParser.parsePresence
     default:
       var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
       throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
diff --git a/docs/search/search_index.json b/docs/search/search_index.json
index 58a960a..93d3c35 100644
--- a/docs/search/search_index.json
+++ b/docs/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"RELOG: Reverse Logistics Optimization RELOG is a supply chain optimization package focusing on reverse logistics and reverse manufacturing. For example, the package can be used to determine where to build recycling plants, what sizes should they have and which customers should be served by which plants. The package supports customized reverse logistics pipelines, with multiple types of plants, multiple types of product and multiple time periods. Table of Contents Installation Modeling Optimizing Source Code https://github.com/iSoron/RELOG Authors Alinson S. Xavier, Argonne National Laboratory < axavier@anl.gov > Nwike Iloeje, Argonne National Laboratory < ciloeje@anl.gov > License RELOG: Reverse Logistics Optimization Copyright \u00a9 2020, UChicago Argonne, LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.","title":"Home"},{"location":"#relog-reverse-logistics-optimization","text":"RELOG is a supply chain optimization package focusing on reverse logistics and reverse manufacturing. For example, the package can be used to determine where to build recycling plants, what sizes should they have and which customers should be served by which plants. The package supports customized reverse logistics pipelines, with multiple types of plants, multiple types of product and multiple time periods.","title":"RELOG: Reverse Logistics Optimization"},{"location":"#table-of-contents","text":"Installation Modeling Optimizing","title":"Table of Contents"},{"location":"#source-code","text":"https://github.com/iSoron/RELOG","title":"Source Code"},{"location":"#authors","text":"Alinson S. Xavier, Argonne National Laboratory < axavier@anl.gov > Nwike Iloeje, Argonne National Laboratory < ciloeje@anl.gov >","title":"Authors"},{"location":"#license","text":"RELOG: Reverse Logistics Optimization Copyright \u00a9 2020, UChicago Argonne, LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.","title":"License"},{"location":"install/","text":"Installation The package was developed and tested with Julia 1.3 and may not be compatible with newer versions. To install it, launch the Julia console, type ] to switch to package manager mode and run: pkg> add https://github.com/iSoron/RELOG.git To make sure that the package has been correctly installed: pkg> test RELOG","title":"Install"},{"location":"install/#installation","text":"The package was developed and tested with Julia 1.3 and may not be compatible with newer versions. To install it, launch the Julia console, type ] to switch to package manager mode and run: pkg> add https://github.com/iSoron/RELOG.git To make sure that the package has been correctly installed: pkg> test RELOG","title":"Installation"},{"location":"model/","text":"Modeling The first step when using RELOG is to describe the reverse logistics pipeline and the relevant data. RELOG accepts as input a JSON file with three sections: parameters , products and plants . Below, we describe each section in more detail. Parameters The parameters section describes details about the simulation itself. Key Description time periods Number of time periods in the simulation. Example { \"parameters\": { \"time periods\": 2 } } Products The products section describes all products and subproducts in the simulation. The field instance[\"products\"] is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys: Key Description transportation cost The cost (in dollars per km per tonnes) to transport this product. Must be a timeseries. initial amounts A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted. Must be a timeseries. Each product may have some amount available at the beginning of each time period. In this case, the key initial amounts maps to a dictionary with the following keys: Key Description latitude The latitude of the location, in degrees. longitude The longitude of the location, in degrees. amount The amount (in tonnes) of the product initially available at the location. Must be a timeseries. Example { \"products\": { \"P1\": { \"transportation cost\": [0.015, 0.015], \"initial amounts\": { \"C1\": { \"latitude\": 7.0, \"longitude\": 7.0, \"amount\": [934.56, 934.56] }, \"C2\": { \"latitude\": 7.0, \"longitude\": 19.0, \"amount\": [198.95, 198.95] }, \"C3\": { \"latitude\": 84.0, \"longitude\": 76.0, \"amount\": [212.97, 212.97] } } }, \"P2\": { \"transportation cost\": [0.02, 0.02] }, \"P3\": { \"transportation cost\": [0.0125, 0.0125] }, \"P4\": { \"transportation cost\": [0.0175, 0.0175] } } } Processing Plants The plants section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field instance[\"plants\"] is a dictionary mapping the name of the plant to a dictionary with the following keys: Key Description input The name of the product that this plant takes as input. Only one input is accepted per plant. outputs A dictionary specifying how many tonnes of each product is produced for each tonnes of input. For example, if the plant outputs 0.5 tonnes of P2 and 0.25 tonnes of P3 for each tonnes of P1 provided, then this entry should be {\"P2\": 0.5, \"P3\": 0.25} . If the plant does not output anything, this key may be omitted. locations A dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below). Each type of plant is associated with a set of potential locations where it can be built. Each location is represented by a dictionary with the following keys: Key Description latitude The latitude of the location, in degrees. longitude The longitude of the location, in degrees. disposal A dictionary describing what products can be disposed locally at the plant. capacities A dictionary describing what plant sizes are allowed, and their characteristics. The keys in the disposal dictionary should be the names of the products. The values are dictionaries with the following keys: Key Description cost The cost (in dollars per tonnes) to dispose of the product. Must be a timeseries. limit The maximum amount (in tonnes) that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries. The keys in the capacities dictionary should be the amounts (in tonnes). The values are dictionaries with the following keys: Key Description opening cost The cost (in dollars) to open a plant of this size. fixed operating cost The cost (in dollars) to keep the plant open, even if the plant doesn't process anything. Must be a timeseries. variable operating cost The cost (in dollars per tonnes) that the plant incurs to process each tonne of input. Must be a timeseries. Example { \"plants\": { \"F1\": { \"input\": \"P1\", \"outputs\": { \"P2\": 0.2, \"P3\": 0.5 }, \"locations\": { \"L1\": { \"latitude\": 0.0, \"longitude\": 0.0, \"disposal\": { \"P2\": { \"cost\": [-10.0, -12.0], \"limit\": [1.0, 1.0] } }, \"capacities\": { \"100\": { \"opening cost\": [500, 530], \"fixed operating cost\": [300.0, 310.0], \"variable operating cost\": [5.0, 5.2] }, \"500\": { \"opening cost\": [750, 760], \"fixed operating cost\": [400.0, 450.0], \"variable operating cost\": [5.0, 5.2] } } } } } } } Current limitations Each plant can only be opened exactly once. After open, the plant remains open until the end of the simulation. Plants can be expanded at any time, even long after they are open. All material available at the beginning of a time period must be entirely processed by the end of that time period. It is not possible to store unprocessed materials from one time period to the next. Up to two plant sizes are currently supported. Variable operating costs must be the same for all plant sizes.","title":"Model"},{"location":"model/#modeling","text":"The first step when using RELOG is to describe the reverse logistics pipeline and the relevant data. RELOG accepts as input a JSON file with three sections: parameters , products and plants . Below, we describe each section in more detail.","title":"Modeling"},{"location":"model/#parameters","text":"The parameters section describes details about the simulation itself. Key Description time periods Number of time periods in the simulation.","title":"Parameters"},{"location":"model/#example","text":"{ \"parameters\": { \"time periods\": 2 } }","title":"Example"},{"location":"model/#products","text":"The products section describes all products and subproducts in the simulation. The field instance[\"products\"] is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys: Key Description transportation cost The cost (in dollars per km per tonnes) to transport this product. Must be a timeseries. initial amounts A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted. Must be a timeseries. Each product may have some amount available at the beginning of each time period. In this case, the key initial amounts maps to a dictionary with the following keys: Key Description latitude The latitude of the location, in degrees. longitude The longitude of the location, in degrees. amount The amount (in tonnes) of the product initially available at the location. Must be a timeseries.","title":"Products"},{"location":"model/#example_1","text":"{ \"products\": { \"P1\": { \"transportation cost\": [0.015, 0.015], \"initial amounts\": { \"C1\": { \"latitude\": 7.0, \"longitude\": 7.0, \"amount\": [934.56, 934.56] }, \"C2\": { \"latitude\": 7.0, \"longitude\": 19.0, \"amount\": [198.95, 198.95] }, \"C3\": { \"latitude\": 84.0, \"longitude\": 76.0, \"amount\": [212.97, 212.97] } } }, \"P2\": { \"transportation cost\": [0.02, 0.02] }, \"P3\": { \"transportation cost\": [0.0125, 0.0125] }, \"P4\": { \"transportation cost\": [0.0175, 0.0175] } } }","title":"Example"},{"location":"model/#processing-plants","text":"The plants section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field instance[\"plants\"] is a dictionary mapping the name of the plant to a dictionary with the following keys: Key Description input The name of the product that this plant takes as input. Only one input is accepted per plant. outputs A dictionary specifying how many tonnes of each product is produced for each tonnes of input. For example, if the plant outputs 0.5 tonnes of P2 and 0.25 tonnes of P3 for each tonnes of P1 provided, then this entry should be {\"P2\": 0.5, \"P3\": 0.25} . If the plant does not output anything, this key may be omitted. locations A dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below). Each type of plant is associated with a set of potential locations where it can be built. Each location is represented by a dictionary with the following keys: Key Description latitude The latitude of the location, in degrees. longitude The longitude of the location, in degrees. disposal A dictionary describing what products can be disposed locally at the plant. capacities A dictionary describing what plant sizes are allowed, and their characteristics. The keys in the disposal dictionary should be the names of the products. The values are dictionaries with the following keys: Key Description cost The cost (in dollars per tonnes) to dispose of the product. Must be a timeseries. limit The maximum amount (in tonnes) that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries. The keys in the capacities dictionary should be the amounts (in tonnes). The values are dictionaries with the following keys: Key Description opening cost The cost (in dollars) to open a plant of this size. fixed operating cost The cost (in dollars) to keep the plant open, even if the plant doesn't process anything. Must be a timeseries. variable operating cost The cost (in dollars per tonnes) that the plant incurs to process each tonne of input. Must be a timeseries.","title":"Processing Plants"},{"location":"model/#example_2","text":"{ \"plants\": { \"F1\": { \"input\": \"P1\", \"outputs\": { \"P2\": 0.2, \"P3\": 0.5 }, \"locations\": { \"L1\": { \"latitude\": 0.0, \"longitude\": 0.0, \"disposal\": { \"P2\": { \"cost\": [-10.0, -12.0], \"limit\": [1.0, 1.0] } }, \"capacities\": { \"100\": { \"opening cost\": [500, 530], \"fixed operating cost\": [300.0, 310.0], \"variable operating cost\": [5.0, 5.2] }, \"500\": { \"opening cost\": [750, 760], \"fixed operating cost\": [400.0, 450.0], \"variable operating cost\": [5.0, 5.2] } } } } } } }","title":"Example"},{"location":"model/#current-limitations","text":"Each plant can only be opened exactly once. After open, the plant remains open until the end of the simulation. Plants can be expanded at any time, even long after they are open. All material available at the beginning of a time period must be entirely processed by the end of that time period. It is not possible to store unprocessed materials from one time period to the next. Up to two plant sizes are currently supported. Variable operating costs must be the same for all plant sizes.","title":"Current limitations"},{"location":"optimize/","text":"Optimizing After creating a JSON file describing the reverse manufacturing process and the input data, the following example illustrates how to use the package to find the optimal set of decisions: using RELOG RELOG.solve(\"/home/user/instance.json\") The optimal logistics plan will be printed to the screen.","title":"Optimize"},{"location":"optimize/#optimizing","text":"After creating a JSON file describing the reverse manufacturing process and the input data, the following example illustrates how to use the package to find the optimal set of decisions: using RELOG RELOG.solve(\"/home/user/instance.json\") The optimal logistics plan will be printed to the screen.","title":"Optimizing"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"RELOG: Reverse Logistics Optimization RELOG is a supply chain optimization package focusing on reverse logistics and reverse manufacturing. For example, the package can be used to determine where to build recycling plants, what sizes should they have and which customers should be served by which plants. The package supports customized reverse logistics pipelines, with multiple types of plants, multiple types of product and multiple time periods. Table of Contents Installation Modeling Optimizing Source Code https://github.com/iSoron/RELOG Authors Alinson S. Xavier, Argonne National Laboratory < axavier@anl.gov > Nwike Iloeje, Argonne National Laboratory < ciloeje@anl.gov > License RELOG: Reverse Logistics Optimization Copyright \u00a9 2020, UChicago Argonne, LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.","title":"Home"},{"location":"#relog-reverse-logistics-optimization","text":"RELOG is a supply chain optimization package focusing on reverse logistics and reverse manufacturing. For example, the package can be used to determine where to build recycling plants, what sizes should they have and which customers should be served by which plants. The package supports customized reverse logistics pipelines, with multiple types of plants, multiple types of product and multiple time periods.","title":"RELOG: Reverse Logistics Optimization"},{"location":"#table-of-contents","text":"Installation Modeling Optimizing","title":"Table of Contents"},{"location":"#source-code","text":"https://github.com/iSoron/RELOG","title":"Source Code"},{"location":"#authors","text":"Alinson S. Xavier, Argonne National Laboratory < axavier@anl.gov > Nwike Iloeje, Argonne National Laboratory < ciloeje@anl.gov >","title":"Authors"},{"location":"#license","text":"RELOG: Reverse Logistics Optimization Copyright \u00a9 2020, UChicago Argonne, LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.","title":"License"},{"location":"install/","text":"Installation The package was developed and tested with Julia 1.3 and may not be compatible with newer versions. To install it, launch the Julia console, type ] to switch to package manager mode and run: pkg> add https://github.com/iSoron/RELOG.git To make sure that the package has been correctly installed: pkg> test RELOG","title":"Install"},{"location":"install/#installation","text":"The package was developed and tested with Julia 1.3 and may not be compatible with newer versions. To install it, launch the Julia console, type ] to switch to package manager mode and run: pkg> add https://github.com/iSoron/RELOG.git To make sure that the package has been correctly installed: pkg> test RELOG","title":"Installation"},{"location":"model/","text":"Modeling The first step when using RELOG is to describe the reverse logistics pipeline and the relevant data. RELOG accepts as input a JSON file with three sections: parameters , products and plants . Below, we describe each section in more detail. Parameters The parameters section describes details about the simulation itself. Key Description Time horizon (years) Number of years in the simulation. Example { \"Parameters\": { \"Time horizon (years)\": 2 } } Products The products section describes all products and subproducts in the simulation. The field instance[\"Products\"] is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys: Key Description Transportation cost ($/km/tonne) The cost to transport this product. Must be a timeseries. Initial amounts A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted. Must be a timeseries. Each product may have some amount available at the beginning of each time period. In this case, the key initial amounts maps to a dictionary with the following keys: Key Description Latitude (deg) The latitude of the location. Longitude (deg) The longitude of the location. Amount (tonne) The amount of the product initially available at the location. Must be a timeseries. Example { \"Products\": { \"P1\": { \"Transportation cost ($/km/tonne)\": [0.015, 0.015], \"Initial amounts\": { \"C1\": { \"Latitude (deg)\": 7.0, \"Longitude (deg)\": 7.0, \"Amount (tonne)\": [934.56, 934.56] }, \"C2\": { \"Latitude (deg)\": 7.0, \"Longitude (deg)\": 19.0, \"Amount (tonne)\": [198.95, 198.95] }, \"C3\": { \"Latitude (deg)\": 84.0, \"Longitude (deg)\": 76.0, \"Amount (tonne)\": [212.97, 212.97] } } }, \"P2\": { \"Transportation cost ($/km/tonne)\": [0.02, 0.02] }, \"P3\": { \"Transportation cost ($/km/tonne)\": [0.0125, 0.0125] }, \"P4\": { \"Transportation cost ($/km/tonne)\": [0.0175, 0.0175] } } } Processing Plants The plants section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field instance[\"Plants\"] is a dictionary mapping the name of the plant to a dictionary with the following keys: Key Description Input The name of the product that this plant takes as input. Only one input is accepted per plant. Outputs (tonne) A dictionary specifying how many tonnes of each product is produced for each tonnes of input. For example, if the plant outputs 0.5 tonnes of P2 and 0.25 tonnes of P3 for each tonnes of P1 provided, then this entry should be {\"P2\": 0.5, \"P3\": 0.25} . If the plant does not output anything, this key may be omitted. Locations A dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below). Each type of plant is associated with a set of potential locations where it can be built. Each location is represented by a dictionary with the following keys: Key Description Latitude (deg) The latitude of the location, in degrees. Longitude (deg) The longitude of the location, in degrees. Disposal A dictionary describing what products can be disposed locally at the plant. Capacities (tonne) A dictionary describing what plant sizes are allowed, and their characteristics. The keys in the disposal dictionary should be the names of the products. The values are dictionaries with the following keys: Key Description Cost ($/tonne) The cost to dispose of the product. Must be a timeseries. Limit (tonne) The maximum amount that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries. The keys in the capacities dictionary should be the amounts (in tonnes). The values are dictionaries with the following keys: Key Description Opening cost ($) The cost to open a plant of this size. Fixed operating cost ($) The cost to keep the plant open, even if the plant doesn't process anything. Must be a timeseries. Variable operating cost ($/tonne) The cost that the plant incurs to process each tonne of input. Must be a timeseries. Example { \"Plants\": { \"F1\": { \"Input\": \"P1\", \"Outputs (tonne)\": { \"P2\": 0.2, \"P3\": 0.5 }, \"Locations\": { \"L1\": { \"Latitude (deg)\": 0.0, \"Longitude (deg)\": 0.0, \"Disposal\": { \"P2\": { \"Cost ($/tonne)\": [-10.0, -12.0], \"Limit (tonne)\": [1.0, 1.0] } }, \"Capacities (tonne)\": { \"100\": { \"Opening cost ($)\": [500, 530], \"Fixed operating cost ($)\": [300.0, 310.0], \"Variable operating cost ($/tonne)\": [5.0, 5.2] }, \"500\": { \"Opening cost ($)\": [750, 760], \"Fixed operating cost ($)\": [400.0, 450.0], \"Variable operating cost ($/tonne)\": [5.0, 5.2] } } } } } } } Current limitations Each plant can only be opened exactly once. After open, the plant remains open until the end of the simulation. Plants can be expanded at any time, even long after they are open. All material available at the beginning of a time period must be entirely processed by the end of that time period. It is not possible to store unprocessed materials from one time period to the next. Up to two plant sizes are currently supported. Variable operating costs must be the same for all plant sizes.","title":"Model"},{"location":"model/#modeling","text":"The first step when using RELOG is to describe the reverse logistics pipeline and the relevant data. RELOG accepts as input a JSON file with three sections: parameters , products and plants . Below, we describe each section in more detail.","title":"Modeling"},{"location":"model/#parameters","text":"The parameters section describes details about the simulation itself. Key Description Time horizon (years) Number of years in the simulation.","title":"Parameters"},{"location":"model/#example","text":"{ \"Parameters\": { \"Time horizon (years)\": 2 } }","title":"Example"},{"location":"model/#products","text":"The products section describes all products and subproducts in the simulation. The field instance[\"Products\"] is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys: Key Description Transportation cost ($/km/tonne) The cost to transport this product. Must be a timeseries. Initial amounts A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted. Must be a timeseries. Each product may have some amount available at the beginning of each time period. In this case, the key initial amounts maps to a dictionary with the following keys: Key Description Latitude (deg) The latitude of the location. Longitude (deg) The longitude of the location. Amount (tonne) The amount of the product initially available at the location. Must be a timeseries.","title":"Products"},{"location":"model/#example_1","text":"{ \"Products\": { \"P1\": { \"Transportation cost ($/km/tonne)\": [0.015, 0.015], \"Initial amounts\": { \"C1\": { \"Latitude (deg)\": 7.0, \"Longitude (deg)\": 7.0, \"Amount (tonne)\": [934.56, 934.56] }, \"C2\": { \"Latitude (deg)\": 7.0, \"Longitude (deg)\": 19.0, \"Amount (tonne)\": [198.95, 198.95] }, \"C3\": { \"Latitude (deg)\": 84.0, \"Longitude (deg)\": 76.0, \"Amount (tonne)\": [212.97, 212.97] } } }, \"P2\": { \"Transportation cost ($/km/tonne)\": [0.02, 0.02] }, \"P3\": { \"Transportation cost ($/km/tonne)\": [0.0125, 0.0125] }, \"P4\": { \"Transportation cost ($/km/tonne)\": [0.0175, 0.0175] } } }","title":"Example"},{"location":"model/#processing-plants","text":"The plants section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field instance[\"Plants\"] is a dictionary mapping the name of the plant to a dictionary with the following keys: Key Description Input The name of the product that this plant takes as input. Only one input is accepted per plant. Outputs (tonne) A dictionary specifying how many tonnes of each product is produced for each tonnes of input. For example, if the plant outputs 0.5 tonnes of P2 and 0.25 tonnes of P3 for each tonnes of P1 provided, then this entry should be {\"P2\": 0.5, \"P3\": 0.25} . If the plant does not output anything, this key may be omitted. Locations A dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below). Each type of plant is associated with a set of potential locations where it can be built. Each location is represented by a dictionary with the following keys: Key Description Latitude (deg) The latitude of the location, in degrees. Longitude (deg) The longitude of the location, in degrees. Disposal A dictionary describing what products can be disposed locally at the plant. Capacities (tonne) A dictionary describing what plant sizes are allowed, and their characteristics. The keys in the disposal dictionary should be the names of the products. The values are dictionaries with the following keys: Key Description Cost ($/tonne) The cost to dispose of the product. Must be a timeseries. Limit (tonne) The maximum amount that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries. The keys in the capacities dictionary should be the amounts (in tonnes). The values are dictionaries with the following keys: Key Description Opening cost ($) The cost to open a plant of this size. Fixed operating cost ($) The cost to keep the plant open, even if the plant doesn't process anything. Must be a timeseries. Variable operating cost ($/tonne) The cost that the plant incurs to process each tonne of input. Must be a timeseries.","title":"Processing Plants"},{"location":"model/#example_2","text":"{ \"Plants\": { \"F1\": { \"Input\": \"P1\", \"Outputs (tonne)\": { \"P2\": 0.2, \"P3\": 0.5 }, \"Locations\": { \"L1\": { \"Latitude (deg)\": 0.0, \"Longitude (deg)\": 0.0, \"Disposal\": { \"P2\": { \"Cost ($/tonne)\": [-10.0, -12.0], \"Limit (tonne)\": [1.0, 1.0] } }, \"Capacities (tonne)\": { \"100\": { \"Opening cost ($)\": [500, 530], \"Fixed operating cost ($)\": [300.0, 310.0], \"Variable operating cost ($/tonne)\": [5.0, 5.2] }, \"500\": { \"Opening cost ($)\": [750, 760], \"Fixed operating cost ($)\": [400.0, 450.0], \"Variable operating cost ($/tonne)\": [5.0, 5.2] } } } } } } }","title":"Example"},{"location":"model/#current-limitations","text":"Each plant can only be opened exactly once. After open, the plant remains open until the end of the simulation. Plants can be expanded at any time, even long after they are open. All material available at the beginning of a time period must be entirely processed by the end of that time period. It is not possible to store unprocessed materials from one time period to the next. Up to two plant sizes are currently supported. Variable operating costs must be the same for all plant sizes.","title":"Current limitations"},{"location":"optimize/","text":"Optimizing After creating a JSON file describing the reverse manufacturing process and the input data, the following example illustrates how to use the package to find the optimal set of decisions: using RELOG RELOG.solve(\"/home/user/instance.json\") The optimal logistics plan will be printed to the screen.","title":"Optimize"},{"location":"optimize/#optimizing","text":"After creating a JSON file describing the reverse manufacturing process and the input data, the following example illustrates how to use the package to find the optimal set of decisions: using RELOG RELOG.solve(\"/home/user/instance.json\") The optimal logistics plan will be printed to the screen.","title":"Optimizing"}]}
\ No newline at end of file
diff --git a/docs/sitemap.xml b/docs/sitemap.xml
index 7f01dc5..6d21c29 100644
--- a/docs/sitemap.xml
+++ b/docs/sitemap.xml
@@ -1,19 +1,23 @@
 
-
+
+    
      None
-     2020-05-22
+     2020-06-05
      daily
-    
+    
+    
      None
-     2020-05-22
+     2020-06-05
      daily
-    
+    
+    
      None
-     2020-05-22
+     2020-06-05
      daily
-    
+    
+    
      None
-     2020-05-22
+     2020-06-05
      daily
     
 
\ No newline at end of file
diff --git a/docs/sitemap.xml.gz b/docs/sitemap.xml.gz
index 73b7739..bdf382c 100644
Binary files a/docs/sitemap.xml.gz and b/docs/sitemap.xml.gz differ
diff --git a/instances/s1.json b/instances/s1.json
index ac1b1b6..32bb671 100644
--- a/instances/s1.json
+++ b/instances/s1.json
@@ -1,188 +1,188 @@
 {
-    "parameters": {
-        "time periods": 2
+    "Parameters": {
+        "Time horizon (years)": 2
     },
-    "products": {
+    "Products": {
         "P1": {
-            "transportation cost": [0.015, 0.015],
-            "initial amounts": {
+            "Transportation cost ($/km/tonne)": [0.015, 0.015],
+            "Initial amounts": {
                 "C1": {
-                    "latitude": 7.0,
-                    "longitude": 7.0,
-                    "amount": [934.56, 934.56]
+                    "Latitude (deg)": 7.0,
+                    "Longitude (deg)": 7.0,
+                    "Amount (tonne)": [934.56, 934.56]
                 },
                 "C2": {
-                    "latitude": 7.0,
-                    "longitude": 19.0,
-                    "amount": [198.95, 198.95]
+                    "Latitude (deg)": 7.0,
+                    "Longitude (deg)": 19.0,
+                    "Amount (tonne)": [198.95, 198.95]
                 },
                 "C3": {
-                    "latitude": 84.0,
-                    "longitude": 76.0,
-                    "amount": [212.97, 212.97]
+                    "Latitude (deg)": 84.0,
+                    "Longitude (deg)": 76.0,
+                    "Amount (tonne)": [212.97, 212.97]
                 },
                 "C4": {
-                    "latitude": 21.0,
-                    "longitude": 16.0,
-                    "amount": [352.19, 352.19]
+                    "Latitude (deg)": 21.0,
+                    "Longitude (deg)": 16.0,
+                    "Amount (tonne)": [352.19, 352.19]
                 },
                 "C5": {
-                    "latitude": 32.0,
-                    "longitude": 92.0,
-                    "amount": [510.33, 510.33]
+                    "Latitude (deg)": 32.0,
+                    "Longitude (deg)": 92.0,
+                    "Amount (tonne)": [510.33, 510.33]
                 },
                 "C6": {
-                    "latitude": 14.0,
-                    "longitude": 62.0,
-                    "amount": [471.66, 471.66]
+                    "Latitude (deg)": 14.0,
+                    "Longitude (deg)": 62.0,
+                    "Amount (tonne)": [471.66, 471.66]
                 },
                 "C7": {
-                    "latitude": 30.0,
-                    "longitude": 83.0,
-                    "amount": [785.21, 785.21]
+                    "Latitude (deg)": 30.0,
+                    "Longitude (deg)": 83.0,
+                    "Amount (tonne)": [785.21, 785.21]
                 },
                 "C8": {
-                    "latitude": 35.0,
-                    "longitude": 40.0,
-                    "amount": [706.17, 706.17]
+                    "Latitude (deg)": 35.0,
+                    "Longitude (deg)": 40.0,
+                    "Amount (tonne)": [706.17, 706.17]
                 },
                 "C9": {
-                    "latitude": 74.0,
-                    "longitude": 52.0,
-                    "amount": [30.08, 30.08]
+                    "Latitude (deg)": 74.0,
+                    "Longitude (deg)": 52.0,
+                    "Amount (tonne)": [30.08, 30.08]
                 },
                 "C10": {
-                    "latitude": 22.0,
-                    "longitude": 54.0,
-                    "amount": [536.52, 536.52]
+                    "Latitude (deg)": 22.0,
+                    "Longitude (deg)": 54.0,
+                    "Amount (tonne)": [536.52, 536.52]
                 }
             }
         },
         "P2": {
-            "transportation cost": [0.02, 0.02]
+            "Transportation cost ($/km/tonne)": [0.02, 0.02]
         },
         "P3": {
-            "transportation cost": [0.0125, 0.0125]
+            "Transportation cost ($/km/tonne)": [0.0125, 0.0125]
         },
         "P4": {
-            "transportation cost": [0.0175, 0.0175]
+            "Transportation cost ($/km/tonne)": [0.0175, 0.0175]
         }
     },
-    "plants": {
+    "Plants": {
         "F1": {
-            "input": "P1",
-            "outputs": {
+            "Input": "P1",
+            "Outputs (tonne)": {
                 "P2": 0.2,
                 "P3": 0.5
             },
-            "locations": {
+            "Locations": {
                 "L1": {
-                    "latitude": 0.0,
-                    "longitude": 0.0,
-                    "disposal": {
+                    "Latitude (deg)": 0.0,
+                    "Longitude (deg)": 0.0,
+                    "Disposal": {
                         "P2": {
-                            "cost": [-10.0, -10.0],
-                            "limit": [1.0, 1.0]
+                            "Cost ($/tonne)": [-10.0, -10.0],
+                            "Limit (tonne)": [1.0, 1.0]
                         },
                         "P3": {
-                            "cost": [-10.0, -10.0],
-                            "limit": [1.0, 1.0]
+                            "Cost ($/tonne)": [-10.0, -10.0],
+                            "Limit (tonne)": [1.0, 1.0]
                         }
                     },
-                    "capacities": {
+                    "Capacities (tonne)": {
                         "250.0": {
-                            "opening cost": [500.0, 500.0],
-                            "fixed operating cost": [30.0, 30.0],
-                            "variable operating cost": [30.0, 30.0]
+                            "Opening cost ($)": [500.0, 500.0],
+                            "Fixed operating cost ($)": [30.0, 30.0],
+                            "Variable operating cost ($/tonne)": [30.0, 30.0]
                         },
                         "1000.0": {
-                            "opening cost": [1250.0, 1250.0],
-                            "fixed operating cost": [30.0, 30.0],
-                            "variable operating cost": [30.0, 30.0]
+                            "Opening cost ($)": [1250.0, 1250.0],
+                            "Fixed operating cost ($)": [30.0, 30.0],
+                            "Variable operating cost ($/tonne)": [30.0, 30.0]
                         }
                     }
                 },
                 "L2": {
-                    "latitude": 0.5,
-                    "longitude": 0.5,
-                    "capacities": {
+                    "Latitude (deg)": 0.5,
+                    "Longitude (deg)": 0.5,
+                    "Capacities (tonne)": {
                         "0.0": {
-                            "opening cost": [1000, 1000],
-                            "fixed operating cost": [50.0, 50.0],
-                            "variable operating cost": [50.0, 50.0]
+                            "Opening cost ($)": [1000, 1000],
+                            "Fixed operating cost ($)": [50.0, 50.0],
+                            "Variable operating cost ($/tonne)": [50.0, 50.0]
                         },
                         "10000.0": {
-                            "opening cost": [10000, 10000],
-                            "fixed operating cost": [50.0, 50.0],
-                            "variable operating cost": [50.0, 50.0]
+                            "Opening cost ($)": [10000, 10000],
+                            "Fixed operating cost ($)": [50.0, 50.0],
+                            "Variable operating cost ($/tonne)": [50.0, 50.0]
                         }
                     }
                 }               
             }
         },
         "F2": {
-            "input": "P2",
-            "outputs": {
+            "Input": "P2",
+            "Outputs (tonne)": {
                 "P3": 0.05,
                 "P4": 0.80
             },
-            "locations": {
+            "Locations": {
                 "L3": {
-                    "latitude": 25.0,
-                    "longitude": 65.0,
-                    "disposal": {
+                    "Latitude (deg)": 25.0,
+                    "Longitude (deg)": 65.0,
+                    "Disposal": {
                         "P3": {
-                            "cost": [100.0, 100.0]
+                            "Cost ($/tonne)": [100.0, 100.0]
                         }
                     },
-                    "capacities": {
+                    "Capacities (tonne)": {
                         "1000.0": {
-                            "opening cost": [3000, 3000],
-                            "fixed operating cost": [50.0, 50.0],
-                            "variable operating cost": [50.0, 50.0]
+                            "Opening cost ($)": [3000, 3000],
+                            "Fixed operating cost ($)": [50.0, 50.0],
+                            "Variable operating cost ($/tonne)": [50.0, 50.0]
                         }
                     }
                 },
                 "L4": {
-                    "latitude": 0.75,
-                    "longitude": 0.20,
-                    "capacities": {
+                    "Latitude (deg)": 0.75,
+                    "Longitude (deg)": 0.20,
+                    "Capacities (tonne)": {
                         "10000": {
-                            "opening cost": [3000, 3000],
-                            "fixed operating cost": [50.0, 50.0],
-                            "variable operating cost": [50.0, 50.0]
+                            "Opening cost ($)": [3000, 3000],
+                            "Fixed operating cost ($)": [50.0, 50.0],
+                            "Variable operating cost ($/tonne)": [50.0, 50.0]
                         }
                     }
                 }
             }
         },
         "F3": {
-            "input": "P4",
-            "locations": {
+            "Input": "P4",
+            "Locations": {
                 "L5": {
-                    "latitude": 100.0,
-                    "longitude": 100.0,
-                    "capacities": {
+                    "Latitude (deg)": 100.0,
+                    "Longitude (deg)": 100.0,
+                    "Capacities (tonne)": {
                         "15000": {
-                            "opening cost": [0.0, 0.0],
-                            "fixed operating cost": [0.0, 0.0],
-                            "variable operating cost": [-15.0, -15.0]
+                            "Opening cost ($)": [0.0, 0.0],
+                            "Fixed operating cost ($)": [0.0, 0.0],
+                            "Variable operating cost ($/tonne)": [-15.0, -15.0]
                         }
                     }
                 }           
             }
         },
         "F4": {
-            "input": "P3",
-            "locations": {
+            "Input": "P3",
+            "Locations": {
                 "L6": {
-                    "latitude": 50.0,
-                    "longitude": 50.0,
-                    "capacities": {
+                    "Latitude (deg)": 50.0,
+                    "Longitude (deg)": 50.0,
+                    "Capacities (tonne)": {
                         "10000": {
-                            "opening cost": [0.0, 0.0],
-                            "fixed operating cost": [0.0, 0.0],
-                            "variable operating cost": [-15.0, -15.0]
+                            "Opening cost ($)": [0.0, 0.0],
+                            "Fixed operating cost ($)": [0.0, 0.0],
+                            "Variable operating cost ($/tonne)": [-15.0, -15.0]
                         }
                     }
                 }           
diff --git a/src/docs/model.md b/src/docs/model.md
index 54b4fb7..b1bd44c 100644
--- a/src/docs/model.md
+++ b/src/docs/model.md
@@ -8,68 +8,68 @@ The **parameters** section describes details about the simulation itself.
 
 | Key                     | Description
 |:------------------------|---------------|
-|`time periods`           | Number of time periods in the simulation.
+|`Time horizon (years)`   | Number of years in the simulation.
 
 
 ### Example
 ```json
 {
-    "parameters": {
-        "time periods": 2
+    "Parameters": {
+        "Time horizon (years)": 2
     }
 }
 ```
 
 ## Products
 
-The **products** section describes all products and subproducts in the simulation. The field `instance["products"]` is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys:
+The **products** section describes all products and subproducts in the simulation. The field `instance["Products"]` is a dictionary mapping the name of the product to a dictionary which describes its characteristics. Each product description contains the following keys:
 
-| Key                     | Description
-|:------------------------|---------------|
-|`transportation cost`    | The cost (in dollars per km per tonnes) to transport this product. Must be a timeseries.
-|`initial amounts`        | A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted. Must be a timeseries.
+| Key                                   | Description
+|:--------------------------------------|---------------|
+|`Transportation cost ($/km/tonne)`    | The cost to transport this product. Must be a timeseries.
+|`Initial amounts`                      | A dictionary mapping the name of each location to its description (see below). If this product is not initially available, this key may be omitted. Must be a timeseries.
 
 Each product may have some amount available at the beginning of each time period. In this case, the key `initial amounts` maps to a dictionary with the following keys:
 
 | Key                     | Description
 |:------------------------|---------------|
-| `latitude`              | The latitude of the location, in degrees.
-| `longitude`             | The longitude of the location, in degrees.
-| `amount`                | The amount (in tonnes) of the product initially available at the location. Must be a timeseries.
+| `Latitude (deg)`        | The latitude of the location.
+| `Longitude (deg)`       | The longitude of the location.
+| `Amount (tonne)`       | The amount of the product initially available at the location. Must be a timeseries.
 
 ### Example
 
 ```json
 {
-    "products": {
+    "Products": {
         "P1": {
-            "transportation cost": [0.015, 0.015],
-            "initial amounts": {
+            "Transportation cost ($/km/tonne)": [0.015, 0.015],
+            "Initial amounts": {
                 "C1": {
-                    "latitude": 7.0,
-                    "longitude": 7.0,
-                    "amount": [934.56, 934.56]
+                    "Latitude (deg)": 7.0,
+                    "Longitude (deg)": 7.0,
+                    "Amount (tonne)": [934.56, 934.56]
                 },
                 "C2": {
-                    "latitude": 7.0,
-                    "longitude": 19.0,
-                    "amount": [198.95, 198.95]
+                    "Latitude (deg)": 7.0,
+                    "Longitude (deg)": 19.0,
+                    "Amount (tonne)": [198.95, 198.95]
                 },
                 "C3": {
-                    "latitude": 84.0,
-                    "longitude": 76.0,
-                    "amount": [212.97, 212.97]
+                    "Latitude (deg)": 84.0,
+                    "Longitude (deg)": 76.0,
+                    "Amount (tonne)": [212.97, 212.97]
                 }
             }
         },
         "P2": {
-            "transportation cost": [0.02, 0.02]
+            "Transportation cost ($/km/tonne)": [0.02, 0.02]
         },
         "P3": {
-            "transportation cost": [0.0125, 0.0125]
+            "Transportation cost ($/km/tonne)": [0.0125, 0.0125]
         },
         "P4": {
-            "transportation cost": [0.0175, 0.0175]
+            "Transportation cost ($/km/tonne)": [0.0175, 0.0175]
         }
     }
 }
@@ -77,70 +77,70 @@ Each product may have some amount available at the beginning of each time period
 
 ## Processing Plants
 
-The **plants** section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field `instance["plants"]` is a dictionary mapping the name of the plant to a dictionary with the following keys:
+The **plants** section describes the available types of reverse manufacturing plants, their potential locations and associated costs, as well as their inputs and outputs. The field `instance["Plants"]` is a dictionary mapping the name of the plant to a dictionary with the following keys:
 
 | Key                     | Description
 |:------------------------|---------------|
-| `input`                 | The name of the product that this plant takes as input. Only one input is accepted per plant.
-| `outputs`               | A dictionary specifying how many tonnes of each product is produced for each tonnes of input. For example, if the plant outputs 0.5 tonnes of P2 and 0.25 tonnes of P3 for each tonnes of P1 provided, then this entry should be `{"P2": 0.5, "P3": 0.25}`. If the plant does not output anything, this key may be omitted.
-| `locations`             | A dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below).
+| `Input`                 | The name of the product that this plant takes as input. Only one input is accepted per plant.
+| `Outputs (tonne)`               | A dictionary specifying how many tonnes of each product is produced for each tonnes of input. For example, if the plant outputs 0.5 tonnes of P2 and 0.25 tonnes of P3 for each tonnes of P1 provided, then this entry should be `{"P2": 0.5, "P3": 0.25}`. If the plant does not output anything, this key may be omitted.
+| `Locations`             | A dictionary mapping the name of the location to a dictionary which describes the site characteristics (see below).
 
 Each type of plant is associated with a set of potential locations where it can be built. Each location is represented by a dictionary with the following keys:
 
-| Key                     | Description
-|:------------------------|---------------|
-| `latitude`              | The latitude of the location, in degrees.
-| `longitude`             | The longitude of the location, in degrees.
-| `disposal`              | A dictionary describing what products can be disposed locally at the plant.
-| `capacities`            | A dictionary describing what plant sizes are allowed, and their characteristics.
+| Key                           | Description
+|:------------------------------|---------------|
+| `Latitude (deg)`              | The latitude of the location, in degrees.
+| `Longitude (deg)`             | The longitude of the location, in degrees.
+| `Disposal`                    | A dictionary describing what products can be disposed locally at the plant.
+| `Capacities (tonne)`         | A dictionary describing what plant sizes are allowed, and their characteristics.
 
 The keys in the `disposal` dictionary should be the names of the products. The values are dictionaries with the following keys:
 
 | Key                     | Description
 |:------------------------|---------------|
-| `cost`                  | The cost (in dollars per tonnes) to dispose of the product. Must be a timeseries.
-| `limit`                 | The maximum amount (in tonnes) that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries.
+| `Cost ($/tonne)`       | The cost to dispose of the product. Must be a timeseries.
+| `Limit (tonne)`        | The maximum amount that can be disposed of. If an unlimited amount can be disposed, this key may be omitted. Must be a timeseries.
 
 
 The keys in the `capacities` dictionary should be the amounts (in tonnes). The values are dictionaries with the following keys:
 
-| Key                     | Description
-|:------------------------|---------------|
-| `opening cost`          | The cost (in dollars) to open a plant of this size.
-| `fixed operating cost`  | The cost (in dollars) to keep the plant open, even if the plant doesn't process anything. Must be a timeseries.
-| `variable operating cost` | The cost (in dollars per tonnes) that the plant incurs to process each tonne of input. Must be a timeseries.
+| Key                                   | Description
+|:--------------------------------------|---------------|
+| `Opening cost ($)`                    | The cost to open a plant of this size.
+| `Fixed operating cost ($)`            | The cost to keep the plant open, even if the plant doesn't process anything. Must be a timeseries.
+| `Variable operating cost ($/tonne)`  | The cost that the plant incurs to process each tonne of input. Must be a timeseries.
 
 ### Example
 
 ```json
 {
-    "plants": {
+    "Plants": {
         "F1": {
-            "input": "P1",
-            "outputs": {
+            "Input": "P1",
+            "Outputs (tonne)": {
                 "P2": 0.2,
                 "P3": 0.5
             },
-            "locations": {
+            "Locations": {
                 "L1": {
-                    "latitude": 0.0,
-                    "longitude": 0.0,
-                    "disposal": {
+                    "Latitude (deg)": 0.0,
+                    "Longitude (deg)": 0.0,
+                    "Disposal": {
                         "P2": {
-                            "cost": [-10.0, -12.0],
-                            "limit": [1.0, 1.0]
+                            "Cost ($/tonne)": [-10.0, -12.0],
+                            "Limit (tonne)": [1.0, 1.0]
                         }
                     },
-                    "capacities": {
+                    "Capacities (tonne)": {
                         "100": {
-                            "opening cost": [500, 530],
-                            "fixed operating cost": [300.0, 310.0],
-                            "variable operating cost": [5.0, 5.2]
+                            "Opening cost ($)": [500, 530],
+                            "Fixed operating cost ($)": [300.0, 310.0],
+                            "Variable operating cost ($/tonne)": [5.0, 5.2]
                         },
                         "500": {
-                            "opening cost": [750, 760],
-                            "fixed operating cost": [400.0, 450.0],
-                            "variable operating cost": [5.0, 5.2]
+                            "Opening cost ($)": [750, 760],
+                            "Fixed operating cost ($)": [400.0, 450.0],
+                            "Variable operating cost ($/tonne)": [5.0, 5.2]
                         }
                     }
                 }
diff --git a/src/instance.jl b/src/instance.jl
index df33cf8..9c87c52 100644
--- a/src/instance.jl
+++ b/src/instance.jl
@@ -60,74 +60,77 @@ function load(path::String)::Instance
     if result !== nothing
         if result isa JSONSchema.SingleIssue
             path = join(result.path, " → ")
-            msg = "$(result.x) $(result.msg) in $(path)"
+            if length(path) == 0
+                path = "root"
+            end
+            msg = "$(result.msg) in $(path)"
         else
             msg = convert(String, result)
         end
         throw(msg)
     end
     
-    T = json["parameters"]["time periods"]
+    T = json["Parameters"]["Time horizon (years)"]
     plants = Plant[]
     products = Product[]
     collection_centers = CollectionCenter[]
     prod_name_to_product = Dict{String, Product}()
     
     # Create products
-    for (product_name, product_dict) in json["products"]
-        product = Product(product_name, product_dict["transportation cost"])
+    for (product_name, product_dict) in json["Products"]
+        product = Product(product_name, product_dict["Transportation cost (\$/km/tonne)"])
         push!(products, product)
         prod_name_to_product[product_name] = product
         
         # Create collection centers
-        if "initial amounts" in keys(product_dict)
-            for (center_name, center_dict) in product_dict["initial amounts"]
+        if "Initial amounts" in keys(product_dict)
+            for (center_name, center_dict) in product_dict["Initial amounts"]
                 center = CollectionCenter(length(collection_centers) + 1,
                                           center_name,
-                                          center_dict["latitude"],
-                                          center_dict["longitude"],
+                                          center_dict["Latitude (deg)"],
+                                          center_dict["Longitude (deg)"],
                                           product,
-                                          center_dict["amount"])
+                                          center_dict["Amount (tonne)"])
                 push!(collection_centers, center)
             end
         end
     end
     
     # Create plants
-    for (plant_name, plant_dict) in json["plants"]
-        input = prod_name_to_product[plant_dict["input"]]
+    for (plant_name, plant_dict) in json["Plants"]
+        input = prod_name_to_product[plant_dict["Input"]]
         output = Dict()
         
         # Plant outputs
-        if "outputs" in keys(plant_dict)
+        if "Outputs (tonne)" in keys(plant_dict)
             output = Dict(prod_name_to_product[key] => value
-                          for (key, value) in plant_dict["outputs"]
+                          for (key, value) in plant_dict["Outputs (tonne)"]
                           if value > 0)
         end
         
-        for (location_name, location_dict) in plant_dict["locations"]
+        for (location_name, location_dict) in plant_dict["Locations"]
             sizes = PlantSize[]
             disposal_limit = Dict(p => [0.0 for t in 1:T] for p in keys(output))
             disposal_cost = Dict(p => [0.0 for t in 1:T] for p in keys(output))
             
             # Disposal
-            if "disposal" in keys(location_dict)
-                for (product_name, disposal_dict) in location_dict["disposal"]
+            if "Disposal" in keys(location_dict)
+                for (product_name, disposal_dict) in location_dict["Disposal"]
                     limit = [1e8 for t in 1:T]
-                    if "limit" in keys(disposal_dict)
-                       limit = disposal_dict["limit"]
+                    if "Limit (tonne)" in keys(disposal_dict)
+                       limit = disposal_dict["Limit (tonne)"]
                     end
                     disposal_limit[prod_name_to_product[product_name]] = limit
-                    disposal_cost[prod_name_to_product[product_name]] = disposal_dict["cost"]
+                    disposal_cost[prod_name_to_product[product_name]] = disposal_dict["Cost (\$/tonne)"]
                 end
             end
             
             # Capacities
-            for (capacity_name, capacity_dict) in location_dict["capacities"]
+            for (capacity_name, capacity_dict) in location_dict["Capacities (tonne)"]
                 push!(sizes, PlantSize(parse(Float64, capacity_name),
-                                       capacity_dict["variable operating cost"],
-                                       capacity_dict["fixed operating cost"],
-                                       capacity_dict["opening cost"]))
+                                       capacity_dict["Variable operating cost (\$/tonne)"],
+                                       capacity_dict["Fixed operating cost (\$)"],
+                                       capacity_dict["Opening cost (\$)"]))
             end
             length(sizes) > 1 ||  push!(sizes, sizes[1])
             sort!(sizes, by = x -> x.capacity)
@@ -145,8 +148,8 @@ function load(path::String)::Instance
                           location_name,
                           input,
                           output,
-                          location_dict["latitude"],
-                          location_dict["longitude"],
+                          location_dict["Latitude (deg)"],
+                          location_dict["Longitude (deg)"],
                           disposal_limit,
                           disposal_cost,
                           sizes)
diff --git a/src/model.jl b/src/model.jl
index 700dd4e..fb942de 100644
--- a/src/model.jl
+++ b/src/model.jl
@@ -228,16 +228,16 @@ function get_solution(model::ManufacturingModel)
     T = instance.time
     
     output = Dict(
-        "plants" => Dict(),
-        "products" => Dict(),
-        "costs" => Dict(
-            "fixed operating" => zeros(T),
-            "variable operating" => zeros(T),
-            "opening" => zeros(T),
-            "transportation" => zeros(T),
-            "disposal" => zeros(T),
-            "expansion" => zeros(T),
-            "total" => zeros(T),
+        "Plants" => Dict(),
+        "Products" => Dict(),
+        "Costs" => Dict(
+            "Fixed operating (\$)" => zeros(T),
+            "Variable operating (\$)" => zeros(T),
+            "Opening (\$)" => zeros(T),
+            "Transportation (\$)" => zeros(T),
+            "Disposal (\$)" => zeros(T),
+            "Expansion (\$)" => zeros(T),
+            "Total (\$)" => zeros(T),
         )
     )
     
@@ -253,13 +253,13 @@ function get_solution(model::ManufacturingModel)
     # Products
     for n in graph.collection_shipping_nodes
         location_dict = Dict{Any, Any}(
-            "marginal cost" => [round(abs(JuMP.shadow_price(eqs.balance[n, t])), digits=2)
-                                for t in 1:T],
+            "Marginal cost (\$/tonne)" => [round(abs(JuMP.shadow_price(eqs.balance[n, t])), digits=2)
+                                           for t in 1:T],
         )
-        if n.product.name ∉ keys(output["products"])
-            output["products"][n.product.name] = Dict()
+        if n.product.name ∉ keys(output["Products"])
+            output["Products"][n.product.name] = Dict()
         end
-        output["products"][n.product.name][n.location.name] = location_dict
+        output["Products"][n.product.name][n.location.name] = location_dict
     end
     
     # Plants
@@ -267,36 +267,36 @@ function get_solution(model::ManufacturingModel)
         skip_plant = true
         process_node = plant_to_process_node[plant]
         plant_dict = Dict{Any, Any}(
-            "input" => Dict(),
-            "output" => Dict(
-                "send" => Dict(),
-                "dispose" => Dict(),
+            "Input" => Dict(),
+            "Output" => Dict(
+                "Send" => Dict(),
+                "Dispose" => Dict(),
             ),
-            "total input" => [0.0 for t in 1:T],
-            "total output" => Dict(),
-            "latitude" => plant.latitude,
-            "longitude" => plant.longitude,
-            "capacity" => [JuMP.value(vars.capacity[process_node, t])
-                           for t in 1:T],
-            "opening cost" => [JuMP.value(vars.open_plant[process_node, t]) *
-                               plant.sizes[1].opening_cost[t]
-                               for t in 1:T],
-            "fixed operating cost" => [JuMP.value(vars.is_open[process_node, t]) *
-                                       plant.sizes[1].fixed_operating_cost[t] +
-                                       JuMP.value(vars.expansion[process_node, t]) *
-                                       slope_fix_oper_cost(plant, t)
-                                       for t in 1:T],
-            "expansion cost" => [JuMP.value(vars.expansion[process_node, t]) *
-                                     (if t < T
-                                         slope_open(plant, t) - slope_open(plant, t + 1)
-                                      else
-                                         slope_open(plant, t)
-                                      end)
-                                 for t in 1:T],
+            "Total input (tonne)" => [0.0 for t in 1:T],
+            "Total output" => Dict(),
+            "Latitude (deg)" => plant.latitude,
+            "Longitude (deg)" => plant.longitude,
+            "Capacity (tonne)" => [JuMP.value(vars.capacity[process_node, t])
+                                   for t in 1:T],
+            "Opening cost (\$)" => [JuMP.value(vars.open_plant[process_node, t]) *
+                                    plant.sizes[1].opening_cost[t]
+                                    for t in 1:T],
+            "Fixed operating cost (\$)" => [JuMP.value(vars.is_open[process_node, t]) *
+                                            plant.sizes[1].fixed_operating_cost[t] +
+                                            JuMP.value(vars.expansion[process_node, t]) *
+                                            slope_fix_oper_cost(plant, t)
+                                            for t in 1:T],
+            "Expansion cost (\$)" => [JuMP.value(vars.expansion[process_node, t]) *
+                                          (if t < T
+                                              slope_open(plant, t) - slope_open(plant, t + 1)
+                                           else
+                                              slope_open(plant, t)
+                                           end)
+                                      for t in 1:T],
         )
-        output["costs"]["fixed operating"] += plant_dict["fixed operating cost"]
-        output["costs"]["opening"] += plant_dict["opening cost"]
-        output["costs"]["expansion"] += plant_dict["expansion cost"]
+        output["Costs"]["Fixed operating (\$)"] += plant_dict["Fixed operating cost (\$)"]
+        output["Costs"]["Opening (\$)"] += plant_dict["Opening cost (\$)"]
+        output["Costs"]["Expansion (\$)"] += plant_dict["Expansion cost (\$)"]
 
         # Inputs
         for a in process_node.incoming_arcs
@@ -306,14 +306,16 @@ function get_solution(model::ManufacturingModel)
             end
             skip_plant = false
             dict = Dict{Any, Any}(
-                "amount" => vals,
-                "distance" => a.values["distance"],
-                "latitude" => a.source.location.latitude,
-                "longitude" => a.source.location.longitude,
-                "transportation cost" => [a.source.product.transportation_cost[t] * vals[t]
-                                          for t in 1:T],
-                "variable operating cost" => [plant.sizes[1].variable_operating_cost[t] * vals[t]
-                                              for t in 1:T],
+                "Amount (tonne)" => vals,
+                "Distance (km)" => a.values["distance"],
+                "Latitude (deg)" => a.source.location.latitude,
+                "Longitude (deg)" => a.source.location.longitude,
+                "Transportation cost (\$)" => [a.source.product.transportation_cost[t] *
+                                                   vals[t] *
+                                                   a.values["distance"]
+                                               for t in 1:T],
+                "Variable operating cost (\$)" => [plant.sizes[1].variable_operating_cost[t] * vals[t]
+                                                   for t in 1:T],
             )
             if a.source.location isa CollectionCenter
                 plant_name = "Origin"
@@ -323,31 +325,31 @@ function get_solution(model::ManufacturingModel)
                 location_name = a.source.location.location_name
             end
             
-            if plant_name ∉ keys(plant_dict["input"])
-                plant_dict["input"][plant_name] = Dict()
+            if plant_name ∉ keys(plant_dict["Input"])
+                plant_dict["Input"][plant_name] = Dict()
             end
-            plant_dict["input"][plant_name][location_name] = dict
-            plant_dict["total input"] += vals
-            output["costs"]["transportation"] += dict["transportation cost"]
-            output["costs"]["variable operating"] += dict["variable operating cost"]
+            plant_dict["Input"][plant_name][location_name] = dict
+            plant_dict["Total input (tonne)"] += vals
+            output["Costs"]["Transportation (\$)"] += dict["Transportation cost (\$)"]
+            output["Costs"]["Variable operating (\$)"] += dict["Variable operating cost (\$)"]
         end
 
         # Outputs
         for shipping_node in plant_to_shipping_nodes[plant]
             product_name = shipping_node.product.name
-            plant_dict["total output"][product_name] = zeros(T)
-            plant_dict["output"]["send"][product_name] = product_dict = Dict()
+            plant_dict["Total output"][product_name] = zeros(T)
+            plant_dict["Output"]["Send"][product_name] = product_dict = Dict()
 
             disposal_amount = [JuMP.value(vars.dispose[shipping_node, t]) for t in 1:T]
             if sum(disposal_amount) > 1e-5
                 skip_plant = false
-                plant_dict["output"]["dispose"][product_name] = disposal_dict = Dict()
-                disposal_dict["amount"] = [JuMP.value(model.vars.dispose[shipping_node, t]) for t in 1:T]
-                disposal_dict["cost"] = [disposal_dict["amount"][t] *
-                                         plant.disposal_cost[shipping_node.product][t]
-                                         for t in 1:T]
-                plant_dict["total output"][product_name] += disposal_amount
-                output["costs"]["disposal"] += disposal_dict["cost"]
+                plant_dict["Output"]["Dispose"][product_name] = disposal_dict = Dict()
+                disposal_dict["Amount (tonne)"] = [JuMP.value(model.vars.dispose[shipping_node, t]) for t in 1:T]
+                disposal_dict["Cost (\$)"] = [disposal_dict["Amount (tonne)"][t] *
+                                              plant.disposal_cost[shipping_node.product][t]
+                                              for t in 1:T]
+                plant_dict["Total output"][product_name] += disposal_amount
+                output["Costs"]["Disposal (\$)"] += disposal_dict["Cost (\$)"]
             end
 
             for a in shipping_node.outgoing_arcs
@@ -357,27 +359,27 @@ function get_solution(model::ManufacturingModel)
                 end
                 skip_plant = false
                 dict = Dict(
-                    "amount" => vals,
-                    "distance" => a.values["distance"],
-                    "latitude" => a.dest.location.latitude,
-                    "longitude" => a.dest.location.longitude,
+                    "Amount (tonne)" => vals,
+                    "Distance (km)" => a.values["distance"],
+                    "Latitude (deg)" => a.dest.location.latitude,
+                    "Longitude (deg)" => a.dest.location.longitude,
                 )
                 if a.dest.location.plant_name ∉ keys(product_dict)
                     product_dict[a.dest.location.plant_name] = Dict()
                 end
                 product_dict[a.dest.location.plant_name][a.dest.location.location_name] = dict
-                plant_dict["total output"][product_name] += vals
+                plant_dict["Total output"][product_name] += vals
             end
         end
             
         if !skip_plant
-            if plant.plant_name ∉ keys(output["plants"])
-                output["plants"][plant.plant_name] = Dict()
+            if plant.plant_name ∉ keys(output["Plants"])
+                output["Plants"][plant.plant_name] = Dict()
             end
-            output["plants"][plant.plant_name][plant.location_name] = plant_dict
+            output["Plants"][plant.plant_name][plant.location_name] = plant_dict
         end
     end
 
-    output["costs"]["total"] = sum(values(output["costs"]))
+    output["Costs"]["Total (\$)"] = sum(values(output["Costs"]))
     return output
 end
diff --git a/src/schemas/input.json b/src/schemas/input.json
index 996ae3a..1617591 100644
--- a/src/schemas/input.json
+++ b/src/schemas/input.json
@@ -1,7 +1,7 @@
 {
     "$schema": "http://json-schema.org/draft-07/schema#",
     "$id": "https://anl-ceeesa.github.io/RELOG/input",
-    "title": "Schema for ReverseManufacturing Input File",
+    "title": "Schema for RELOG Input File",
     "definitions": {
         "TimeSeries": {
             "type": "array",
@@ -12,10 +12,10 @@
         "Parameters": {
             "type": "object",
             "properties": {
-                "time": { "type": "number" }
+                "Time horizon (years)": { "type": "number" }
             },
             "required": [
-                "time periods"
+                "Time horizon (years)"
             ]
         },
         "Plant": {
@@ -23,16 +23,16 @@
             "additionalProperties": {
                 "type": "object",
                 "properties": {
-                    "input": { "type": "string" },
-                    "outputs": {
+                    "Input": { "type": "string" },
+                    "Outputs (tonne)": {
                         "type": "object",
                         "additionalProperties": { "type": "number" }
                     },
-                    "locations": { "$ref": "#/definitions/PlantLocation" }
+                    "Locations": { "$ref": "#/definitions/PlantLocation" }
                 },
                 "required": [
-                    "input",
-                    "locations"
+                    "Input",
+                    "Locations"
                 ]
             }
         },
@@ -41,42 +41,42 @@
             "additionalProperties": {
                 "type": "object",
                 "properties": {
-                    "latitude": { "type": "number" },
-                    "longitude": { "type": "number" },
-                    "disposal": {
+                    "Latitude (deg)": { "type": "number" },
+                    "Longitude (deg)": { "type": "number" },
+                    "Disposal": {
                         "type": "object",
                         "additionalProperties": {
                             "type": "object",
                             "properties": {
-                                "cost": { "$ref": "#/definitions/TimeSeries" },
-                                "limit": { "$ref": "#/definitions/TimeSeries" }
+                                "Cost ($/tonne)": { "$ref": "#/definitions/TimeSeries" },
+                                "Limit (tonne)": { "$ref": "#/definitions/TimeSeries" }
                             },
                             "required": [
-                                "cost"
+                                "Cost ($/tonne)"
                             ]
                         }
                     },
-                    "capacities": {
+                    "Capacities (tonne)": {
                         "type": "object",
                         "additionalProperties": {
                             "type": "object",
                             "properties": {
-                                "variable operating cost": { "$ref": "#/definitions/TimeSeries" },
-                                "fixed operating cost": { "$ref": "#/definitions/TimeSeries" },
-                                "opening cost": { "$ref": "#/definitions/TimeSeries" }
+                                "Variable operating cost ($/tonne)": { "$ref": "#/definitions/TimeSeries" },
+                                "Fixed operating cost ($)": { "$ref": "#/definitions/TimeSeries" },
+                                "Opening cost ($)": { "$ref": "#/definitions/TimeSeries" }
                             },
                             "required": [
-                                "variable operating cost",
-                                "fixed operating cost",
-                                "opening cost"
+                                "Variable operating cost ($/tonne)",
+                                "Fixed operating cost ($)",
+                                "Opening cost ($)"
                             ]
                         }
                     }                    
                 },
                 "required": [
-                    "latitude",
-                    "longitude",
-                    "capacities"
+                    "Latitude (deg)",
+                    "Longitude (deg)",
+                    "Capacities (tonne)"
                 ]
             }
         },
@@ -85,14 +85,14 @@
             "additionalProperties": {
                 "type": "object",
                 "properties": {
-                    "latitude": { "type": "number" },
-                    "longitude": { "type": "number" },
-                    "amount": { "$ref": "#/definitions/TimeSeries" }
+                    "Latitude (deg)": { "type": "number" },
+                    "Longitude (deg)": { "type": "number" },
+                    "Amount (tonne)": { "$ref": "#/definitions/TimeSeries" }
                 },
                 "required": [
-                    "latitude",
-                    "longitude",
-                    "amount"
+                    "Latitude (deg)",
+                    "Longitude (deg)",
+                    "Amount (tonne)"
                 ]
             }
         },
@@ -101,23 +101,24 @@
             "additionalProperties": {
                 "type": "object",
                 "properties": {
-                    "transportation cost": { "$ref": "#/definitions/TimeSeries" },
-                    "initial amounts": { "$ref": "#/definitions/InitialAmount" }
+                    "Transportation cost ($/km/tonne)": { "$ref": "#/definitions/TimeSeries" },
+                    "Initial amounts": { "$ref": "#/definitions/InitialAmount" }
                 },
                 "required": [
-                    "transportation cost"
+                    "Transportation cost ($/km/tonne)"
                 ]
             }            
         }
     },
     "type": "object",
     "properties": {
-        "parameters": { "$ref": "#/definitions/Parameters" },
-        "plants": { "$ref": "#/definitions/Plant" },
-        "products": { "$ref": "#/definitions/Product" }
+        "Parameters": { "$ref": "#/definitions/Parameters" },
+        "Plants": { "$ref": "#/definitions/Plant" },
+        "Products": { "$ref": "#/definitions/Product" }
     },
     "required": [
-        "plants",
-        "products"
+        "Parameters",
+        "Plants",
+        "Products"
     ]
 }
\ No newline at end of file
diff --git a/test/model_test.jl b/test/model_test.jl
index e58a525..8aa06f1 100644
--- a/test/model_test.jl
+++ b/test/model_test.jl
@@ -45,17 +45,17 @@ using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats
         solution = RELOG.solve("$(pwd())/../instances/s1.json")
         JSON.print(stdout, solution, 4)
         
-        @test "costs" in keys(solution)
-        @test "fixed operating" in keys(solution["costs"])
-        @test "transportation" in keys(solution["costs"])
-        @test "variable operating" in keys(solution["costs"])
-        @test "total" in keys(solution["costs"])
-
-        @test "plants" in keys(solution)
-        @test "F1" in keys(solution["plants"])
-        @test "F2" in keys(solution["plants"])
-        @test "F3" in keys(solution["plants"])
-        @test "F4" in keys(solution["plants"])
+        @test "Costs" in keys(solution)
+        @test "Fixed operating (\$)" in keys(solution["Costs"])
+        @test "Transportation (\$)" in keys(solution["Costs"])
+        @test "Variable operating (\$)" in keys(solution["Costs"])
+        @test "Total (\$)" in keys(solution["Costs"])
+
+        @test "Plants" in keys(solution)
+        @test "F1" in keys(solution["Plants"])
+        @test "F2" in keys(solution["Plants"])
+        @test "F3" in keys(solution["Plants"])
+        @test "F4" in keys(solution["Plants"])
     end
 end