Skip to content

Commit

Permalink
HSIC control UI (whatsopt#188)
Browse files Browse the repository at this point in the history
* Add react slider widget library

* Implement HSIC control UI

* Handle HSIC control parameters in front/back-end

* Throttle HSIC service calls

* Linting

* Fix HSIC sensitivity analyser test
  • Loading branch information
relf authored Sep 29, 2023
1 parent 74eba77 commit ee55e2d
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 22 deletions.
10 changes: 10 additions & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
@import "react-bootstrap-typeahead/Typeahead";
@import "react-bootstrap-typeahead/Typeahead.bs5";
@import "diff2html/bundles/css/diff2html.min";
@import 'rc-slider/assets';

html {
height: 100%;
Expand Down Expand Up @@ -140,4 +141,13 @@ div.field_with_errors {
.hidden_list {
padding: 0;
list-style-type: none;
}

/* HSIC controls*/

.hsic-control-slider {
width: 300px;
height: 20px;
margin-bottom: 50px;
margin-left: 50px;
}
19 changes: 16 additions & 3 deletions app/controllers/api/v1/sensitivity_analyses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,23 @@ def _get_sensitivity_analysis_infos(ope)
end
when Operation::CAT_DOE
analyser = WhatsOpt::HsicSensitivityAnalyser.new(ope)
status, sa, err = analyser.get_hsic_sensitivity
return { statusOk: status, sensitivity: sa, error: err }
thresholding = case params[:thresholding]
when "Zero_th"
WhatsOpt::Services::HsicThresholding::ZERO
when "Cond_th"
WhatsOpt::Services::HsicThresholding::COND
when "Ind_th"
WhatsOpt::Services::HsicThresholding::IND
else
err_msg = "Unknown thresholding type: should be [Zero|Cond|Ind]_th, got #{params[:thresholding]}"
Rails.logger.error "Unknown thresholding type: should be [Zero|Cond|Ind]_th, got #{params[:thresholding]}"
end
quantile = params[:quantile].to_f
g_threshold = params[:g_threshold].to_f
status, sa, err_msg = analyser.get_hsic_sensitivity(thresholding, quantile, g_threshold)
return { statusOk: status, sensitivity: sa, error: err_msg }
end
{ statusOk: false, sensitivity: sa,
{ statusOk: false, sensitivity: {},
error: "Bad operation category: Should be #{Operation::CAT_SENSITIVITY} or #{Operation::CAT_DOE} (got #{ope.category})" }
end
end
75 changes: 75 additions & 0 deletions app/javascript/plotter/components/HsicControls.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
import Slider from 'rc-slider';

const ZEROTH = 'Zero_th';
const CONDTH = 'Cond_th';
const INDTH = 'Ind_th';

const quantileMarks = {
0: '0', 25: '0.25', 50: '0.5', 75: '0.75', 100: '1',
};
const gThresholdMarks = {
0: '0', 25: '0.25', 50: '0.5', 75: '0.75', 100: '1',
};

class HsicControls extends React.PureComponent {
render() {
const {
thresholding, quantile, gThreshold, onThresholdingChange,
onQuantileChange, onGThresholdChange,
} = this.props;

return (
<div>
<div className="col hsic-control-slider">
<div className="dropdown">
<select
className="form-control"
id="type"
defaultValue={thresholding}
onChange={onThresholdingChange}
>
<option value={ZEROTH}>{ZEROTH}</option>
<option value={CONDTH}>{CONDTH}</option>
<option value={INDTH}>{ INDTH}</option>
</select>
</div>
</div>
<div className="col hsic-control-slider">
Quantile :
{' '}
{ quantile }
<Slider
marks={quantileMarks}
value={quantile * 100}
onChange={(val) => onQuantileChange(val / 100.0)}
afterChange={(val) => onQuantileChange(val / 100.0)}
/>
</div>
<div className="col hsic-control-slider">
Cstr Threshold:
{' '}
{ gThreshold }
<Slider
marks={gThresholdMarks}
value={gThreshold * 100}
onChange={(val) => onGThresholdChange(val / 100.0)}
afterChange={(val) => onGThresholdChange(val / 100.0)}
/>
</div>
</div>
);
}
}

HsicControls.propTypes = {
thresholding: PropTypes.string.isRequired,
quantile: PropTypes.number.isRequired,
gThreshold: PropTypes.number.isRequired,
onThresholdingChange: PropTypes.func.isRequired,
onQuantileChange: PropTypes.func.isRequired,
onGThresholdChange: PropTypes.func.isRequired,
};

export default HsicControls;
2 changes: 1 addition & 1 deletion app/javascript/plotter/components/HsicScatterPlot.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class HsicScatterPlot extends React.PureComponent {
};

const tracePvperm = {
name: 'p-value by permutation',
name: 'p-value',
x: parameterNames.map((_, i) => i + 1.1),
y: pvperm,
type: 'scatter',
Expand Down
95 changes: 82 additions & 13 deletions app/javascript/plotter/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,88 @@ import AnalysisDatabase from '../utils/AnalysisDatabase';
import * as caseUtils from '../utils/cases';
import DistributionHistogram from './components/DistributionHistogram';
import HsicScatterPlot from './components/HsicScatterPlot';
import HsicControls from './components/HsicControls';

const PLOTS_TAB = 'plots';
const VARIABLES_TAB = 'variables';
const METAMODEL_TAB = 'metamodel';

const ZEROTH = 'Zero_th';

let throttleTimer;

const throttle = (callback, time) => {
if (throttleTimer) return;
throttleTimer = true;
setTimeout(() => {
callback();
throttleTimer = false;
}, time);
};

class PlotPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
sensitivity: null,
thresholding: ZEROTH,
quantile: 0.2,
gThreshold: 0.0,
};

this.handleThresholdingChange = this.handleThresholdingChange.bind(this);
this.handleQuantileChange = this.handleQuantileChange.bind(this);
this.handleGThresholdChange = this.handleGThresholdChange.bind(this);
this.updateSensitivityAnalysis = this.updateSensitivityAnalysis.bind(this);
}

componentDidMount() {
const { db } = this.props;
console.log('component did mount');
const { thresholding, quantile, gThreshold } = this.state;
const obj = db.getObjective();
if (obj) {
const { api, opeId } = this.props;
console.log('OCUCOU');
this.updateSensitivityAnalysis(thresholding, quantile, gThreshold);
}
}

shouldComponentUpdate(nextProps /* , nextState */) {
return nextProps.active;
}

handleThresholdingChange(event) {
const { quantile, gThreshold } = this.state;
const { value } = event.target;
this.setState({ thresholding: value });
this.updateSensitivityAnalysis(value, quantile, gThreshold);
}

handleQuantileChange(value) {
const { thresholding, gThreshold } = this.state;
this.setState({ quantile: value });
this.updateSensitivityAnalysis(thresholding, value, gThreshold);
}

handleGThresholdChange(value) {
const { thresholding, quantile } = this.state;
this.setState({ gThreshold: value });
this.updateSensitivityAnalysis(thresholding, quantile, value);
}

updateSensitivityAnalysis(thresholding, quantile, gThreshold) {
const { api, opeId } = this.props;

throttle(() => {
api.analyseSensitivity(
opeId,
thresholding,
quantile,
gThreshold,
(response) => {
console.log(response.data);
this.setState({ ...response.data });
},
);
}
}

shouldComponentUpdate(nextProps /* , nextState */) {
return nextProps.active;
}, 500); // trigger one call each 500ms at most
}

render() {
Expand Down Expand Up @@ -127,12 +177,31 @@ class PlotPanel extends React.Component {
}
let plothsic;
if (sensitivity) {
const { thresholding, quantile, gThreshold } = this.state;

plothsic = (
<HsicScatterPlot
hsicData={sensitivity}
title={title}
width={600}
/>
<div>
<div className="row align-items-center">
<div style={{ minWidth: 600 }} className="col-md-6">
<HsicScatterPlot
hsicData={sensitivity}
title={title}
width={600}
/>
</div>
<div className="col-md-4">
<HsicControls
thresholding={thresholding}
quantile={quantile}
gThreshold={gThreshold}
onThresholdingChange={this.handleThresholdingChange}
onQuantileChange={this.handleQuantileChange}
onGThresholdChange={this.handleGThresholdChange}
/>
</div>
</div>

</div>
);
}

Expand Down
7 changes: 5 additions & 2 deletions app/javascript/utils/WhatsOptApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ class WhatsOptApi {
.catch((error) => console.log(error));
}

analyseSensitivity(opeId, callback) {
const path = `/operations/${opeId}/sensitivity_analysis`;
analyseSensitivity(opeId, thresholding, quantile, gThreshold, callback) {
let path = `/operations/${opeId}/sensitivity_analysis?`;
path += `thresholding=${thresholding}`;
path += `&quantile=${quantile}`;
path += `&g_threshold=${gThreshold}`;
axios.get(this.apiUrl(path))
.then(callback)
.catch((error) => console.log(error));
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"pnp-webpack-plugin": "1",
"prop-types": "^15.7.2",
"qs": "^6.9.4",
"rc-slider": "^10.3.0",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.0.0",
"react-bootstrap": "^2.1.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class Api::V1::SensitivityAnalysisControllerTest < ActionDispatch::IntegrationTe

test "should return hsic sensitivity analysis" do
@ope = operations(:doe_hsic)
get api_v1_operation_sensitivity_analysis_url(@ope), as: :json, headers: @auth_headers
get api_v1_operation_sensitivity_analysis_url(@ope,
params: {thresholding: "Zero_th", quantile: 0.2, g_threshold: 0.0}), as: :json, headers: @auth_headers
assert_response :success
res = JSON.parse(response.body)["sensitivity"]

Expand Down
28 changes: 26 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,13 @@
core-js-pure "^3.30.2"
regenerator-runtime "^0.14.0"

"@babel/runtime@^7.10.1", "@babel/runtime@^7.18.3":
version "7.23.1"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d"
integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==
dependencies:
regenerator-runtime "^0.14.0"

"@babel/runtime@^7.12.1", "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.20.1", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.22.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682"
Expand Down Expand Up @@ -2748,7 +2755,7 @@ clamp@^1.0.1:
resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634"
integrity sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==

classnames@^2.2.0, classnames@^2.3.1, classnames@^2.3.2:
classnames@^2.2.0, classnames@^2.2.5, classnames@^2.3.1, classnames@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
Expand Down Expand Up @@ -7023,6 +7030,23 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"

rc-slider@^10.3.0:
version "10.3.0"
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.3.0.tgz#f5012bfc166b93cf79c5ccdd5f5bb560ab3ace57"
integrity sha512-kt8ehfvPLoZFYJS3Caaf3l+9OF8JUyexC84qy878vf9rOy9IulO/PEdha8E90i8mnj4mtJMSxojk1fJGkYWjpQ==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.5"
rc-util "^5.27.0"

rc-util@^5.27.0:
version "5.37.0"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.37.0.tgz#6df9a55cb469b41b6995530a45b5f3dd3219a4ea"
integrity sha512-cPMV8DzaHI1KDaS7XPRXAf4J7mtBqjvjikLpQieaeOO7+cEbqY2j7Kso/T0R0OiEZTNcLS/8Zl9YrlXiO9UbjQ==
dependencies:
"@babel/runtime" "^7.18.3"
react-is "^16.12.0"

rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
Expand Down Expand Up @@ -7128,7 +7152,7 @@ react-inspector@^6.0.1:
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-6.0.2.tgz#aa3028803550cb6dbd7344816d5c80bf39d07e9d"
integrity sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==

react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0:
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
Expand Down

0 comments on commit ee55e2d

Please sign in to comment.