Create solver page; add Dockerfile

feature/gui
Alinson S. Xavier 3 years ago
parent 9112d9fde5
commit ee767b9ebd

@ -0,0 +1,4 @@
build
jobs
relog-web/node_modules
relog-web/build

@ -0,0 +1,27 @@
FROM julia:1.7-buster
# Install Node.js & zip
RUN apt-get update -yq && \
apt-get -yq install curl gnupg ca-certificates && \
curl -L https://deb.nodesource.com/setup_18.x | bash && \
apt-get update -yq && \
apt-get install -yq nodejs zip
# Install Julia dependencies
ADD Project.toml /app/
ADD src/RELOG.jl /app/src/
RUN julia --project=/app -e 'using Pkg; Pkg.update()'
# Install JS dependencies
ADD relog-web/package*.json /app/relog-web/
RUN cd /app/relog-web && npm install
# Copy source code
ADD . /app
RUN julia --project=/app -e 'using Pkg; Pkg.precompile()'
# Build JS app
RUN cd /app/relog-web && npm run build
WORKDIR /app
CMD julia --project=/app -e 'import RELOG; RELOG.web("0.0.0.0")'

@ -17,6 +17,9 @@ clean:
docs: docs:
mkdocs build -d ../docs/$(VERSION)/ mkdocs build -d ../docs/$(VERSION)/
docker-build:
docker build --tag relog:0.6 .
format: format:
julia -e 'using JuliaFormatter; format(["src", "test"], verbose=true);' julia -e 'using JuliaFormatter; format(["src", "test"], verbose=true);'
@ -26,3 +29,4 @@ test-watch:
bash -c "while true; do make test --quiet; sleep 1; done" bash -c "while true; do make test --quiet; sleep 1; done"
.PHONY: docs test .PHONY: docs test

@ -10,9 +10,11 @@ Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d" Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63" GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63"
Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d" Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572" JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
@ -20,8 +22,10 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568" ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4" Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
@ -36,6 +40,7 @@ DataFrames = "0.21"
DataStructures = "0.17" DataStructures = "0.17"
GZip = "0.5" GZip = "0.5"
Geodesy = "0.5" Geodesy = "0.5"
HTTP = "0.9"
JSON = "0.21" JSON = "0.21"
JSONSchema = "0.3" JSONSchema = "0.3"
JuMP = "0.21" JuMP = "0.21"

