From fc9f431e9be951f884f8412611e1732aac92d3f7 Mon Sep 17 00:00:00 2001 From: Pratham Khanal Date: Thu, 16 Apr 2026 00:55:59 -0500 Subject: [PATCH 1/8] Update index.js Update index.js to use batch endpoints, add caching, and fix request handling. --- components/redux/index.js | 130 ++++++++++++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 27 deletions(-) diff --git a/components/redux/index.js b/components/redux/index.js index c4d3c80..e0fd0fb 100644 --- a/components/redux/index.js +++ b/components/redux/index.js @@ -26,15 +26,19 @@ async function fetchJson(url, failMsg) { * @returns the JSON format of the fetch request. * @returns `undefined` on failure and logs the error. */ +//Batch endpoints are POSTs with no body. async function fetchPostJson(url, body, failMsg) { try { - const resp = await fetch(url, { + const options = { method: "POST", - body: JSON.stringify(body), headers: { - 'Content-Type': 'application/json; charset=UTF-8' - } - }); + "Content-Type": "application/json; charset=UTF-8", + }, + }; + if (body !== undefined && body !== null) { + options.body = JSON.stringify(body); + } + const resp = await fetch(url, options); if (resp.ok) { return await resp.json(); } @@ -45,6 +49,8 @@ async function fetchPostJson(url, body, failMsg) { return undefined; } + + /** * This function is a temporary solution for validating user input until it is ported to the Redux API. * @returns `true` if the specified verifier certificate is valid. @@ -92,6 +98,7 @@ function isCertificateValid(problem, certificate) { * @returns `undefined` on failure and logs the error. */ export async function requestGadgetMap(url, reduction, instance) { + if (!reduction) return; return await fetchPostJson( `${url}ProblemProvider/gadgets?reduction=${reduction}`, instance, @@ -226,17 +233,33 @@ export function remapIdsDeep(obj, idMap) { * @returns information regarding the problem/solver/verifier. * @returns `undefined` on failure and logs the error. */ + +const infoCache = new Map(); +const brokenInfoInterfaces = new Set(["DijkstraPriorityQueue"]); export async function requestInfo(url, apiCall) { - return await fetchJson(`${url}ProblemProvider/info?interface=${apiCall}`, () => `${apiCall} INFO REQUEST FAILED`); + if (!apiCall) return; + if (brokenInfoInterfaces.has(apiCall)) return; + if (infoCache.has(apiCall)) { + return await infoCache.get(apiCall); + } + const promise = fetchJson( + `${url}ProblemProvider/info?interface=${apiCall}`, + () => `${apiCall} INFO REQUEST FAILED` + ); + infoCache.set(apiCall, promise); + const result = await promise; + if (!result) { + infoCache.delete(apiCall); + } + return result; } - /** * @param reductionPath a hyphen (`-`) separated list of reductions to perform on the instance. * @returns the reduced `instance` list of reductions, the reduction path. * @returns `undefined` on failure and logs the error. */ export async function requestReducedInstanceFromPath(url, reductionPath, instance) { - for (const path of reductionPath.split("-")) { + for (const path of reductionPath.split("-").filter(p => p)) { const reducedInst = await requestReducedInstance(url, path, instance); if (!reducedInst) { console.log(`${reductionPath} AT ${path} REDUCED INSTANCE FROM PATH REQUEST FAILED`); @@ -252,6 +275,7 @@ export async function requestReducedInstanceFromPath(url, reductionPath, instanc * @returns `undefined` on failure and logs the error. */ export async function requestReducedInstance(url, reduction, instance) { + if (!reduction) return; return await fetchPostJson( `${url}ProblemProvider/reduce?reduction=${reduction}`, instance, @@ -299,7 +323,8 @@ export async function processReductionVisualizations(url, reductionPath, instanc for (const reduction of reductions) { finalVisualization = await requestReductionVisualization(url, reduction, currentInstance, solution); - const info = await requestInfo(url, reduction, instance); + const allInfo = await requestAllInfo(url); + const info = allInfo[reduction]; currentInstance = info.reductionTo.defaultInstance; solution = requestMappedSolution(url, solution, currentInstance); } @@ -348,7 +373,6 @@ export async function requestMappedSolution(url, reduction, problemFrom, problem export async function requestProblems(url) { return await fetchJson(`${url}navigation/ALL_ProblemsRefactor/`, () => `PROBLEMS REQUEST FAILED`); } - /** * @returns the generic instance of the `problem` with the given `instance`. * @returns `undefined` on failure and logs the error. @@ -360,8 +384,6 @@ export async function requestProblemGenericInstance(url, problem, instance) { () => `${problem} PROBLEM GENERIC INSTANCE REQUEST FAILED` ); } - - /** * @returns an array of arrays of reductions implemented for reducing a problem to another problem. * @returns `undefined` on failure and logs the error. @@ -372,7 +394,6 @@ export async function requestReductions(url, problemFrom, problemTo, problemType () => `${problemFrom} TO ${problemTo} REDUCTIONS REQUEST FAILED` ); } - /** * @returns the solved `instance` from the specified `solver`. * @returns `undefined` on failure and logs the error. @@ -384,7 +405,6 @@ export async function requestSolvedInstance(url, solver, instance) { () => `${solver} SOLVED INSTANCE REQUEST FAILED` ); } - /** * Temporary solution to allow solving 3 SAT with a Clique solver. * All calls to this function should eventually be replace with `requestSolvedInstance`. @@ -401,24 +421,20 @@ export async function requestSolvedInstanceTemporarySat3CliqueSolver(url, solver if (!reduction) { return undefined; } - const solution = await requestSolvedInstance(url, "CliqueBruteForce", reduction.reductionTo.instance); if (!solution) { return undefined; } - const mappedSolution = await fetchPostJson( `${url}SipserReduceToCliqueStandard/reverseMappedSolution`, { problemFrom: instance, problemTo: reduction.reductionTo.instance, problemFromSolution: solution }, () => "TRANSITIVE SOLVED REQUEST FAILED" ); - return mappedSolution; } else { return await requestSolvedInstance(url, solver, instance); } } - /** * @returns the solved graph visualization of the problem instance. * @returns `undefined` on failure and logs the error. @@ -439,19 +455,17 @@ export async function requestSolvedVisualization(url, problem, instance, solutio ); } } - /** * @returns the `steps` from the specified `solver`. * @returns `undefined` on failure and logs the error. */ export async function requestSolverSteps(url, solver, instance) { return await fetchPostJson( - `${url}ProlemProvider/steps?solver=${solver}`, + `${url}ProblemProvider/steps?solver=${solver}`, instance, () => `${solver} SOLVED INSTANCE REQUEST FAILED` ); } - /** * @returns an array of solvers implemented for the `problem`. * @returns `undefined` on failure and logs the error. @@ -462,7 +476,6 @@ export async function requestSolvers(url, problem, problemType = "NPC") { () => `${problem} SOLVERS REQUEST FAILED` ); } - /** * @returns an array of verifiers implemented for the `problem`. * @returns `undefined` on failure and logs the error. @@ -473,7 +486,6 @@ export async function requestVerifiers(url, problem, problemType = "NPC") { () => `${problem} VERIFIERS REQUEST FAILED` ); } - /** * @returns the verified `instance` results from the specified `verifier`. * @returns `undefined` on failure and logs the error. @@ -483,14 +495,12 @@ export async function requestVerifiedInstance(url, problem, verifier, instance, if (!isCertificateValid(problem, certificate)) { return "Invalid Input" } - return await fetchPostJson( `${url}ProblemProvider/verify?verifier=${verifier}`, { problemInstance: instance, certificate: certificate }, () => `${verifier} VERIFIED INSTANCE REQUEST FAILED` ); } - /** * @returns the graph visualization of the problem instance. * @returns `undefined` on failure and logs the error. @@ -502,7 +512,6 @@ export async function requestVisualization(url, visualization, instance, solver) () => `${visualization} VISUALIZE REQUEST FAILED` ); } - /** * @returns an array of visualizations implemented for the `problem`. * @returns `undefined` on failure and logs the error. @@ -512,4 +521,71 @@ export async function requestVisualizations(url, problem, problemType = "NPC") { `${url}Navigation/Problem_VisualizationsRefactor/?chosenProblem=${problem}&problemType=${problemType}`, () => `${problem} VISUALIZATIONS REQUEST FAILED` ); -} \ No newline at end of file +} +/** + * @returns an object mapping problem names for the given `problemType`. + * @returns `undefined` on failure and logs the error. + */ +export async function requestAllProblems(url, problemType = "NPC") { + return await fetchPostJson( + `${url}Navigation/Batch/allProblems?problemType=${problemType}`, + undefined, + () => `ALL PROBLEMS REQUEST FAILED` + ); +} +/** + * @returns an object mapping each problem to its available solvers. + * @returns `undefined` on failure and logs the error. + */ +export async function requestAllSolvers(url, problemType = "NPC") { + return await fetchPostJson( + `${url}Navigation/Batch/allSolvers?problemType=${problemType}`, + undefined, + () => `ALL SOLVERS REQUEST FAILED` + ); +} +/** + * @returns an object mapping each problem to its available verifiers. + * @returns `undefined` on failure and logs the error. + */ +export async function requestAllVerifiers(url, problemType = "NPC") { + return await fetchPostJson( + `${url}Navigation/Batch/allVerifiers?problemType=${problemType}`, + undefined, + () => `ALL VERIFIERS REQUEST FAILED` + ); +} +/** + * @returns an object mapping each problem to its available visualizations. + * @returns `undefined` on failure and logs the error. + */ +export async function requestAllVisualizations(url, problemType = "NPC") { + return await fetchPostJson( + `${url}Navigation/Batch/allVisualizations?problemType=${problemType}`, + undefined, + () => `ALL VISUALIZATIONS REQUEST FAILED` + ); +} +const allInfoCache = new Map(); +/** + * @returns an object containing metadata (`info`) for all interfaces (problems, solvers, verifiers, visualizations). + * @returns cached data when available to reduce API calls. + * @returns `undefined` on failure and logs the error. + */ +export async function requestAllInfo(url, problemType = "NPC") { + const cacheKey = `${url}|${problemType}`; + if (allInfoCache.has(cacheKey)) { + return await allInfoCache.get(cacheKey); + } + const promise = fetchPostJson( + `${url}Navigation/Batch/allInfo?problemType=${problemType}`, + undefined, + () => `ALL INFO REQUEST FAILED` + ); + allInfoCache.set(cacheKey, promise); + const result = await promise; + if (!result) { + allInfoCache.delete(cacheKey); + } + return result; +} From 45d4e159db1385cef0ee727a3ac4575bf6748243 Mon Sep 17 00:00:00 2001 From: Pratham Khanal Date: Thu, 16 Apr 2026 01:32:30 -0500 Subject: [PATCH 2/8] Update Solver.js Update Solver.js to use batched solver and info requests --- components/hooks/ProblemProvider/Solver.js | 80 +++++++++++----------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/components/hooks/ProblemProvider/Solver.js b/components/hooks/ProblemProvider/Solver.js index 58de9c1..741350e 100644 --- a/components/hooks/ProblemProvider/Solver.js +++ b/components/hooks/ProblemProvider/Solver.js @@ -1,14 +1,14 @@ import { useGenericInfo } from "../ProblemProvider"; -import { requestInfo, requestSolvers } from "../../redux"; +import { requestAllSolvers, requestAllInfo } from "../../redux"; import React, { useEffect, useState, useRef } from "react"; export function useSolver(url, problemName, problemType, problemNameMap, problemInfoMap, problemInstance) { const state = {}; /// Maps each problem name to its default solver name. - [state.defaultSolverMap] = useDefaultSolverMap(url, problemInfoMap); + [state.defaultSolverMap] = useDefaultSolverMap(url, problemInfoMap, problemType); [state.solverOptions] = useSolverOptions(url, problemName, problemType); [state.chosenSolver, state.setChosenSolver] = useChosenSolver(problemName, state.defaultSolverMap); - [state.solverNameMap] = useSolverNameMap(url, problemNameMap); + [state.solverNameMap] = useSolverNameMap(url, problemNameMap, problemType); [state.solvedInstance, state.setSolvedInstance] = useSolvedInstance(problemInstance, state.chosenSolver); return state; } @@ -33,74 +33,76 @@ function useSolvedInstance(problemInstance, chosenSolver) { return [solvedInstance, setSolvedInstance]; } -function useSolverNameMap(url, problemNameMap) { +function useSolverNameMap(url, problemNameMap, problemType) { const [solverNameMap, setSolverNameMap] = useState(new Map()); useEffect(() => { const problems = Array.from(problemNameMap.keys()); - requestSolverNameMap(url, problems).then((solverMap) => { - setSolverNameMap(solverMap); - }); - }, [problemNameMap]); - - //The following the functions are used to set the solver names - async function requestSolverNameMap(url, problems) { + (async () => { + const allSolvers = (await requestAllSolvers(url, problemType)) ?? {}; + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; let map = new Map(); + for (const problem of problems) { - const solvers = (await requestSolvers(url, problem)) ?? []; + const solvers = allSolvers[problem] ?? []; for (const s of solvers) { - let solver = s.split(" ")[0]; - const info = await requestInfo(url, solver); - if (info) { - map.set(s, info.solverName); - } + const solver = s.split(" ")[0]; + const info = allInfo[solver]; + map.set(s, info?.solverName || s); } } - return map; - } + setSolverNameMap(map); + })(); +}, [url, problemNameMap, problemType]); + return [solverNameMap, setSolverNameMap]; } -function useDefaultSolverMap(url, problemInfoMap) { +function useDefaultSolverMap(url, problemInfoMap, problemType) { const [defaultSolverMap, setDefaultSolverMap] = useState(new Map()); - useEffect(() => { - const problems = [...problemInfoMap.keys()]; - const defaultSolverNames = [...problemInfoMap.values()].map((info) => info.defaultSolver.solverName); - requestDefaultSolverFileMap(url, problems, defaultSolverNames).then((defaultSolverFileNames) => { - setDefaultSolverMap(defaultSolverFileNames); - }); - }, [problemInfoMap]); - - //The requestDefaultSolverFileMap sets the solver names by the file name - async function requestDefaultSolverFileMap(url, problems, defaultSolverNames) { +useEffect(() => { + const problems = [...problemInfoMap.keys()]; + const defaultSolverNames = [...problemInfoMap.values()] + .map((info) => info?.defaultSolver?.solverName) + .filter(Boolean); + + (async () => { + const allSolvers = (await requestAllSolvers(url, problemType)) ?? {}; + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; let map = new Map(); for (const problem of problems) { - const solvers = (await requestSolvers(url, problem)) ?? []; + const solvers = allSolvers[problem] ?? []; for (const s of solvers) { - let solver = s.split(" ")[0]; - const info = await requestInfo(url, solver); + const solver = s.split(" ")[0]; + const info = allInfo[solver]; if (info && defaultSolverNames.includes(info.solverName)) { map.set(problem, s); } } } - return map; - } + setDefaultSolverMap(map); + })(); +}, [url, problemInfoMap, problemType]); + return [defaultSolverMap, setDefaultSolverMap]; } function useSolverOptions(url, problemName, problemType) { const [solverOptions, setSolverOptions] = useState([]); - + useEffect(() => { (async () => { - setSolverOptions(problemName && problemType ? (await requestSolvers(url, problemName, problemType)) ?? [] : []); + if (!problemName || !problemType) { + setSolverOptions([]); + return; + } + const allSolvers = (await requestAllSolvers(url, problemType)) ?? {}; + setSolverOptions(allSolvers[problemName] ?? []); })(); - }, [problemName, problemType]); - + }, [url, problemName, problemType]); return [solverOptions, setSolverOptions]; } From 11b97a5ede1db493828a64057738d2a3e8687da4 Mon Sep 17 00:00:00 2001 From: Pratham Khanal Date: Thu, 16 Apr 2026 01:50:18 -0500 Subject: [PATCH 3/8] Update Verifier.js Update Verifier.js to use batched verifier and info requests --- components/hooks/ProblemProvider/Verifier.js | 79 ++++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/components/hooks/ProblemProvider/Verifier.js b/components/hooks/ProblemProvider/Verifier.js index 8a42ea6..668e76c 100644 --- a/components/hooks/ProblemProvider/Verifier.js +++ b/components/hooks/ProblemProvider/Verifier.js @@ -1,13 +1,12 @@ import { useGenericInfo } from "../ProblemProvider"; -import { requestInfo, requestVerifiers } from "../../redux"; +import { requestAllVerifiers, requestAllInfo } from "../../redux"; import React, { useEffect, useState, useRef } from "react"; - export function useVerifier(url, problemName, problemType, problemNameMap, problemInfoMap) { const state = {}; - [state.defaultVerifierMap] = useDefaultVerifierMap(url, problemInfoMap); + [state.defaultVerifierMap] = useDefaultVerifierMap(url, problemInfoMap, problemType); [state.verifierOptions] = useVerifierOptions(url, problemName, problemType); [state.chosenVerifier, state.setChosenVerifier] = useChosenVerifier(problemName, state.defaultVerifierMap); - [state.verifierNameMap] = useVerifierNameMap(url, problemNameMap); + [state.verifierNameMap] = useVerifierNameMap(url, problemNameMap, problemType); return state; } @@ -15,32 +14,32 @@ export function useVerifierInfo(url, verifier) { return useGenericInfo(url, verifier); } -function useDefaultVerifierMap(url, problemInfoMap) { +function useDefaultVerifierMap(url, problemInfoMap, problemType) { const [defaultVerifierMap, setDefaultVerifierMap] = useState(new Map()); - useEffect(() => { - const problems = [...problemInfoMap.keys()]; - const defaultVerifierNames = [...problemInfoMap.values()].map((info) => info.defaultVerifier.verifierName); - requestDefaultVerifierFileMap(url, problems, defaultVerifierNames).then((defaultVerifierFileNames) => { - setDefaultVerifierMap(defaultVerifierFileNames); - }); - }, [problemInfoMap]); - - //The requestDefaultVerifierFileMap sets the verifier names by the file name - async function requestDefaultVerifierFileMap(url, problems, defaultVerifierNames) { +useEffect(() => { + const problems = [...problemInfoMap.keys()]; + const defaultVerifierNames = [...problemInfoMap.values()] + .map((info) => info?.defaultVerifier?.verifierName) + .filter(Boolean); + + (async () => { + const allVerifiers = (await requestAllVerifiers(url, problemType)) ?? {}; + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; let map = new Map(); for (const problem of problems) { - const verifiers = (await requestVerifiers(url, problem)) ?? []; + const verifiers = allVerifiers[problem] ?? []; for (const v of verifiers) { const verifier = v.split(" ")[0]; - const info = await requestInfo(url, verifier); + const info = allInfo[verifier]; if (info && defaultVerifierNames.includes(info.verifierName)) { map.set(problem, v); } } } - return map; - } + setDefaultVerifierMap(map); + })(); +}, [url, problemInfoMap, problemType]); return [defaultVerifierMap, setDefaultVerifierMap]; } @@ -50,11 +49,14 @@ function useVerifierOptions(url, problemName, problemType) { useEffect(() => { (async () => { - setVerifierOptions( - problemName && problemType ? (await requestVerifiers(url, problemName, problemType)) ?? [] : [] - ); + if (!problemName || !problemType) { + setVerifierOptions([]); + return; +} +const allVerifiers = (await requestAllVerifiers(url, problemType)) ?? {}; +setVerifierOptions(allVerifiers[problemName] ?? []); })(); - }, [problemName, problemType]); + }, [url, problemName, problemType]); return [verifierOptions, setVerifierOptions]; } @@ -85,30 +87,27 @@ function useChosenVerifier(problemName, defaultVerifierMap) { return [chosenVerifier, setChosenVerifier]; } -function useVerifierNameMap(url, problemNameMap) { +function useVerifierNameMap(url, problemNameMap, problemType) { const [verifierNameMap, setVerifierNameMap] = useState(new Map()); - useEffect(() => { - const problems = Array.from(problemNameMap.keys()); - requestVerifierNameMap(url, problems).then((verifierMap) => { - setVerifierNameMap(verifierMap); - }); - }, [problemNameMap]); - - //The following the functions are used to set the verifier names - async function requestVerifierNameMap(url, problems) { +useEffect(() => { + const problems = Array.from(problemNameMap.keys()); + + (async () => { + const allVerifiers = (await requestAllVerifiers(url, problemType)) ?? {}; + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; let map = new Map(); + for (const problem of problems) { - const verifiers = (await requestVerifiers(url, problem)) ?? []; + const verifiers = allVerifiers[problem] ?? []; for (const verifier of verifiers) { - const info = await requestInfo(url, verifier); - if (info) { - map.set(verifier, info.verifierName); - } + const info = allInfo[verifier]; + map.set(verifier, info?.verifierName || verifier); } } - return map; - } + setVerifierNameMap(map); + })(); +}, [url, problemNameMap, problemType]); return [verifierNameMap, setVerifierNameMap]; } From 9af3eb044364b267816ee8671d78961d23ee4189 Mon Sep 17 00:00:00 2001 From: Pratham Khanal Date: Thu, 16 Apr 2026 02:07:39 -0500 Subject: [PATCH 4/8] Update Visualization.js Refactor Visualization.js to use batch endpoints for loading visualizations and metadata --- .../hooks/ProblemProvider/Visualization.js | 95 ++++++++++--------- 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/components/hooks/ProblemProvider/Visualization.js b/components/hooks/ProblemProvider/Visualization.js index 16ac2a4..2838cde 100644 --- a/components/hooks/ProblemProvider/Visualization.js +++ b/components/hooks/ProblemProvider/Visualization.js @@ -1,13 +1,13 @@ import { useGenericInfo } from "../ProblemProvider"; -import { requestInfo, requestVisualizations } from "../../redux"; +import { requestAllVisualizations, requestAllInfo } from "../../redux"; import React, { useEffect, useState, useRef } from "react"; export function useVisualization(url, problemName, problemType, problemNameMap, problemInfoMap) { const state = {}; - [state.defaultVisualizationMap] = useDefaultVisualizationMap(url, problemInfoMap); + [state.defaultVisualizationMap] = useDefaultVisualizationMap(url, problemInfoMap, problemType); [state.VisualizationOptions] = useVisualizationOptions(url, problemName, problemType); [state.chosenVisualization, state.setChosenVisualization] = useChosenVisualization(problemName, state.defaultVisualizationMap); - [state.VisualizationNameMap] = useVisualizationNameMap(url, problemNameMap); + [state.VisualizationNameMap] = useVisualizationNameMap(url, problemNameMap, problemType); return state; } @@ -15,32 +15,38 @@ export function useVisualizationInfo(url, Visualization) { return useGenericInfo(url, Visualization); } -function useDefaultVisualizationMap(url, problemInfoMap) { +function useDefaultVisualizationMap(url, problemInfoMap, problemType) { const [defaultVisualizationMap, setDefaultVisualizationMap] = useState(new Map()); - useEffect(() => { - const problems = [...problemInfoMap.keys()]; - const defaultVisualizationNames = [...problemInfoMap.values()].map((info) => info.defaultVisualization.VisualizationName); - requestDefaultVisualizationFileMap(url, problems, defaultVisualizationNames).then((defaultVisualizationFileNames) => { - setDefaultVisualizationMap(defaultVisualizationFileNames); - }); - }, [problemInfoMap]); - - //The requestDefaultVisualizationFileMap sets the Visualization names by the file name - async function requestDefaultVisualizationFileMap(url, problems, defaultVisualizationNames) { +useEffect(() => { + const problems = [...problemInfoMap.keys()]; + const defaultVisualizationNames = [...problemInfoMap.values()] + .map( + (info) => + info?.defaultVisualization?.visualizationName || + info?.defaultVisualization?.VisualizationName + ) + .filter(Boolean); + (async () => { + const allVisualizations = (await requestAllVisualizations(url, problemType)) ?? {}; + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; let map = new Map(); + for (const problem of problems) { - const Visualizations = (await requestVisualizations(url, problem)) ?? []; - for (const v of Visualizations) { - const Visualization = v.split(" ")[0]; - const info = await requestInfo(url, Visualization); - if (info && defaultVisualizationNames.includes(info.VisualizationName)) { + const visualizations = allVisualizations[problem] ?? []; + for (const v of visualizations) { + const visualization = v.split(" ")[0]; + const info = allInfo[visualization]; + const visName = info?.visualizationName || info?.VisualizationName; + if (visName && defaultVisualizationNames.includes(visName)) { map.set(problem, v); } } } - return map; - } + + setDefaultVisualizationMap(map); + })(); +}, [url, problemInfoMap, problemType]); return [defaultVisualizationMap, setDefaultVisualizationMap]; } @@ -50,11 +56,14 @@ function useVisualizationOptions(url, problemName, problemType) { useEffect(() => { (async () => { - setVisualizationOptions( - problemName && problemType ? (await requestVisualizations(url, problemName, problemType)) ?? [] : [] - ); + if (!problemName || !problemType) { + setVisualizationOptions([]); + return; +} +const allVisualizations = (await requestAllVisualizations(url, problemType)) ?? {}; +setVisualizationOptions(allVisualizations[problemName] ?? []); })(); - }, [problemName, problemType]); + }, [url, problemName, problemType]); return [VisualizationOptions, setVisualizationOptions]; } @@ -96,30 +105,30 @@ function useChosenVisualization(problemName, defaultVisualizationMap) { } -function useVisualizationNameMap(url, problemNameMap) { +function useVisualizationNameMap(url, problemNameMap, problemType) { const [VisualizationNameMap, setVisualizationNameMap] = useState(new Map()); - useEffect(() => { - const problems = Array.from(problemNameMap.keys()); - requestVisualizationNameMap(url, problems).then((VisualizationMap) => { - setVisualizationNameMap(VisualizationMap); - }); - }, [problemNameMap]); - - //The following the functions are used to set the Visualization names - async function requestVisualizationNameMap(url, problems) { +useEffect(() => { + const problems = Array.from(problemNameMap.keys()); + + (async () => { + const allVisualizations = (await requestAllVisualizations(url, problemType)) ?? {}; + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; let map = new Map(); for (const problem of problems) { - const Visualizations = (await requestVisualizations(url, problem)) ?? []; - for (const Visualization of Visualizations) { - const info = await requestInfo(url, Visualization); - if (info) { - map.set(Visualization, info.visualizationName); - } + const visualizations = allVisualizations[problem] ?? []; + for (const visualization of visualizations) { + const info = allInfo[visualization]; + map.set( + visualization, + info?.visualizationName || info?.VisualizationName || visualization + ); } } - return map; - } + setVisualizationNameMap(map); + })(); +}, [url, problemNameMap, problemType]); + return [VisualizationNameMap, setVisualizationNameMap]; } From 7b11124ae3892b732ea5e16df29c43ad8e5879cc Mon Sep 17 00:00:00 2001 From: Pratham Khanal Date: Thu, 16 Apr 2026 02:28:54 -0500 Subject: [PATCH 5/8] Update Reducer.js Refactor Reducer.js to use batched info endpoint for reduction visualization and improve dependency handling --- components/hooks/ProblemProvider/Reducer.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/components/hooks/ProblemProvider/Reducer.js b/components/hooks/ProblemProvider/Reducer.js index 863e560..7213a76 100644 --- a/components/hooks/ProblemProvider/Reducer.js +++ b/components/hooks/ProblemProvider/Reducer.js @@ -1,5 +1,5 @@ import { useGenericInfo } from "../ProblemProvider"; -import { requestReductionOptions, requestReductionInfo, requestReductions, requestReducedInstanceFromPath, requestInfo } from "../../redux"; +import { requestReductionOptions, requestReductionInfo, requestReductions, requestReducedInstanceFromPath, requestAllInfo } from "../../redux"; import React, { useEffect, useState, useRef } from "react"; // For initial startup defaults @@ -26,9 +26,10 @@ export function useReducer(url, problemName, problemType, problemInstance) { state.chosenReductionType ); [state.reductionVisualization, state.setReductionVisualization] = useReductionVisualization( - url, - state.chosenReduceTo - ); + url, + state.chosenReduceTo, + problemType +); return state; } @@ -66,7 +67,7 @@ function useReducedInstance(url, problemInstance, chosenReduceTo, chosenReductio return [reducedInstance, setReducedInstance]; } -function useReductionVisualization(url, chosenReduceTo) { +function useReductionVisualization(url, chosenReduceTo, problemType) { const [reductionVisualization, setReductionVisualization] = useState(""); useEffect(() => { @@ -76,10 +77,11 @@ function useReductionVisualization(url, chosenReduceTo) { } (async () => { - const info = await requestInfo(url, chosenReduceTo); - setReductionVisualization(info?.defaultVisualization.visualizationType ?? ""); + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; + const info = allInfo[chosenReduceTo]; + setReductionVisualization(info?.defaultVisualization?.visualizationType ?? ""); })(); - }, [url, chosenReduceTo]); + }, [url, chosenReduceTo, problemType]); return [reductionVisualization, setReductionVisualization]; } From 25236e710325896d49cfb1bda37f7b8f02ddf8a2 Mon Sep 17 00:00:00 2001 From: Pratham Khanal Date: Thu, 16 Apr 2026 02:48:46 -0500 Subject: [PATCH 6/8] Update Problem.js Refactor Problem.js to load problems and problem metadata from batch API endpoints --- components/hooks/ProblemProvider/Problem.js | 46 ++++++++++----------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/components/hooks/ProblemProvider/Problem.js b/components/hooks/ProblemProvider/Problem.js index 3b8a82a..32339d7 100644 --- a/components/hooks/ProblemProvider/Problem.js +++ b/components/hooks/ProblemProvider/Problem.js @@ -1,4 +1,4 @@ -import { requestProblems, requestInfo } from "../../redux"; +import { requestAllProblems, requestAllInfo } from "../../redux"; import React, { useEffect, useState } from "react"; // For initial startup defaults @@ -6,48 +6,44 @@ const DEFAULT_PROBLEM_NAME = "SAT3"; export function useProblem(url) { const state = {}; - [state.problemInfoMap] = useProblemInfoMap(url); - [state.problemNameMap] = useProblemNameMap(state.problemInfoMap); [state.problemType, state.setProblemType] = useState("NPC"); + [state.problemInfoMap] = useProblemInfoMap(url, state.problemType); + [state.problemNameMap] = useProblemNameMap(state.problemInfoMap); [state.problemName, state.setProblemName] = useProblemName(state.problemNameMap); - [state.problemInstance, state.setProblemInstance] = useState("{{1,2,3},{1,2},GENERIC}"); // Careful about changing this value, the application boot up sequence is dependent on having a default value. + [state.problemInstance, state.setProblemInstance] = useState("{{1,2,3},{1,2},GENERIC}"); return state; } -export function useProblemInfo(url, problemName) { +export function useProblemInfo(url, problemName, problemType = "NPC") { const [problemInfo, setProblemInfo] = useState({}); useEffect(() => { if(!problemName) return; (async () => { - setProblemInfo(problemName ? (await requestInfo(url, problemName)) ?? {} : {}); + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; + setProblemInfo(allInfo[problemName] ?? {}); })(); - }, [problemName]); + }, [url, problemName, problemType]); return problemInfo; // There should be no reason to set the problem information } -function useProblemInfoMap(url) { +function useProblemInfoMap(url, problemType) { const [problemInfoMap, setProblemInfoMap] = useState(new Map()); - useEffect(() => { (async () => { - const problems = (await requestProblems(url)) ?? []; - setProblemInfoMap(await requestProblemInfoMap(url, problems)); - })(); - }, []); - - async function requestProblemInfoMap(url, problems) { - let map = new Map(); - for (const problem of problems) { - const info = await requestInfo(url, problem); - if (info) { - map.set(problem, info); + const problems = (await requestAllProblems(url, problemType)) ?? []; + const allInfo = (await requestAllInfo(url, problemType)) ?? {}; + let map = new Map(); + for (const problem of problems) { + const info = allInfo[problem]; + if (info) { + map.set(problem, info); + } } - } - return map; - } - + setProblemInfoMap(map); + })(); + }, [url, problemType]); return [problemInfoMap, setProblemInfoMap]; } @@ -73,7 +69,7 @@ function useProblemNameMap(problemInfoMap) { const [problemNameMap, setProblemNameMap] = useState(new Map()); useEffect(() => { - setProblemNameMap(new Map([...problemInfoMap].map(([name, info]) => [name, info.problemName]))); + setProblemNameMap(new Map([...problemInfoMap].map(([name, info]) => [name, info?.problemName || name]))); }, [problemInfoMap]); return [problemNameMap, setProblemNameMap]; From d138a6074b45e16f9bb64ce74bae232dd7553c38 Mon Sep 17 00:00:00 2001 From: Pratham Khanal Date: Wed, 6 May 2026 03:13:49 -0500 Subject: [PATCH 7/8] Update index.js Fixed visualization and reduction API requests by including solver parameters in frontend visualization calls. --- components/redux/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/redux/index.js b/components/redux/index.js index 75eed5f..76aedc2 100644 --- a/components/redux/index.js +++ b/components/redux/index.js @@ -306,9 +306,9 @@ export async function requestReductionOptions(url, problem, problemType = "NPC") * @returns the graph visualization of the reduced problem instance. * @returns `undefined` on failure and logs the error. */ -export async function requestReductionVisualization(url, reduction, solution, instance) { +export async function requestReductionVisualization(url, reduction, solver, instance) { return await fetchPostJson( - `${url}ProblemProvider/visualizeReduction?reduction=${reduction}&solution=${solution}`, + `${url}ProblemProvider/visualizeReduction?reduction=${reduction}&solver=${solver}`, instance, () => `${reduction} VISUALIZE REQUEST FAILED` ); @@ -505,9 +505,9 @@ export async function requestVerifiedInstance(url, problem, verifier, instance, * @returns the graph visualization of the problem instance. * @returns `undefined` on failure and logs the error. */ -export async function requestVisualization(url, visualization, instance) { +export async function requestVisualization(url, visualization, solver, instance) { return await fetchPostJson( - `${url}ProblemProvider/visualize?visualization=${visualization}`, + `${url}ProblemProvider/visualize?visualization=${visualization}&solver=${solver}`, instance, () => `${visualization} VISUALIZE REQUEST FAILED` ); From 28979e0ce259d9cf0cfa1cecc27708eb973c6fc6 Mon Sep 17 00:00:00 2001 From: Pratham Khanal Date: Wed, 6 May 2026 03:19:37 -0500 Subject: [PATCH 8/8] Update VisualizeRowReact.js Fixed visualization and reduction rendering issues by passing solver parameters into frontend visualization API requests. --- components/pageblocks/VisualizeRowReact.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/components/pageblocks/VisualizeRowReact.js b/components/pageblocks/VisualizeRowReact.js index d6f66ec..3d34a17 100644 --- a/components/pageblocks/VisualizeRowReact.js +++ b/components/pageblocks/VisualizeRowReact.js @@ -158,11 +158,11 @@ export default function VisualizeRowReact({ const fetch = async () => { try { const data = await requestReductionVisualization( - url, - chosenReductionType, - solution, - problemInstance - ); + url, + chosenReductionType, + chosenSolver, + problemInstance +); setProblemReductionData(data ?? []); } catch (err) { console.error("Failed to load reduction visualization:", err); @@ -170,7 +170,7 @@ export default function VisualizeRowReact({ }; fetch(); - }, [showReduction, chosenReduceTo, problemInstance, solution]); + }, [showReduction, chosenReduceTo, chosenReductionType, problemInstance, chosenSolver]); // Fetch main visualization data @@ -182,10 +182,11 @@ export default function VisualizeRowReact({ const fetch = async () => { try { const data = await requestVisualization( - url, - chosenVisualization, - problemInstance, - ); + url, + chosenVisualization, + chosenSolver, + problemInstance +); if (!alive) return; @@ -214,6 +215,7 @@ export default function VisualizeRowReact({ }, [ instanceReady, chosenVisualization, + chosenSolver, problemInstance, showReduction, ]);