Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d620e92
fork and typing of NSGA II algorithm
snabela Apr 20, 2024
7452efc
comments and async support
snabela Apr 20, 2024
de9abea
NSGA-II docs
snabela Apr 20, 2024
99c38fd
install geometry backend sdk
snabela Apr 20, 2024
9e17371
blueprint for model-optimizer
snabela Apr 20, 2024
f525aab
optimizer widget blueprint
snabela Apr 20, 2024
3d327f0
optimization prototype works
snabela Apr 20, 2024
3940e8e
dummy optimizer
mathieu-shapediver Apr 20, 2024
2fc9684
clean up result of optimization
snabela Apr 20, 2024
a9a5dc1
clean up result of optimization (type fixes)
snabela Apr 20, 2024
d8c57b3
set parameter values after optimization
snabela Apr 20, 2024
7fbf167
modifications
mathieu-shapediver Apr 20, 2024
ade1078
Merge branch 'task/optimizer' of https://github.com/shapediver/ShapeD…
mathieu-shapediver Apr 20, 2024
8c60912
optim results computed
mathieu-shapediver Apr 20, 2024
23a5db5
progress callback
snabela Apr 20, 2024
e5ab7a7
updates
mathieu-shapediver Apr 20, 2024
459aeab
Merge branch 'task/optimizer' of https://github.com/shapediver/ShapeD…
mathieu-shapediver Apr 20, 2024
8556783
cancellation support
snabela Apr 20, 2024
33f3a66
version update
snabela Apr 20, 2024
6b7b2ad
progress
mathieu-shapediver Apr 20, 2024
ba6969c
Merge branch 'task/optimizer' of https://github.com/shapediver/ShapeD…
mathieu-shapediver Apr 20, 2024
e0d92fa
cancel
snabela Apr 20, 2024
79832a1
Merge branch 'task/optimizer' of https://github.com/shapediver/AppBui…
snabela Apr 20, 2024
e104ac1
parallelize
snabela Apr 20, 2024
ebcb2b3
improved progress bar
snabela Apr 21, 2024
2b592f7
callback for generations
snabela Apr 21, 2024
814ae0e
weight sliders
mathieu-shapediver Apr 21, 2024
022f4a3
Merge branch 'task/optimizer' of https://github.com/shapediver/ShapeD…
mathieu-shapediver Apr 21, 2024
4d1355c
README update
snabela Apr 21, 2024
6a50272
Merge branch 'task/optimizer' of https://github.com/shapediver/AppBui…
snabela Apr 21, 2024
19cbc84
generation best update
mathieu-shapediver Apr 21, 2024
c361e07
Merge branch 'task/optimizer' of https://github.com/shapediver/ShapeD…
mathieu-shapediver Apr 21, 2024
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
8 changes: 5 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "shapediver-appbuilder",
"version": "0.3.0-beta.4",
"version": "0.3.0-optimizer",
"private": true,
"type": "module",
"homepage": "./",
"dependencies": {
"@emotion/react": "^11.11.4",
"@mantine/core": "^7.6.2",
"@mantine/hooks": "^7.6.2",
"@shapediver/sdk.geometry-api-sdk-v2": "^1.8.2",
"@shapediver/sdk.platform-api-sdk-v1": "^2.18.10",
"@shapediver/viewer": "2.12.7",
"@shapediver/viewer.utils.mime-type": "1.0.1",
Expand Down
25 changes: 25 additions & 0 deletions public/testoptimizer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"version": "1.0",
"sessions": [
{
"id": "Test optimizer",
"ticket": "2b94da591fa259fc612403a43f2b49c5e0ca3011692130e68c2eb2c6dac6168bbe2296ccb0a3a743bfa3387f7e1ac2d138ac9f7cf6502a5fb599ba92ff16c5c6f56015d2ae8e732c7eb2fb2522a2d6217ec67d7a2b5d84519e3db4cef104b44cf8363e686b8212-925db841bfe1e38401560841c4220c52",
"modelViewUrl": "https://sdr8euc1.eu-central-1.shapediver.com"
}
],
"appBuilderOverride": {
"version": "1.0",
"containers": [
{
"name": "right",
"widgets": [
{
"type": "optimizer",
"props": {
}
}
]
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import { AppBuilderSettingsContext } from "context/AppBuilderContext";
import { Paper, Title, Checkbox, Button, Switch, Slider, Text, Progress, Group} from "@mantine/core";
import { useSessionPropsParameter } from "hooks/shapediver/parameters/useSessionPropsParameter";
import { useOutputContent } from "hooks/shapediver/viewer/useOutputContent";
import { INSGA2ResultExt, IObjectiveOutputData, IShapeDiverModelOptimizer, OBJECTIVE_OUTPUT_NAME, ParameterValuesType, ShapeDiverModelOptimizerNsga2 } from "optimization/optimizer";
import AlertPage from "pages/misc/AlertPage";
import React, { useContext, useEffect, useState } from "react";
import { IAppBuilderWidgetPropsOptimizer } from "types/shapediver/appbuilder";
import { useParametersStateless } from "hooks/shapediver/parameters/useParametersStateless";
import { useParameterChanges } from "hooks/shapediver/parameters/useParameterChanges";
import { ShapeDiverResponseParameterType } from "@shapediver/sdk.geometry-api-sdk-v2";
import { INSGA2Props } from "optimization/nsga2";

interface Props extends IAppBuilderWidgetPropsOptimizer {
/**
* Default session id to use for optimization.
*/
sessionId: string
}

export default function AppBuilderOptimizerWidgetComponent(props: Props) {
const {
sessionId,
} = props;

// get information about the AppBuilder settings (session, etc.)
const { settings: appBuilderSettings } = useContext(AppBuilderSettingsContext);
const sessionDto = appBuilderSettings!.sessions.find(s => s.id === sessionId)!;

// get the objectives output
const { outputContent } = useOutputContent( sessionId, OBJECTIVE_OUTPUT_NAME );
const objectives = outputContent?.[0]?.data as IObjectiveOutputData | undefined;
if (!objectives) return <AlertPage title="Error">An output named {OBJECTIVE_OUTPUT_NAME} could not be found or provides no data.</AlertPage>;

const [solution, setSolution] = useState<ParameterValuesType>({});

// get parameter stores
const parameters = useParametersStateless(sessionId);
const customize = (values: ParameterValuesType) => {
Object.keys(values).forEach(id => {
const actions = parameters[id].actions;
if (actions.setUiValue(values[id])) {
actions.execute(false);
}
});
setSolution(values);
};

// get parameter changes
const parameterProps = useSessionPropsParameter(sessionId);
const parameterChanges = useParameterChanges(parameterProps);
useEffect(() => {
parameterChanges.forEach(c => c.accept());
}, [solution]);

// get default values for the parameters
const defaultValues: ParameterValuesType = {};
Object.keys(parameters).forEach(id => {
defaultValues[id] = parameters[id].definition.defval;
});

// define parameters to be optimized
const parameterIds = Object.keys(parameters).filter(id =>
parameters[id].definition.type === ShapeDiverResponseParameterType.FLOAT ||
parameters[id].definition.type === ShapeDiverResponseParameterType.INT ||
parameters[id].definition.type === ShapeDiverResponseParameterType.BOOL ||
parameters[id].definition.type === ShapeDiverResponseParameterType.ODD ||
parameters[id].definition.type === ShapeDiverResponseParameterType.EVEN
);

const [populationSize, setPopulationSize] = useState(10);
const [maxGenerations, setMaxGenerations] = useState(10);
const [checkboxValues, setCheckboxValues] = useState([true, true]);
const [isRunning, setIsRunning] = useState(false);
const [optimizer, setOptimizer] = useState<IShapeDiverModelOptimizer<INSGA2Props, INSGA2ResultExt<string>>|undefined>(undefined);
const [progress, setProgress] = useState(0);
const [result, setResult] = useState< INSGA2ResultExt<string>>();

const updateWeights = (index,value,custom) => {
const newWeights = [...weights];
newWeights[index].value = value;
setWeights(newWeights);
if (result&&custom) {
console.log(currentBest(result));
customize(result.parameterValues[currentBest(result)]);
}
};

const setInitialWeights = () => {
const newWeights= [];
{Object.entries(objectives).map(([key, value], index) => (
newWeights.push({key: key, value: 0.5})
))}
return newWeights;
};

const [weights, setWeights] = useState(setInitialWeights);

const individualScore= (ind) => {

let sum=0;
const weightValues = weights.sort((a, b) => a.key.localeCompare(b.key)).map(item => item.value);

for (let i = 0; i < ind.objectives.length; i++) {
sum += ind.objectives[i]*weightValues[i];
}

return sum;
}

const currentBest = (result: INSGA2ResultExt<string>) => {

let bestIndex = 0;
let best = individualScore(result.individuals[0]);
console.log("looking for curr best: ");
console.log(result.individuals);
for (let i = 0; i < result.individuals.length; i++) {
let curr = individualScore(result.individuals[i]);
console.log(curr);
if (curr < best) {
best=curr;
bestIndex=i;
}
}

return bestIndex;
};

const runOptimizer = async (values) => {
const optimizer = await ShapeDiverModelOptimizerNsga2.create({
sessionDto,
defaultParameterValues: defaultValues,
});
setOptimizer(optimizer);
console.log("Running optimizer with values:", values);
const currResult = await optimizer.optimize({
parameterIds,
optimizerProps: {
populationSize: populationSize,
maxGenerations: maxGenerations,
mutationRate: 0.5,
crossoverRate: 0.5,
progressCallback: (progress) => setProgress(progress*100),
},
generationCallback: (generation) => customize(generation.parameterValues[currentBest(generation)])
});
setIsRunning(false);
console.log(currResult);
setResult(currResult);
customize(currResult.parameterValues[currentBest(currResult)]);

};

const [isVisible, setIsVisible] = useState(false);

const handleToggle = (event) => {
setIsVisible(event.currentTarget.checked);
};

const handleCheckboxChange = (index) => {
const newValues = [...checkboxValues];
newValues[index] = !newValues[index];
setCheckboxValues(newValues);
};

const handleStartClick = () => {
setIsRunning(true);
runOptimizer(checkboxValues);
};

const handleEndClick = () => {
optimizer?.requestCancellation();
};

return <>

<Paper>
<Title order={2}>Goals to optimize</Title>
{Object.entries(objectives).map(([key, value], index) => (

<Group style={{ marginBottom: 20, alignItems: 'center' }}>
<Checkbox
defaultChecked
label={key}
onChange={() => handleCheckboxChange(index)}
key={index}
style={{ marginTop: 20 }}
/>

<Slider
min={0}
max={1}
step={0.1}
value={ weights[index].value }
onChange={(val) => updateWeights(index,val, false)}
onChangeEnd={(val) => updateWeights(index,val, true)}
marks={[
{ value: 0, label: "0" },
{ value: 1, label: "1" }
]}
style={{ flexGrow: 1 }}
/>
</Group>



))}

<Button onClick={isRunning ? handleEndClick : handleStartClick}
variant="filled"
color={isRunning ? "orange" : "blue"}
style={{ marginTop: 20 }}
>
{isRunning ? "Stop Optimization" : "Run Optimization"}
</Button>

{isRunning && (
<Progress value={progress} style={{ marginTop: 20 }} />
)}

</Paper>

<Switch
label="Avanced options"
checked={isVisible}
onChange={handleToggle}
/>

{isVisible && (
<Paper>
<Title order={2}>Optimization options</Title>
<Text>Population size</Text>
<Slider
label={populationSize}
min={1}
max={100}
value={populationSize}
onChange={setPopulationSize}
showLabelOnHover={false}
marks={[
{ value: 1, label: "1" },
{ value: 100, label: "100" }
]}
style={{ marginTop: 20 }}
/>

<Text>Maximum number of generations</Text>
<Slider
label={maxGenerations}
min={1}
max={100}
value={maxGenerations}
onChange={setMaxGenerations}
showLabelOnHover={false}
marks={[
{ value: 1, label: "1" },
{ value: 100, label: "100" }
]}
style={{ marginTop: 20, marginBottom: 20 }}
/>

</Paper>
)}
</>;

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from "react";
import { IAppBuilderWidget, isAccordionWidget, isImageWidget, isTextWidget } from "types/shapediver/appbuilder";
import { IAppBuilderWidget, isAccordionWidget, isImageWidget, isOptimizerWidget, isTextWidget } from "types/shapediver/appbuilder";
import AppBuilderTextWidgetComponent from "./AppBuilderTextWidgetComponent";
import AppBuilderImageWidgetComponent from "./AppBuilderImageWidgetComponent";
import AppBuilderAccordionWidgetComponent from "./AppBuilderAccordionWidgetComponent";
import AppBuilderOptimizerWidgetComponent from "./AppBuilderOptimizerWidgetComponent";

interface Props {
/**
Expand All @@ -28,6 +29,8 @@ export default function AppBuilderWidgetsComponent({ sessionId, widgets }: Props
return <AppBuilderImageWidgetComponent key={i} sessionId={sessionId} {...w.props} />;
else if (isAccordionWidget(w))
return <AppBuilderAccordionWidgetComponent key={i} sessionId={sessionId} {...w.props} />;
else if (isOptimizerWidget(w))
return <AppBuilderOptimizerWidgetComponent key={i} sessionId={sessionId} {...w.props} />;
else
return null;
})}
Expand Down
10 changes: 10 additions & 0 deletions src/context/AppBuilderContext.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createContext } from "react";
import { IAppBuilderSettings } from "types/shapediver/appbuilder";

/** Types of containers */
export type AppBuilderContainerOrientationType = "unspecified" | "horizontal" | "vertical";
Expand All @@ -24,4 +25,13 @@ export interface IAppBuilderTemplateContext {
/** Information about a template. */
export const AppBuilderTemplateContext = createContext<IAppBuilderTemplateContext>({
name: "unspecified"
});

export interface IAppBuilderSettingsContext {
settings?: IAppBuilderSettings
}

/** Information about AppBuilder settings. */
export const AppBuilderSettingsContext = createContext<IAppBuilderSettingsContext>({
settings: undefined
});
Loading