@ -13,23 +13,22 @@
**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. **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.
<img src="https://anl-ceeesa.github.io/RELOG/0.5/images/ex_transportation.png" width="1000px"/> <img src="https://anl-ceeesa.github.io/RELOG/0.5/images/ex_transportation.png" width="1000px"/>
### Documentation ### Documentation
* [Usage](https://anl-ceeesa.github.io/RELOG/0.5/usage) - [Usage](https://anl-ceeesa.github.io/RELOG/0.5/usage)
* [Input and Output Data Formats](https://anl-ceeesa.github.io/RELOG/0.5/format) - [Input and Output Data Formats](https://anl-ceeesa.github.io/RELOG/0.5/format)
* [Simplified Solution Reports](https://anl-ceeesa.github.io/RELOG/0.5/reports) - [Simplified Solution Reports](https://anl-ceeesa.github.io/RELOG/0.5/reports)
* [Optimization Model](https://anl-ceeesa.github.io/RELOG/0.5/model) - [Optimization Model](https://anl-ceeesa.github.io/RELOG/0.5/model)
### Authors ### Authors
* **Alinson S. Xavier** <<axavier@anl.gov>> - **Alinson S. Xavier** <<axavier@anl.gov>>
* **Nwike Iloeje** <<ciloeje@anl.gov>> - **Nwike Iloeje** <<ciloeje@anl.gov>>
* **John Atkins** - **John Atkins**
* **Kyle Sun** - **Kyle Sun**
- **Audrey Gallier**
### License ### License

@ -18,6 +18,7 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-flow-renderer": "^9.7.4", "react-flow-renderer": "^9.7.4",
"react-router-dom": "^5.3.3",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
} }
@ -8495,6 +8496,19 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/hoist-non-react-statics": { "node_modules/hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -11486,6 +11500,19 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/mini-create-react-context": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
"dependencies": {
"@babel/runtime": "^7.12.1",
"tiny-warning": "^1.0.3"
},
"peerDependencies": {
"prop-types": "^15.0.0",
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/mini-css-extract-plugin": { "node_modules/mini-css-extract-plugin": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz",
@ -13769,6 +13796,61 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-router": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz",
"integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==",
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/react-router-dom": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz",
"integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==",
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.3.3",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/react-router/node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
},
"node_modules/react-router/node_modules/path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"dependencies": {
"isarray": "0.0.1"
}
},
"node_modules/react-router/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-scripts": { "node_modules/react-scripts": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
@ -14083,6 +14165,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"node_modules/resolve-url-loader": { "node_modules/resolve-url-loader": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
@ -15365,6 +15452,16 @@
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
}, },
"node_modules/tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"node_modules/tmpl": { "node_modules/tmpl": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@ -15538,6 +15635,19 @@
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
}, },
"node_modules/typescript": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@ -15703,6 +15813,11 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -22751,6 +22866,19 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
}, },
"history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"requires": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"hoist-non-react-statics": { "hoist-non-react-statics": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -24911,6 +25039,15 @@
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
}, },
"mini-create-react-context": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
"requires": {
"@babel/runtime": "^7.12.1",
"tiny-warning": "^1.0.3"
}
},
"mini-css-extract-plugin": { "mini-css-extract-plugin": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz",
@ -26433,6 +26570,57 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
}, },
"react-router": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz",
"integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==",
"requires": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
},
"path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"requires": {
"isarray": "0.0.1"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
"react-router-dom": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz",
"integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==",
"requires": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.3.3",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
}
},
"react-scripts": { "react-scripts": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
@ -26674,6 +26862,11 @@
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
}, },
"resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"resolve-url-loader": { "resolve-url-loader": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
@ -27619,6 +27812,16 @@
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
}, },
"tiny-invariant": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tmpl": { "tmpl": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@ -27753,6 +27956,12 @@
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
}, },
"typescript": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
"peer": true
},
"unbox-primitive": { "unbox-primitive": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@ -27877,6 +28086,11 @@
} }
} }
}, },
"value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

