Skip to content

Commit

Permalink
Added show optimization, creation of SEGOMOE
Browse files Browse the repository at this point in the history
  • Loading branch information
ColasPinson committed Jul 13, 2022
1 parent 6634d0b commit b50f8f1
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 108 deletions.
17 changes: 9 additions & 8 deletions app/controllers/api/v1/optimizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ def show
def create
@optim = Optimization.new(optim_params)
authorize @optim
@optim.save!
@optim.create_optimizer
@optim.set_owner(current_user)
json_response @optim, :created
rescue Optimization::OptimizationError => e
skip_authorization
Rails.logger.error e
json_response({ message: e.message }, :bad_request)
if @optim.save
@optim.create_optimizer
@optim.set_owner(current_user)
json_response @optim, :created
else
skip_authorization
Rails.logger.error @optim.errors
json_response({ message: "Validation failed", errors: @optim.errors }, :bad_request)
end
end

# PATCH /api/v1/optimizations/1
Expand Down
38 changes: 37 additions & 1 deletion app/controllers/optimizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,42 @@ def destroy_selected
redirect_to optimizations_url, notice: params[:optimization_request_ids].length > 1 ? "The #{params[:optimization_request_ids].length} optimizations were successfully deleted." : "The optimization was successfully deleted."
end

def show
end

def download
authorize Optimization.find(params[:optimization_id])
path = "#{Rails.root}/log/optimizations/optim_#{params[:optimization_id]}.log"
if File.exists?(path)
send_file(path)
else
redirect_to optimizations_url, notice: "There isn't a log file"
end
end

def new
@optimization = Optimization.new
authorize @optimization
end

def create
if params[:cancel_button]
skip_authorization
redirect_to optimizations_url, notice: "Optimization creation cancelled."
else
@optimization = Optimization.new(optimization_params)
@optimization.config["n_obj"] = optimization_params[:n_obj].to_i
@optimization.config["xlimits"] = @optimization.str_to_array(optimization_params[:xlimits])
@optimization.outputs["status"] = -1
authorize @optimization
if @optimization.save
@optimization.set_owner(current_user)
redirect_to optimizations_url, notice: "Optimization ##{@optimization.id} was successfully created."
else
render :new
end
end
end

private
def set_optimization
Expand All @@ -24,6 +60,6 @@ def set_optimization
end

def optimization_params
params.require(:optimization).permit(:name, :description)
params.require(:optimization).permit(:kind, :n_obj, :xlimits, :x, :y)
end
end
Empty file.
58 changes: 58 additions & 0 deletions app/javascript/optview/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable max-classes-per-file */
import React from 'react';
import Plot from 'react-plotly.js';
import PropTypes from 'prop-types';

class OptView extends React.PureComponent {
constructor(props) {
super(props);
const {
optim,
} = this.props;
console.log("Kenoobii");
}
render() {
return (
<Plot
data={[
{
x: Array.from(Array(this.props.optim.inputs['x'].length).keys()),
y: this.props.optim.inputs['x'].map( x => x[0] ),
type: 'scatter',
mode: 'markers lines',
name: 'x[0]',
marker: {color: 'orange'}
},
{
x: Array.from(Array(this.props.optim.inputs['x'].length).keys()),
y: this.props.optim.inputs['x'].map( x => x[1] ),
type: 'scatter',
mode: 'markers lines',
name: 'x[1]',
marker: {color: 'red'},
},
{
x: Array.from(Array(this.props.optim.inputs['y'].length).keys()),
y: this.props.optim.inputs['y'].map( y => y[0] ),
type: 'scatter',
mode: 'markers lines',
name: 'y',
marker: {color: 'green'},
}
]}
layout={{
width: 800,
height: 500,
title: 'Visual representation of the input points',
xaxis: {title: "point's number"},
yaxis: {title: "variable's values"}
}}
/>
);
}
}

OptView.propTypes = {
optim: PropTypes.object.isRequired,
};
export default OptView;
13 changes: 13 additions & 0 deletions app/javascript/packs/optview_application.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { useState} from 'react'
import ReactDOM from 'react-dom'
import OptView from 'optview';


document.addEventListener('DOMContentLoaded', () => {
const node = document.getElementById('optimization_plot')
const optimization_data = JSON.parse(node.getAttribute('optimization_data'))
ReactDOM.render(
<OptView optim={optimization_data}/>,
document.getElementById("optimization_plot"),
)
})
110 changes: 63 additions & 47 deletions app/models/optimization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,84 +28,52 @@ class Optimization < ApplicationRecord

