Skip to content

Commit 4704559

Browse files
authored
Website: add solvers page & relative performance plot (#125)
closes #115 ![image](https://github.com/user-attachments/assets/8b2a93bd-e711-4973-bfda-fda81fba0eb5)
1 parent 5eb01a6 commit 4704559

File tree

8 files changed

+731
-1
lines changed

8 files changed

+731
-1
lines changed

website-nextjs/package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website-nextjs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"papaparse": "^5.5.1",
2525
"react": "^19.0.0",
2626
"react-dom": "^19.0.0",
27+
"react-icons": "^5.5.0",
2728
"react-select": "^5.9.0",
2829
"reactjs-popup": "^2.0.6",
2930
"redux": "^5.0.1",
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { useEffect, useState, useMemo } from "react"
2+
import { useSelector } from "react-redux"
3+
import { IResultState } from "@/types/state"
4+
import PerformanceBarChart from "@/components/shared/PerformanceBarChart"
5+
import { FaGlobe, FaGithub, FaBalanceScale } from "react-icons/fa"
6+
7+
const SOLVES_DATA = [
8+
{
9+
label: "HiGHS",
10+
name: "highs",
11+
sourceCode: "https://github.com/ERGO-Code/HiGHS",
12+
website: "https://highs.dev/",
13+
},
14+
{
15+
label: "SCIP",
16+
name: "scip",
17+
sourceCode: "https://github.com/scipopt/scip",
18+
website: "https://www.scipopt.org/",
19+
},
20+
{
21+
label: "CBC",
22+
name: "cbc",
23+
sourceCode: "https://github.com/coin-or/Cbc",
24+
website: "https://coin-or.github.io/Cbc/intro.html",
25+
},
26+
{
27+
label: "GLPK",
28+
name: "glpk",
29+
sourceCode: "https://github.com/firedrakeproject/glpk",
30+
website: "https://www.gnu.org/software/glpk/",
31+
},
32+
]
33+
34+
const SolverSection = () => {
35+
const availableSolvers = useSelector((state: { results: IResultState }) => {
36+
return state.results.availableSolvers
37+
})
38+
39+
const benchmarkLatestResults = useSelector(
40+
(state: { results: IResultState }) => {
41+
return state.results.benchmarkLatestResults
42+
}
43+
)
44+
45+
const [selectedSolver, setSelectedSolver] = useState("")
46+
47+
const [solverOptions, setSolverOptions] = useState<string[]>([])
48+
49+
useEffect(() => {
50+
if (!availableSolvers.length) return
51+
setSelectedSolver(availableSolvers[0])
52+
setSolverOptions(availableSolvers)
53+
}, [availableSolvers])
54+
55+
function calculateFactor(baseTime: number, solverTime: number) {
56+
return Math.log2((solverTime + 10) / (baseTime + 10))
57+
}
58+
59+
const chartData = useMemo(() => {
60+
if (!selectedSolver) return []
61+
62+
// Get base solver data points (these will show as factor = 0)
63+
const baseData = benchmarkLatestResults
64+
.filter((result) => result.solver === selectedSolver)
65+
.map((result) => ({
66+
benchmark: result.benchmark,
67+
solver: result.solver,
68+
size: result.size,
69+
status: result.status,
70+
runtime: result.runtime || 0,
71+
baseSolverRuntime: result.runtime || 0,
72+
factor: 0, // log2(1) = 0
73+
}))
74+
75+
// Get comparison data points
76+
const comparisonData = benchmarkLatestResults
77+
.filter((result) => result.solver !== selectedSolver)
78+
.map((oData) => {
79+
const sData = baseData.find(
80+
(sData) => sData.benchmark === oData.benchmark && sData.size === oData.size
81+
)
82+
return {
83+
benchmark: oData.benchmark,
84+
solver: oData.solver,
85+
status: oData.status,
86+
size: oData.size,
87+
runtime: oData.runtime || 0,
88+
baseSolverRuntime: sData?.runtime || 0,
89+
factor: calculateFactor(sData?.runtime || 0, oData.runtime || 0),
90+
}
91+
})
92+
93+
const baseRuntimes = new Map(
94+
baseData.map((d) => [`${d.benchmark}-${d.size}`, d.runtime])
95+
)
96+
97+
// Combine and sort all data
98+
return [...baseData, ...comparisonData].sort((a, b) => {
99+
const aBaseRuntime = baseRuntimes.get(`${a.benchmark}-${a.size}`) || 0
100+
const bBaseRuntime = baseRuntimes.get(`${b.benchmark}-${b.size}`) || 0
101+
102+
// Sort by base solver runtime
103+
if (aBaseRuntime !== bBaseRuntime) {
104+
return aBaseRuntime - bBaseRuntime
105+
}
106+
107+
// Put base solver first within each benchmark group
108+
return a.solver === selectedSolver ? -1 : 1
109+
})
110+
}, [selectedSolver, benchmarkLatestResults])
111+
112+
const selectedSolverInfo = useMemo(() => {
113+
if (!selectedSolver) return null
114+
const solverName = selectedSolver.split("--")[0].toLowerCase()
115+
return SOLVES_DATA.find((s) => s.name.toLowerCase() === solverName)
116+
}, [selectedSolver])
117+
118+
return (
119+
<div>
120+
<div className="flex gap-4 mt-6 mb-4">
121+
{/* Solver select */}
122+
<div className="w-1/2 bg-[#F0F4F2] rounded-lg shadow-sm">
123+
<div className="p-3 pl-3.5 font-bold font-lato text-lg border-b border-gray-200">
124+
Select Solver
125+
</div>
126+
<select
127+
name="solver"
128+
value={selectedSolver}
129+
onChange={(event) => setSelectedSolver(event.target.value)}
130+
className="w-full font-bold pl-3 bg-[#F0F4F2] px-6 py-4 border-r-[1.5rem]
131+
border-transparent text-dark-grey text-base rounded-b-lg block focus-visible:outline-none"
132+
>
133+
<option disabled>Solver</option>
134+
{solverOptions.map((solver, idx) => (
135+
<option key={idx} value={solver}>
136+
{solver}
137+
</option>
138+
))}
139+
</select>
140+
</div>
141+
142+
{/* Enhanced solver info section */}
143+
{selectedSolverInfo && (
144+
<div className="w-1/2 bg-[#F0F4F2] rounded-lg shadow-sm">
145+
<div className="p-3 pl-3.5 font-bold font-lato text-lg border-b border-gray-200">
146+
Solver Information
147+
</div>
148+
<div className="p-4">
149+
<h3 className="text-xl font-bold mb-4 text-gray-800">
150+
{selectedSolverInfo.label}
151+
</h3>
152+
<div className="space-y-3">
153+
<a
154+
href={selectedSolverInfo.website}
155+
target="_blank"
156+
rel="noopener noreferrer"
157+
className="flex items-center gap-3 transition-colors"
158+
>
159+
<FaGlobe className="w-5 h-5" />
160+
<span className="hover:underline">Official Website</span>
161+
</a>
162+
<a
163+
href={selectedSolverInfo.sourceCode}
164+
target="_blank"
165+
rel="noopener noreferrer"
166+
className="flex items-center gap-3 transition-colors"
167+
>
168+
<FaGithub className="w-5 h-5" />
169+
<span className="hover:underline">Source Code</span>
170+
</a>
171+
<div className="flex items-center gap-3 transition-colors">
172+
<FaBalanceScale className="w-5 h-5" />
173+
<span>License: MIT</span>
174+
</div>
175+
</div>
176+
</div>
177+
</div>
178+
)}
179+
</div>
180+
181+
{chartData.length > 0 && (
182+
<PerformanceBarChart
183+
data={chartData}
184+
baseSolver={selectedSolver.split("--")[0]}
185+
availableSolvers={availableSolvers}
186+
/>
187+
)}
188+
</div>
189+
)
190+
}
191+
export default SolverSection

website-nextjs/src/components/shared/Navbar.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
BalanceScaleIcon,
55
ChartBarIcon,
66
ChartLineIcon,
7+
VectorSquareIcon,
78
WindowIcon,
89
} from "@/assets/icons"
910
import Image from "next/image"
@@ -29,6 +30,11 @@ const Navbar = () => {
2930
route: PATH_DASHBOARD.benchmarkDetail.list,
3031
icon: <ChartBarIcon />,
3132
},
33+
{
34+
label: "Solvers",
35+
route: "/dashboard/solvers",
36+
icon: <VectorSquareIcon />,
37+
},
3238
{
3339
label: "Compare solvers",
3440
route: "/dashboard/compare-solvers",

0 commit comments

Comments
 (0)