@ -2,7 +2,7 @@
"name": "relog-web", "name": "relog-web",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"homepage": "/RELOG/0.6/casebuilder", "homepage": "/",
"jest": { "jest": {
"moduleNameMapper": { "moduleNameMapper": {
"d3": "<rootDir>/node_modules/d3/dist/d3.min.js" "d3": "<rootDir>/node_modules/d3/dist/d3.min.js"
@ -19,6 +19,7 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-flow-renderer": "^9.7.4", "react-flow-renderer": "^9.7.4",
"react-router-dom": "^5.3.3",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },

@ -1,19 +1,18 @@
import React, { useState, useRef, useEffect } from "react"; import { openDB } from "idb";
import { openDB, deleteDB, wrap, unwrap } from "idb"; import React, { useEffect, useRef, useState } from "react";
import Button from "../common/Button";
import "./index.css"; import Footer from "../common/Footer";
import PipelineBlock from "./PipelineBlock"; import Header from "../common/Header";
import ParametersBlock from "./ParametersBlock"; import "../index.css";
import ProductBlock from "./ProductBlock"; import { generateFile } from "./csv";
import PlantBlock from "./PlantBlock";
import Button from "./Button";
import Header from "./Header";
import Footer from "./Footer";
import { defaultData, defaultPlant, defaultProduct } from "./defaults"; import { defaultData, defaultPlant, defaultProduct } from "./defaults";
import { randomPosition } from "./PipelineBlock";
import { exportData, importData } from "./export"; import { exportData, importData } from "./export";
import { generateFile } from "./csv"; import ParametersBlock from "./ParametersBlock";
import PipelineBlock, { randomPosition } from "./PipelineBlock";
import PlantBlock from "./PlantBlock";
import ProductBlock from "./ProductBlock";
import { validate } from "./validate"; import { validate } from "./validate";
import { useHistory } from "react-router-dom";
const setDefaults = (actualDict, defaultDict) => { const setDefaults = (actualDict, defaultDict) => {
for (const [key, defaultValue] of Object.entries(defaultDict)) { for (const [key, defaultValue] of Object.entries(defaultDict)) {
@ -83,6 +82,8 @@ const InputPage = () => {
if (data) setData(data); if (data) setData(data);
}, []); }, []);
const history = useHistory();
const promptName = (prevData) => { const promptName = (prevData) => {
const name = prompt("Name"); const name = prompt("Name");
if (!name || name.length === 0) return; if (!name || name.length === 0) return;
@ -301,6 +302,24 @@ const InputPage = () => {
); );
} }
const onSubmit = () => {
const exported = exportData(data);
const valid = validate(exported);
if (valid) {
fetch("/submit", {
method: "POST",
body: JSON.stringify(exported),
})
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
history.push(`/solver/${data.job_id}`);
});
}
};
let plantComps = []; let plantComps = [];
for (const [plantName, plant] of Object.entries(data.plants)) { for (const [plantName, plant] of Object.entries(data.plants)) {
plantComps.push( plantComps.push(
@ -341,6 +360,7 @@ const InputPage = () => {
<Button label="Clear" onClick={onClear} /> <Button label="Clear" onClick={onClear} />
<Button label="Load" onClick={(e) => fileElem.current.click()} /> <Button label="Load" onClick={(e) => fileElem.current.click()} />
<Button label="Save" onClick={onSave} /> <Button label="Save" onClick={onSave} />
<Button label="Submit" onClick={onSubmit} />
<input <input
type="file" type="file"
ref={fileElem} ref={fileElem}
@ -349,27 +369,29 @@ const InputPage = () => {
onChange={onFileSelected} onChange={onFileSelected}
/> />
</Header> </Header>
<div id="content"> <div id="contentBackground">
<PipelineBlock <div id="content">
onAddPlant={onAddPlant} <PipelineBlock
onAddPlantOutput={onAddPlantOutput} onAddPlant={onAddPlant}
onAddProduct={onAddProduct} onAddPlantOutput={onAddPlantOutput}
onMovePlant={onMovePlant} onAddProduct={onAddProduct}
onMoveProduct={onMoveProduct} onMovePlant={onMovePlant}
onRenamePlant={onRenamePlant} onMoveProduct={onMoveProduct}
onRenameProduct={onRenameProduct} onRenamePlant={onRenamePlant}
onSetPlantInput={onSetPlantInput} onRenameProduct={onRenameProduct}
onRemovePlant={onRemovePlant} onSetPlantInput={onSetPlantInput}
onRemoveProduct={onRemoveProduct} onRemovePlant={onRemovePlant}
plants={data.plants} onRemoveProduct={onRemoveProduct}
products={data.products} plants={data.plants}
/> products={data.products}
<ParametersBlock />
value={data.parameters} <ParametersBlock
onChange={(v) => onChange(v, "parameters")} value={data.parameters}
/> onChange={(v) => onChange(v, "parameters")}
{productComps} />
{plantComps} {productComps}
{plantComps}
</div>
</div> </div>
<div id="messageTray">{messageComps}</div> <div id="messageTray">{messageComps}</div>
<Footer /> <Footer />

@ -1,7 +1,7 @@
import Section from "./Section"; import Section from "../common/Section";
import Card from "./Card"; import Card from "../common/Card";
import Form from "./Form"; import Form from "../common/Form";
import TextInputRow from "./TextInputRow"; import TextInputRow from "../common/TextInputRow";
const ParametersBlock = (props) => { const ParametersBlock = (props) => {
const onChangeField = (field, val) => { const onChangeField = (field, val) => {

@ -1,8 +1,8 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import ReactFlow, { Background, isNode, Controls } from "react-flow-renderer"; import ReactFlow, { Background, isNode, Controls } from "react-flow-renderer";
import Section from "./Section"; import Section from "../common/Section";
import Card from "./Card"; import Card from "../common/Card";
import Button from "./Button"; import Button from "../common/Button";
import styles from "./PipelineBlock.module.css"; import styles from "./PipelineBlock.module.css";
import dagre from "dagre"; import dagre from "dagre";

@ -1,9 +1,9 @@
import Section from "./Section"; import Section from "../common/Section";
import Card from "./Card"; import Card from "../common/Card";
import Form from "./Form"; import Form from "../common/Form";
import TextInputRow from "./TextInputRow"; import TextInputRow from "../common/TextInputRow";
import FileInputRow from "./FileInputRow"; import FileInputRow from "../common/FileInputRow";
import DictInputRow from "./DictInputRow"; import DictInputRow from "../common/DictInputRow";
import { csvFormat, csvParse, generateFile } from "./csv"; import { csvFormat, csvParse, generateFile } from "./csv";
const PlantBlock = (props) => { const PlantBlock = (props) => {

@ -1,9 +1,9 @@
import Section from "./Section"; import Section from "../common/Section";
import Card from "./Card"; import Card from "../common/Card";
import Form from "./Form"; import Form from "../common/Form";
import TextInputRow from "./TextInputRow"; import TextInputRow from "../common/TextInputRow";
import FileInputRow from "./FileInputRow"; import FileInputRow from "../common/FileInputRow";
import DictInputRow from "./DictInputRow"; import DictInputRow from "../common/DictInputRow";
import { csvParse, extractNumericColumns, generateFile } from "./csv"; import { csvParse, extractNumericColumns, generateFile } from "./csv";
import { csvFormat } from "d3"; import { csvFormat } from "d3";

@ -1,7 +1,5 @@
.Footer { .Footer {
background-color: rgba(0, 0, 0, 0.8); padding: 12px;
padding: 24px;
margin-top: 24px;
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
text-align: center; text-align: center;
font-size: 14px; font-size: 14px;

@ -3,6 +3,7 @@
--box-border: 1px solid rgba(0, 0, 0, 0.2); --box-border: 1px solid rgba(0, 0, 0, 0.2);
--box-shadow: 0px 2px 4px -3px rgba(0, 0, 0, 0.2); --box-shadow: 0px 2px 4px -3px rgba(0, 0, 0, 0.2);
--border-radius: 4px; --border-radius: 4px;
--primary: #0d6efd;
} }
html, html,
@ -14,15 +15,19 @@ body {
} }
body { body {
background-color: #f6f6f6; background-color: #333;
color: rgba(0, 0, 0, 0.95); color: rgba(0, 0, 0, 0.95);
} }
#contentBackground {
background-color: #f6f6f6;
}
#content { #content {
max-width: var(--site-width); max-width: var(--site-width);
min-width: 900px; min-width: 900px;
margin: 0 auto; margin: 0 auto;
padding: 0 6px; padding: 1px 6px 32px 6px;
} }
.react-flow__node.selected { .react-flow__node.selected {

@ -1,11 +1,25 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import "./index.css"; import "./index.css";
import InputPage from "./InputPage"; import InputPage from "./casebuilder/InputPage";
import SolverPage from "./solver/SolverPage";
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <BrowserRouter>
<InputPage /> <React.StrictMode>
</React.StrictMode>, <Switch>
<Route path="/casebuilder">
<InputPage />
</Route>
<Route path="/solver/:job_id">
<SolverPage />
</Route>
<Route path="/">
<Redirect to="/casebuilder" />
</Route>
</Switch>
</React.StrictMode>
</BrowserRouter>,
document.getElementById("root") document.getElementById("root")
); );

@ -0,0 +1,47 @@
import { useState } from "react";
import { useEffect } from "react";
import Section from "../common/Section";
import Card from "../common/Card";
import styles from "./FilesBlock.module.css";
import { useRef } from "react";
const FilesBlock = (props) => {
const [filesFound, setFilesFound] = useState(false);
const fetchFiles = async () => {
const response = await fetch(`/jobs/${props.job}/output.json`);
if (response.ok) {
setFilesFound(true);
}
};
// Fetch files periodically from the server
useEffect(() => {
fetchFiles();
console.log(filesFound);
if (!filesFound) {
const interval = setInterval(() => {
fetchFiles();
}, 1000);
return () => clearInterval(interval);
}
}, [filesFound]);
let content = <div className={styles.nodata}>No files available</div>;
if (filesFound) {
content = (
<div className={styles.files}>
<a href={`/jobs/${props.job}/output.zip`}>output.zip</a>
</div>
);
}
return (
<>
<Section title="Output Files" />
<Card>{content}</Card>
</>
);
};
export default FilesBlock;

@ -0,0 +1,19 @@
.files a {
display: block;
padding: 16px;
text-decoration: none;
color: var(--primary);
}
.files a:hover {
background-color: var(--primary);
color: white;
border-radius: var(--border-radius);
}
.nodata {
text-align: center;
padding: 24px 0;
color: #888;
margin: 0;
}

@ -0,0 +1,46 @@
import { useState } from "react";
import { useEffect } from "react";
import Section from "../common/Section";
import Card from "../common/Card";
import styles from "./LogBlock.module.css";
import { useRef } from "react";
const LogBlock = (props) => {
const [log, setLog] = useState();
const preRef = useRef(null);
const fetchLog = async () => {
const response = await fetch(`/jobs/${props.job}/solve.log`);
const data = await response.text();
if (log !== data) {
setLog(data);
}
};
// Fetch log periodically from the server
useEffect(() => {
fetchLog();
const interval = setInterval(() => {
fetchLog();
}, 1000);
return () => clearInterval(interval);
}, []);
// Scroll to bottom whenever the log is updated
useEffect(() => {
preRef.current.scrollTop = preRef.current.scrollHeight;
}, [log]);
return (
<>
<Section title="Optimization Log" />
<Card>
<pre ref={preRef} className={styles.log}>
{log}
</pre>
</Card>
</>
);
};
export default LogBlock;

@ -0,0 +1,8 @@
.log {
max-height: 500px;
min-height: 500px;
border: 0;
margin: 0;
overflow: auto;
line-height: 1.4em;
}

@ -0,0 +1,26 @@
import React from "react";
import { useParams } from "react-router-dom";
import Footer from "../common/Footer";
import Header from "../common/Header";
import LogBlock from "./LogBlock";
import FilesBlock from "./FilesBlock";
const SolverPage = () => {
const params = useParams();
return (
<>
<Header title="Solver"></Header>
<div id="contentBackground">
{" "}
<div id="content">
<LogBlock job={params.job_id} />
<FilesBlock job={params.job_id} />
</div>
</div>
<Footer />
</>
);
};
export default SolverPage;

@ -25,4 +25,5 @@ include("reports/products.jl")
include("reports/tr_emissions.jl") include("reports/tr_emissions.jl")
include("reports/tr.jl") include("reports/tr.jl")
include("reports/write.jl") include("reports/write.jl")
include("web/web.jl")
end end

@ -0,0 +1,126 @@
println("Initializing...")
using Logging
using Cbc
using JSON
using JuMP
using RELOG
function solve(root, filename)
ref_file = "$root/$filename"
optimizer = optimizer_with_attributes(
Cbc.Optimizer,
"seconds" => 900,
)
ref_solution, ref_model = RELOG.solve(
ref_file,
optimizer=optimizer,
return_model=true,
marginal_costs=false,
)
Libc.flush_cstdio()
flush(stdout)
sleep(1)
if length(ref_solution) == 0
return
end
RELOG.write_products_report(
ref_solution,
replace(ref_file, ".json" => "_products.csv"),
)
RELOG.write_plants_report(
ref_solution,
replace(ref_file, ".json" => "_plants.csv"),
)
RELOG.write_plant_outputs_report(
ref_solution,
replace(ref_file, ".json" => "_plant_outputs.csv"),
)
RELOG.write_plant_emissions_report(
ref_solution,
replace(ref_file, ".json" => "_plant_emissions.csv"),
)
RELOG.write_transportation_report(
ref_solution,
replace(ref_file, ".json" => "_tr.csv"),
)
RELOG.write_transportation_emissions_report(
ref_solution,
replace(ref_file, ".json" => "_tr_emissions.csv"),
)
isdir("$root/scenarios") || return
for filename in readdir("$root/scenarios")
scenario = "$root/scenarios/$filename"
endswith(filename, ".json") || continue
sc_solution = RELOG.resolve(
ref_model,
scenario,
optimizer=optimizer,
)
if length(sc_solution) == 0
return
end
RELOG.write_plants_report(
sc_solution,
replace(scenario, ".json" => "_plants.csv"),
)
RELOG.write_products_report(
sc_solution,
replace(scenario, ".json" => "_products.csv"),
)
RELOG.write_plant_outputs_report(
sc_solution,
replace(scenario, ".json" => "_plant_outputs.csv"),
)
RELOG.write_plant_emissions_report(
sc_solution,
replace(scenario, ".json" => "_plant_emissions.csv"),
)
RELOG.write_transportation_report(
sc_solution,
replace(scenario, ".json" => "_tr.csv"),
)
RELOG.write_transportation_emissions_report(
sc_solution,
replace(scenario, ".json" => "_tr_emissions.csv"),
)
end
end
function solve_recursive(path)
# Solve instances
for (root, dirs, files) in walkdir(path)
if occursin(r"scenarios"i, root)
continue
end
for filename in files
endswith(filename, ".json") || continue
solve(root, filename)
end
end
# Collect results
results = []
for (root, dirs, files) in walkdir(path)
for filename in files
endswith(filename, "_plants.csv") || continue
push!(
results,
joinpath(
replace(root, path => ""),
replace(filename, "_plants.csv" => ""),
),
)
end
end
open("$path/output.json", "w") do file
JSON.print(file, results)
end
run(`zip -r $path/output.zip $path`)
end
solve_recursive(ARGS[1])

@ -0,0 +1,66 @@
import HTTP
import JSON
using Random
const ROUTER = HTTP.Router()
const PROJECT_DIR = joinpath(dirname(@__FILE__), "..", "..")
const STATIC_DIR = joinpath(PROJECT_DIR, "relog-web", "build", "static")
const JOBS_DIR = joinpath(PROJECT_DIR, "jobs")
function serve_file(req::HTTP.Request, filename)
if isfile(filename)
open(filename) do file
return HTTP.Response(200, read(file))
end
else
return HTTP.Response(404)
end
end
function submit(req::HTTP.Request)
# Generate random job id
job_id = lowercase(randstring(12))
# Create job folder
job_path = joinpath(JOBS_DIR, job_id)
mkpath(job_path)
# Write JSON file
case = JSON.parse(String(req.body))
open(joinpath(job_path, "case.json"), "w") do file
JSON.print(file, case)
end
# Run job
run(`bash -c "(julia --project=$PROJECT_DIR $PROJECT_DIR/src/web/run.jl $job_path 2>&1 | tee $job_path/solve.log) >/dev/null 2>&1 &"`)
response = Dict(
"job_id" => job_id,
)
return HTTP.Response(200, body = JSON.json(response))
end
function get_index(req::HTTP.Request)
return serve_file(req, joinpath(STATIC_DIR, "..", "index.html"))
end
function get_static(req::HTTP.Request)
return serve_file(req, joinpath(STATIC_DIR, req.target[9:end]))
end
function get_jobs(req::HTTP.Request)
return serve_file(req, joinpath(JOBS_DIR, req.target[7:end]))
end
HTTP.@register(ROUTER, "GET", "/static", get_static)
HTTP.@register(ROUTER, "GET", "/jobs", get_jobs)
HTTP.@register(ROUTER, "POST", "/submit", submit)
HTTP.@register(ROUTER, "GET", "/", get_index)
function web(host = "127.0.0.1", port = 8080)
@info "Launching web interface: http://$(host):$(port)/"
Base.exit_on_sigint(false)
HTTP.serve(ROUTER, host, port)
Base.exit_on_sigint(true)
end

@ -3,9 +3,11 @@
using RELOG using RELOG
BASEDIR = dirname(@__FILE__)
@testset "Resolve" begin @testset "Resolve" begin
# Shoud not crash # Shoud not crash
filename = "$(pwd())/../instances/s1.json" filename = joinpath(BASEDIR, "..", "..", "instances", "s1.json")
solution_old, model_old = RELOG.solve(filename, return_model = true) solution_old, model_old = RELOG.solve(filename, return_model = true)
solution_new = RELOG.resolve(model_old, filename) solution_new = RELOG.resolve(model_old, filename)
end end

@ -4,9 +4,11 @@
using RELOG, JSON, GZip using RELOG, JSON, GZip
BASEDIR = dirname(@__FILE__)
@testset "Reports" begin @testset "Reports" begin
@testset "from solve" begin @testset "from solve" begin
solution = RELOG.solve("$(pwd())/../instances/s1.json") solution = RELOG.solve(joinpath(BASEDIR, "..", "instances", "s1.json"))
tmp_filename = tempname() tmp_filename = tempname()
# The following should not crash # The following should not crash
RELOG.write_plant_emissions_report(solution, tmp_filename) RELOG.write_plant_emissions_report(solution, tmp_filename)

Loading…
Cancel
Save