scope :owned_by, ->(user) { with_role(:owner, user) }

class OptimizationError < Exception; end

after_initialize :check_optimization_config

def create_optimizer
unless new_record?
if self.kind == "SEGOMOE"
proxy.create_optimizer(Optimization::OPTIMIZER_KINDS[self.kind], self.xlimits, self.cstr_specs, self.options)
else
proxy.create_mixint_optimizer(Optimization::OPTIMIZER_KINDS[self.kind], self.xtypes, self.n_obj, self.cstr_specs, self.options)
end
end
rescue WhatsOpt::OptimizationProxyError => err
log_error("#{err}: #{err.message}") # do not fail in case of proxy error, let the client handle the error
end

def perform
self.update!(outputs: { status: RUNNING, x_suggested: nil, x_best: nil, y_best: nil })
self.proxy.tell(self.x, self.y)
res = self.proxy.ask(self.with_best)
outputs = { status: res.status, x_suggested: res.x_suggested }
outputs["x_best"] = res.x_best if self.with_best
outputs["y_best"] = res.y_best if self.with_best
self.update!(outputs: outputs)
rescue WhatsOpt::OptimizationProxyError, WhatsOpt::Services::OptimizerException => err
log_error("#{err}: #{err.message}") # asynchronous: just set error state and log the error
end

def xdim
0 if self.xlimits.blank?
Matrix[*self.xlimits]
end

def proxy
WhatsOpt::OptimizerProxy.new(id: self.id.to_s)
end
validate :check_optimization_config
validate :optimization_number_limit

def check_optimization_config
self.options = {} if self.options.blank?
self.kind = "SEGOMOE" if self.kind.blank?
self.n_obj = 1 if self.n_obj.blank?
unless self.kind == "SEGOMOE" || self.kind == "SEGMOOMOE"
raise_error("optimizer kind should be SEGOMOE or SEGMOOMOE, got '#{self.kind}'")
errors.add(:base, "optimizer kind should be SEGOMOE or SEGMOOMOE, got '#{self.kind}'")
end

if self.kind == "SEGOMOE"
if self.n_obj != 1
raise_error("SEGOMOE is mono-objective only, got '#{self.n_obj}'")
errors.add(:base, "SEGOMOE is mono-objective only, got '#{self.n_obj}'")
end

unless self.xlimits
raise_error("xlimits field should be present, got '#{self.xlimits}'")
errors.add(:base, "xlimits field should be present, got '#{self.xlimits}'")
return
end

begin
m = Matrix[*self.xlimits]
raise if (m.row_count < 1) || (m.column_count != 2)
rescue Exception
raise_error("xlimits should be a matrix (nx, 2), got '#{self.xlimits}'")
errors.add(:base, "xlimits should be a matrix (nx, 2), got '#{self.xlimits}'")
end
else
self.xlimits = []
end

if self.kind == "SEGMOOMOE"
unless self.xtypes
raise_error("xtypes field should be present, got '#{self.xtypes}'")
errors.add(:base, "xtypes field should be present, got '#{self.xtypes}'")
return
end

xtypes.each_with_index do |xt, i|
case xt['type']
when "float_type"
if xt['limits'].size != 2 && xt['limits'][0].to_f != xt['limits'][0] && xt['limits'][1].to_f != xt['limits'][1]
raise_error("xtype.limits should be float [lower, upper], got '#{xt['limits']}'")
errors.add(:base, "xtype.limits should be float [lower, upper], got '#{xt['limits']}'")
end
when "int_type"
if xt['limits'].size != 2 && xt['limits'][0].to_i != xt['limits'][0] && xt['limits'][1].to_i != xt['limits'][1]
raise_error("xtype.limits should be int [lower, upper], got '#{xt['limits']}'")
errors.add(:base, "xtype.limits should be int [lower, upper], got '#{xt['limits']}'")
end
when "ord_type"
begin
Expand All @@ -114,16 +82,16 @@ def check_optimization_config
raise unless l.to_i == l
end
rescue Exception
raise_error("xtype.limits should be a list of int, got '#{xt['limits']}'")
errors.add(:base, "xtype.limits should be a list of int, got '#{xt['limits']}'")
end
when "enum_type"
begin
raise unless xt['limits'].kind_of?(Array)
rescue Exception
raise_error("xtype.limits should be a list of string, got '#{xt['limits']}'")
errors.add(:base, "xtype.limits should be a list of string, got '#{xt['limits']}'")
end
else
raise_error("xtype.type should be FLOAT, INT, ORD or ENUM, got '#{xt['limits']}'")
errors.add(:base, "xtype.type should be FLOAT, INT, ORD or ENUM, got '#{xt['limits']}'")
end
end

