Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Model Anomaly + Subroutine Framework #14

Merged
merged 27 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
448b362
Add reusable Feature Model problem mask
Elscrux Mar 20, 2023
91ec3f5
Add feature model anomaly problem mask
Elscrux Mar 20, 2023
1e93efa
Remove demo algorithm
Elscrux Apr 10, 2023
a97134f
Implement subroutine framework
Elscrux Apr 10, 2023
3b9b072
Make generic feature model a generic text input mask
Elscrux Apr 10, 2023
1575b9d
Use TextInputMask for MaxCut
Elscrux Apr 10, 2023
d9b0ff4
Use TextInputMask for Feature Model Anomalies
Elscrux Apr 10, 2023
2ee05e6
Button formatting
Elscrux Apr 10, 2023
2348327
Remove old imports
Elscrux Apr 10, 2023
208f1d1
Add subroutine description
Elscrux Apr 11, 2023
910e1b9
Fix resizing multi select
Elscrux Apr 11, 2023
052a018
Make solve request mandatory
Elscrux Apr 21, 2023
dc640be
Use explicit map type
Elscrux Apr 21, 2023
4c2a12c
Add comment for requestedSolverId
Elscrux Apr 21, 2023
1c475b9
Explicitly type problemInput/requestContent and create SubSolveReques…
Elscrux Apr 21, 2023
2073f89
Add key to box
Elscrux Apr 21, 2023
03bacd6
Add description for feature model anomaly problem card
Elscrux Apr 21, 2023
ee6745a
Explicitly type anomalies
Elscrux Apr 21, 2023
aca6044
Reformat problem url
Elscrux Apr 21, 2023
4e98644
Add description for ProgressHandlerProps
Elscrux Apr 21, 2023
680ceb0
Add description for requestedSubSolveRequests
Elscrux Apr 21, 2023
b98e879
Rename problemUrl to problemUrlFragment
Elscrux May 1, 2023
3a74a7a
Rename SubSolveRequest to SolverChoice
Elscrux May 1, 2023
3c7c52e
Make requestContent mandatory
Elscrux May 1, 2023
eb627e6
Remove old comment
Elscrux May 3, 2023
32076ed
Use component the usual way
Elscrux May 3, 2023
214a561
Revert "Use explicit map type"
Elscrux May 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.4.0",
"react-simple-code-editor": "^0.13.0"
"react-simple-code-editor": "^0.13.0",
"react-multi-select-component": "^4.3.4"
},
"devDependencies": {
"@types/node": "18.7.14",
Expand Down
38 changes: 27 additions & 11 deletions src/api/ToolboxAPI.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { SubRoutineDefinition } from "../components/solvers/SubRoutineDefinition";
import { ProblemSolver } from "../components/solvers/ProblemSolver";
import { Solution } from "../components/solvers/Solution";
import {ProblemSolver} from "../components/solvers/ProblemSolver";
import { SolutionStatus } from "../components/solvers/SolutionStatus";
import { SolveRequest } from "../components/solvers/SolveRequest";

export async function postProblem(problemType: string, content: any, solver: ProblemSolver | undefined): Promise<Solution> {
export async function postProblem(problemUrl: string, solveRequest?: SolveRequest): Promise<Solution> {
return fetch(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/solve/${problemType}`,
`${process.env.NEXT_PUBLIC_API_BASE_URL}/solve/${problemUrl}`,
{
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
requestContent: content,
requestedSolverId: solver?.id
})
body: JSON.stringify(solveRequest)
})
.then(response => response.json())
.then(json => json as Solution)
Expand All @@ -31,9 +30,9 @@ export async function postProblem(problemType: string, content: any, solver: Pro
});
}

export async function fetchSolvers(problemType: string): Promise<ProblemSolver[]> {
export async function fetchSolvers(problemUrl: string): Promise<ProblemSolver[]> {
return fetch(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/solvers/${problemType}`,
`${process.env.NEXT_PUBLIC_API_BASE_URL}/solvers/${problemUrl}`,
{
method: "GET",
headers: {
Expand All @@ -44,7 +43,24 @@ export async function fetchSolvers(problemType: string): Promise<ProblemSolver[]
.then(json => json as ProblemSolver[])
.catch(reason => {
console.error(reason)
alert(`Could not retrieve solvers of type ${problemType}.`)
alert(`Could not retrieve solvers of type ${problemUrl}.`)
return []
});
}
}

export async function fetchSubRoutines(problemUrl: string, solverId: string): Promise<SubRoutineDefinition[]> {
return fetch(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/sub-routines/${problemUrl}?${new URLSearchParams({ id: solverId })}`,
{
method: "GET",
headers: {
'Content-Type': 'application/json'
},
})
.then(response => response.json())
.catch(reason => {
console.error(reason)
alert(`Could not retrieve subroutines of solver ${solverId}.`)
return []
});
}
10 changes: 5 additions & 5 deletions src/components/landing-page/ProblemChooser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ export const ProblemChooser = (props: GridProps) => (
</GridItem>
<GridItem>
<ProblemCard
href="solve/#"
href="solve/FeatureModelAnomaly"
new={false}
qubits={2}
speedup="superpolynomial"
problemName="DemoAlgorithm"
description="I'm running out of ideas for descriptive texts, please help me!"
qubits={12}
speedup="polynomial"
problemName="Feature Model Anomaly"
description="For a given undirected, weighted graph, this algorithm finds a cut that is a maximum in some way or another."
/>
</GridItem>
</Grid>
Expand Down
68 changes: 43 additions & 25 deletions src/components/solvers/ProgressHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,68 @@
import {HStack} from "@chakra-ui/react";
import React, {useState} from "react";
import {GoButton} from "./buttons/GoButton";
import {postProblem} from "../../api/ToolboxAPI";
import {SolutionView} from "./SolutionView";
import {Container} from "../Container";
import {Solution} from "./Solution";
import {SolverPicker} from "./SolverPicker";
import {ProblemSolver} from "./ProblemSolver";
import { Box, Center, VStack } from "@chakra-ui/react";
import React, { useState } from "react";
import { GoButton } from "./buttons/GoButton";
import { postProblem } from "../../api/ToolboxAPI";
import { SolutionView } from "./SolutionView";
import { Container } from "../Container";
import { Solution } from "./Solution";
import { SolveRequest } from "./SolveRequest";
import { SolverPicker } from "./SolverPicker";

export interface ProgressHandlerProps {
problemType: string;
explicitSolvers?: string[];
problemUrl: string;
problemInput: any;
}

export const ProgressHandler = (props: ProgressHandlerProps) => {
const [wasClicked, setClicked] = useState<boolean>(false);
const [finished, setFinished] = useState<boolean>(false);
const [solution, setSolution] = useState<Solution>();
const [solver, setSolver] = useState<ProblemSolver | undefined>(undefined);
const [solutions, setSolutions] = useState<Solution[]>();
const [solveRequest, setSolveRequest] = useState<SolveRequest>({
requestedSubSolveRequests: {}
});

async function startSolving() {
setClicked(true);
setFinished(false);

postProblem(props.problemType, props.problemInput, solver)
.then(solution => {
setSolution(solution);
setFinished(true);
setSolver(undefined);
});
solveRequest.requestContent = props.problemInput;
if (props.explicitSolvers == undefined) {
postProblem(props.problemUrl, solveRequest)
.then(solution => {
setSolutions([solution]);
setFinished(true);
});
} else {
Promise.all(props.explicitSolvers.map(s => postProblem(s, solveRequest)))
.then(solutions => {
setSolutions(solutions);
setFinished(true);
});
}
}

return (
<Container>
{!wasClicked || finished
? (
<HStack>
<SolverPicker problemType={props.problemType} setSolver={solver => setSolver(solver)}/>
<GoButton clicked={startSolving}/>
</HStack>
<VStack>
<Center>
<GoButton clicked={startSolving}/>
</Center>
<SolverPicker problemUrl={props.problemUrl}
setSolveRequest={solveRequest => setSolveRequest(solveRequest)}/>
</VStack>
)

: null}

{wasClicked
? <SolutionView solution={solution} finished={finished}/>
? solutions?.map(s => (
<Box w="50pc" m={2} borderWidth="1px" borderRadius="lg" overflow="hidden" p={2}>
<SolutionView key={s.id} solution={s} finished={finished}/>
</Box>)
)
: null}
</Container>
);
}
}
9 changes: 9 additions & 0 deletions src/components/solvers/SolveRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface SolveRequest {
requestContent?: any;
requestedSolverId?: string,
requestedSubSolveRequests: SolveMap
}

export type SolveMap = {
[key: string]: SolveRequest
}
88 changes: 61 additions & 27 deletions src/components/solvers/SolverPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,83 @@
import React, {useEffect, useState} from "react";
import {Select, Text, Tooltip} from "@chakra-ui/react";
import {fetchSolvers} from "../../api/ToolboxAPI";
import {ProblemSolver} from "./ProblemSolver";
import {Box, Container, HStack, Select, Text, Tooltip} from "@chakra-ui/react";
import React, { ChangeEvent, useEffect, useState } from "react";
import { fetchSolvers, fetchSubRoutines } from "../../api/ToolboxAPI";
import { SubRoutineDefinition } from "./SubRoutineDefinition";
import { ProblemSolver } from "./ProblemSolver";
import { SolveRequest } from "./SolveRequest";

export interface SolverPickerProps {
problemType: string;
setSolver?: (solver: ProblemSolver | undefined) => void;
problemUrl: string;
problemDescription?: string;
setSolveRequest: (subRoutines: SolveRequest) => void;
}

export const SolverPicker = (props: SolverPickerProps) => {
const [loadingSolvers, setLoadingSolvers] = useState<boolean>(true);
const [solvers, setSolvers] = useState<ProblemSolver[]>([]);
const [subRoutines, setSubRoutines] = useState<SubRoutineDefinition[] | undefined>(undefined);
const [solveRequest, setSolveRequest] = useState<SolveRequest>({
requestedSubSolveRequests: {}
});

useEffect(() => {
setSubRoutines(undefined);
setLoadingSolvers(true);
fetchSolvers(props.problemType)
.then(solvers => {
fetchSolvers(props.problemUrl)
.then((solvers: ProblemSolver[]) => {
setSolvers(solvers);
setLoadingSolvers(false);
})
}, [props.problemType]);
}, [props.problemUrl]);

function onSolverChanged(e: ChangeEvent<HTMLSelectElement>) {
if (e.target.selectedIndex == 0 || e.target.selectedIndex > solvers.length) {
setSubRoutines(undefined);
props.setSolveRequest?.(solveRequest)
} else {
let solver = solvers[e.target.selectedIndex - 1];
solveRequest.requestedSolverId = solver.id;
fetchSubRoutines(props.problemUrl, solver.id)
.then(subRoutines => setSubRoutines(subRoutines));
props.setSolveRequest?.(solveRequest)
}
}

const getSolvers = () => {
return (
<Select onChange={e =>
props.setSolver?.(e.target.selectedIndex == 0 || e.target.selectedIndex > solvers.length
? undefined
: solvers[e.target.selectedIndex - 1])
}>
<option>Automated Solver Selection</option>
<optgroup label="Use Specific Solvers">
{solvers.map((s: ProblemSolver) => (
<option key={s.id}>{s.name}</option>
))}
</optgroup>
</Select>
<Container>
<Text>{props.problemDescription}</Text>
<Select onChange={onSolverChanged}>
<option>Automated Solver Selection</option>
<optgroup label="Use Specific Solvers">
{solvers.map((s: ProblemSolver) => (
<option key={s.id}>{s.name}</option>
))}
</optgroup>
</Select>
</Container>
)
}

return (
<Tooltip label="Use this dropdown to select the meta solver strategy" color="white">
{loadingSolvers
? <Text>Loading solvers...</Text>
: getSolvers()
}
</Tooltip>
<Box borderWidth="1px" borderRadius="lg" overflow="hidden" p={2}>
<Tooltip label="Use this dropdown to select the meta solver strategy" color="white">
{loadingSolvers
? <Text>Loading solvers...</Text>
: getSolvers()
}
</Tooltip>

<HStack>
{subRoutines == undefined
? null
: subRoutines.map(def => <SolverPicker
key={def.type}
problemUrl={def.url}
problemDescription={def.description}
setSolveRequest={subSolveRequest => {
solveRequest.requestedSubSolveRequests[def.type] = subSolveRequest;
}}/>)}
</HStack>
</Box>
);
}
5 changes: 5 additions & 0 deletions src/components/solvers/SubRoutineDefinition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface SubRoutineDefinition {
type: string,
url: string,
description: string,
}
66 changes: 66 additions & 0 deletions src/components/solvers/TextInputMask.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Divider, Text, Textarea } from "@chakra-ui/react";
import Head from "next/head";
import React, { ChangeEvent, ReactElement, useState } from "react";
import { Container } from "../Container";
import { Main } from "../Main";
import { InputButtonPanel } from "./buttons/InputButtonPanel";
import { Help } from "./SAT/Help";
import { SolverTitle } from "./SolverTitle";

export interface TextInputMaskProperties {
title: string;
description: string;
textPlaceholder: string;
onTextChanged: (text: string) => void;
body?: ReactElement;
}

export const TextInputMask = (props: TextInputMaskProperties) => {
const [text, setText] = useState<string>("");
const [errorString, setErrorString] = useState("");

function onTextAreaChange(event: ChangeEvent<HTMLTextAreaElement>): void {
try {
setText(event.target.value);
props.onTextChanged(event.target.value);

setErrorString('');
} catch (e: any) {
setErrorString(e.message);
}
}

return (
<Container minHeight="100vh">
<Head>
<title>ProvideQ</title>
<meta name="description" content="Generated by create next app"/>
<link rel="icon" href="/favicon.ico"/>
{/* TODO: replace favicon */}
</Head>
<SolverTitle title={props.title}
text={props.description}/>
<Main mb="20vh">

<Textarea placeholder={props.textPlaceholder}
value={text}
minHeight="10rem"
isInvalid={errorString != ""}
onChange={onTextAreaChange}/>

<Text backgroundColor="tomato">{errorString}</Text>

<InputButtonPanel
helpBody={<Help/>}
problemString={text}
setProblemString={setText}
uploadString={str => str}
downloadString={str => str}/>

<Divider/>

{props.body}
</Main>
</Container>
);
};
Loading