\n
RELOG: Reverse Logistics Optimization
\n
Copyright © 2020—2022, UChicago Argonne, LLC. All Rights Reserved.
\n
;\n};\n\nexport default Footer;","import React, { useState, useRef } from 'react';\n\nimport './index.css';\nimport PipelineBlock from './PipelineBlock';\nimport ParametersBlock from './ParametersBlock';\nimport ProductBlock from './ProductBlock';\nimport PlantBlock from './PlantBlock';\nimport Button from './Button';\nimport Header from './Header';\nimport Footer from './Footer';\n\n\nconst defaultProduct = {\n \"initial amounts\": {},\n \"acquisition cost ($/tonne)\": \"0\",\n \"disposal cost ($/tonne)\": \"0\",\n \"disposal limit (tonne)\": \"0\",\n \"disposal limit (%)\": \"\",\n \"transportation cost ($/km/tonne)\": \"0\",\n \"transportation energy (J/km/tonne)\": \"0\",\n \"transportation emissions (tonne/km/tonne)\": {},\n \"x\": 0,\n \"y\": 0,\n};\n\nconst defaultPlantLocation = {\n \"area cost factor\": 1.0,\n \"latitude (deg)\": 0,\n \"longitude (deg)\": 0,\n};\n\nconst defaultPlant = {\n \"locations\": {},\n \"outputs (tonne/tonne)\": {},\n \"disposal cost ($/tonne)\": {},\n \"disposal limit (tonne)\": {},\n \"emissions (tonne/tonne)\": {},\n \"storage\": {\n \"cost ($/tonne)\": 0,\n \"limit (tonne)\": 0,\n },\n \"maximum capacity (tonne)\": 0,\n \"minimum capacity (tonne)\": 0,\n \"opening cost (max capacity) ($)\": 0,\n \"opening cost (min capacity) ($)\": 0,\n \"fixed operating cost (max capacity) ($)\": 0,\n \"fixed operating cost (min capacity) ($)\": 0,\n \"variable operating cost ($/tonne)\": 0,\n \"energy (GJ/tonne)\": 0,\n \"x\": 0,\n \"y\": 0,\n};\n\nconst defaultData = {\n parameters: {\n \"time horizon (years)\": \"1\",\n \"building period (years)\": \"[1]\",\n \"annual inflation rate (%)\": \"0\",\n },\n products: {\n },\n plants: {\n }\n};\n\nconst randomPosition = () => {\n return Math.round(Math.random() * 30) * 15 + 15;\n};\n\nconst setDefaults = (actualDict, defaultDict) => {\n for (const [key, defaultValue] of Object.entries(defaultDict)) {\n if (!(key in actualDict)) {\n if (typeof defaultValue === \"object\") {\n actualDict[key] = { ...defaultValue };\n } else {\n actualDict[key] = defaultValue;\n }\n }\n }\n};\n\nconst cleanDict = (dict, defaultDict) => {\n for (const [key, defaultValue] of Object.entries(dict)) {\n if (!(key in defaultDict)) {\n delete dict[key];\n }\n }\n};\n\nconst fixLists = (dict, blacklist, stringify) => {\n for (const [key, val] of Object.entries(dict)) {\n if (blacklist.includes(key)) continue;\n if (Array.isArray(val)) {\n // Replace constant lists by a single number\n let isConstant = true;\n for (let i = 1; i < val.length; i++) {\n if (val[i - 1] !== val[i]) {\n isConstant = false;\n break;\n }\n }\n if (isConstant) dict[key] = val[0];\n\n // Convert lists to JSON strings\n if (stringify) dict[key] = JSON.stringify(dict[key]);\n }\n if (typeof val === \"object\") {\n fixLists(val, blacklist, stringify);\n }\n }\n};\n\nconst InputPage = () => {\n const fileElem = useRef();\n\n let savedData = JSON.parse(localStorage.getItem(\"data\"));\n if (!savedData) savedData = defaultData;\n\n let [data, setData] = useState(savedData);\n\n const save = (data) => {\n localStorage.setItem(\"data\", JSON.stringify(data));\n };\n\n const promptName = (prevData) => {\n const name = prompt(\"Name\");\n if (!name || name.length === 0) return;\n if (name in prevData.products || name in prevData.plants) return;\n return name;\n };\n\n const onAddPlant = () => {\n setData((prevData) => {\n const name = promptName(prevData);\n if (name === undefined) return prevData;\n const newData = { ...prevData };\n newData.plants[name] = {\n ...defaultPlant,\n x: randomPosition(),\n y: randomPosition(),\n };\n save(newData);\n return newData;\n });\n };\n\n const onAddProduct = () => {\n setData((prevData) => {\n const name = promptName(prevData);\n if (name === undefined) return prevData;\n const newData = { ...prevData };\n newData.products[name] = {\n ...defaultProduct,\n x: randomPosition(),\n y: randomPosition(),\n };\n save(newData);\n return newData;\n });\n };\n\n const onRenamePlant = (prevName, newName) => {\n setData((prevData) => {\n const newData = { ...prevData };\n newData.plants[newName] = newData.plants[prevName];\n delete newData.plants[prevName];\n save(newData);\n return newData;\n });\n };\n\n const onRenameProduct = (prevName, newName) => {\n setData((prevData) => {\n const newData = { ...prevData };\n newData.products[newName] = newData.products[prevName];\n delete newData.products[prevName];\n for (const [, plant] of Object.entries(newData.plants)) {\n if (plant.input === prevName) {\n plant.input = newName;\n }\n let outputFound = false;\n for (const [outputName] of Object.entries(plant[\"outputs (tonne/tonne)\"])) {\n if (outputName === prevName) outputFound = true;\n }\n if (outputFound) {\n plant[\"outputs (tonne/tonne)\"][newName] = plant[\"outputs (tonne/tonne)\"][prevName];\n delete plant[\"outputs (tonne/tonne)\"][prevName];\n }\n }\n save(newData);\n return newData;\n });\n };\n\n const onMovePlant = (plantName, x, y) => {\n setData((prevData) => {\n const newData = { ...prevData };\n newData.plants[plantName].x = x;\n newData.plants[plantName].y = y;\n save(newData);\n return newData;\n });\n };\n\n const onMoveProduct = (productName, x, y) => {\n setData((prevData) => {\n const newData = { ...prevData };\n newData.products[productName].x = x;\n newData.products[productName].y = y;\n save(newData);\n return newData;\n });\n };\n\n const onRemovePlant = (plantName) => {\n setData((prevData) => {\n const newData = { ...prevData };\n delete newData.plants[plantName];\n save(newData);\n return newData;\n });\n };\n\n const onRemoveProduct = (productName) => {\n setData((prevData) => {\n const newData = { ...prevData };\n delete newData.products[productName];\n for (const [, plant] of Object.entries(newData.plants)) {\n if (plant.input === productName) {\n delete plant.input;\n }\n let outputFound = false;\n for (const [outputName] of Object.entries(plant[\"outputs (tonne/tonne)\"])) {\n if (outputName === productName) outputFound = true;\n }\n if (outputFound) {\n delete plant[\"outputs (tonne/tonne)\"][productName];\n }\n }\n save(newData);\n return newData;\n });\n };\n\n const onSetPlantInput = (plantName, productName) => {\n setData((prevData) => {\n const newData = { ...prevData };\n newData.plants[plantName].input = productName;\n save(newData);\n return newData;\n });\n };\n\n const onAddPlantOutput = (plantName, productName) => {\n setData((prevData) => {\n if (productName in prevData.plants[plantName][\"outputs (tonne/tonne)\"]) {\n return prevData;\n }\n const newData = { ...prevData };\n [\"outputs (tonne/tonne)\", \"disposal cost ($/tonne)\", \"disposal limit (tonne)\"].forEach(key => {\n newData.plants[plantName][key] = { ...newData.plants[plantName][key] };\n newData.plants[plantName][key][productName] = 0;\n\n });\n save(newData);\n return newData;\n });\n\n };\n\n const onSave = () => {\n console.log(data);\n };\n\n const onClear = () => {\n setData(defaultData);\n save(defaultData);\n };\n\n const onLoad = (contents) => {\n data = JSON.parse(contents);\n [\"parameters\", \"plants\", \"products\"].forEach(key => {\n if (!(key in data)) {\n console.log(\"File not recognized.\");\n return;\n }\n });\n fixLists(data, [\"initial amounts\"], false);\n\n setDefaults(data.parameters, defaultData.parameters);\n for (const plantDict of Object.values(data.plants)) {\n setDefaults(plantDict, defaultPlant);\n\n // Initialize disposal limits and costs\n for (const outputName of Object.keys(plantDict[\"outputs (tonne/tonne)\"])) {\n plantDict[\"disposal cost ($/tonne)\"][outputName] = 0;\n plantDict[\"disposal limit (tonne)\"][outputName] = 0;\n }\n\n for (const locationDict of Object.values(plantDict[\"locations\"])) {\n setDefaults(locationDict, defaultPlantLocation);\n\n // Read disposal limits and costs\n for (const [dispName, dispDict] of Object.entries(locationDict[\"disposal\"])) {\n if (\"cost ($/tonne)\" in dispDict) {\n plantDict[\"disposal cost ($/tonne)\"][dispName] = dispDict[\"cost ($/tonne)\"];\n }\n if (\"limit (tonne)\" in dispDict) {\n plantDict[\"disposal limit (tonne)\"][dispName] = dispDict[\"limit (tonne)\"];\n } else {\n plantDict[\"disposal limit (tonne)\"][dispName] = \"\";\n }\n }\n delete locationDict[\"disposal\"];\n\n // Read capacities and costs\n const caps = [];\n const capDicts = [];\n for (const [c, cDict] of Object.entries(locationDict[\"capacities (tonne)\"])) {\n caps.push(c);\n capDicts.push(cDict);\n }\n const last = caps.length - 1;\n if (plantDict[\"minimum capacity (tonne)\"] === 0) {\n plantDict[\"minimum capacity (tonne)\"] = caps[0];\n plantDict[\"maximum capacity (tonne)\"] = caps[last];\n plantDict[\"opening cost (min capacity) ($)\"] = capDicts[0][\"opening cost ($)\"];\n plantDict[\"opening cost (max capacity) ($)\"] = capDicts[last][\"opening cost ($)\"];\n plantDict[\"fixed operating cost (min capacity) ($)\"] = capDicts[0][\"fixed operating cost ($)\"];\n plantDict[\"fixed operating cost (max capacity) ($)\"] = capDicts[last][\"fixed operating cost ($)\"];\n plantDict[\"variable operating cost ($/tonne)\"] = capDicts[0][\"variable operating cost ($/tonne)\"];\n } else {\n const plantCost = plantDict[\"opening cost (min capacity) ($)\"];\n const locationCost = capDicts[0][\"opening cost ($)\"];\n if (!Array.isArray(plantCost) && !Array.isArray(locationCost) && (plantCost !== locationCost)) {\n locationDict[\"area cost factor\"] = plantCost / locationCost;\n }\n }\n delete locationDict[\"capacities (tonne)\"];\n\n // Read storage\n plantDict[\"storage\"] = { ...locationDict[\"storage\"] };\n delete locationDict[\"storage\"];\n }\n\n if (plantDict[\"x\"] === 0 && plantDict[\"y\"] === 0) {\n plantDict[\"x\"] = randomPosition();\n plantDict[\"y\"] = randomPosition();\n }\n }\n for (const productDict of Object.values(data.products)) {\n setDefaults(productDict, defaultProduct);\n\n // Replace absolute disposal limits by percentage disposal limits\n // if the ratio is constant.\n let totalAmount = null;\n for (const [locName, locDict] of Object.entries(productDict[\"initial amounts\"])) {\n if (totalAmount === null) {\n totalAmount = [...locDict[\"amount (tonne)\"]];\n } else {\n for (let i = 0; i < totalAmount.length; i++) {\n totalAmount[i] += locDict[\"amount (tonne)\"][i];\n }\n }\n }\n if (totalAmount !== null && \"disposal limit (tonne)\" in productDict) {\n const disposalPerc = [...productDict[\"disposal limit (tonne)\"]];\n for (let i = 0; i < disposalPerc.length; i++) {\n disposalPerc[i] /= totalAmount[i];\n disposalPerc[i] = Math.round(disposalPerc[i] * 1e6) / 1e4;\n }\n let isConstant = true;\n for (let i = 1; i < disposalPerc.length; i++) {\n if (disposalPerc[i - 1] !== disposalPerc[i]) isConstant = false;\n }\n if (isConstant) {\n productDict[\"disposal limit (tonne)\"] = \"\";\n productDict[\"disposal limit (%)\"] = disposalPerc[0];\n }\n }\n\n if (productDict[\"x\"] === 0 && productDict[\"y\"] === 0) {\n productDict[\"x\"] = randomPosition();\n productDict[\"y\"] = randomPosition();\n }\n }\n fixLists(data, [\"initial amounts\"], true);\n cleanDict(data, defaultData);\n setData(data);\n save(data);\n };\n\n const onChange = (val, field1, field2) => {\n setData(prevData => {\n const newData = { ...prevData };\n if (field2 !== undefined) {\n newData[field1][field2] = val;\n } else {\n newData[field1] = val;\n }\n save(newData);\n return newData;\n });\n };\n let productComps = [];\n for (const [prodName, prod] of Object.entries(data.products)) {\n productComps.push(\n