Expand All @@ -136,12 +104,60 @@ def check_optimization_config
else
self.cstr_specs.each do |cspec|
unless /^[<>=]$/.match?(cspec["type"])
raise_error("Invalid constraint specification #{cspec} type should match '<>='")
errors.add(:base, "Invalid constraint specification #{cspec} type should match '<>='")
end
end
end
end

def optimization_number_limit
optim_num = Optimization.owned_by(self.owner).size
errors.add(:base, "You own too many optimizations (#{optim_num}), you must delete some before creating new ones") unless optim_num < 20
end



def str_to_array string
JSON.parse(string) rescue nil
end

class OptimizationError < Exception; end

#after_initialize :check_optimization_config

def create_optimizer
unless new_record?
if self.kind == "SEGOMOE"
proxy.create_optimizer(Optimization::OPTIMIZER_KINDS[self.kind], self.xlimits, self.cstr_specs, self.options)
else
proxy.create_mixint_optimizer(Optimization::OPTIMIZER_KINDS[self.kind], self.xtypes, self.n_obj, self.cstr_specs, self.options)
end
end
rescue WhatsOpt::OptimizationProxyError => err
log_error("#{err}: #{err.message}") # do not fail in case of proxy error, let the client handle the error
end

def perform
self.update!(outputs: { status: RUNNING, x_suggested: nil, x_best: nil, y_best: nil })
self.proxy.tell(self.x, self.y)
res = self.proxy.ask(self.with_best)
outputs = { status: res.status, x_suggested: res.x_suggested }
outputs["x_best"] = res.x_best if self.with_best
outputs["y_best"] = res.y_best if self.with_best
self.update!(outputs: outputs)
rescue WhatsOpt::OptimizationProxyError, WhatsOpt::Services::OptimizerException => err
log_error("#{err}: #{err.message}") # asynchronous: just set error state and log the error
end

def xdim
0 if self.xlimits.blank?
Matrix[*self.xlimits]
end

def proxy
WhatsOpt::OptimizerProxy.new(id: self.id.to_s)
end

def check_optimization_inputs(params)
unless params["x"] && params["y"]
raise_error("x and y fields should be present, got #{params}")
Expand Down
16 changes: 12 additions & 4 deletions app/policies/optimization_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,27 @@ def create?
true
end

def index?
(@user.admin? || @user.has_role?(:owner, @record))
end

def show?
(@user.admin? || @user.has_role?(:owner, @record) || @user.has_role?(:co_owner, @record))
(@user.admin? || @user.has_role?(:owner, @record))
end

def update?
(@user.admin? || @user.has_role?(:owner, @record) || @user.has_role?(:co_owner, @record))
(@user.admin? || @user.has_role?(:owner, @record))
end

def destroy?
(@user.admin? || @user.has_role?(:owner, @record) || @user.has_role?(:co_owner, @record))
(@user.admin? || @user.has_role?(:owner, @record))
end

def destroy_selected?
(@user.admin? || @user.has_role?(:owner, @record) || @user.has_role?(:co_owner, @record))
(@user.admin? || @user.has_role?(:owner, @record))
end

def download?
(@user.admin? || @user.has_role?(:owner, @record))
end
end
35 changes: 35 additions & 0 deletions app/views/optimizations/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<%= form_for(optimization, url: (optimization.new_record? ? optimizations_path : optimization_path(@optimization)), :html => { :multipart => true, :role => "form" }) do |f| %>
<% if @optimization.errors.any? %>
<% @optimization.errors.full_messages.each do |message| %>
<div class="alert <%= bootstrap_class_for :error %>" role="alert">
<a href="#" data-dismiss="alert" class="close">×</a>
<%= message %>
</div>
<% end %>
<% end %>

<div class="row">
<div class=" col-4">

<div class="form-group">
<%= f.label :kind %>
<%= f.select :kind , Optimization::OPTIMIZER_KINDS.each_key, {}, {class: "form-control"} %>
</div>

<div class="form-group">
<%= f.label :n_obj %>
<%= f.number_field :n_obj, class: "form-control" %>
</div>

<div class="form-group">
<%= f.label :xlimits %>
<%= f.text_field :xlimits, class: "form-control" %>
</div>

<div class="form-group">
<%= f.submit "Submit", class: "btn btn-primary submit" %>
<%= f.submit "Cancel", class: "btn btn-secondary", name: "cancel_button" %>
</div>
</div>
</div>
<% end %>
Loading

0 comments on commit b50f8f1

Please sign in to comment.