diff --git a/.gitignore b/.gitignore index afd41e2d..358d2ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ docrun_backup.mat *_ert_rtw *.7z *.slxc +/Online/onlineMonitorlib.a +AbstractFuelControl_breach_acc.* +Examples/Wordgen/driving_TA.pcmp +Examples/Wordgen/res.txt diff --git a/@STL_Formula/STL_Break.m b/@STL_Formula/STL_Break.m index 6a417b5b..d41adffc 100644 --- a/@STL_Formula/STL_Break.m +++ b/@STL_Formula/STL_Break.m @@ -1,4 +1,4 @@ -function [out, num_op, num_nested_temp_op] = STL_Break(phi, n) +function [out, num_op, num_nested_temp_op] = STL_Break(phi, n, skip_params) % STL_BREAK breaks a formula into subformulas % % Synopsys: out = STL_Break(phi, [n]) @@ -38,6 +38,7 @@ if nargin==1 n = inf; + skip_params = false; end if n <= 0 diff --git a/@STL_Formula/STL_Culprit.m b/@STL_Formula/STL_Culprit.m index 748218db..7ebc4f08 100644 --- a/@STL_Formula/STL_Culprit.m +++ b/@STL_Formula/STL_Culprit.m @@ -88,7 +88,7 @@ t = tau*ones(1,numParamSet); [mu{:}] = deal(phi); % fill mu with phi for ii = 1:numParamSet - traj_time = P.traj{P.traj_ref(ii}).time; + traj_time = P.traj{P.traj_ref(ii)}.time; idx(ii) = find(traj_time>=tau,1,'first'); % index of tau if tau belong in traj_time if isempty(idx(ii)) idx(ii) = traj_time(end); % there is no time point higher than tau @@ -107,7 +107,7 @@ % mus = STL_ExtractPredicates(phi); for ii = 1:numParamSet % for each param set - traj = P.traj{P.traj_ref(ii}); + traj = P.traj{P.traj_ref(ii)}; Pi = Sselect(P,ii); found = false; jj = 1; diff --git a/CHANGELOG.md b/CHANGELOG.md index 37fc99c1..f4a35120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Release 1.11.0 +- Coverage features, to be documented +- GUI stuff, tbdocu itou +- Fixes and stuff, tbdocu too but less + # Release 1.10.0 - updated README.md - **really** updated README.md diff --git a/Core/Algos/@BreachProblem/BreachProblem.m b/Core/Algos/@BreachProblem/BreachProblem.m index 58045fa8..d67b6e15 100644 --- a/Core/Algos/@BreachProblem/BreachProblem.m +++ b/Core/Algos/@BreachProblem/BreachProblem.m @@ -73,7 +73,7 @@ end % properties related to the function to minimize - properties + properties BrSet BrSys % BreachSystem - reset for each objective evaluation BrSet_Best @@ -133,8 +133,6 @@ display = 'on' use_parallel = 0 max_time = inf - time_start = tic - time_spent = 0 nb_obj_eval = 0 max_obj_eval = 300 num_constraints_failed = 0 @@ -143,6 +141,11 @@ mixed_integer_optim_solvers = {'ga'}; end + properties (Hidden=true) + time_start = tic + time_spent = 0 + end + %% Static Methods methods (Static) @@ -537,6 +540,20 @@ function set_stochastic_params(this, sparams, sdomains) this.solver_options = solver_opt; end + function solver_opt = setup_turbo(this, varargin) + this.solver = 'turbo'; + %this.solver_options.num_corners = length(this.ub); + solver_opt.lb = this.lb; + solver_opt.ub = this.ub; + solver_opt.start_sample = []; + solver_opt.start_function_values = []; + solver_opt.use_java_runtime_call = 1; + solver_opt= varargin2struct_breach(solver_opt, varargin{:}); + + this.display = 'on'; + this.solver_options = solver_opt; + end + function solver_opt = setup_gnmLausen(this) %disp('Setting options for GNM Lausen solver - use help gnm for details'); % solver_opt = saoptimset('Display', 'off'); @@ -719,21 +736,18 @@ function set_stochastic_params(this, sparams, sdomains) res = struct('x',x, 'fval',fval, 'counteval', counteval, 'stopflag', stopflag, 'out', out, 'bestever', bestever); this.add_res(res); - case 'gnmLausen' - [x, fval, output, used_options] = gbnm(this.objective,this.lb,this.ub,this.solver_options); + case 'turbo' + res = this.solve_turbo(); + + case 'gnmLausen' + [x, fval, output, used_options] = gbnm(this.objective,this.lb,this.ub,this.solver_options); res = struct('x', output.usedPoints, 'fval', output.usedVals,... 'counteval', output.nEval, 'output', output, 'used_options', used_options); -% === example of output for 2D problem: -% usedPoints: [2×30 double] -% usedVals: [1×30 double] -% usedSimplex: {1×30 cell} -% reason: {1×15 cell} -% nEval: 831 this.add_res(res); - + case 'meta' - res = this.solve_meta(); + res = this.solve_meta(); case 'ga' res = solve_ga(this, problem); @@ -784,16 +798,6 @@ function set_stochastic_params(this, sparams, sdomains) [x,fval,exitflag,output] = feval(this.solver, problem); res = struct('x',x,'fval',fval, 'exitflag', exitflag, 'output', output); -% num_works = this.BrSys.Sys.Parallel; -% for idx = 1:num_works -% F(idx) = parfeval(this.solver, 4, problem); -% end -% res = cell(1,num_works); -% for idx = 1:num_works -% [completedIdx, x, fval,exitflag,output] = fetchNext(F); -% this.nb_obj_eval = this.nb_obj_eval + output.funccount; -% res{completedIdx} = struct('x',x,'fval',fval, 'exitflag', exitflag, 'output', output); -% end else [x,fval,exitflag,output] = feval(this.solver, problem); res = struct('x',x,'fval',fval, 'exitflag', exitflag, 'output', output); @@ -832,8 +836,6 @@ function set_stochastic_params(this, sparams, sdomains) end end - - % Store if it's best if min(rob) < fbest xbest = x; @@ -849,9 +851,13 @@ function set_stochastic_params(this, sparams, sdomains) end % Exit if robustness negative - if this.StopAtFalse && min(rob) < 0 - disp(['FALSIFIED at sample ' num2str(iterationCounter) '!']); - break + + if min(rob)<0 + this.StopAtFalse = this.StopAtFalse-1; + if this.StopAtFalse==1 && min(rob) < 0 + disp(['FALSIFIED at sample ' num2str(iterationCounter) '!']); + break + end end end res = struct('bestRob',[],'bestSample',[],'nTests',[],'bestCost',[],'paramVal',[],'falsified',[],'time',[]); @@ -866,9 +872,7 @@ function set_stochastic_params(this, sparams, sdomains) this.add_res(res); end - % TESTRON: Mute these outputs this.DispResultMsg(); - %this.Display_Best_Results(this.obj_best, this.x_best); %% Saving run in cache folder this.SaveInCache(); @@ -1048,8 +1052,7 @@ function SetupDiskCaching(this, varargin) this.BrSys.SetupDiskCaching(varargin{:}); end - %% Objective function and wrapper - + %% Objective function and wrapper function [obj, cval, x_stoch] = objective_fn(this,x) % reset this.Spec @@ -1354,6 +1357,25 @@ function Display_Best_Results(this, best_fval, param_values) end end + +%% Display function + + function varargout = disp_variables(this) + variables = list_manip.to_string(this.params); + + + + st = sprintf('Variables: %s. \n',variables); + + if nargout == 0 + varargout = {}; + fprintf(st); + else + varargout{1} = st; + end + + end + end methods (Access=protected) diff --git a/Core/Algos/@BreachProblem/solve_binsearch.m b/Core/Algos/@BreachProblem/solve_binsearch.m index c3b48a4b..2d1ea391 100644 --- a/Core/Algos/@BreachProblem/solve_binsearch.m +++ b/Core/Algos/@BreachProblem/solve_binsearch.m @@ -39,5 +39,5 @@ this.X_log = p; this.obj_log = rob; this.BrSet.SetParam(this.params, p, true); -this.BrSet.CheckSpec(phi); +%this.BrSet.CheckSpec(phi); end diff --git a/Core/Algos/@BreachProblem/solve_turbo.m b/Core/Algos/@BreachProblem/solve_turbo.m new file mode 100755 index 00000000..72aa433a --- /dev/null +++ b/Core/Algos/@BreachProblem/solve_turbo.m @@ -0,0 +1,129 @@ +function res = solve_turbo(this) + +%% Initialization of the parameters +fcn = 'turbo_wrapper'; +numberOfIterations = 1; % Counter to count the number of iterations +maxNumberOfIterations = this.max_obj_eval ; % limit on the number of iterations +lower_bound = this.lb; % lower bound +upper_bound = this.ub; % upper bound +lengthOfInputVector = length(upper_bound); +xbest = []; +fbest = []; + +%% Saving the necessary data in a json file, "scenarion.json", by calling SavingAllDataInJsonFolderTurbo.m +SavingAllDataInJsonFolderTurbo(lower_bound, upper_bound, maxNumberOfIterations) + +% Share files between MATLAB and turbo +nameOfInputFile = 'turboToMatlab.csv'; +nameOfFileToWaitFor = 'turboFinishedWriting.dummy'; +nameOfOutputFile = 'matlabToTurbo.csv'; % The name to save objective value values in a csv file +nameOfTurboWaitFile = 'matlabFinishedWriting.dummy'; +nameOfMatlabInitDataFile = 'initDataFromMatlabToTurbo.csv'; + +% Make sure the files don't exist to begin with +if exist(nameOfInputFile, 'file') + delete(nameOfInputFile); +end +if exist(nameOfFileToWaitFor, 'file') + delete(nameOfFileToWaitFor); +end +if exist(nameOfOutputFile, 'file') + delete(nameOfOutputFile); +end +if exist(nameOfTurboWaitFile, 'file') + delete(nameOfTurboWaitFile); +end +if exist(nameOfMatlabInitDataFile, 'file') + delete(nameOfMatlabInitDataFile); +end + +% Write initial data to .csv file +M = [this.solver_options.start_sample' , ... + this.solver_options.start_function_values']; +csvwrite(nameOfMatlabInitDataFile, M); + +%% Main loop: +% Running main turbo_optimization.py file in python that runs the turbo in the background + +turboPythonFileLocation = which('turbo_optimization.py'); +if isempty(turboPythonFileLocation) + error('Cannot find turbo_optimization.py. Make sure to add turbo_optimization.py to MATLAB path'); +end + +% Note: Using java runtime to run turbo will not open up a command window, +% which is cleaner, but we also don't access the output from turbo. +% To access output from turbo, use system() to call the python script +% instead. +if this.solver_options.use_java_runtime_call + runtime = java.lang.Runtime.getRuntime(); + process = runtime.exec(sprintf('python %s', ... + turboPythonFileLocation)); +else + [status, commandOut] = system(sprintf('python %s &', ... + turboPythonFileLocation)); +end + +%% Main loop to calculate the objective function value in Breach +while (numberOfIterations <= maxNumberOfIterations) % While loop over the number of iteration to search falsified point + + while ~exist(nameOfFileToWaitFor, 'file') % While loop to wait until that input value be generated from Turbo and saved in a csv file + pause(1); + end + + inputValue = csvread(nameOfInputFile); % Reading input values saved in a csv file + delete(nameOfInputFile); + delete(nameOfFileToWaitFor); + + if length(inputValue) ~= lengthOfInputVector % Make sure that the number of dimensions is true + error('Wrong number of dimensions in input file %s', nameOfInputFile); + end + + objectValue = feval(fcn, inputValue, this); % Calculating the objective function value for the current input value + + % Check to see that objective value is negative or not + if objectValue < 0 + xbest = inputValue ; + fbest = objectValue; + break; + end + + % saving the objective value in a csv file + fileIdOut = fopen(nameOfOutputFile, 'w'); + fprintf(fileIdOut,'%4.4f\n', objectValue); + fclose(fileIdOut); % Closing the csv file + + % Write file to tell Turbo that MATLAB is finished writing + fclose(fopen(nameOfTurboWaitFile, 'w')); + + %disp("The number of evaluation is: " + numberOfIterations) % Displaying the number of evaluation + numberOfIterations = numberOfIterations + 1; %Increasing the number of iterations + +end + +% Saving the point with lowest objective value +if isempty(xbest) + xbest = this.x_best; + fbest = this.obj_best; +end + +res = struct('bestRob',[],'bestSample',[],'nTests',[],'bestCost',[],'paramVal',[],'falsified',[],'time',[]); +res.bestSample = xbest; +res.bestRob = fbest; + +% Clean up files +if exist(nameOfInputFile, 'file') + delete(nameOfInputFile); +end +if exist(nameOfFileToWaitFor, 'file') + delete(nameOfFileToWaitFor); +end +if exist(nameOfMatlabInitDataFile, 'file') + delete(nameOfMatlabInitDataFile); +end + +if this.solver_options.use_java_runtime_call + % Kill python process + process.destroy(); +end + +end \ No newline at end of file diff --git a/Core/Algos/FalsificationProblem.m b/Core/Algos/FalsificationProblem.m index 86bfbfc5..bd7255ad 100644 --- a/Core/Algos/FalsificationProblem.m +++ b/Core/Algos/FalsificationProblem.m @@ -5,8 +5,8 @@ % BrSet_False - BreachSet updated with falsifying parameter vectors % and traces whenever some are found % X_false - parameter values found falsifying the formula - % StopAtFalse - (default: true) if true, will stop as soon as a falsifying - % parameter is found. + % StopAtFalse - (default: 1) number of counter example to find + % before stopping. If not a finite integer, never stop % % FalsificationProblem Methods % GetBrSet_False - returns BrSet_False @@ -17,7 +17,7 @@ BrSet_False X_false obj_false - StopAtFalse=true + StopAtFalse=1 Rio_Mode Rio_Mode_log=[] val_max=inf @@ -180,7 +180,13 @@ function LogX(this, x, fval, cval, x_stoch) function b = stopping(this) b = this.stopping@BreachProblem(); - b= b||(this.StopAtFalse&&any(this.obj_best<0)); + + if this.StopAtFalse~=0 && ... % StopAtFalse is not false + rem(this.StopAtFalse,1)==0 &&... % StopAtFalse is integer + this.StopAtFalse<=sum(this.obj_log<0) % found enough + b = true; + end + end function [BrFalse, BrFalse_Err, BrFalse_badU] = GetFalse(this) diff --git a/Core/BreachDomain.m b/Core/BreachDomain.m index 626007b0..2fe0810e 100644 --- a/Core/BreachDomain.m +++ b/Core/BreachDomain.m @@ -5,9 +5,9 @@ % properties - type='double' % can be 'int', 'bool', 'enum', 'double' - domain % always be an interval, empty means singleton - enum % only used with type enum and 'bool' + type='double' % can be 'int', 'bool', 'enum', 'double' + domain % always be an interval, empty means singleton + enum % only used with type enum and 'bool' end methods @@ -93,6 +93,14 @@ bool = isequal(this.type, 'double') && isempty(this.domain); end + function bool = is_out(this,x) + % bool = BreachDomain.is_out(x) + % returns true if x is not inside domain interval + bool = (~isempty(this.domain))&&... + ((x>this.domain(2))||(x this is where we need some smarts if num_dom>1 if combine_x @@ -317,10 +319,8 @@ else x= x{1}; end - - + end - - + end end \ No newline at end of file diff --git a/Core/BreachOpenSystem.m b/Core/BreachOpenSystem.m index 1e5d7a56..15175f4d 100644 --- a/Core/BreachOpenSystem.m +++ b/Core/BreachOpenSystem.m @@ -476,6 +476,81 @@ function SetDomainCfg(this, cfg) atts = [atts {'model_param'}]; end end + + function [sigs_in, sigs_out] = GetI0SignalsList(this) + sig_list = this.P.ParamList(1:this.P.DimX); + idx = this.GetInputSignalsIdx(); + sigs_in = sig_list(idx); + sigs_out = setdiff(sig_list, sigs_in, 'stable'); + end + + %% Disp + + function varargout = disp_signals(this) + max_length = 200; + sig_list = this.P.ParamList(1:this.P.DimX); + if isfield(this.P, 'traj') + num_traces = numel(this.P.traj); + else + num_traces = 0; + end + + switch num_traces + case 0 + str_num = '0 trace.\n'; + case 1 + str_num = '1 trace.\n'; + otherwise + str_num= [num2str(num_traces) ' traces\n']; + end + + + [in_sigs, out_sigs] = this.GetI0SignalsList(); + % inputs + switch numel(in_sigs) + case 0 + str_in = '0 input signals.\n----\n'; + case 1 + str_in = ['1 input signal: ' in_sigs{1} '.\n']; + list_input_gen = cellfun(@(c)(class(c)), this.InputGenerator.signalGenerators, 'UniformOutput', false); + str_input_gen = ['Input generator: ' list_manip.to_string(list_input_gen) '.\n----\n']; + str_in = [str_in str_input_gen]; + otherwise + str_in_sig_list = list_manip.to_string(in_sigs, ', '); + if numel(str_in_sig_list) > max_length + str_in_sig_list = [str_in_sig_list(1:max_length) ', ...']; + end + str_in = [num2str(numel(in_sigs)) ' input signals: ' str_in_sig_list '\n']; + list_input_gen = cellfun(@(c)(class(c)), this.InputGenerator.signalGenerators, 'UniformOutput', false); + str_input_gen = ['Input generator(s): ' list_manip.to_string(list_input_gen) '.\n----\n']; + str_in = [str_in str_input_gen]; + end + + + % outputs + switch numel(out_sigs) + case 0 + str_out = '0 output signals.\n'; + case 1 + str_out = ['1 output signal: ' out_sigs{1} '.\n']; + otherwise + str_out_sig_list = list_manip.to_string(out_sigs, ', '); + if numel(str_out_sig_list) > max_length + str_out_sig_list = [str_out_sig_list(1:max_length) ', ...']; + end + str = [str_in num2str(numel(out_sigs)) ' output signals: ' str_out_sig_list '.\n' ]; + end + str = [str str_num]; + + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + end + + end end diff --git a/Core/BreachRequirement.m b/Core/BreachRequirement.m index 8a3995dc..7a32c08f 100644 --- a/Core/BreachRequirement.m +++ b/Core/BreachRequirement.m @@ -20,7 +20,7 @@ end methods - + %% Construction function this = BreachRequirement(req_monitors, postprocess_signal_gens, precond_monitors) this = this@BreachTraceSystem({}, [], {'data_trace_idx_'}); @@ -92,8 +92,7 @@ function AddRandReq(this, n, Ops) end end - - + function AddPostProcess(this, postprocess_signal_gens) if ~iscell(postprocess_signal_gens) @@ -141,6 +140,8 @@ function ResetSigMap(this) this.signals_in = this.get_signals_in(); end + %% Evaluation + function ResetEval(this) this.BrSet = []; this.SetParam('data_trace_idx_', 0); @@ -268,11 +269,12 @@ function ResetSimulations(this) % eval requirement execTimesForThisReq = zeros(1, numel(this.req_monitors)); for it = 1:num_eval - currentTime = datestr(now, 'HH:MM:ss'); - if num_eval > 30 && (mod(it, 10) == 0) - fprintf(['*** START traj ' num2str(it) '/' num2str(num_eval) ' at ' currentTime '\n']); + if this.verbose>=2 + currentTime = datestr(now, 'HH:MM:ss'); + if num_eval > 30 && (mod(it, 10) == 0) + fprintf(['*** START traj ' num2str(it) '/' num2str(num_eval) ' at ' currentTime '\n']); + end end - if any(traces_vals_precond(it,:)<0) traces_vals( it, :) = NaN; else @@ -358,6 +360,7 @@ function ResetSimulations(this) end end + %% Post Evaluation function F = PlotDiagnostics(this, idx_req_monitors, itraj) if nargin<2 idx_req_monitors = 1; @@ -428,72 +431,14 @@ function ResetSimulations(this) h = BreachSignalsPlot(this,varargin{:}); end - function summary = GetSummary(this, varargin) - - summary = GetStatement(this); - summary.signature = this.GetSignature(varargin{:}); - summary.num_violations_per_trace =sum(this.traces_vals<0 , 2 )'; - [~,idxm ] = sort(summary.num_violations_per_trace, 2, 'descend'); - summary.idx_traces_with_most_violations = idxm; - - end - - function summary = GetStatement(this) - - if this.CountTraces()==1 - summary.statement = sprintf('%d trace evaluated', this.CountTraces()); - else - summary.statement = sprintf('%d traces evaluated', this.CountTraces()); - end - summary.num_traces_evaluated =size(this.traces_vals,1); - summary.requirements.names = cell(1,numel(this.req_monitors)); - for ir = 1:numel(this.req_monitors) - if isa(this.req_monitors{ir}, 'stl_monitor') - summary.requirements.names{ir} = this.req_monitors{ir}.formula_id; - else - summary.requirements.names{ir} = class(this.req_monitors{ir}); - end + function Rfalse = GetFalseSubset(this) + Rfalse = []; + idx_false = find( sum(this.traces_vals<0,2) ); + if ~isempty(idx_false) + Rfalse =this.ExtractSubset(idx_false); end - if summary.num_traces_evaluated>0 - summary.val = this.val; - summary.requirements.rob = this.traces_vals; - summary.requirements.rob_vac = this.traces_vals_vac; - summary.requirements.sat = this.traces_vals >=0; - summary.num_requirements = size(this.traces_vals,2); - if summary.num_requirements == 1 - summary.statement = sprintf([summary.statement ' on %d requirement'], summary.num_requirements); - else - summary.statement = sprintf([summary.statement ' on %d requirements'], summary.num_requirements); - end - summary.num_traces_violations = sum( any(this.traces_vals<0, 2) ); - summary.statement = sprintf([summary.statement ', %d traces have violations'], summary.num_traces_violations); - summary.num_total_violations = sum( sum(this.traces_vals<0) ); - if summary.num_total_violations == 1 - summary.statement = sprintf([summary.statement ', %d requirement violation' ], summary.num_traces_violations); - elseif summary.num_total_violations >1 - summary.statement = sprintf([summary.statement ', %d requirement violations total' ], summary.num_traces_violations); - end - - summary.num_vacuous_sat = sum(sum(summary.requirements.rob == inf)); - - if summary.num_vacuous_sat == 1 - summary.statement = sprintf([summary.statement ', %d vacuous satisfaction.' ], summary.num_vacuous_sat); - elseif summary.num_vacuous_sat >1 - summary.statement = sprintf([summary.statement ', %d vacuous satisfactions.' ], summary.num_vacuous_sat); - else - summary.statement = [summary.statement '.']; - end - - - else - summary.statement = [summary.statement '.']; - end - if isa(this.BrSet, 'BreachImportData') - summary.file_names = this.BrSet.signalGenerators{1}.file_list; - end - end - + function values = GetParam(this, params, ip) % GetParam if not found, look into BrSet [idx, ifound] = FindParam(this.P, params); @@ -522,34 +467,6 @@ function ResetSimulations(this) end end - function [idx, ifound, idxB, ifoundB] = FindSignalsIdx(this, signals ) - if ischar(signals) - signals = {signals}; - end - idxB = zeros(1, numel(signals)); ifoundB = zeros(1, numel(signals)); - [idx, ifound] = FindSignalsIdx@BreachSet(this, signals); % - - if any(~ifound)&&isa(this.BrSet, 'BreachSet') % if not a signal of the requirement, look into BrSet (system) signals - idx_not_found = find(~ifound); - for isig = 1:numel(idx_not_found) - s0 = signals{idx_not_found(isig)}; - aliases = this.getAliases(s0); - - [idx_s, ifound_s] = FindParam(this.P, aliases); - if any(ifound_s) % one alias is the one ! - idx(idx_not_found(isig)) = idx_s(find(ifound_s, 1)); - ifound(idx_not_found(isig)) = 1; - elseif (~isempty(this.BrSet)) - [idx_sB, ifB] = FindSignalsIdx(this.BrSet, aliases); - if (any(ifB)) - idxB(idx_not_found(isig)) = idx_sB(find(ifB, 1)); - ifoundB(idx_not_found(isig)) = 1; - end - end - end - end - end - function [X, idxR] = GetSignalValues(this,varargin) % GetSignalValues if not found, look into BrSet nb_traj = 0; @@ -615,6 +532,92 @@ function ResetSimulations(this) end end end + + function Rextract = ExtractSubset(this, idx) + Rextract = this.copy(); % likely overkill... + Rextract.P = Sselect(this.P,idx); + if ~isempty(this.BrSet) + Rextract.BrSet.P = Sselect(this.BrSet.P, idx); + end + + if ~isempty(this.traces_vals) + Rextract.traces_vals = this.traces_vals(idx,:); + end + if ~isempty(this.traces_vals_precond) + Rextract.traces_vals_precond = this.traces_vals_precond(idx,:); + end + + global_val = min(min(Rextract.traces_vals)); + global_precond_val = min(min(Rextract.traces_vals_precond)); + Rextract.val = min([global_val,-global_precond_val]); + + end + + function summary = GetSummary(this, varargin) + + summary = GetStatement(this); + summary.signature = this.GetSignature(varargin{:}); + summary.num_violations_per_trace =sum(this.traces_vals<0 , 2 )'; + [~,idxm ] = sort(summary.num_violations_per_trace, 2, 'descend'); + summary.idx_traces_with_most_violations = idxm; + + end + + function summary = GetStatement(this) + + if this.CountTraces()==1 + summary.statement = sprintf('%d trace evaluated', this.CountTraces()); + else + summary.statement = sprintf('%d traces evaluated', this.CountTraces()); + end + summary.num_traces_evaluated =size(this.traces_vals,1); + summary.requirements.names = cell(1,numel(this.req_monitors)); + for ir = 1:numel(this.req_monitors) + if isa(this.req_monitors{ir}, 'stl_monitor') + summary.requirements.names{ir} = this.req_monitors{ir}.formula_id; + else + summary.requirements.names{ir} = class(this.req_monitors{ir}); + end + end + if summary.num_traces_evaluated>0 + summary.val = this.val; + summary.requirements.rob = this.traces_vals; + summary.requirements.rob_vac = this.traces_vals_vac; + summary.requirements.sat = this.traces_vals >=0; + summary.num_requirements = size(this.traces_vals,2); + if summary.num_requirements == 1 + summary.statement = sprintf([summary.statement ' on %d requirement'], summary.num_requirements); + else + summary.statement = sprintf([summary.statement ' on %d requirements'], summary.num_requirements); + end + summary.num_traces_violations = sum( any(this.traces_vals<0, 2) ); + summary.statement = sprintf([summary.statement ', %d traces have violations'], summary.num_traces_violations); + summary.num_total_violations = sum( sum(this.traces_vals<0) ); + if summary.num_total_violations == 1 + summary.statement = sprintf([summary.statement ', %d requirement violation' ], summary.num_traces_violations); + elseif summary.num_total_violations >1 + summary.statement = sprintf([summary.statement ', %d requirement violations total' ], summary.num_traces_violations); + end + + summary.num_vacuous_sat = sum(sum(summary.requirements.rob == inf)); + + if summary.num_vacuous_sat == 1 + summary.statement = sprintf([summary.statement ', %d vacuous satisfaction.' ], summary.num_vacuous_sat); + elseif summary.num_vacuous_sat >1 + summary.statement = sprintf([summary.statement ', %d vacuous satisfactions.' ], summary.num_vacuous_sat); + else + summary.statement = [summary.statement '.']; + end + + + else + summary.statement = [summary.statement '.']; + end + if isa(this.BrSet, 'BreachImportData') + summary.file_names = this.BrSet.signalGenerators{1}.file_list; + end + + end function dom = GetDomain(this, param) if ischar(param) @@ -662,6 +665,34 @@ function ResetSimulations(this) end + function [idx, ifound, idxB, ifoundB] = FindSignalsIdx(this, signals ) + if ischar(signals) + signals = {signals}; + end + idxB = zeros(1, numel(signals)); ifoundB = zeros(1, numel(signals)); + [idx, ifound] = FindSignalsIdx@BreachSet(this, signals); % + + if any(~ifound)&&isa(this.BrSet, 'BreachSet') % if not a signal of the requirement, look into BrSet (system) signals + idx_not_found = find(~ifound); + for isig = 1:numel(idx_not_found) + s0 = signals{idx_not_found(isig)}; + aliases = this.getAliases(s0); + + [idx_s, ifound_s] = FindParam(this.P, aliases); + if any(ifound_s) % one alias is the one ! + idx(idx_not_found(isig)) = idx_s(find(ifound_s, 1)); + ifound(idx_not_found(isig)) = 1; + elseif (~isempty(this.BrSet)) + [idx_sB, ifB] = FindSignalsIdx(this.BrSet, aliases); + if (any(ifB)) + idxB(idx_not_found(isig)) = idx_sB(find(ifB, 1)); + ifoundB(idx_not_found(isig)) = 1; + end + end + end + end + end + function [req, idx_req] = get_req_from_name(this, req_name) req = []; @@ -684,6 +715,7 @@ function ResetSimulations(this) end %% Display + function varargout = disp(this) signals_in_st = cell2mat(cellfun(@(c) (['''' c ''', ']), this.signals_in, 'UniformOutput', false)); signals_in_st = ['{' signals_in_st(1:end-2) '}']; @@ -1075,6 +1107,94 @@ function Concat(this,other,fast) function names = get_monitor_names(this) names = cellfun(@(c)(c.name), this.req_monitors, 'UniformOutput', false); end + + + function varargout = dispwip(this) + + + str = [class(this) ' object.\n']; + str = [str this.disp_req_monitors()]; + str_params = this.disp_params_in(); + if ~isempty(str_params) + str = [str str_params]; + end + + str_signals = this.disp_signals_in(); + if ~isempty(str_signals) + str = [str str_signals]; + end + + if nargout == 0 + varargout = {}; + fprintf(str); + fprintf(['\n...More details...\n']); + else + varargout{1} = str; + end + + + end + + + function varargout = disp_req_monitors(this) + + mons = list_manip.to_string(this.get_monitor_names(),', '); + str = ['Monitor(s): ' mons '\n']; + + + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + + + end + + function varargout = disp_params_in(this) + + params_in = setdiff(this.GetParamList(), 'data_trace_idx_', 'stable'); + if isempty(params_in) + str = ''; + else + params_in_str = list_manip.to_string(params_in,', '); + + str = ['Parameters: ' params_in_str '\n']; + end + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + + + end + + + function varargout = disp_signals_in(this) + + signals_in = this.get_signals_in(); + if isempty(signals_in) + str = ''; + else + signals_in_str = list_manip.to_string(signals_in,', '); + + str = ['Signal(s): ' signals_in_str '\n']; + end + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + + + end + + + end @@ -1316,7 +1436,7 @@ function Concat(this,other,fast) Xin = []; end % checks if a signal is missing (need postprocess) - while any(isnan(Xin)) + while any(all(isnan(Xin),2)) % should do only one iteration if postprocessing function are % properly ordered following dependency for ipp = 1:numel(this.postprocess_signal_gens) diff --git a/Core/BreachSet.m b/Core/BreachSet.m index b9d879fa..09855369 100644 --- a/Core/BreachSet.m +++ b/Core/BreachSet.m @@ -8,7 +8,7 @@ % BreachSet Properties % ParamRanges - ranges of possible values for each parameter - determines the parameter sampling domain % SignalRanges - ranges of values taken by each signal variable - % AppendWhenSample=false - when true, sampling appends new param vectors, otherwise replace. + % AppendWhenSample=false - when true, sampling appends new param samples, otherwise replace. % % BreachSet Methods % GetParam - get values for parameters, given their names @@ -27,92 +27,64 @@ % PlotParams - plots parameter vectors % PlotSignals - plots signals vs time % PlotSigPortrait - plots signals portrait - + properties (SetObservable) P % legacy parameter structure - contains points data (in P.pts) and traces (P.traj) and many other fields whose purpose is slowly falling into oblivion end - + properties Domains = BreachDomain('double', []) ParamGens % optional classes performing parameter value transformation after a SetParam SignalRanges % ranges of values taken by each signal variable AppendWhenSample=false % when true, sampling appends new param vectors, otherwise replace. log_folder - sigMap + sigMap sigMapInv AliasMap end - + properties % coverage stuff - % Gridsize is a vector that contains one value for each dimension, - % where each value corresponds to the length of a grid element in - % the corresponding dimension. The eps value for each - % dimension corresponds to the smallest distance allowed - % between any two points in the parameter space. - epsgridsize = []; - - % Gridsize is a vector that contains one value for each dimension, - % where each value corresponds to the length of a grid element in - % the corresponding dimension. The delta value for each - % dimension corresponds to the grid element size that is used to - % measure the cell count and entropy coverage measures. - deltagridsize = []; - - % The following flag determines whether all points should be - % shifted to the center of the appropriate grid element. If the - % flag is set, then this class will assume that only one point - % should occupy any given grid element, and that point will be in - % the center of the grid element. - snap_to_grid = false; - - % Will use containers for now. This will be an efficient - % representation to a.) allow the query of any cell and b.) know - % the total number of cells that are populated with points. - % This DOES NOT permit an easy way to check a region (e.g., a - % hyperbox) that contains points. - % In the future, we can switch this for BDDs. A BDD representation - % will allow us to do things like use an SMT query to determine - % whether points in a region (hyperbox) are populated. - EpsGridMapObj + CoverOpts % options for coverage computation + CoverRes % results for previous coverage computation DeltaGridMapObj end - + methods (Hidden=true) function P = GetP(this) % Get the legacy parameter set structure P = this.P; end - + function SetP(this, P) % Get the legacy parameter set structure this.P = P; this.CheckinDomain(); end end - + methods + %% Constructor function this = BreachSet(Sys, params, ranges) % BreachSet constructor from a legacy P or Sys, parameter names and ranges - + this.sigMap = containers.Map(); this.sigMapInv = containers.Map(); - this.AliasMap = containers.Map(); - - this.EpsGridMapObj = containers.Map(); + this.AliasMap = containers.Map(); + this.DeltaGridMapObj = containers.Map(); - + if nargin>=1 && ischar(Sys) Sys= {Sys}; end - - + + switch nargin case 0 return; case 1 if isaSys(Sys) - this.P = CreateParamSet(Sys); + this.P = CreateParamSet(Sys); elseif iscell(Sys) % assumes parameter names Sys = CreateSystem({}, Sys, zeros(1, numel(Sys))); this.P = CreateParamSet(Sys); @@ -121,28 +93,28 @@ function SetP(this, P) if isaSys(Sys) this.P = CreateParamSet(Sys,params); elseif iscell(Sys) % assumes parameter names - ranges = params; - params = Sys; - Sys = CreateSystem({}, Sys, zeros(1, numel(Sys))); + ranges = params; + params = Sys; + Sys = CreateSystem({}, Sys, zeros(1, numel(Sys))); this.P = CreateParamSet(Sys,params,ranges); this.SetParamRanges(params,ranges); - end + end case 3 this.P = CreateParamSet(Sys, params, ranges); end this.CheckinDomain(); - + end - + %% Domains function SetDomain(this, params, type, domain, enum) % SetDomain(params, breach_domains) or SetDomain(params, types, domains, enums) - + % First let's collect idx for params if nargin <3 error('BreachSet:SetDomain:not_enough_args', 'SetDomain requires at least parameter names and one or several types.' ); else - + params = check_arg_is_cell(params); idxs = zeros(1, numel(params)); for ip = 1:numel(params) @@ -155,7 +127,7 @@ function SetDomain(this, params, type, domain, enum) idxs(ip) = idx; end end - + params = params(logical(idxs)); % create domains switch nargin @@ -163,20 +135,20 @@ function SetDomain(this, params, type, domain, enum) % is type a BreachDomain already? if isa(type,'BreachDomain')||(iscell(type)&&~isempty(type)&&isa(type{1}, 'BreachDomain')) if iscell(type) % FIXME: this mess should be avoided by using cell everywhere for domains.. - cell_type = type; + cell_type = type; type = BreachDomain; for ic = 1:numel(cell_type) % converts cell of domains into array... type(ic)= cell_type{ic}; end end - + if numel(type)==1 type = repmat(type,1, numel(params)); end if numel(type) ~= numel(params) error('number of types and parameters or signals mismatch.') end - + % Now we have as many BreachDomain objects as parameters for ip = 1:numel(idxs) this.Domains(idxs(ip)) = type(ip); @@ -200,8 +172,8 @@ function SetDomain(this, params, type, domain, enum) error('BreachSet:SetDomain:wrong_domain_size', 'Domain should be of size num_param x 2.'); end end - - + + end case 4 type = check_arg_is_cell(type, numel(params)); @@ -227,7 +199,7 @@ function SetDomain(this, params, type, domain, enum) this.Domains(idxs(ip)) = BreachDomain(type{ip}, domain{ip}); end end - + case 5 type = check_arg_is_cell(type, numel(params)); domain = check_arg_is_cell(domain, numel(params)); @@ -237,7 +209,7 @@ function SetDomain(this, params, type, domain, enum) end end end - + function dom = GetDomain(this, param) % BreachSet.GetDomain idx = FindParam(this.P, param); @@ -252,16 +224,16 @@ function SetDomain(this, params, type, domain, enum) dom = []; end end - + function CheckinDomain(this) - % BreachSet.CheckinDomain() Enforce parameters to adhere to their domains + % BreachSet.CheckinDomain() Enforce parameters to adhere to their domains this.CheckinDomainParam(); this.CheckinDomainTraj(); end - + function CheckinDomainTraj(this) % BreachSet.CheckinDomainTraj() Enforce signals to adhere to their domains - + if this.hasTraj() for itraj = 1:numel(this.P.traj) for i=1:this.P.DimX @@ -272,7 +244,7 @@ function CheckinDomainTraj(this) end end end - + function CheckinDomainParam(this) pts = this.P.pts; if numel(this.Domains)< size(pts,1) @@ -282,73 +254,10 @@ function CheckinDomainParam(this) if ~isequal(this.Domains(i).type, 'double')||~isempty(this.Domains(i).domain) pts(i,:) = this.Domains(i).checkin(pts(i,:)); end - end - this.P.pts = pts; - end - - function Bg = GridFilter(this, delta) - % Bg = this.GridFilter(delta) - % - % delta is a number of sample per dimensions, either vector or scalar - % - - [params, idx_params] = this.GetVariables(); - if ~isempty(params) - if isscalar(delta) - delta = repmat(delta, 1, numel(params)); - elseif numel(delta)~=numel(params) - error('delta should be the same size as the number of variable parameters in the set.'); - end - %Bg = BreachSet(params); - for ip = 1:numel(params) - p = params{ip}; - - this_domain = this.Domains(idx_params(ip)+this.P.DimX); - if ~isequal(this_domain.type,'double') % shall strikes back at me when double is not the only non integer domain... - grid_domain(ip) = this_domain; - else - dom = this_domain.domain; - dom = linspace(dom(1),dom(2), delta(ip)); % delta samples in dim ip - grid_domain(ip) = BreachDomain('enum',dom); - end - % - end - end - - pts = this.GetParam(params); - bg_params = [params {'count' 'idx'}]; - Bg = BreachSet(bg_params); - Bg.SetDomain(bg_params, [grid_domain BreachDomain('int'), BreachDomain('int')]); - for i_pts = 1:size(pts,2) - pt = pts(:,i_pts); - for i_dim=1:numel(params) - if ~isequal(grid_domain(i_dim).type, 'double')||~isempty(grid_domain(i_dim).domain) - pt(i_dim) = grid_domain(i_dim).checkin(pt(i_dim)); % is a grid point now - end - end - % pt is now a grid pts, we add it to the map - hash = DataHash(pt); - if Bg.DeltaGridMapObj.isKey(hash) - grid_pt = Bg.DeltaGridMapObj(hash); - grid_pt.count = grid_pt.count+1; - grid_pt.idx(end+1) = i_pts; - else - grid_pt.pt = pt; - grid_pt.count = 1; - grid_pt.idx = i_pts; end - Bg.DeltaGridMapObj(hash) = grid_pt; - end - grid_pts = Bg.DeltaGridMapObj.values; - num_pts = numel(grid_pts); - pts = zeros(numel(bg_params), num_pts); - for i_pt= 1:num_pts - pts(:, i_pt) = [grid_pts{i_pt}.pt; grid_pts{i_pt}.count ; grid_pts{i_pt}.idx(1)]; + this.P.pts = pts; end - Bg.SetParam(bg_params,pts); - end - - + %% Params function SetParam(this, params, values, is_spec_param) % BreachSet.SetParam(params, values [, is_spec_param]) sets values to @@ -356,17 +265,17 @@ function SetParam(this, params, values, is_spec_param) % creates as many sample as there are values. If the set has % several samples and there is only one value, set this value to % all samples. Otherwise, returns an error. - - if nargin==2 - values = params; - params = this.GetParamList(); + + if nargin==2 + values = params; + params = this.GetParamList(); end - - + + if (~exist('is_spec_param', 'var')) is_spec_param = false; - end - + end + ip = FindParam(this.P, params); i_not_sys = find(ip>this.P.DimP); if ~isempty(i_not_sys) @@ -383,26 +292,26 @@ function SetParam(this, params, values, is_spec_param) this.Domains(end+1) = BreachDomain(); % default domain for new parameters end end - + num_params = numel(ip); - + if size(values, 1)~= num_params if size(values,1) == 1 && size(values,2 ) == num_params values = values'; elseif numel(values)==1 - values = repmat(values, num_params,1); - else - error('SetParam:wrong_arguments_size', 'Dimension mismatch between values and parameters.'); + values = repmat(values, num_params,1); + else + error('SetParam:wrong_arguments_size', 'Dimension mismatch between values and parameters.'); end end - + num_pts = size(this.P.pts,2); num_values = size(values, 2); saved_traj = false; - + if ischar(is_spec_param)&&strcmp(is_spec_param, 'combine')&&... - ~(num_pts==1) % no need to combine (and mess order) in this case - + ~(num_pts==1)&&~(num_values==1) % no need to combine (and mess order) in this case + if this.hasTraj() traj= this.P.traj; saved_traj = true; @@ -419,15 +328,15 @@ function SetParam(this, params, values, is_spec_param) this.P.Xf = saved_Xf; end this.P = SetParam(this.P, params, values(:, idx(2,:))); - + elseif ischar(is_spec_param)&&strcmp(is_spec_param, 'append') - + new_values = [GetParam(this.P, params) values]; this.P.pts = [this.P.pts repmat(this.P.pts(:,end),1, size(values, 2))]; this.P.epsi= [this.P.epsi repmat(this.P.epsi(:,end),1, size(values, 2))]; this.P.selected = zeros(1, size(new_values, 2)); - this.P = SetParam(this.P, params, new_values); - + this.P = SetParam(this.P, params, new_values); + else % legacy, i.e., not combine version if num_values==1 || num_values == num_pts this.P = SetParam(this.P, params, values); @@ -440,23 +349,22 @@ function SetParam(this, params, values, is_spec_param) error('SetParam:wrong_arguments_size', 'Dimension mismatch between values and parameters.'); end end - + this.ApplyParamGens(params); - + end - + function AddParam(this, params,values) - % short for SetParam(..., 'append') - + % short for SetParam(..., 'append') + if nargin == 2 values = params; params = this.GetParamList; - end - this.SetParam(params, values, 'append'); - + end + this.SetParam(params, values, 'append'); + end - function SetParamCfg(this, list_cfg) % SetParamCfg applies a cfg structure to set parameters. Struct % must have *char* fields params and values @@ -464,7 +372,7 @@ function SetParamCfg(this, list_cfg) if ~iscell(list_cfg) list_cfg = {list_cfg}; end - + this.Reset(); B0 = this.copy(); % first operation @@ -475,7 +383,7 @@ function SetParamCfg(this, list_cfg) v = eval([ '[' cfg.values{ip} ']']); B.SetParam(p, v, 'combine'); end - + % subsequent operations this.P= B.P; for ic = 2:numel(list_cfg) @@ -489,27 +397,27 @@ function SetParamCfg(this, list_cfg) this.Concat(B); end end - + function SetDomainCfg(this, cfg) - for ip = 1:numel(cfg.params) - val = cfg.values{ip}; - if ischar(val) - val = str2num(val); - end - typ = cfg.types{ip}; - dom = cfg.domains{ip}; - if isempty(dom) - dom = []; - elseif ischar(dom) - dom = str2num(dom); %#ok - elseif iscell(dom) - dom = cell2mat(dom); - end - this.SetParam(cfg.params{ip}, val); - this.SetDomain(cfg.params{ip},typ,dom); - end + for ip = 1:numel(cfg.params) + val = cfg.values{ip}; + if ischar(val) + val = str2num(val); + end + typ = cfg.types{ip}; + dom = cfg.domains{ip}; + if isempty(dom) + dom = []; + elseif ischar(dom) + dom = str2num(dom); %#ok + elseif iscell(dom) + dom = cell2mat(dom); + end + this.SetParam(cfg.params{ip}, val); + this.SetDomain(cfg.params{ip},typ,dom); + end end - + function SetParamSpec(this, params, values, ignore_sys_param) % BreachSet.SetParamSpec ip = FindParam(this.P, params); @@ -524,22 +432,22 @@ function SetParamSpec(this, params, values, ignore_sys_param) elseif ~exist('ignore_sys_param', 'var')||ignore_sys_param==false error('Attempt to modify a system parameter - use SetParam instead.'); end - this.ApplyParamGens(params); - + this.ApplyParamGens(params); + end - + function SetParamGen(this, pg) - if iscell(pg) + if iscell(pg) cellfun(@this.SetParamGenItem, pg); - else + else this.SetParamGenItem(pg); end - + end - + function SetParamGenItem(this, pg) this.ParamGens{end+1} = pg; - + % create/update domain of input parameters this.SetParam(pg.params, pg.p0, true); if ~isempty(pg.domain) @@ -548,23 +456,30 @@ function SetParamGenItem(this, pg) domain = repmat(BreachDomain, 1, numel(pg.params)); this.SetDomain(pg.params, domain); end + + + % this.SetParam(pg.params_out, nan, true); % why ? + params_nan = setdiff(pg.params_out, pg.params); + if ~isempty(params_nan) + this.SetParam(params_nan, nan, true); + end % update domain of output parameters if ~isempty(pg.domain_out) - for ip =1:numel(pg.params_out) - this.SetDomain(pg.params_out{ip}, pg.domain_out(ip)); - end + for ip =1:numel(pg.params_out) + this.SetDomain(pg.params_out{ip}, pg.domain_out(ip)); + end end - + this.ApplyParamGens(); - end - + end + function ApplyParamGens(this, params) if ~isempty(this.ParamGens) if nargin==1 - params = this.GetParamList(); + params = this.GetParamList(); end - + % ensures params are names and ip are indices if ~isnumeric(params) ip = FindParam(this.P, params); @@ -576,52 +491,53 @@ function ApplyParamGens(this, params) pg = this.ParamGens{ig}; params_in= pg.params; if ~isempty(intersect(params, params_in)) - p_in = this.GetParam(params_in); - p_out = pg.computeParams(p_in); - ip_out = FindParam(this.P, pg.params_out); - this.P.pts(ip_out,:) = p_out; + p_in = this.GetParam(params_in); + p_out = pg.computeParams(p_in); + ip_out = FindParam(this.P, pg.params_out); + this.P.pts(ip_out,:) = p_out; end end if ~isempty(ig) - this.P = Preset_traj_ref(this.P); + this.P = Preset_traj_ref(this.P); end end end - + function values = GetParam(this, params, ip) if nargin==1||isempty(params) params= this.GetParamList; - end - + end + values = GetParam(this.P,params); - if exist('ip', 'var') + + if ~isempty(values)&&nargin>=3 values = values(:, ip); end end - + function [values, i1, i2] = GetUParam(this,params,argu) % [values, i1, i2] = GetUParam(this, params,argu) returns unique values of parameters % i1 are indices of unique values, i2 is a cell of (unique) indices % for where the unique values were found. argu is an argument for % unique function call (for 'stable' essentially) % - + if nargin<3 argu = 0; end - + all_values = this.GetParam(params); if argu [values, i1, ib] = unique(all_values','rows', argu); else [values, i1, ib] = unique(all_values','rows'); end - + values = values'; if nargout >= 1 i1 = i1'; end - + if nargout >=2 ib = ib'; i2 = cell(1,numel(i1)); @@ -633,11 +549,23 @@ function ApplyParamGens(this, params) function RemoveDuplicateParams(this) params = this.GetVariables(); - [~, i1, ~] = GetUParam(this,params,'stable'); - this.P = Sselect(this.P, i1); + [~, i1, ~] = GetUParam(this,params,'stable'); + this.P = Sselect(this.P, i1); end - - + + function RemoveParams(this,params, values) + val_before = this.GetParam(params); + [~, idx_after] = setdiff(round(val_before,14)',round(values,14)',"rows"); + if ~isempty(idx_after) + this.P = Sselect(this.P, idx_after); + else + this.ResetParamSet(); + this.P.pts = this.P.pts(:, []); + this.P.epsi = this.P.epsi(:,[]); + end + end + + function ResetParamSet(this) % ResetParamSet remove samples and keeps one in the domain this.P = SPurge(this.P); @@ -652,7 +580,7 @@ function ResetParamSet(this) this.CheckinDomain(); end end - + %% Get and Set param ranges function SetParamRanges(this, params, ranges) % BreachSet.SetParamRanges set intervals for parameters (set domains as @@ -664,11 +592,11 @@ function SetParamRanges(this, params, ranges) else i_params=params; end - + if size(ranges,1) == 1 ranges = repmat(ranges, numel(i_params),1); end - + if ~isempty(i_not_found) if iscell(params) param_not_found = params{i_not_found(1)}; @@ -677,7 +605,7 @@ function SetParamRanges(this, params, ranges) end error('SetParamRanges:param_not_found', ['Parameter ' param_not_found ' not found.']); end - + % Set domain for ip = 1:numel(i_params) type = this.Domains(i_params(ip)).type; @@ -685,49 +613,49 @@ function SetParamRanges(this, params, ranges) this.Domains(i_params(ip)).domain = ranges(ip,:); %warning('SetParamRanges:enum_or_bool', 'Use SetDomain %for enum or bool types.' ); % Maybe should keep the - %warning + %warning else this.Domains(i_params(ip)) = BreachDomain(type, ranges(ip,:)); end end - + % kept for backward compatibility with legacy stuff this.P.dim = i_params; this.CheckinDomainParam(); end - + function ranges = GetParamRanges(this, params) % BreachSet.GetParamRanges i_params = FindParam(this.P, params); ranges= zeros(numel(params),2); ranges(:,1) = -inf; ranges(:,2) = inf; - + for ip = 1:numel(i_params) if (~isempty(this.Domains(i_params(ip)).domain)) ranges(ip,:) = this.Domains(i_params(ip)).domain; end end - + end - + function params = GetParamList(this) % GetParamList returns parameter names params = this.P.ParamList(this.P.DimX+1:end); end - + function [params, idx] = GetSysParamList(this) - % GetSysParamList + % GetSysParamList idx = this.P.DimX+1:this.P.DimP; params = this.P.ParamList(idx); end - + function prop_params = GetPropParamList(this) % GetSysParamList returns system parameter names prop_params = this.P.ParamList(this.P.DimP+1:end); end - - % Get the number of param vectors - -1 means P is empty + + % Get the number of param samples - -1 means P is empty function nb_pts = GetNbParamVectors(this) if isempty(this.P) nb_pts = -1; @@ -735,7 +663,15 @@ function SetParamRanges(this, params, ranges) nb_pts= size(this.P.pts,2); end end - + % Same + function nb_pts= GetNumSamples(this) + if isempty(this.P) + nb_pts = -1; + else + nb_pts= size(this.P.pts,2); + end + end + function [params, ipr] = GetVariables(this) [params, ipr] = GetBoundedDomains(this); if this.GetNbParamVectors()>1 @@ -747,7 +683,7 @@ function SetParamRanges(this, params, ranges) end end end - + function [params, ipr] = GetSysVariables(this) [params, ipr] = GetVariables(this); if ~isempty(params) @@ -756,7 +692,7 @@ function SetParamRanges(this, params, ranges) ipr = ipr(i_diff); end end - + function [params, ipr] = GetReqVariables(this) [params, ipr] = GetVariables(this); if ~isempty(params) @@ -765,16 +701,16 @@ function SetParamRanges(this, params, ranges) ipr = ipr(i_intersect); end end - + function [ params, ipr] = GetBoundedDomains(this) % GetNonEmptyDomains ipr = cellfun(@(c)(~isempty(c)), {this.Domains.domain}); ipr = find(ipr); params = this.P.ParamList(ipr); end - + function SetEmptyDomain(this, params) - + if ~iscell(params) params = {params}; end @@ -782,15 +718,15 @@ function SetEmptyDomain(this, params) idx_param_in_this = FindParam(this.P, params{idx_param}); this.Domains(idx_param_in_this).domain = []; end - + end - - %% Signals + + %% Signals function this = SetSignalMap(this, varargin) - % SetSignalMap defines aliases for signals - used by GetSignalValues + % SetSignalMap defines aliases for signals - used by GetSignalValues % % Input: a map or a list of pairs or two cells - + arg_err_msg = 'Argument should be a containers.Map object, or a list of pairs of signal names, or two cells of signal names with same size.'; switch nargin case 2 @@ -807,84 +743,84 @@ function SetEmptyDomain(this, params) for is = 1:numel(varargin{2}) sig1 = varargin{1}{is}; sig2 = varargin{2}{is}; - add_sigs(sig1, sig2); + add_sigs(sig1, sig2); end else sig1 = varargin{1}; sig2 = varargin{2}; - add_sigs(sig1, sig2); + add_sigs(sig1, sig2); end otherwise for is = 1:numel(varargin)/2 try sig1 = varargin{2*is-1}; sig2 = varargin{2*is}; - add_sigs(sig1, sig2); + add_sigs(sig1, sig2); catch error('SetSignalMap:wrong_arg', arg_err_msg); end end end - + if this.verbose >= 2 this.PrintSigMap(); end - + function add_sigs(sig1, sig2) if ischar(sig1)&&ischar(sig2) if ~strcmp(sig1,sig2) this.sigMap(sig1) = sig2; this.sigMapInv(sig2) = sig1; - + % get current aliases for sig1 and sig2 - if this.AliasMap.isKey(sig1) - SIG1 = [{sig1} this.AliasMap(sig1)]; + if this.AliasMap.isKey(sig1) + SIG1 = [{sig1} this.AliasMap(sig1)]; else - SIG1 = {sig1}; + SIG1 = {sig1}; end - - if this.AliasMap.isKey(sig2) - SIG2 = [{sig2} this.AliasMap(sig2)]; + + if this.AliasMap.isKey(sig2) + SIG2 = [{sig2} this.AliasMap(sig2)]; else - SIG2 = {sig2}; + SIG2 = {sig2}; end - - % add aliases of each other + + % add aliases of each other if ~this.AliasMap.isKey(sig1) this.AliasMap(sig1) = SIG2; else - this.AliasMap(sig1) = unique([this.AliasMap(sig1) SIG2], 'stable'); + this.AliasMap(sig1) = unique([this.AliasMap(sig1) SIG2], 'stable'); end if ~this.AliasMap.isKey(sig2) this.AliasMap(sig2) = SIG1; else - this.AliasMap(sig2) = unique([this.AliasMap(sig2) SIG1], 'stable'); - end + this.AliasMap(sig2) = unique([this.AliasMap(sig2) SIG1], 'stable'); + end end else error('SetSignalMap:wrong_arg', arg_err_msg); - end + end end - + end - + function ResetSigMap(this) this.sigMap = containers.Map(); this.sigMapInv = containers.Map(); - this.AliasMap =containers.Map(); + this.AliasMap =containers.Map(); end function PrintSigMap(this) st = 'Signals Map:\n'; - + keys = this.sigMap.keys; for ip = 1:numel(keys) st = sprintf([ st '%s ---> %s\n' ],keys{ip}, this.sigMap(keys{ip})); end fprintf(st); - + end - + function traces = GetTraces(this) % Get computed trajectories traces= []; @@ -892,18 +828,18 @@ function PrintSigMap(this) traces = this.P.traj; end end - + function [idx_ok, idx_sim_error, idx_invalid_input, st_status] = GetTraceStatus(this) % BreachSet.GetTraceStatus returns indices of ok traces, error and input invalid. idx_ok = []; idx_sim_error = []; idx_invalid_input = []; - + if this.hasTraj nb_pts = size(this.P.pts, 2); idx_ok = 1:nb_pts; if isfield(this.P.traj{1}, 'status') - + traj_status = zeros(1, nb_pts); for it = 1:nb_pts if this.P.traj_ref(it) @@ -915,7 +851,7 @@ function PrintSigMap(this) idx_invalid_input = find(traj_status == -2); end end - + st_status = ''; if ~isempty(idx_sim_error) st_status = sprintf('%s %d samples caused a simulation error.', st_status, numel(idx_sim_error)); @@ -923,38 +859,38 @@ function PrintSigMap(this) if ~isempty(idx_invalid_input) st_status = sprintf('%s %d simulations skipped for invalid inputs.', st_status, numel(idx_invalid_input)); end - + end - + function dispTraceStatus(this) [~,~, ~, st_status] = GetTraceStatus(this); if ~isempty(st_status) fprintf('%s\n', st_status); end end - + function [Bok, Bsim_error, Binvalid_input] = FilterTraceStatus(this) % BreachSet.FilterTraceStatus() Removes and extract traces with simulator errors or input not satisfying constraints % - + Bok = []; Bsim_error =[]; Binvalid_input = []; [idx_ok, idx_sim_error, idx_invalid_input] = GetTraceStatus(this); - + if ~isempty(idx_ok) Bok = this.ExtractSubset(idx_ok); end - + if ~isempty(idx_sim_error) Bsim_error = this.ExtractSubset(idx_sim_error); end - + if ~isempty(idx_invalid_input) Binvalid_input = this.ExtractSubset(idx_invalid_input); end end - + function val = UpdateSignalRanges(this) % Update ranges for variables from trajectories in P if isfield(this.P, 'traj') @@ -980,35 +916,35 @@ function dispTraceStatus(this) this.SignalRanges = [minX, maxX]; this.P.SignalRanges = this.SignalRanges; % duplicate - never good for sure... end - + end - + function SigNames = GetSignalNames(this) % Get signal names - same as GetSignalList SigNames = this.GetSignalList(); end - + function [signals, idx] = GetSignalList(this) % GetSignalList returns signal names signals = this.P.ParamList(1:this.P.DimX); idx = 1:this.P.DimX; end - + function SigNames = GetAllSignalsList(this) - % GetAllSignalsList returns all signals names including aliases + % GetAllSignalsList returns all signals names including aliases SigNames = this.expand_signal_name('.*'); end - + function X = GetSignalValues(this, signals, itrajs, t) % BreachSet.GetSignalValues(signals, idx_traces, time) - in case of several trajectories, return cell array if (~isfield(this.P,'traj')) error('GetTrajValues:NoTrajField','Compute/import trajectories first.') end - + if ischar(signals) signals = {signals}; end - + if iscell(signals) [signals_idx, type] = this.FindSignalsIdx(signals); if any(type==0) @@ -1025,13 +961,13 @@ function dispTraceStatus(this) else error('GetSignalValues:signals_type', 'signals should be a string or cell of string or numeric array ') end - + if ~exist('itrajs','var') itrajs= 1:numel(this.P.traj); end - + nb_traj = numel(itrajs); - + X = cell(nb_traj,1); for i_traj = 1:numel(itrajs) traj = this.P.traj{itrajs(i_traj)}; @@ -1049,7 +985,7 @@ function dispTraceStatus(this) X = X{1}; end end - + function [idx, ifound] = FindSignalsIdx(this, signals) % resolve sigMap if ischar(signals) @@ -1057,11 +993,11 @@ function dispTraceStatus(this) end % For all signals, first use sigMap-less search, then check % aliases - + idx = zeros(1, numel(signals)); - ifound = idx; + ifound = idx; for isig = 1:numel(signals) - sig = signals{isig}; + sig = signals{isig}; [idx(isig), ifound(isig)] = FindParam(this.P, sig); if this.AliasMap.isKey(sig) aliases_sig = this.AliasMap(sig); @@ -1075,14 +1011,14 @@ function dispTraceStatus(this) end end end - + function SaveSignals(this, signals, folder, name,i_trajs) % BreachSet SaveSignals Save signals in mat files as simple time series - + if ~this.hasTraj() error('No signals computed for this set.' ); end - + % arguments if ~exist('signals','var')||isempty(signals) signals = this.GetSignalList(); @@ -1096,7 +1032,7 @@ function SaveSignals(this, signals, folder, name,i_trajs) if ~exist('i_trajs','var')||isempty(i_trajs) i_trajs = 1:numel(this.P.traj); end - + [success,msg,msg_id] = mkdir(folder); if success == 1 if isequal(msg_id, 'MATLAB:MKDIR:DirectoryExists') @@ -1107,7 +1043,7 @@ function SaveSignals(this, signals, folder, name,i_trajs) else error(['Couldn''t create folder' folder '. mkdir returned error: ' msg]); end - + [sys_param_list, sys_param_idx] = this.GetSysParamList(); for ip = 1:numel(i_trajs) fname = [folder filesep name num2str(ip) '.mat']; @@ -1127,30 +1063,36 @@ function SaveSignals(this, signals, folder, name,i_trajs) end end end - + end - + function h = PlotSignals(this, varargin) % Plot signals if (~isfield(this.P,'traj')) error('No signal to plot. Use Sim command first.') end - + gca; h = SplotVar(this.P, varargin{:}); - + end - + function h = PlotSigPortrait(this, varargin) % Plot signals, phase portrait if (~isfield(this.P,'traj')) error('No signal to plot. Use Sim command first.') end - + gca; SplotTraj(this.P, varargin{:}); end - + + function h= PlotSurf(this) + + + end + + %% Sampling function SampleDomain(this, params, num_samples, method, opt_multi, max_num_samples) % BreachSet.SampleDomain generic sampling function @@ -1172,15 +1114,15 @@ function SampleDomain(this, params, num_samples, method, opt_multi, max_num_samp % % B.SampleDomain(...,...,..., 'combine') % - + % process parameters if ischar(params) params = {params}; - elseif isnumeric(params) + elseif isnumeric(params) switch nargin - case 2 % Sample randomly - case 3 - method = num_samples; + case 2 % Sample randomly + case 3 + method = num_samples; case 4 opt_multi = method; method = num_samples; @@ -1189,35 +1131,35 @@ function SampleDomain(this, params, num_samples, method, opt_multi, max_num_samp opt_multi = method; method = num_samples; end - num_samples =params; + num_samples =params; params = this.GetVariables(); end - + idx_param = FindParam(this.P, params); - + % if we have traces, we'll need to save and restore them saved_traj = false; if this.hasTraj() saved_traj = true; P0 = this.P; end - + domains = this.Domains(idx_param); domains = num2cell(domains); % convert array to cell - + if isempty(domains) error('sample:empty_domain', 'Domain to sample is empty or undefined.'); end - - + + if ~exist('method')||isempty(method) method = 'rand'; end - + if ~exist('opt_multi') opt_multi='replace'; % end - + if isequal(method, 'quasi-random')&&(~isequal(num_samples, 'all')|| (iscell(num_samples)&&any(strcmp(num_samples, 'all')))) % FIXME can do better Pold = this.P; @@ -1233,11 +1175,11 @@ function SampleDomain(this, params, num_samples, method, opt_multi, max_num_samp else x = sample(domains{:}, num_samples, method); end - + else x = sample(domains{:}, num_samples, method); end - + % combine (or not) with others num_old = this.GetNbParamVectors(); switch opt_multi @@ -1250,7 +1192,7 @@ function SampleDomain(this, params, num_samples, method, opt_multi, max_num_samp this.ResetParamSet(); this.SetParam(params, x, true); end - + case 'append' % FIXME deep copy here maybe not smartest Btmp = this.copy(); if num_old==1 @@ -1260,7 +1202,7 @@ function SampleDomain(this, params, num_samples, method, opt_multi, max_num_samp Btmp.SetParam(params, x, true); this.Concat(Btmp); end - + case 'combine' num_new = size(x,2); if exist('max_num_samples', 'var') @@ -1273,14 +1215,14 @@ function SampleDomain(this, params, num_samples, method, opt_multi, max_num_samp this.ResetParamSet(); this.SetParam(1:size(pts,1),pts, true); end - + % restore traj if needed if saved_traj this.P = Pimport_traj(this.P, P0); end - + end - + %% Legacy sampling function GridSample(this, delta) % BreachSet.GridSample(num_samples) sample all bounded domain @@ -1293,83 +1235,83 @@ function GridSample(this, delta) else this.SampleDomain(bnd_params, delta, 'grid'); end - + end - + % Get corners of parameter domain function CornerSample(this, max_num_samples) - + if nargin ==1 max_num_samples = inf; end - + bnd_params = this.GetBoundedDomains(); if this.AppendWhenSample this.SampleDomain(bnd_params, 2, 'corners', 'append', max_num_samples); else this.SampleDomain(bnd_params,2, 'corners', 'replace', max_num_samples); end - - - + + + end - + function QuasiRandomSample(this, nb_sample, step) % Quasi-Random Sampling - + if this.AppendWhenSample Pold = this.P; end - + this.ResetParamSet(); if nargin==3 newP = QuasiRefine(this.P,nb_sample, step); else newP = QuasiRefine(this.P, nb_sample); end - + if this.AppendWhenSample this.P = SConcat(Pold, newP); else this.P = newP; - end + end this.CheckinDomainParam(); this.ApplyParamGens(); end - + function PseudoRandomSample(this, nb_sample) % Pseudo-random sampling - + this.ResetParamSet(); - + newP = TestronRefine(this.P, nb_sample); - - this.P = newP; + + this.P = newP; this.CheckinDomainParam(); this.ApplyParamGens(); end - + function Bm = MorrisSample(this, vars, ranges, num_path, size_grid, seed) - + if isempty(vars) - vars = this.GetVariables(); + vars = this.GetVariables(); end - + if isempty(ranges) - ranges = this.GetParamRanges(vars); + ranges = this.GetParamRanges(vars); end - + if isequal(size(ranges), [2 1]) - ranges = [1 2]; + ranges = [1 2]; end - + if isequal(size(ranges), [1 2]) ranges = repmat(ranges, numel(vars),1); end - - + + Bm = BreachSet(vars); - Bm.SetParamRanges(vars, ranges); + Bm.SetParamRanges(vars, ranges); Bm.P.epsi = ((ranges(:,2)+ranges(:,1))/2)'; % legacy stuff Pr = pRefine(Bm.P, size_grid,num_path,seed); X0 = Pr.pts; @@ -1381,56 +1323,55 @@ function PseudoRandomSample(this, nb_sample) Bm.P = Pr; end end - - %% Concatenation, ExtractSubset - needs some additional compatibility checks... - + + %% Concatenation, ExtractSubset - needs some additional compatibility checks... function Concat(this, other, fast) if nargin<=2 - fast = false; + fast = false; end this.P = SConcat(this.P, other.P, fast); end - + function other = ExtractSubset(this, idx) other = this.copy(); other.P = Sselect(this.P, idx); end - + function SavedTrajectorySample(this, paramValues) % TESTRON % Fixes the "sampling" for a stored trajectory - + this.ResetParamSet(); - + newP = SavedTrajectoryRefine(this.P, paramValues); - + this.P = newP; this.CheckinDomainParam(); end - - - + + + %% Plot parameters function ax= PlotPts(this, params, ax, varargin) % Simple plot function. if ~exist('params','var') params = this.GetParamList(); end - + if ~exist('ax','var') ax = gca; end - + if isempty(varargin) varargin = {'bx'}; end - + switch numel(params) case 1 values = this.GetParam(params); axes(ax); plot(values, 0*values, varargin{:}); - + case 2 values = this.GetParam(params); axes(ax); @@ -1442,21 +1383,21 @@ function SavedTrajectorySample(this, paramValues) plot3(values(1,:), values(2,:), values(3,:), varargin{:}); end end - + function PlotParams(this, varargin) % Plot parameters gca; %P = DiscrimPropValues(this.P); params = SplotPts(this.P, varargin{:}); - + %% Datacursor mode customization h = datacursormode(gcf); h.UpdateFcn = @myupdatefcn; h.SnapToDataVertex = 'on'; datacursormode on - - function [txt] = myupdatefcn(obj,event_obj) - + + function [txt] = myupdatefcn(~,event_obj) + pos = event_obj.Position; switch numel(params) case 1 @@ -1466,7 +1407,7 @@ function PlotParams(this, varargin) txt = {[params{1} ':' num2str(pos(1))],... [params{2} ': ',num2str(pos(2))],... }; - + case 3 txt = {[params{1} ':' num2str(pos(1))],... [params{2} ': ',num2str(pos(2))],... @@ -1474,6 +1415,14 @@ function PlotParams(this, varargin) }; end end + + end + + function ax = PlotBoxPts(this, varargin) + % legacy plot box function. Rescucitated for grids returned by + % GridFilter and GridFilterSignals + ax = gca; + SplotBoxPts(this.P, varargin{:}); end @@ -1481,23 +1430,23 @@ function PlotParams(this, varargin) function PlotDomain(this, params) % BreachSet.PlotMixedDomain UNFINISHED, tries to plot enum/int % domain differently from dense domains - + gca; set(gca, 'XLimMode', 'auto', 'YLimMode', 'auto', 'ZLimMode', 'auto') - + % default style col = [0 0 1]; alpha = 0.05; - + % default params if ~exist('params', 'var') || isempty('params') params =this.GetBoundedDomains(); end - + if ischar(params) params = {params}; end - + switch numel(params) case 1 % one domain domain1 = this.GetDomain(params); @@ -1506,7 +1455,7 @@ function PlotDomain(this, params) xlabel(params{1}, 'Interpreter', 'none'); set(gca, 'YTick', []); grid on; - + %% unzoom xlim = get(gca, 'XLim'); dxlim = xlim(2) - xlim(1); @@ -1514,15 +1463,15 @@ function PlotDomain(this, params) case 2 % two domains domain1 = this.GetDomain(params{1}); domain2 = this.GetDomain(params{2}); - + start = [domain1.domain(1), domain2.domain(1)]; sz = [domain1.domain(2) - domain1.domain(1),domain2.domain(2) - domain2.domain(1)]; d= rect(start, sz, col,alpha); - + xlabel(params{1}, 'Interpreter', 'none'); ylabel(params{2}, 'Interpreter', 'none'); grid on; - + %% Unzoom slightly xlim = get(gca, 'XLim'); dxlim = xlim(2) - xlim(1); @@ -1530,22 +1479,22 @@ function PlotDomain(this, params) ylim = get(gca, 'YLim'); dylim = ylim(2) - ylim(1); set(gca, 'YLim', [ylim(1)-dylim/30, ylim(2)+dylim/30]); - + otherwise - + domain1 = this.GetDomain(params{1}); domain2 = this.GetDomain(params{2}); domain3 = this.GetDomain(params{3}); - + start = [domain1.domain(1), domain2.domain(1), domain3.domain(1)]; sz = [domain1.domain(2) - domain1.domain(1),domain2.domain(2) - domain2.domain(1),domain3.domain(2) - domain3.domain(1)]; d= voxel(start, sz, col, alpha); - + xlabel(params{1}, 'Interpreter', 'none'); ylabel(params{2}, 'Interpreter', 'none'); zlabel(params{3}, 'Interpreter', 'none'); grid on; - + xlim = get(gca, 'XLim'); dxlim = xlim(2) - xlim(1); set(gca, 'XLim', [xlim(1)-dxlim/30, xlim(2)+dxlim/30]); @@ -1558,8 +1507,8 @@ function PlotDomain(this, params) view(-37.5, 30); end set(d,'EdgeAlpha',0.1); - - + + % 1d x direction function plotxdomain(dom, y0) width_y = .1; @@ -1568,32 +1517,32 @@ function plotxdomain(dom, y0) d= rect(start, sz, col, alpha); set(gca, 'YLim', [y0-10*width_y, y0+10*width_y]); end - + end - + function PlotMixedDomain(this, params) % BreachSet.PlotMixedDomain UNFINISHED, tries to plot enum/int % domain differently from dense domains - + gca; - + % default style col = [0 0 1]; alpha = 0.03; pts_style = 'sb'; - + % default params if ~exist('params', 'var') || isempty('params') params =this.GetBoundedDomains(); end - + if ischar(params) params = {params}; end - + switch numel(params) case 1 % one domain - + domain1 = this.GetDomain(params); plotxdomain(domain1,0); % Labels @@ -1603,12 +1552,12 @@ function PlotMixedDomain(this, params) xlim = get(gca, 'XLim'); dxlim = xlim(2) - xlim(1); set(gca, 'XLim', [xlim(1)-dxlim/10, xlim(2)+dxlim/10]); - + case 2 % two domains domain1 = this.GetDomain(params{1}); domain2 = this.GetDomain(params{2}); if isempty(domain1.enum) % domain1 is dense - + if isempty(domain2.enum) % domain2 is dense start = [domain1.domain(1), domain2.domain(1)]; sz = [domain1.domain(2) - domain1.domain(1),domain2.domain(2) - domain2.domain(1)]; @@ -1620,9 +1569,9 @@ function PlotMixedDomain(this, params) plotxdomain( domain1, y(i_y) ); end end - + else % domain1 not dense - + if isempty(domain2.enum) % domain2 is dense x = domain1.sample_all(); hold on; @@ -1634,7 +1583,7 @@ function PlotMixedDomain(this, params) plot(X(1,:), X(2,:), pts_style); end end - + xlabel(params{1}, 'Interpreter', 'none'); ylabel(params{2}, 'Interpreter', 'none'); grid on; @@ -1644,24 +1593,24 @@ function PlotMixedDomain(this, params) ylim = get(gca, 'YLim'); dylim = ylim(2) - ylim(1); set(gca, 'YLim', [ylim(1)-dylim/10, ylim(2)+dylim/10]); - + case 3 - + domain1 = this.GetDomain(params{1}); domain2 = this.GetDomain(params{2}); domain3 = this.GetDomain(params{3}); - + if ~isempty(domain1.enum)&&~isempty(domain2.enum)&&~isempty(domain3.enum) X = sample( domain1, domain2, domain3, 'all'); plot3(X(1,:), X(2,:), X(3,:), pts_style); - + else start = [domain1.domain(1), domain2.domain(1), domain3.domain(1)]; sz = [domain1.domain(2) - domain1.domain(1),domain2.domain(2) - domain2.domain(1),domain3.domain(2) - domain3.domain(1)]; voxel(start, sz, col, alpha); end % TODO missing cases combining enum and dense - + xlabel(params{1}, 'Interpreter', 'none'); ylabel(params{2}, 'Interpreter', 'none'); zlabel(params{3}, 'Interpreter', 'none'); @@ -1677,7 +1626,7 @@ function PlotMixedDomain(this, params) set(gca, 'ZLim', [zlim(1)-dzlim/10, zlim(2)+dzlim/10]); view(-37.5, 30); end - + % 1d x direction function plotxdomain(dom, y0) if isempty(dom.enum) % it's a dense box @@ -1689,7 +1638,7 @@ function plotxdomain(dom, y0) plot(x, 0*x+y0, pts_style); end end - + % 1d y-direction function plotydomain(dom, x0) if isempty(dom.enum) % it's a dense box @@ -1702,97 +1651,1753 @@ function plotydomain(dom, x0) end end end - + function cfg= GetDomainCfg(this) params = this.GetParamList(); cfg.params = params; for ip = 1:numel(params) - dom = this.GetDomain(params{ip}); - cfg.types{ip} = dom.type; + dom = this.GetDomain(params{ip}); + cfg.types{ip} = dom.type; cfg.values{ip} = this.GetParam(params{ip},1); if strcmp(dom.type, 'enum') cfg.domains{ip}=dom.enum; else cfg.domains{ip}=dom.domain; end - end - end - - %% Coverage - function [cnt, grd1, grd2] = GetSignalCoverage(this,sigs, delta1,delta2) - % 1d or 2d - X = this.GetSignalValues(sigs); - - switch (numel(sigs)) - case 1 - [cnt, grd1] = cover(X,delta1); - case 2 - [cnt, grd1, grd2] = cover2d(X,delta1,delta2); - otherwise - error('Coverage for more than 2 signals is not supported'); end end - - function [cnt, grd1, grd2] = PlotSignalCoverage(this,sigs, delta1,delta2) - % 1d or 2d - X = this.GetSignalValues(sigs); - switch (numel(sigs)) - case 1 - [cnt, grd1] = cover(X,delta1); - X = grd1+delta1/2; % centers of bins - bar(X,cnt) - set(gca, 'XTick', round([grd1 grd1(end)+delta1])) - title('number of samples per bin') - xlabel(sigs{1}); - case 2 - [cnt, grd1, grd2] = cover2d(X,delta1,delta2); - X = grd1+delta1/2; % centers of bins - Y = grd2+delta2/2; % centers of bins - figure; - b= bar3(cnt'); - set(gca,'XTickLabel', round([X X(end)+delta1]) ) - set(gca,'YTickLabel', round([Y Y(end)+delta2]) ) - %surface(grd2, grd1, cnt); - xlabel(sigs{1}); - ylabel(sigs{2}); - colormap(jet); - colorbar; - - for k = 1:length(b) - zdata = b(k).ZData; - b(k).CData = zdata; - b(k).FaceColor = 'interp'; - end - title('num. samples per grid element') - otherwise - error('Coverage for more than 2 signals is not supported'); - end + + %% Coverage + + function [cover_opts] = SetCoverageOptions(this,cover_opts, varargin) + % Create a structure objects for grid filtering and coverage computation options + + other_opts.ParamsMode = 'auto'; % 'auto' detects variable parameters and sets ranges manually if specified or + other_opts.SignalsMode = 'auto'; % 'off' means don't compute + other_opts.SignalsFreqsMode = 'off'; % freq domain off by default for now + + other_opts.IncludeParams = 'all'; + other_opts.ExcludeParams = {}; + + other_opts.IncludeSignals = 'all'; + other_opts.ExcludeSignals = {}; + + other_opts.GridsMode = 'auto'; % 'auto' means try to guess if it is a resolution or a size + % other choices are + % 'size_cells', 'num_cells' + + + if nargin>1&&ischar(cover_opts) + other_opts = varargin2struct_breach(other_opts,cover_opts, varargin{:}); + cover_opts = []; + else + other_opts = varargin2struct_breach(other_opts, varargin{:}); + end + + if ~exist('cover_opts', 'var')||isempty(cover_opts) + cover_opts =struct(); + grid_spec = 10; + elseif isnumeric(cover_opts) + grid_spec = cover_opts; + cover_opts = struct; + else + grid_spec = 10; % default grid spec for when it is not specified by cover_opts already + end + + %% parameters coverage + if ~isequal(other_opts.ParamsMode, 'off' ) + + % if parameters are specified in opts, take those, + % otherwise take all variables + if isfield(cover_opts, 'params') + params = fieldnames(cover_opts.params)'; + elseif isequal(other_opts.IncludeParams,'all') + params = this.GetVariables(); + else + params = this.expand_param_name(other_opts.IncludeParams); + end + + if ~isempty(other_opts.ExcludeParams) + exclude_list = this.expand_param_name(other_opts.ExcludeParams); + params = setdiff(params,exclude_list, 'stable'); + end + + [idx_params, found] = FindParam(this.P,params); + if ~isempty(params) + if ~isfield(cover_opts,'params') + cover_opts.params = struct(); + end + + for ip = 1:numel(params) + this_param = params{ip}; + if ~found(ip) + warning('Parameter %s not found, ignored', this_param); + continue + end + idx_pts = idx_params(ip); + this_domain = this.Domains(idx_pts); % existing breach_domain for this param + + % add the parameter if not in opts already + if ~isfield(cover_opts.params, this_param) + cover_opts.params.(this_param) =struct(); + end + + % checks its range + if ~isfield(cover_opts.params.(this_param),'range') + if ~isempty(this_domain.domain) + range = this_domain.domain; + else + Rmin = min(this.P.pts(idx_pts,:)); % could be more efficient with bool and enum but whatever + Rmax = max(this.P.pts(idx_pts,:)); + range = [Rmin, Rmax]; + end + else + range = cover_opts.params.(this_param).range; + end + + % checks grid specifications - we get the grid, then + % makes sure it intersects with the range (if it was + % specified) + if isfield(cover_opts.params.(this_param), 'grid') + grid =cover_opts.params.(this_param).grid; + elseif iscell(grid_spec) + try + grid = grid_spec{ip}; + catch + warning('Problem with grid_spec dimensions, using grid_spec(1)'); + grid = grid_spec{1}; + end + elseif isscalar(grid_spec) + grid = grid_spec; + else + try + grid = grid_spec(ip); + catch + warning('Problem with grid_spec dimensions, trying grid_spec(1)'); + grid = grid_spec(1); + end + end + cover_opts.params.(this_param).grid = grid; + % if enum is not included in range, intersect it + % this is where we checks consistency with original + % type if defined + switch this_domain.type + case 'bool' + enum = [0 1]; + range = [0 1]; + this_domain.domain = [0 1]; % not automatic I believe + eps = enum*0+.1; + case 'int' % here we consider the grid should be all integers between min and max, regardless of grid spec + this_domain.domain = range; + this_domain.enum = range(1):range(2); + enum = this_domain.enum; + eps = enum*0+.1; + case 'enum' % we exclude the enum outside the range + this_domain.domain = range; + if min(this_domain.enum) < range(1) + this_domain.enum = this_domain.enum(this_domain.enum>= range(1)); + end + if max(this_domain.enum) > range(2) + this_domain.enum = this_domain.enum(this_domain.enum <= range(2)); + end + enum = this_domain.enum; + eps = min(diff(enum))/10; + eps = repmat(eps, 1, numel(enum)); + case 'double' + % translates grid spec (ex delta) into enum + switch other_opts.GridsMode + case 'auto' + if isscalar(grid)&&grid==floor(grid)&&grid>1 + eps = (range(2)-range(1))/(grid*2); + enum = linspace(range(1)+eps,range(2)-eps, grid); % delta samples in dim ip + eps = repmat(eps, 1, numel(enum)); + elseif isscalar(grid)&&grid>0 + eps = min([grid/2, (range(2)-range(1))/2]); + num_cell = floor((range(2)-range(1))/grid); + start_enum = range(1)+((range(2)-range(1)) -num_cell*grid)/2 ; % ensure the grid is centered wrt range, inside range and cells cover range + enum = start_enum:grid:range(2); % grid is resolution + eps = repmat(eps, 1, numel(enum)); + else + % we need to adjust (center) + eps = diff(grid)/2; + enum = grid(2:end)-eps; + end + case 'num_cells' + eps = (range(2)-range(1))/(grid*2); + enum = linspace(range(1)+eps,range(2)-eps, grid); % delta samples in dim ip + eps = repmat(eps, 1, numel(enum)); + + case 'size_cells' + eps = grid/2; + num_cell = floor((range(2)-range(1))/grid); + start_enum = range(1)+((range(2)-range(1)) -num_cell*grid)/2 ; % ensure the grid is centered wrt range, inside range and cells cover range + enum = start_enum:grid:range(2); % grid is resolution + eps = repmat(eps, 1, numel(enum)); + + otherwise + error('Wrong mode ''%s'' for GridsMode, should be ''auto'',''num_cells'' or ''size_cells''', other_opts.GridsMode); + end + + this_domain = BreachDomain('enum', range, enum); + end + + % feed back enum as grid -- not. ( problematic with + % custom grid) + cover_opts.params.(this_param).range = range; + %cover_opts.params.(this_param).grid = enum; + cover_opts.params.(this_param).eps = eps; + cover_opts.params.(this_param).breach_domain = this_domain; + + end + for size_proj = 1:min(3, numel(params)) % default max projection size is 3 + cover_opts.projections{size_proj} = get_projections(params,size_proj); + end + end + else + if isfield(cover_opts,'params') + cover_opts = rmfield(cover_opts,'params'); + end + end + + + %% Signals + if ~isequal(other_opts.SignalsMode, 'off' )&&this.hasTraj() + + + % if signals are specified in opts, take those, + % otherwise take all + + if isfield(cover_opts, 'signals') + signals = fieldnames(cover_opts.signals)'; + elseif isequal(other_opts.IncludeSignals,'all') + signals = this.GetSignalList(); + else + signals = this.expand_signal_name(other_opts.IncludeSignals); + end + + if ~isempty(other_opts.ExcludeSignals) + exclude_list = this.expand_signal_name(other_opts.ExcludeSignals); + signals = setdiff(signals,exclude_list, 'stable'); + end + + % time range + if ~isfield(cover_opts,'signals') + cover_opts.signals = struct(); + end + if ~isfield(cover_opts.signals, 'time') + cover_opts.signals.time = struct(); + end + + % default time range + time = this.GetTime(); + time_range= [time(1) time(end)]; + if ~isfield(cover_opts.signals.time,'range') + cover_opts.signals.time.range = time_range; + else + range = cover_opts.signals.time.range; + if size(range,2)~=2 + warning('Invalid time range, using default.'); + cover_opts.signals.time.range = time_range; + end + end + + %% time grid + if isfield(cover_opts.signals.time, 'grid') + time_grid = cover_opts.signals.time.grid; + else + time_grid = 10; + end + + if isscalar(time_grid)&&time_grid==floor(time_grid)&&time_grid>1 + eps = (time_range(2)-time_range(1))/(time_grid*2); + enum = linspace(time_range(1)+eps,time_range(2)-eps, time_grid); % delta samples in dim ip + eps = repmat(eps, 1, numel(enum)); + elseif isscalar(time_grid)&&time_grid>0 + eps = time_grid/2; + num_cell = floor((range(2)-range(1))/time_grid); + start_enum = range(1)+((range(2)-range(1)) -num_cell*time_grid)/2 ; % ensure the grid is centered wrt range, inside range and cells cover range + enum = start_enum:time_grid:range(2); % grid is resolution + eps = repmat(eps, 1, numel(enum)); + else + eps = diff(time_grid)/2; + enum = time_grid; + end + + cover_opts.signals.time.range = time_range; + cover_opts.signals.time.grid = enum; + cover_opts.signals.time.eps = eps; + cover_opts.signals.time.breach_domain = BreachDomain('enum', time_range, enum); + + %% signals range and grid + [~, found_signals] = FindParam(this.P, signals); + for ip = 1:numel(signals) + this_signal = signals{ip}; + if ~found_signals(ip) + warning('Signal %s not found, ignore and continue (at your own risk).', this_signal); + end + + this_domain = this.GetDomain(this_signal); % existing breach_domain for this signal + + % add the signal if not in opts already + if ~isfield(cover_opts.signals, this_signal) + cover_opts.signals.(this_signal) =struct(); + end + + % checks its range + if ~isfield(cover_opts.signals.(this_signal),'range') + if ~isempty(this_domain.domain) + range = this_domain.domain; + else + sig_values = this.GetSignalValues(this_signal); + if ~iscell(sig_values) + sig_values = {sig_values}; + end + Rmin = inf; + Rmax =-inf; + for itraj = 1:numel(sig_values) + Rmin = min(Rmin, min(sig_values{itraj})); + Rmax = max(Rmax, max(sig_values{itraj})); + end + range = [Rmin, Rmax]; + end + else + range = cover_opts.signals.(this_signal).range; + end + + % checks grid specifications - we get the grid, then + % makes sure it intersects with the range (if it was + % specified) + if isfield(cover_opts.signals.(this_signal), 'grid') + grid =cover_opts.signals.(this_signal).grid; + elseif iscell(grid_spec) + try + grid = grid_spec{ip}; + catch + warning('Problem with grid_spec dimensions for %s, trying grid_spec{1}',this_signal); + grid = grid_spec{1}; + end + elseif isscalar(grid_spec) + grid = grid_spec; + else + try + grid = grid_spec(ip); + catch + warning('Problem with grid_spec dimensions for %s, trying grid_spec{1}',this_signal); + grid = grid_spec(1); + end + end + + + % if enum is not included in range, intersect it + % this is where we checks consistency with original + % type if defined + switch this_domain.type + case 'bool' + enum = [0 1]; + range = [0 1]; + this_domain.domain = [0 1]; % not automatic I believe + case 'int' % here we consider the grid should be all integers between min and max, regardless of grid spec + this_domain.domain = range; + this_domain.enum = range(1):range(2); + enum = this_domain.enum; + eps = enum*0+.1; + case 'enum' % we exclude the enum outside the range + this_domain.domain = range; + if min(this_domain.enum) < range(1) + this_domain.enum = this_domain.enum(this_domain.enum>= range(1)); + end + if max(this_domain.enum) > range(2) + this_domain.enum = this_domain.enum(this_domain.enum <= range(2)); + end + enum = this_domain.enum; + eps = min(diff(enum))/10; + eps = repmat(eps, 1, numel(enum)); + case 'double' + % translates grid spec (ex delta) into enum + switch other_opts.GridsMode + case 'auto' + if isscalar(grid)&&grid==floor(grid)&&grid>1 + eps = (range(2)-range(1))/(grid*2); + enum = linspace(range(1)+eps,range(2)-eps, grid); % delta samples in dim ip + eps = repmat(eps, 1, numel(enum)); + elseif isscalar(grid)&&grid>0 + eps = grid/2; + num_cell = floor((range(2)-range(1))/grid); + start_enum = range(1)+((range(2)-range(1)) -num_cell*grid)/2 ; % ensure the grid is centered wrt range, inside range and cells cover range + enum = start_enum:grid:range(2); % grid is resolution + eps = repmat(eps, 1, numel(enum)); + else + % we need to adjust (center) + eps = diff(grid)/2; + enum = grid(2:end)-eps; + end + case 'num_cells' + eps = (range(2)-range(1))/(grid*2); + enum = linspace(range(1)+eps,range(2)-eps, grid); % delta samples in dim ip + eps = repmat(eps, 1, numel(enum)); + + case 'size_cells' + eps = grid/2; + num_cell = floor((range(2)-range(1))/grid); + start_enum = range(1)+((range(2)-range(1)) -num_cell*grid)/2 ; % ensure the grid is centered wrt range, inside range and cells cover range + enum = start_enum:grid:range(2); % grid is resolution + eps = repmat(eps, 1, numel(enum)); + + otherwise + error('Wrong mode ''%s'' for GridsMode, should be ''auto'',''num_cells'' or ''size_cells''', other_opts.GridsMode); + end + this_domain = BreachDomain('enum', range, enum); + end + + % feed back enum as grid (or not ? ) + cover_opts.signals.(this_signal).range = range; + cover_opts.signals.(this_signal).grid = enum; + cover_opts.signals.(this_signal).eps =eps; + cover_opts.signals.(this_signal).breach_domain = this_domain; + + end + + % Projections + for size_proj = 1:min(3, numel(signals)) % default max projection size is 3 + cover_opts.signals_projections{size_proj} = get_projections(signals,size_proj); + end + + for size_proj = 1:min(3, numel(signals)) % time projections, append time before the above + cover_opts.time_signals_projections{size_proj} = cellfun( @(c) (['time' c ]), cover_opts.signals_projections{size_proj} , 'UniformOutput', false); + end + else + if isfield(cover_opts,'signals') + cover_opts = rmfield(cover_opts,'signals'); + end + end + + if ~isequal(other_opts.SignalsFreqsMode, 'off' )&&this.hasTraj() + % freqs ranges and grid + if ~isfield(cover_opts, 'signals_freqs') + cover_opts.signals_freqs = struct(); + end + + % get target frequency range from time + traj = this.P.traj{1}; % assumes uniform traces.... + Ts = min(diff(traj.time)); + time = 0:Ts:traj.time(end); + if rem(numel(time),2) + time = time(1:end-1); % ensures pair number of samples for FFT + end + Fs = 1/Ts; + %L = numel(time); + fs_max = min(100, Fs/2); % Max a hundred hertz unless time step is too large for that + freq_range = [0 fs_max]; + + signals=this.GetSignalList(); + for ip = 1:numel(signals) + this_signal = [signals{ip} '_freqs']; + + if ~isfield(cover_opts.signals_freqs,this_signal) + cover_opts.signals_freqs.(this_signal) = struct(); + end + + % range: default unless specified otherwise + if ~isfield(cover_opts.signals_freqs.(this_signal),'range') + cover_opts.signals_freqs.(this_signal).range = freq_range; + else + freq_range = cover_opts.signals_freqs.(this_signal).range; + end + + % grid spec - as for double domains, and defaults to 10 + if isfield(cover_opts.signals_freqs.(this_signal), 'grid') + freq_grid = cover_opts.signals_freqs.(this_signal).grid; + elseif iscell(grid_spec) + try + freq_grid = grid_spec{ip}; + catch + warning('Problem with grid_spec dimensions for %s, trying grid_spec{1}',this_signal); + freq_grid = grid_spec{1}; + end + elseif isscalar(grid_spec) + freq_grid = grid_spec; + else + try + freq_grid = grid_spec(ip); + catch + warning('Problem with grid_spec dimensions for %s, trying grid_spec{1}',this_signal); + freq_grid = grid_spec(1); + end + end + + + switch other_opts.GridsMode + case 'auto' + + if isscalar(freq_grid)&&freq_grid==floor(freq_grid)&&freq_grid>1 + eps = (freq_range(2)-freq_range(1))/(freq_grid*2); + enum = linspace(freq_range(1)+eps,freq_range(2)-eps, freq_grid); % delta samples in dim ip + eps = repmat(eps, 1, numel(enum)); + elseif isscalar(freq_grid)&&freq_grid>0 + eps = freq_grid/2; + num_cell = floor((range(2)-range(1))/freq_grid); + start_enum = range(1)+((range(2)-range(1)) -num_cell*freq_grid)/2 ; % ensure the grid is centered wrt range, inside range and cells cover range + enum = start_enum:freq_grid:range(2); % grid is resolution + eps = repmat(eps, 1, numel(enum)); + else + eps = diff(freq_grid)/2; + enum = freq_grid; + end + case 'num_cells' + eps = (freq_range(2)-freq_range(1))/(freq_grid*2); + enum = linspace(freq_range(1)+eps,freq_range(2)-eps, freq_grid); % delta samples in dim ip + eps = repmat(eps, 1, numel(enum)); + + case 'size_cells' + eps = freq_grid/2; + num_cell = floor((range(2)-range(1))/freq_grid); + start_enum = range(1)+((range(2)-range(1)) -num_cell*freq_grid)/2 ; % ensure the grid is centered wrt range, inside range and cells cover range + enum = start_enum:freq_grid:range(2); % grid is resolution + eps = repmat(eps, 1, numel(enum)); + + otherwise + error('Wrong mode ''%s'' for GridsMode, should be ''auto'',''num_cells'' or ''size_cells''', other_opts.GridsMode); + end + + cover_opts.signals_freqs.(this_signal).range = freq_range; + cover_opts.signals_freqs.(this_signal).grid = enum; + cover_opts.signals_freqs.(this_signal).eps = eps; + cover_opts.signals_freqs.(this_signal).breach_domain = BreachDomain('enum', freq_range,enum); + + end + + signals_for_freqs_coverage = fieldnames(cover_opts.signals_freqs)'; + + for size_proj = 1:min(3, numel(signals_for_freqs_coverage)) + cover_opts.signals_freqs_projections{size_proj} = get_projections(signals_for_freqs_coverage,size_proj); + end + + end + + this.CoverOpts = cover_opts; + + %% Auxiliary functions + function projs = get_projections(params, n) + + if n==1 + projs= cell(1, numel(params)); + for idx_p= 1:numel(params) + projs{idx_p} =params(idx_p); + end + else + set_params_idx = nchoosek(1:numel(params),n); + size_set = size(set_params_idx,1); + projs = cell(1,size_set); + for idx_set= 1:size_set % for each combination + this_set_params_idx = set_params_idx(idx_set,:); + this_proj = cell(1, n); + for idx_p = 1:n + this_proj{idx_p} = params{this_set_params_idx(idx_p)}; + end + projs{idx_set} = this_proj; % parameters in this combination + end + end + end + + + end + + function [cov, Bg, Bs, Bgf] = GetCoverage(this, opts) + % Returns coverage structure, and occupancy grid for + % parameters and signals + + % Checks options. If empty or not given, checks this.CoverOpts + opts_is_same_as_this = false; + if nargin<=1||isempty(opts) + if ~isempty(this.CoverOpts) + opts = this.CoverOpts; + opts_is_same_as_this = true; + else + opts = this.SetCoverageOptions(); + end + end + + if ~opts_is_same_as_this % not sure yet + if isequal(opts,this.CoverOpts) + opts_is_same_as_this = true; % in fact it is + end + end + + this.CoverOpts = opts; % ok, will go with that. + + % checks if we have previous results, and they are consistent + if opts_is_same_as_this&&~isempty(this.CoverRes) + + pts_prev = this.P.pts(:, 1:this.CoverRes.num_samples); + hash_prev = DataHash(pts_prev); + if ~isequal(hash_prev, this.CoverRes.hash_samples) + this.CoverRes=[]; % not same as before, start over + end + end + + % previous results ? + if ~isempty(this.CoverRes) + cov = this.CoverRes; + else + cov = struct(); + Bg = []; + Bs = []; + Bgf = []; + end + + if isfield(opts,'params') + if ~isequal(cov, struct())% we got previous results + try + Bg = this.CoverRes.params.Bg; + catch + warning('Previous grid set absent ?'); % should not happen, for debug + Bg= []; + end + end + + Bg = this.GridFilter([], opts, Bg); + params= setdiff(Bg.GetVariables(),{'traj__ref__','count','idx'},'stable'); + + for iproj_set =1:numel(opts.projections) % loop over dimensions of projections + cov_dim = 0; + proj_set = opts.projections{iproj_set}; + + num_proj = numel(proj_set); + for iproj = 1:num_proj + this_proj= proj_set{iproj}; + this_cov = compute_coverage(this_proj); + cov_dim = cov_dim + this_cov; + end + cov_dim = cov_dim/num_proj; + cov.params.(['cov' num2str(iproj_set) 'd']) = cov_dim; + cov.params.Bg = Bg; + end + + end + + % signals + if isfield(opts,'signals') + + %% Todo incremental coverage from previous result + Bs_all = this.sig2params(); + opts_sigs.params = opts.signals; + opts_sigs.projections = opts.signals_projections; + num_sig_proj = numel( opts.signals_projections); + + timed_projs = opts.time_signals_projections; + num_time_sig_proj = numel( opts.time_signals_projections); + + opts_sigs.projections = [opts_sigs.projections timed_projs]; + [cov_signals, Bs] = Bs_all.GetCoverage(opts_sigs); + cov.signals = cov_signals.params; + + % rename aggregated measures + for ip = 1:num_sig_proj + old_name = ['cov' num2str(ip) 'd']; + name = ['cov' num2str(ip) 'd_sig'] ; + cov.signals.(name) = cov.signals.(old_name); + cov.signals = rmfield(cov.signals,old_name); + end + + for ip = 1:num_time_sig_proj + old_name = ['cov' num2str(ip+num_sig_proj) 'd']; + name = ['cov' num2str(ip) 'd_time_sig'] ; + cov.signals.(name) = cov.signals.(old_name); + cov.signals = rmfield(cov.signals,old_name); + end + cov.signals.Bg = Bs; + cov.signals.Bs_all = Bs_all; + end + + if isfield(opts,'signals_freqs') + sigs_freqs = fieldnames(opts.signals_freqs)'; + Bf = BreachSet(sigs_freqs); + for i=1:numel(sigs_freqs) + this_freq_sig = sigs_freqs{i} ; + Bf.SetParamRanges(this_freq_sig, opts.signals_freqs.(this_freq_sig).range); + end + sigs_names_for_freq_coverage = cellfun(@(c)(c(1:end-6)), sigs_freqs, 'UniformOutput',false); + sigs_values_for_freq_coverage = this.GetSignalValues(sigs_names_for_freq_coverage); + if ~iscell(sigs_values_for_freq_coverage) + sigs_values_for_freq_coverage={sigs_values_for_freq_coverage}; + end + + for i=1:numel(sigs_freqs) + this_freq_sig = sigs_freqs{i}; + cov_opts_for_freq_coverage.params.(this_freq_sig) = opts.signals_freqs.(this_freq_sig); + cov_opts_for_freq_coverage.projections = opts.signals_freqs_projections; + end + + % Apply FFT + for itraj = 1:numel(sigs_values_for_freq_coverage) + Bf_itraj = BreachSet(sigs_freqs); + time = this.P.traj{itraj}.time; + Ts = time(2)-time(1); + if any(diff(time)-Ts>1e-10) + error('Yup, irregular sampling time, time to implement this time interpolation again.'); + end + %time = 0:Ts:traj.time(end); + %if rem(numel(time),2) + % time = time(1:end-1); % ensures pair number of samples for FFT + %end + Fs = 1/Ts; + L = numel(time); + X = sigs_values_for_freq_coverage{itraj}'; + all_freqs = Fs*(0:(L/2))/L; % data resolution + + dim_sigs =numel(sigs_freqs); + if size(X,2)~=dim_sigs + X=X'; + end + for isig =1:dim_sigs + this_freq_sig = sigs_freqs{isig} ; + Y = fft(X(:,isig)); + P2 = abs(Y/L); + X_hat = P2(1:floor(L/2)+1); + X_hat(2:end-1) = 2*X_hat(2:end-1); + X_hat_threshold = 0.1; + + %MaxX = max(X_hat); + %X_hat_threshold = MaxX/10; % kind of arbitrary... + % TODO: options for threshold, or better default + % choice... + + freqs = all_freqs(X_hat>X_hat_threshold); + DEBUG = 0; + if DEBUG + figure; + subplot(2,1,1) + plot(X(:,isig)); + subplot(2,1,2) + plot(all_freqs, X_hat) + hold on + plot(all_freqs, 0*X_hat+X_hat_threshold); + plot(freqs, X_hat(X_hat>X_hat_threshold),'x-r'); + pause; + close; + + end + % collect frequencies for this trace and signal, + % filter them before combining + Bf_itraj_isig = BreachSet(this_freq_sig); + Bf_itraj_isig.SetParamRanges(this_freq_sig,opts.signals_freqs.(this_freq_sig).range); + this_opts.params = struct(); + this_opts.params.(this_freq_sig)= opts.signals_freqs.(this_freq_sig); + + Bf_itraj_isig.SetParam(this_freq_sig, freqs); + Bg_itraj_isig =Bf_itraj_isig.GridFilter([], this_opts); + filtered_freqs = Bg_itraj_isig.GetParam(this_freq_sig); + + if isig==1 + Bf_itraj.SetParam(this_freq_sig, filtered_freqs); + else + Bf_itraj.SetParam(this_freq_sig,filtered_freqs, 'combine') + end + + end + + if itraj==1 % first trace + Bf=Bf_itraj; + else + Bf.Concat(Bf_itraj,1) % concat freqs for subsequent traces + end + end + + % apply coverage on BF and copy to freq_signals stuff + [cov_for_freq_coverage, Bgf] = Bf.GetCoverage(cov_opts_for_freq_coverage); + cov.signals_freqs = cov_for_freq_coverage.params; + if isfield(cov.signals_freqs, 'cov1d') + cov.signals_freqs.cov1d_freqs = cov.signals_freqs.cov1d; + cov.signals_freqs = rmfield(cov.signals_freqs, 'cov1d'); + end + if isfield(cov.signals_freqs, 'cov2d') + cov.signals_freqs.cov2d_freqs = cov.signals_freqs.cov2d; + cov.signals_freqs = rmfield(cov.signals_freqs, 'cov2d'); + end + + cov.signals_freqs.Bg = Bgf; + + end + cov.num_samples = size(this.P.pts,2); + cov.hash_samples = DataHash(this.P.pts); + % for resuming coverage computation; + % only checks pts even if we have signals... + this.CoverRes = cov; + + + %% Aux function + function this_cov = compute_coverage(proj) + % computes coverage for one projection + + idim = numel(proj); + proj_st = proj{1}; + for j = 2:idim + proj_st = [proj_st '__x__' proj{j}]; + end + num_grid = 1; + for ip = 1:numel(proj) + num_grid = num_grid*numel(opts.params.(proj{ip}).breach_domain.enum); + end + vals = Bg.GetParam(proj); + vals = unique(vals','rows'); + this_cov = 100*size(vals,1)/num_grid; + cov.params.(proj_st) = this_cov; + end + + end + + function [Bgrid, Bsub] = GridFilter(this, params, delta, Bgrid) + % Bg = this.GridFilter(params, opts [, Bg]) + % + % opts is an option structure defined with SetCoverageOptions + % Bg is the set of grid points covered by this, with cell + % count. + % This method works by counting samples into a hashtable, + % where keys are defined by their position in the grid (sparse + % encoding in the grid). + % + + if nargin == 2 + if isstruct(params) % first argument is a options structure already + delta = params; + params = fieldnames(delta.params)'; + elseif isnumeric(params) + delta=params; + params = this.GetParamList(); + end + end + + if ~exist('params', 'var')||isequal(params, 'all') + params = this.GetParamList(); + end + + params = setdiff(params,{'traj__ref__'}, 'stable'); + + if ~exist('delta','var')||isempty(delta) + delta = this.SetCoverageOptions(); + end + + if isnumeric(delta) + if isscalar(delta) + delta= ones(1, numel(params))*delta; + end + for id= 1:numel(params) + opts.params.(params{id}).grid = delta(id); + end + opts = this.SetCoverageOptions(opts); + elseif iscell(delta) + for id= 1:numel(params) + opts.params.(params{id}).grid = delta{id}; + end + opts = this.SetCoverageOptions(opts); + else % supports legacy delta, scalar or array to specify grid + opts = this.SetCoverageOptions(delta); + end + + if isempty(params) + params = fieldnames(opts.params)'; + end + + % gets grid specs for params + grids = cell(1,numel(params)); + sizes = cell(1,numel(params)); + for ip = 1:numel(params) + param_ip = params{ip}; + grids{ip} = opts.params.(param_ip).grid; + sizes{ip} = opts.params.(param_ip).eps; + if isfield(opts.params.(param_ip), 'breach_domain') + grid_domain(ip) = opts.params.(param_ip).breach_domain; + else + opts.params.(param_ip).breach_domain = BreachDomain('double', [opts.params.(param_ip).grid(1) opts.params.(param_ip).grid(end)]); + grid_domain(ip) = opts.params.(param_ip).breach_domain; + end + end + % TODO: double check this for iterative computation + if ~isempty(this.CoverRes) + last_idx_in = this.CoverRes.num_samples; + else + last_idx_in = 0; + end + + pts_idx =last_idx_in+1:size(this.P.pts, 2); + pts = this.GetParam(params,last_idx_in+1:size(this.P.pts, 2)); + bg_params= [params {'count' 'idx'}]; + nb_params= numel(params); + traj_ref = this.GetParam('traj__ref__', pts_idx); % returns [] if no traj ref + + % TODO that too + if ~exist('Bg','Var')||isempty(Bgrid) + Bgrid = BreachSet(bg_params); + Bgrid.SetDomain(bg_params, [grid_domain BreachDomain('int'), BreachDomain('int')]); + end + + % From now on we have Bgrid, a breach set with parameters + % params, subset of parameters of *this* + + for i_pts = 1:size(pts,2) + keep = 1; % or not + pt = pts(:,i_pts); + center_pt =0* pt; + epsi = 0*pt; + for i_dim=1:numel(params) + % if outside range, throw away + if pt(i_dim)grid_domain(i_dim).domain(2) + keep = 0; % out + break; + end + + cell_centers = grid_domain(i_dim).enum; + [~ , imin] = min(abs(cell_centers-pt(i_dim))); % finds closest element + center_pt(i_dim,1) = cell_centers(imin); + epsi(i_dim) = sizes{i_dim}(imin); + end + if keep==0 + continue; + end + + % center_pt is now a grid pts, we add it to the map + hash = DataHash(center_pt); + if Bgrid.DeltaGridMapObj.isKey(hash) + grid_pt = Bgrid.DeltaGridMapObj(hash); % grid pt is in already + if isempty(traj_ref) % we count samples + grid_pt.count = grid_pt.count+1; + grid_pt.idx(end+1) = i_pts; + grid_pt.dist2grid(end+1) = sqrt(sum((pt-center_pt).^2)); % distance from point to center, see if L2 dist should be sth else ..? + else % we count traces + i_traj = traj_ref(i_pts); + grid_pt.idx = union(grid_pt.idx, i_traj,'stable'); % add unique + grid_pt.count = numel(grid_pt.idx); + grid_pt.dist2grid(end+1) = sqrt(sum((pt-center_pt).^2)); % discrepancy between idx and dist2grid, is this bad ? + end + else + grid_pt.pt = center_pt; + grid_pt.epsi = epsi; + grid_pt.dist2grid = sqrt(sum((pt-center_pt).^2)); % distance from point to center, see if L2 dist should be sth else ..? + if isempty(traj_ref) + grid_pt.count = 1; + grid_pt.idx = i_pts; + else % we count traces + i_traj = traj_ref(i_pts); + grid_pt.idx = i_traj; + grid_pt.count = 1; + end + end + Bgrid.DeltaGridMapObj(hash) = grid_pt; + + end + grid_pts = Bgrid.DeltaGridMapObj.values; + num_pts = numel(grid_pts); + Bgrid.P.dim = 1:nb_params; + Bgrid.P.epsi = zeros(nb_params, num_pts); + + % Legacy SetParam... + if isempty(traj_ref) % we count samples + for i_pt= 1:num_pts + [~, idx_min_dist] = min(grid_pts{i_pt}.dist2grid); + + Bgrid.P.pts(:,i_pt) = [grid_pts{i_pt}.pt; + grid_pts{i_pt}.count; + grid_pts{i_pt}.idx(idx_min_dist)]; + + Bgrid.P.epsi(:,i_pt) = grid_pts{i_pt}.epsi; + end + else % for traces, see how we can still exploit min_dist to centers... + for i_pt= 1:num_pts + + Bgrid.P.pts(:,i_pt) = [grid_pts{i_pt}.pt; + grid_pts{i_pt}.count; + grid_pts{i_pt}.idx(1)]; + + Bgrid.P.epsi(:,i_pt) = grid_pts{i_pt}.epsi; + end + + + end + + Bgrid.P = Preset_traj_ref(Bgrid.P); + + if nargout>=2 + idx_sub = Bgrid.GetParam('idx'); + Bsub= this.ExtractSubset(idx_sub); + end + + + function delta = checks_delta(n, delta) + % first checks if delta is all integers >=1 + + % checks if cells + if iscell(delta) + if numel(delta)~=n + if numel(delta) == 1 + delta= repmat(delta, 1,n); + else + error('argument is not a valid grid specification, wrong size'); + end + end + % nothing more. + else + % not a cell + if ~isnumeric(delta) % what then ? + error('argument is not a valid grid specification, should be a scalar, and array or a cell'); + end + + if all(delta==floor(delta))&&all(delta>=1) % numbers + if isscalar(delta) + delta= repmat({delta}, 1,n); + elseif size(delta,1)==1||size(delta, 2) == 1 + if numel(delta) ~= n + error('argument is not a valid grid specification, wrong size'); + else + delta = num2cell(delta); + end + end + + elseif size(delta,1)==1||size(delta, 2) == 1 + delta = repmat({delta}, 1, n); + elseif size(delta,1)== n + for i = 1:n + delta_out{i} = delta(i, :); + end + delta= delta_out; + elseif size(delta,2)== n + for i = 1:n + delta_out{i} = delta(i,:); + end + delta = delta_out; + end + end + end + + end + + + function [Bg,Bf,Ba] = GridFilterSignals(this, signals, varargin) + % [Bg, Bf, Ba] = GridFilterSignals(this, signals, varargin) + % + % - signals is the list of signals to project on, including (or not), + % time. + % - varargin is the same as SetCoverageOpts + % + % Returns filtered data, grid data and all. + + switch nargin + case 1 % no argument, include all signals with default grid spec (10 bins each dim) + sigs = this.GetSignalList(); + params = ['time' sigs]; % parameters for grid filtering call + case 2 % signals is specified or a struct is provided + if isstruct(signals) + params = ['time' this.GetSignalList()]; + opts.params = signals.signals; % assumes a valid options structure... + varargin = {opts}; + else + params =signals; % time has to be explicitly requested here + end + otherwise + if ischar(signals) % allow for a pair of options arguments to be started - probably confusing + % yup, I'm confused. + params = ['time' this.GetSignalList()]; + varargin = [{signals} varargin]; + else + params =signals; + if isstruct(varargin{1}) + opts_sigs = varargin{1}; + opts.params = opts_sigs.signals; % assumes a valid options structure... + varargin{1} = opts; + end + end + end + + %% fill in signal values + all_sigs = this.GetSignalList(); + all_params = ['time' all_sigs]; % all params for grid filtering + Ba = BreachSet(all_params); + + sig_values = this.GetSignalValues(all_sigs); + if ~iscell(sig_values) + sig_values={sig_values}; + end + + for itraj = 1:numel(sig_values) + this_time= this.P.traj{itraj}.time; + val =[this_time ; sig_values{itraj}]; + % cleaning nan in trace + idx_not_nan = find(sum(isnan(val), 1)==0); + val = val(:,idx_not_nan); + + if itraj==1 + Ba.SetParam(all_params, val); + else + Ba.AddParam(all_params, val); + end + end + + %% Applies grid filter + [Bg,Bf] = Ba.GridFilter(params, varargin{:}); + + + end + + + + function idx_in_this = get_cell_content_pts(this, param, value) + if isempty(this.CoverRes) + error('Compute coverage first with SetCoverageOpts and GetCoverage.') + end + Bg = this.CoverRes.params.Bg; + domains = Bg.GetDomain(param); + for ip = 1:numel(domains) + value(ip) = domains(ip).checkin(value(ip)); + end + all_pts = Bg.GetParam(param); + idx_val_in_Bg = find(all(bsxfun(@eq, all_pts, value), 1)); % find value in Bg.P.pts + idx_in_this = []; + for ip = idx_val_in_Bg + pt = Bg.P.pts(1:end-2, ip); + hash_pt = DataHash(pt); + pt_info = Bg.DeltaGridMapObj(hash_pt); + idx_in_this = [idx_in_this pt_info.idx]; + end + end + + function idx_in_this = get_cell_content_signals(this, param, value) + if isempty(this.CoverRes) + error('Compute coverage first with SetCoverageOpts and GetCoverage.') + end + Bg = this.CoverRes.signals.Bg; + domains = Bg.GetDomain(param); + for ip = 1:numel(domains) + value(ip) = domains(ip).checkin(value(ip)); + end + all_pts = Bg.GetParam(param); + idx_val_in_Bg = find(all(bsxfun(@eq, all_pts, value), 1)); % find value in Bg.P.pts + idx_in_this = []; + for ip = idx_val_in_Bg + pt = Bg.P.pts(1:end-2, ip); + hash_pt = DataHash(pt); + pt_info = Bg.DeltaGridMapObj(hash_pt); + idx_in_this = [idx_in_this pt_info.idx]; + end + end + + function idx_in_this = get_cell_content_freqs(this, param, value) + if isempty(this.CoverRes) + error('Compute coverage first with SetCoverageOpts and GetCoverage.') + end + Bg = this.CoverRes.signals_freqs.Bg; + domains = Bg.GetDomain(param); + for ip = 1:numel(domains) + value(ip) = domains(ip).checkin(value(ip)); + end + all_pts = Bg.GetParam(param); + idx_val_in_Bg = find(all(bsxfun(@eq, all_pts, value), 1)); % find value in Bg.P.pts + idx_in_this = []; + for ip = idx_val_in_Bg + pt = Bg.P.pts(1:end-2, ip); + hash_pt = DataHash(pt); + pt_info = Bg.DeltaGridMapObj(hash_pt); + idx_in_this = [idx_in_this pt_info.idx]; + end + end + + function idx_traces = get_traces_in_cell(this, param,value) + if isempty(this.CoverRes) + error('Compute coverage first with SetCoverageOpts and GetCoverage.') + end + if size(value, 2)>1 + value=value'; + end + + Bg = this.CoverRes.signals.Bg; + domains = Bg.GetDomain(param); + for ip = 1:numel(domains) + value(ip) = domains(ip).checkin(value(ip)); + end + all_pts = Bg.GetParam(param); + idx_val_in_Bg = find(all(bsxfun(@eq, all_pts, value), 1)); % find value in Bg.P.pts + idx_traces = []; + for ip = idx_val_in_Bg + pt = Bg.P.pts(1:end-2, ip); + hash_pt = DataHash(pt); + pt_info = Bg.DeltaGridMapObj(hash_pt); + idx_traces = [idx_traces pt_info.idx]; + end + end + + function idx_traces = get_min_cover_traces(this) + + if isempty(this.CoverRes) + error('Compute coverage first with SetCoverageOpts and GetCoverage.') + end + + Bg = this.CoverRes.signals.Bg; + all_pts_info = Bg.DeltaGridMapObj.values; + + for ip = 1:numel(all_pts_info) + num_traces(ip) = all_pts_info{ip}.count; + end + % sort by number of traces in each cell, first ones only contain + % one trace + [~, ia] = sort(num_traces); + idx_traces = []; % current traces for coverage + for ii = ia % loop over cells in this order + traces_ia = all_pts_info{ii}.idx; % traces contained in this cell + if isempty(intersect(traces_ia, idx_traces)) % is there no trace in current traces that cover this cell ? + idx_traces = [idx_traces traces_ia(1)]; % we pick the first one. This is where we could optimize, pick the most covering trace + end + end + + end + + function idx_traces = get_min_cover_traces_freqs(this) + + if isempty(this.CoverRes) + error('Compute coverage first with SetCoverageOpts and GetCoverage.') + end + + Bg = this.CoverRes.signals_freqs.Bg; + all_pts_info = Bg.DeltaGridMapObj.values; + + for ip = 1:numel(all_pts_info) + num_traces(ip) = all_pts_info{ip}.count; + end + % sort by number of traces in each cell, first ones only contain + % one trace + [~, ia] = sort(num_traces); + idx_traces = []; % current traces for coverage + for ii = ia % loop over cells in this order + traces_ia = all_pts_info{ii}.idx; % traces contained in this cell + if isempty(intersect(traces_ia, idx_traces)) % is there no trace in current traces that cover this cell ? + idx_traces = [idx_traces traces_ia(1)]; % we pick the first one. This is where we could optimize, pick the most covering trace + end + end + + end + + function is_covered = check_covered_pts(this, params, pts) + % checks if pts are covered by the current grid set - grid set + % needs to be computed already using + % FIXME (?): assumes this is a grid set, with enum domains + + + is_covered = zeros(1, size(pts, 2)); + [iparams, ifound] = FindParam(this.P, params); + + if all(ifound) + pts_in_this = this.GetParam(params); + for ipt = 1:size(pts,2) + pt = pts(:,ipt); + is_in = 1; % is inside the domain or not ? + + for idim =1:numel(iparams) + this_idx_param = iparams(idim); + + if this.Domains(this_idx_param).is_out(pt(idim)) + is_in = 0; % not inside the domain + break + else + pt(idim) = this.Domains(this_idx_param).checkin(pt(idim)); + end + end + + if is_in + % pt is now a grid pt if grid there is + if ~isempty(this.DeltaGridMapObj) % checks in map if not empty + hash_pt = DataHash(pt); + if this.DeltaGridMapObj.isKey(hash_pt); + is_covered(ipt)=1; + else + idx_pt_in_this = find(all(bsxfun(@eq, pts_in_this, pt)), 1); + if ~isempty(idx_pt_in_this) + is_covered(ipt)=1; + end + end + end + end + end + else + not_found = find(~ifound); + warning('Parameter %s not found.', params(not_found)); + end + + + end + + function h = plot_cover_stats(this) + % Plots coverage results in all projections as percentage bars + if ~isempty(this.CoverRes) + cov = this.CoverRes; + if isfield(cov,'params') + all_stats = fieldnames(cov.params)'; + labels = {}; + for istat = 1:numel(all_stats) + stat = all_stats{istat}; + if strcmp(stat,'Bg')||~isempty(regexp(stat, 'cov[0-9]+d')) + continue; + end + + params_stat = strsplit(stat,'__x__'); + num_dim = numel(params_stat); + % projection of size num_dim, if not there + % already, creates it and adds projection label + if num_dim>size(labels, 2) + labels{1,num_dim} ={}; + max_dim = num_dim; + end + labels{num_dim}{end+1} = stat; + end + + % loops over all dimensions: + h = []; + for dim = 1:max_dim + covxd = ['cov' num2str(dim) 'd']; + if isfield(cov.params,covxd) + h(end+1) = figure; + % first thing, get all fields of struct, barh the values + labels_xd = labels{dim}; + values_xd = cellfun(@(name)(cov.params.(name)), labels_xd); + b = barh(values_xd); + yticklabels(labels_xd); + set(gca, 'TickLabelInterpreter', 'None', 'XLim', [0 100]) + title([num2str(dim) 'd param coverage: ' num2str(cov.params.(covxd)) ' %']); + grid on; + end + end + end + + if isfield(cov,'signals') + all_stats = fieldnames(cov.signals)'; + labels = {}; + time_labels = {}; + for istat = 1:numel(all_stats) + stat = all_stats{istat}; + % is it not a projection + if any(strcmp(stat,{'Bg', 'Bs_all'}))||~isempty(regexp(stat, 'cov[0-9]+d')) + continue; + end + + signals_stat = strsplit(stat,'__x__'); + % is it time ? + + if ismember('time', signals_stat) + num_dim = numel(signals_stat)-1; + % projection of size num_dim, if not there + % already, creates it and adds projection label + if num_dim>size(time_labels, 2) + time_labels{1,num_dim} ={}; + max_dim_time = num_dim; + end + time_labels{num_dim}{end+1} = stat; + else % it is not time + num_dim = numel(signals_stat); + % projection of size num_dim, if not there + % already, creates it and adds projection label + if num_dim>size(labels, 2) + labels{1,num_dim} ={}; + max_dim = num_dim; + end + labels{num_dim}{end+1} = stat; + end + end + % loops over all dimensions: + + h = []; + for dim = 1:max_dim_time + covxd = ['cov' num2str(dim) 'd_time_sig']; + if isfield(cov.signals,covxd) + h(end+1) = figure; + % first thing, get all fields of struct, barh the values + labels_xd = labels{dim}; + values_xd = cellfun(@(name)(cov.signals.(name)), labels_xd); + b = barh(values_xd); + yticklabels(labels_xd); + set(gca, 'TickLabelInterpreter', 'None', 'XLim', [0 100]) + title([num2str(dim) 'd time signals coverage: ' num2str(cov.signals.(covxd)) ' %']); + grid on; + end + end + + for dim = 1:max_dim + covxd = ['cov' num2str(dim) 'd_sig']; + if isfield(cov.signals,covxd) + h(end+1) = figure; + % first thing, get all fields of struct, barh the values + labels_xd = labels{dim}; + values_xd = cellfun(@(name)(cov.signals.(name)), labels_xd); + b = barh(values_xd); + yticklabels(labels_xd); + set(gca, 'TickLabelInterpreter', 'None', 'XLim', [0 100]) + title([num2str(dim) 'd signals coverage: ' num2str(cov.signals.(covxd)) ' %']); + grid on; + end + end + end + + if isfield(cov,'signals_freqs') + all_stats = fieldnames(cov.signals_freqs)'; + labels = {}; + for istat = 1:numel(all_stats) + stat = all_stats{istat}; + if any(strcmp(stat,{'Bg', 'Bs_all'}))||~isempty(regexp(stat, 'cov[0-9]+d')) + continue; + end + + params_stat = strsplit(stat,'__x__'); + num_dim = numel(params_stat); + % projection of size num_dim, if not there + % already, creates it and adds projection label + if num_dim>size(labels, 2) + labels{1,num_dim} ={}; + max_dim = num_dim; + end + labels{num_dim}{end+1} = stat; + end + + % loops over all dimensions: + h = []; + for dim = 1:max_dim + covxd = ['cov' num2str(dim) 'd']; + if isfield(cov.signals_freqs,covxd) + h(end+1) = figure; + % first thing, get all fields of struct, barh the values + labels_xd = labels{dim}; + values_xd = cellfun(@(name)(cov.signals_freqs.(name)), labels_xd); + b = barh(values_xd); + yticklabels(labels_xd); + set(gca, 'TickLabelInterpreter', 'None', 'XLim', [0 100]) + title([num2str(dim) 'd frequencies coverage: ' num2str(cov.signals_freqs.(covxd)) ' %']); + grid on; + end + end + end + + end + end + + function h = plot_cover_proj(this,proj, cov_type) + + if ~isempty(this.CoverRes) + cov = this.CoverRes; + opts = this.CoverOpts; + + % find projections + if nargin<3 + cov_type =[]; + end + + % look into parameters + if isfield(cov,'params') + if isfield(cov.params,proj) + Bg= cov.params.Bg; + cov_type = 'params'; + end + end + + % look into signals, if not found yet + if isfield(cov,'signals')&&isempty(cov_type) + if isfield(cov.signals,proj) + Bg= cov.signals.Bg; + cov_type = 'signals'; + end + end + + % look into freqs, if not found yet + if isfield(cov,'signals_freqs')&&isempty(cov_type) + if isfield(cov.signals_freqs,proj) + Bg= cov.signals_freqs.Bg; + cov_type = 'signals_freqs'; + end + end + if isempty(cov_type) + error('plot_cover_proj:proj_not_found',['Projection ' proj ' not found.']); + end + + h = gcf; + params= strsplit(proj,'__x__'); % assumes p1__x__p2__x__... + if numel(params)>2 + warning('more than two parameters, not supported'); + return; + end + + values= Bg.GetParam(params); + + [vu, ~, iv] = unique(values','row'); + vu = vu'; + iv=iv'; + switch cov_type + case 'params' + for idx = 1:size(vu,2) % loop over cells that share the same projections + idx_in_set = this.get_cell_content_pts(params, vu(:,idx)); + cu(idx) = numel(unique(idx_in_set)); + end + case 'signals' + for idx = 1:size(vu,2) % loop over cells that share the same projections + idx_in_set = this.get_cell_content_signals(params, vu(:,idx)); + cu(idx) = numel(unique(idx_in_set)); + end + case 'signals_freqs' + for idx = 1:size(vu,2) % loop over cells that share the same projections + idx_in_set = this.get_cell_content_freqs(params, vu(:,idx)); + cu(idx) = numel(unique(idx_in_set)); + end + end + + switch(numel(params)) + case 1 + x_all = opts.(cov_type).(params{1}).breach_domain.enum; + widthx = opts.(cov_type).(params{1}).eps; % TODO replace or update bar function call to use width of bars + [~,~,idx_vu] = intersect(vu, x_all); % non empty bins + + y = zeros(1,numel(x_all)); + y(idx_vu) = cu; + + bar(x_all, y); + hold on; + grid on; + + case 2 + + x_all = opts.(cov_type).(params{1}).breach_domain.enum; + widthx = 2*opts.(cov_type).(params{1}).eps; + y_all = opts.(cov_type).(params{2}).breach_domain.enum; + widthy = 2*opts.(cov_type).(params{2}).eps; + + x = vu(1,:); + y = vu(2,:); + plot3(x,y,cu, 's'); + hold on; + view(0,90); + cu_matrix = zeros(numel(y_all),numel(x_all)); + for i_x =1:numel(x_all) + idx = find(x== x_all(i_x)); + [~, ~, idx_y] = intersect(y(idx), y_all); + cu_matrix(idx_y,i_x) = cu(idx); + end + [X, Y] = meshgrid(x_all,y_all); + + % colormap, copper reversed + cmap= flipud(colormap('gray')); + cmap = [1 1 1; + repmat(cmap(32,:),32,1); + cmap(33:220,:); + repmat(cmap(220,:),46,1)]; + colormap(cmap); + + grid on; + %scatterbar3(X,Y,cu_matrix,widthx, widthy); % TODO: option for 3d plot ? + flatbar3(X,Y,cu_matrix,widthx, widthy); + caxis([0 max(max(cu),1e-3)]); + c = colorbar; + ticks = get(c,'Ticks'); + ticks = unique([0 floor(ticks(ticks>0))], 'stable'); + set(c, 'Ticks',ticks); + + xlabel(params{1},'Interpreter','none'); + ylabel(params{2},'Interpreter','none'); + zlabel('#samples'); + + + + otherwise + return; + end + title([proj ' ' cov_type ' coverage: ' num2str(cov.(cov_type).(proj)) ' %'],'interpreter', 'none'); + end + + end + + function Bg_proj = SimpleProject(this,params) + % Projects on provided dimensions + Bg_proj = BreachSet(params); + values= this.GetParam(params); + [vu, iv] = unique(values','row'); + Bg_proj.SetParam(params,vu'); + Bg_proj.P.dim = 1:numel(params); + Bg_proj.P.epsi = zeros(numel(params), size(vu, 1)); + + idx_params = FindParam(this.P, params); + for ip = 1:numel(idx_params) + idx_this_param = idx_params(ip); + idx_epsi= find(this.P.dim==idx_this_param,1); + if ~isempty(idx_epsi) + Bg_proj.P.epsi(idx_this_param,:) = this.P.epsi(idx_epsi,iv); + end + end + % we might want to remove 0 epsi from dim, if you see what I + % mean (surely not if you're not me) + + end - - %% Requirements + + function r_list = plot_cover_grid(this, proj, varargin) + + Bproj = this.SimpleProject(proj); + + P = Bproj.P; + if ~isnumeric(proj) + proj = FindParam(P,proj); + end + col = 'b'; + alph = .1; + proj = proj(proj>0); + proj = proj(proj<=size(P.pts,1)); + if isempty(proj) + proj = 1:min(3,numel(P.dim)); + proj = P.dim(proj); + elseif(numel(proj)>3) + proj = proj(1:3); + end + + if(~exist('ipts','var')||isempty(ipts)) + ipts = 1:size(P.pts,2); + end + + if ~exist('col','var') + col = 'b'; + end + + if ~exist('alph','var') + alph = .1; + end + + hold on; + r_list = []; + switch(numel(proj)) + + case 1 + if isfield(P,'ParamList') + xlabel(P.ParamList{proj(1)},'Interpreter','none'); + else + xlabel(['x_' num2str(proj(1))],'Interpreter','tex'); + end + + DX(2) = 0; + idx = find(P.dim == proj); + + for kk=ipts % for each parameter sample + if ~isempty(idx) + DX(1) = P.epsi(idx,kk); + else + DX(1) = 0; + end + X = [P.pts(proj,kk)-DX(1),-DX(2)]; + r= rect(X, 2*DX, col, alph); + set(r, varargin{:}); + + end + %set(gca, 'YLim', [-1 1], 'YtickLabel', {},'Interpreter','none'); + + case 2 + if isfield(P,'ParamList') + xlabel(P.ParamList{proj(1)},'Interpreter','none'); + ylabel(P.ParamList{proj(2)},'Interpreter','none'); + else + xlabel(['x_' num2str(proj(1))],'Interpreter','tex'); + ylabel(['x_' num2str(proj(2))],'Interpreter','tex'); + end + + DX = zeros(1,2); + for kk=ipts + for jj = 1:2 % if proj(jj) in P.dim, set the width + idx = find(P.dim == proj(jj)); + if isempty(idx) + DX(jj) = 0; + else + DX(jj) = P.epsi(idx,kk); + end + end + X = P.pts(proj,kk)'-DX; + r = rect(X, 2*DX, col, alph); + set(r, varargin{:}); + end + + case 3 + if isfield(P,'ParamList') + xlabel(P.ParamList{proj(1)},'Interpreter','none'); + ylabel(P.ParamList{proj(2)},'Interpreter','none'); + zlabel(P.ParamList{proj(3)},'Interpreter','none'); + else + xlabel(['x_' num2str(proj(1))],'Interpreter','tex'); + ylabel(['x_' num2str(proj(2))],'Interpreter','tex'); + zlabel(['x_' num2str(proj(3))],'Interpreter','tex'); + end + + DX = zeros(1,3); + for kk=ipts + for jj = 1:3 % if proj(jj) in P.dim, set the width + idx = find(P.dim == proj(jj)); + if isempty(idx) + DX(jj) = 0; + else + DX(jj) = P.epsi(idx,kk); + end + end + + X = P.pts(proj,kk)'-DX; + r= voxel(X, 2*DX, col, alph); + set(r, varargin{:}); + end + end + r_list(end+1) =r; + + grid on; + hold off; + + end + + function Bp = sig2params(this) + + sigs = this.GetSignalList(); + params = ['traj__ref__' 'time' sigs]; + Bp = BreachSet(params); + + sig_values = this.GetSignalValues(sigs); + if ~iscell(sig_values) + sig_values={sig_values}; + end + + for itraj = 1:numel(sig_values) + val = [ones(1,size(sig_values{itraj},2))*itraj; this.P.traj{itraj}.time; sig_values{itraj}]; % keep itraj for traj_ref__ + if itraj==1 + Bp.SetParam(params, val); + else + Bp.AddParam(params, val); + end + end + + X = Bp.GetParam(sigs); + minX = min(X,[],2); + maxX = max(X,[],2); + tolAmp = (maxX-minX)/200; + Bp.SetParamRanges(sigs, [(minX - tolAmp), (maxX+tolAmp)]); + + end + + + %% Requirements - sort of deprecated per BreachRequirement class function SortbyRob(this) sat_values = this.GetSatValues(); [ ~, order_rob] = sort(sum(sat_values,1)); this.P = Sselect(this.P, order_rob); end - + function SortbySat(this) sat_values = this.GetSatValues(); [ ~, order_rob] = sort(sum(sat_values>=0,1)); this.P = Sselect(this.P, order_rob); end - + %% Printing/Exporting function [success, msg, msg_id] = SaveResults(this, folder_name, varargin) - % FIXME does not support attributes? - + % FIXME does not support attributes? + % Additional options if ~exist('folder_name', 'var') folder_name = ''; end options = struct('FolderName', folder_name, 'SaveBreachSystem', true, 'ExportToExcel', false, 'ExcelFileName', 'Results.xlsx'); options = varargin2struct_breach(options, varargin{:}); - + if isempty(options.FolderName) try options.FolderName = [this.mdl.name '_Results_' datestr(now, 'dd_mm_yyyy_HHMM')]; @@ -1800,12 +3405,12 @@ function SortbySat(this) options.FolderName = [this.whoamI '_Results_' datestr(now, 'dd_mm_yyyy_HHMM')]; end end - + folder_name = options.FolderName; [success,msg,msg_id] = mkdir(folder_name); trace_folder_name = [folder_name filesep 'traces']; [success,msg,msg_id] = mkdir(trace_folder_name); - + if success == 1 if isequal(msg_id, 'MATLAB:MKDIR:DirectoryExists') this.disp_msg(['Saving in existing result folder at ' folder_name]); @@ -1816,91 +3421,91 @@ function SortbySat(this) else error(['Couldn''t create folder' folder_name '.']); end - + if ~this.hasTraj() error('Breach:SaveResult:no_trace', 'No trace to save - run Sim command first'); return; end - + [summary, traces] = this.ExportTracesToStruct(); %saving summary summary_filename = [folder_name filesep 'summary']; save(summary_filename,'-struct', 'summary'); - + if options.SaveBreachSystem breachsys_filename = [folder_name filesep 'breach_system']; breachsys_name = this.whoamI; evalin('base', ['save(''' breachsys_filename ''', ''' breachsys_name ''', ''-v7.3'');'] ); end - + for it=1:numel(traces) trace_filename = [trace_folder_name filesep num2str(it) '.mat']; trace = traces(it); save(trace_filename,'-struct', 'trace'); end - + if options.ExportToExcel this.ExportToExcel(options.ExcelFileName); end end - + function [summary, traces] = ExportTracesToStruct(this,i_traces, varargin) % BreachSet.ExportTracesToStruct - + summary = this.GetSummary(); traces = []; if ~this.hasTraj() error('Breach:ExportTrace:no_trace', 'No trace to export - run Sim command first'); return; end - + num_traces = numel(this.P.traj); if nargin==1 i_traces = 1:num_traces; end - + % Additional options options = struct('FolderName', []); options = varargin2struct_breach(options, varargin{:}); - + if isempty(options.FolderName) options.FolderName = ['Results_' datestr(now, 'dd_mm_yyyy_HHMM')]; end - + %% Common stuff - + % parameter names param_names = this.GetSysParamList(); - + % input signal names signal_names = this.GetSignalList(); - + if isfield(this.P,'props_names') spec_names = this.P.props_names; end - - + + %% traces summary.filenames = {}; summary.paths = {}; for it = i_traces - + % check status if isfield(this.P.traj{it}, 'status')&&(this.P.traj{it}.status~=0) - warning('SaveResults:suspicious_trace','Trace %d has suspicious status, likely resulting from simulation error.', it) + warning('SaveResults:suspicious_trace','Trace %d has suspicious status, likely resulting from simulation error.', it) end - + % params traces(it).params.names = param_names; traces(it).params.values = this.GetParam(param_names,it)'; - + % time traces(it).time = this.P.traj{it}.time; - + % input signals traces(it).signals.names = signal_names; traces(it).signals.values = this.GetSignalValues(signal_names, it); - + % specifications if isfield(this.P,'props_names') traces(it).specs.ids = spec_names; @@ -1908,7 +3513,7 @@ function SortbySat(this) traces(it).specs.stl_formula{ip} = disp(this.P.props(ip)); traces(it).specs.stl_formula_full{ip} = disp(this.P.props(ip),0); params = get_params(this.P.props(ip)); - traces(it).specs.params(ip).names = fieldnames(params); + traces(it).specs.params(ip).names = fieldnames(params)'; traces(it).specs.params(ip).values = this.GetParam(fieldnames(params), it)'; traces(it).specs.rob(ip).time =this.P.props_values(ip, it).tau; traces(it).specs.rob(ip).values = this.P.props_values(ip, it).val; @@ -1916,22 +3521,22 @@ function SortbySat(this) end end end - + end function traces = ExportTraces(this, signals, params, varargin) - + if ~exist('signals','var') signals = {}; % means all end if ~exist('params','var')||isempty(params) params = {}; % means all end - + % Options options = struct('WriteToFolder',''); options = varargin2struct_breach(options, varargin{:}); - + if ~isempty(options.WriteToFolder) if ~exist(options.WriteToFolder,'dir' ) [success, err_msg] = mkdir(options.WriteToFolder); @@ -1943,7 +3548,7 @@ function SortbySat(this) else dir_traces = ''; end - + [signature,~, params] = this.GetSignature(signals, params); num_traces = numel(this.P.traj); signals = signature.signals_reps; % signal representants, assuming there are aliases @@ -1951,11 +3556,11 @@ function SortbySat(this) for it = 1:num_traces traj = this.P.traj{it}; X = this.GetSignalValues(signals, it); - + if ~isempty(dir_traces) traces{it} = matfile([dir_traces filesep num2str(it) '.mat'], 'Writable',true); end - + if isfield(traj, 'status') traces{it}.status = traj.status; end @@ -1963,29 +3568,28 @@ function SortbySat(this) traces{it}.param = [zeros(1,numel(signals)) param_values(:,it)']; traces{it}.time = traj.time; traces{it}.X = X; - + if ~isempty(dir_traces) traces{it}.Properties.Writable= false; end end end - -%% Signatures + %% Signatures function [signature, signals, params] = GetSignature(this, signal_list, param_list) - % GetSignature returns information about signals and parameters - + % GetSignature returns information about signals and parameters + % gets signals signature if ~exist('signal_list', 'var')||isempty(signal_list) signal_list = this.GetSignalList(); end - + [signals, unknown] = this.expand_signal_name(signal_list); if ~isempty(unknown) warning('GetSignature:signal_unknown', 'Signal or attribute %s not found.', unknown{1}); end signature = this.GetSignalSignature(signals); - + % gets params signature if ~exist('param_list', 'var')||isempty(param_list) param_list = this.GetParamList(); @@ -1996,40 +3600,40 @@ function SortbySat(this) warning('GetSignature:param_unknown', 'Parameter %s unknown.', unknown{1}); end sigp = this.GetParamSignature(params); - - f = fieldnames(sigp); + + f = fieldnames(sigp)'; for i = 1:length(f) signature.(f{i}) = sigp.(f{i}); end end - + function [sigs] = GetSignalSignature(this, signals) - if nargin <=1 - signals = this.GetSignalList(); - end - - if ischar(signals) + if nargin <=1 + signals = this.GetSignalList(); + end + + if ischar(signals) signals= {signals}; end sigs.signals = signals; sigs.signal_attributes = {}; sigs.signals_map_idx = []; - sigs.signals_reps={}; + sigs.signals_reps={}; for is = 1:numel(signals) sig= signals{is}; sig_aliases = this.getAliases(sig); - idx = inf; + idx = inf; for ia = 1:numel(sig_aliases) idx_ia = find(strcmp(sig_aliases(ia), signals),1); if ~isempty(idx_ia) idx = min(idx,idx_ia ); % find first position in signals an alias appears end end - + if idx==inf warning('BreachSet:GetSignalSignature:not_found', 'Signal or alias %s not found.', sig); end - + if idx==is % first time we see this guy, take it as rep sigs.signals_reps{end+1} = sig; idx = numel(sigs.signals_reps); @@ -2037,14 +3641,14 @@ function SortbySat(this) else % idx is smaller than is so signals_map_idx(idx) is defined and correct, not necessarilly equal to idx ... sigs.signals_map_idx(is) = sigs.signals_map_idx(idx); end - - + + dom = this.GetDomain(signals{idx}); sigs.signal_types{is} = dom.type; - if isempty(dom.type) % ? - sigs.signal_types{is} = 'double'; + if isempty(dom.type) % ? + sigs.signal_types{is} = 'double'; end - + % Add attributes indexes atts = this.get_signal_attributes(sig); sigs.signal_attributes = union(sigs.signal_attributes, atts); @@ -2061,9 +3665,9 @@ function SortbySat(this) sigs.signal_attributes = sigs.signal_attributes'; end end - + function sigp = GetParamSignature(this, params) - + if nargin<=1 params = this.GetParamList(); end @@ -2073,12 +3677,12 @@ function SortbySat(this) par =params(ip); dom = this.GetDomain(par); sigp.param_types{ip} = dom.type ; - if isempty(dom.type) % ? - sigp.param_types{is} = 'double'; + if isempty(dom.type) % ? + sigp.param_types{is} = 'double'; end % Add attributes indexes atts = this.get_param_attributes(par); - sigp.param_attributes = union(sigp.param_attributes, atts); + sigp.param_attributes = union(sigp.param_attributes, atts); for ia = 1:numel(atts) f = [atts{ia} 's_idx']; if ~isfield(sigp, f) @@ -2087,24 +3691,24 @@ function SortbySat(this) sigp.(f)(end+1) = ip; end end - + end end - + function summary = GetSummary(this) - + if this.hasTraj() num_traces = numel(this.P.traj); else num_traces = 0; end - + % parameter names param_names = this.GetSysParamList(); - + % input signal names signal_names = this.GetSignalList(); - + if isfield(this.P,'props_names') spec_names = this.P.props_names; end @@ -2113,7 +3717,7 @@ function SortbySat(this) summary.num_traces = num_traces; summary.params.names = param_names; summary.params.values = this.GetParam(summary.params.names); - + if isfield(this.P, 'props') summary.specs.names = spec_names; this.SortbyRob(); @@ -2122,9 +3726,9 @@ function SortbySat(this) summary.specs.sat = summary.specs.rob>=0; summary.num_sat = - sum( ~summary.specs.sat, 1 ); end - + end - + function b = is_variable(this, par) b = false ; [i, f] = FindParam(this.P, par); @@ -2136,7 +3740,7 @@ function SortbySat(this) end end end - + function att = get_signal_attributes(this, sig) % returns nature to be included in signature att = {}; @@ -2148,7 +3752,7 @@ function SortbySat(this) if this.is_variable(param) atts = [atts {'variable'}]; else - atts = [atts {'const'}]; + atts = [atts {'const'}]; end end @@ -2160,7 +3764,7 @@ function SortbySat(this) signals = {signals}; end S = this.GetSignalSignature(); - + for isig = 1:numel(signals) sig = signals{isig}; if ismember(sig,S.signals) % is a signal name already @@ -2177,9 +3781,9 @@ function SortbySat(this) sig_names = unique(sig_names, 'stable'); % adds in aliases sig_names = union(sig_names,this.getAliases(sig_names), 'stable'); - + end - + function [param_names, unknown] = expand_param_name(this, params) % expand_signal_name expands a string into a set of signal names by attribute or regular expression search param_names = {}; @@ -2188,7 +3792,7 @@ function SortbySat(this) params = {params}; end S = this.GetParamSignature(); - + for ipar = 1:numel(params) param = params{ipar}; if ismember(param,S.params) % is a signal name already @@ -2204,36 +3808,152 @@ function SortbySat(this) end end - %% Stuff + function varargout = disp_signals(this) + max_length = 200; + sig_list = this.P.ParamList(1:this.P.DimX); + if isfield(this.P, 'traj') + num_traces = numel(this.P.traj); + else + num_traces = 0; + end + + switch num_traces + case 0 + str_num = '0 trace.\n'; + case 1 + str_num = '1 trace.\n'; + otherwise + str_num= [num2str(num_traces) ' traces\n']; + end + + switch numel(sig_list) + case 0 + str = '0 signals.\n'; + case 1 + str = ['1 signal: ' sig_list{1} '.\n' str_num]; + otherwise + str_sig_list = list_manip.to_string(sig_list, ', '); + if numel(str_sig_list) > max_length + str_sig_list = [str_sig_list(1:max_length) ', ...']; + end + str = [num2str(numel(sig_list)) ' signals: ' str_sig_list '.\n' str_num]; + end + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + end + + function varargout = disp_params(this) + max_length = 200; + params_list = this.P.ParamList(this.P.DimX+1:end); + num_samples = this.GetNbParamVectors(); + + switch num_samples + case 0 + str_num = '0 samples.\n'; + case 1 + str_num = '1 sample.\n'; + otherwise + str_num= [num2str(num_samples) ' samples\n']; + end + + switch numel(params_list) + case 0 + str = '0 Parameters.\n'; + case 1 + str = ['One Parameter: ' params_list{1} '.\n' str_num]; + otherwise + str_params_list = list_manip.to_string(params_list, ', '); + if numel(str_params_list) > max_length + str_params_list = [str_params_list(1:max_length) ', ...']; + end + str = [num2str(numel(params_list)) ' Parameters: ' str_params_list '.\n' str_num]; + end + + + + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + end + + function varargout = disp_short_description(this) + + param_desc = this.disp_params(); + + str = ['----\n' ... + param_desc ... + '----\n']; + + if this.P.DimX>0 + sig_desc = this.disp_signals(); + str= [str ... + sig_desc ... + '----\n']; + end + + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + + end + + + function varargout = disp(this) + + str = 'BreachSet object.\n'; + str = [str this.disp_short_description()]; + + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + + end + + + %% Stuff + function ExportToExcel(this, excel_file) [summary, traces] = this.ExportTracesToStruct(); global BreachGlobOpt - + if ~isfield(summary, 'specs') warning('Breach:SaveResult:no_spec_for_Excel','Export to Excel requested but there is no requirement result to report. Excel file not created.'); else breach_dir = BreachGlobOpt.breach_dir; template_file_path = [breach_dir filesep 'Ext' filesep 'Toolboxes' filesep 'ExportResults' filesep 'BreachResults_template.xlsx']; copyfile(template_file_path, excel_file); - + % Write header for ispec = 1:numel(summary.specs.names) hdr{ispec} = ['Req. ' num2str(ispec)]; end - + for iparam = ispec+1:ispec+numel(summary.test_params.names) hdr{iparam} = ['param. ' num2str(iparam-ispec) ]; end xlswrite(excel_file, hdr, 1, 'B1'); xlswrite(excel_file, [summary.specs.names summary.test_params.names], 1, 'B2'); - + % Write data xlswrite(excel_file, [ summary.num_sat' summary.specs.rob' summary.test_params.values'] , 1, 'A3'); this.disp_msg(['Summary written into ' excel_file]); end end - + function varargout = PrintSignals(this) st = sprintf('---- SIGNALS ----\n'); for isig = 1:this.P.DimX @@ -2241,16 +3961,16 @@ function ExportToExcel(this, excel_file) end st = sprintf([st '\n']); st = [st this.PrintAliases()]; - + if nargout == 0 varargout = {}; fprintf(st); else varargout{1} = st; end - + end - + function varargout = PrintAliases(this) st = ''; if ~isempty(this.sigMap) @@ -2263,7 +3983,7 @@ function ExportToExcel(this, excel_file) else sig =this.sigMapInv(keys{ik}); end - + [idx, found] = this.FindSignalsIdx(sig); if found sig = this.P.ParamList{idx}; @@ -2288,10 +4008,10 @@ function ExportToExcel(this, excel_file) fprintf(st); else varargout{1} = st; - end + end end - - function st = get_signal_attributes_string(this, sig) + + function st = get_signal_attributes_string(this, sig) atts = this.get_signal_attributes(sig); if isempty(atts) st = ''; @@ -2303,21 +4023,21 @@ function ExportToExcel(this, excel_file) st = sprintf([st '%s)'], atts{end}); end end - + function varargout = PrintParams(this,params, header) st = ''; nb_pts= this.GetNbParamVectors(); - + if ~exist('params','var') params = this.P.DimX+1:numel(this.P.ParamList); elseif ischar(params)||iscell(params) params = FindParam(this.P, params); end - + if ~exist('header', 'var') - header = '-- PARAMETERS --'; + header = '-- PARAMETERS --'; end - + if (nb_pts<=1) if ~isempty(header) st = [header sprintf('\n')]; @@ -2325,33 +4045,33 @@ function ExportToExcel(this, excel_file) for ip = params st = [st sprintf('%s=%g %s\n',this.P.ParamList{ip},this.P.pts(ip,1), this.Domains(ip).short_disp(1))]; end - + else if ~isempty(header) - st = [header sprintf(' (%d vectors):\n',nb_pts)]; + st = [header sprintf(' (%d samples):\n',nb_pts)]; end for ip = params st = [st sprintf('%s %s\n',this.P.ParamList{ip}, this.Domains(ip).short_disp(1))]; end - + end - + st = [st sprintf('\n')]; - + if nargout == 0 varargout = {}; fprintf(st); else varargout{1} = st; end - + end - + function varargout = PrintAll(this) this.UpdateSignalRanges(); st = this.PrintSignals(); st = sprintf([st this.PrintParams()]); - + if nargout == 0 varargout = {}; fprintf(st); @@ -2359,423 +4079,184 @@ function ExportToExcel(this, excel_file) varargout{1} = st; end end - - %% Misc + + function s= isSignal(this,params) idx_s = FindParam(this.P, params); s = idx_s <= this.P.DimX; end - + % Warning handler function WarningResetP(this, fname) if this.GetNbParamVectors()>1 - warning('BreachSet:warning_will_reset_pts',['This set contains more than one parameter vector or traces - the function ' fname ' will likely erase them.']) + warning('BreachSet:warning_will_reset_pts',['This set contains more than one parameter sample or traces - the function ' fname ' will likely erase them.']) end end - + function bl = hasTraj(this) % checks if this has traces bl=isfield(this.P, 'traj'); end - + %% Reset functions - will need some cleaning and rationalizing eventually function Reset(this) % BreachSet.Reset() - - this.P = Sselect(this.P,1); + + this.P = Sselect(this.P,1); p_req = this.P.pts(this.P.DimP+1:end,1); - + this.P = CreateParamSet(this.P); try this.P.pts = this.Sys.p; if ~isempty(p_req) - this.P.pts = [this.P.pts; p_req]; + this.P.pts = [this.P.pts; p_req]; end % Add property params props = this.Specs.values; for i=1:numel(props) phi = props{i}; params_prop = get_params(phi); - this.SetParamSpec(fieldnames(params_prop)', cellfun(@(c) (params_prop.(c)), fieldnames(params_prop)),1); + this.SetParamSpec(fieldnames(params_prop)', cellfun(@(c) (params_prop.(c)), fieldnames(params_prop)'),1); end end for ip = 1:numel(this.P.ParamList) this.Domains(ip).domain=[]; end - + this.resetStatus(); end - + function ResetEpsi(this) - % (Legacy) Set param ranges around individual parameter vectors to zero + % (Legacy) Set param ranges around individual parameter samples to zero this.P.epsi(:,:) = 0; end - + function ResetDomains(this) % BreachSet.ResetDomains Sets all domains to empty for id = 1:numel(this.Domains) this.Domains(id).domain = []; end end - + function ResetDomain(this, params) % BreachSet.ResetDomains Sets given domain(s) to empty - + idx_params = FindParam(this.P, params); for id = 1:numel(idx_params) if (idx_params(id) <= numel(this.Domains)) % param was found, otherwise do nothing this.Domains(idx_params(id)).domain = []; end end - end - + end + function ResetSimulations(this) % Removes computed trajectories this.P = SPurge(this.P); - + if size(this.P.pts,2)>1 % get rid of redundant pts [~, iu] = unique(this.P.pts','rows'); this.P = Sselect(this.P, iu); end this.SignalRanges = []; - + end - + function ResetSelected(this) nb_pts = this.GetNbParamVectors(); this.P.selected = zeros(1,nb_pts); end - + function aliases = getAliases(this, signals) - + if ischar(signals) signals = {signals}; end - + aliases = signals; for is = 1:numel(signals) - sig = signals{is} ; - if this.AliasMap.isKey(sig) - aliases = union(aliases, this.AliasMap(sig),'stable'); - end - end - - end - - end - - - %% Coverage Methods - methods - - function SetEpsGridsize(this,eps_size_vector) - % Assign a grid sizes for each parameter dimension. The eps - % grid size indicates the length of grid elements for each of - % the dimensions in parameter space. The eps value for each - % dimension corresponds to the smallest distance allowed - % between any two points in the parameter space. - - if ~isempty(this.epsgridsize) - error('Eps grid size already set. You cannot change the grid size after it has been defined.'); - end - - if isrow(eps_size_vector) - eps_size_vector = eps_size_vector'; - end - - % Number of parameters with ranges associated with them. - varying_parameter_indices = this.VaryingParamList(); - - if length(eps_size_vector)~=length(varying_parameter_indices) - error('Length of the size vector does not match the number of parameters wth ranges associated with them.'); - end - this.epsgridsize = eps_size_vector; - end - - function SetDeltaGridsize(this,delta_size_vector) - % Assign a grid sizes for each parameter dimension. The eps - % grid size indicates the length of grid elements for each of - % the dimensions in parameter space. The delta value for each - % dimension corresponds to the grid element size that is used to - % measure the cell count and entropy coverage measures. - - if ~isempty(this.deltagridsize) - error('Delta grid size already set. You cannot change the grid size after it has been defined.'); - end - if isrow(delta_size_vector) - delta_size_vector = delta_size_vector'; - end - % Number of parameters with ranges associated with them. - - varying_parameter_indices = this.VaryingParamList; - if length(delta_size_vector)~=length(varying_parameter_indices) - error('Length of the size vector does not match the number of parameters wth ranges associated with them.'); - end - this.deltagridsize = delta_size_vector; - end - - function SetSnapToGrid(this,snapflag) - this.snap_to_grid = snapflag; - end - - function inRange = TestPointInRange(this,new_point) - % Identify the "lower, left" and "upper, right" corners of the - % parameter set - if isrow(new_point) - new_point = new_point'; - end - inRange = true; - varying_parameter_indices = this.VaryingParamList; - lower_left_corner = []; - upper_right_corner = []; - for ip = varying_parameter_indices - lower_left_corner = [lower_left_corner; this.Domains(ip).domain(1)]; - upper_right_corner = [upper_right_corner; this.Domains(ip).domain(2)]; - end - - if any(new_point < lower_left_corner)||any(new_point>upper_right_corner) - %fprintf('\nNew point out of range.\n'); - inRange = false; - end - end - - function lower_left_corner = LowerLeftCorner(this) - % Identify the "lower, left" corner of the parameter set - varying_parameter_indices = this.VaryingParamList; - lower_left_corner = []; - for ip = varying_parameter_indices - lower_left_corner = [lower_left_corner; this.Domains(ip).domain(1)]; - end - end - - function upper_right_corner = UpperRightCorner(this) - % Identify the "upper, right" corner of the parameter set - varying_parameter_indices = this.VaryingParamList; - upper_right_corner = []; - for ip = varying_parameter_indices - upper_right_corner = [upper_right_corner; this.Domains(ip).domain(2)]; - end - end - - function [varargout] = AddPoints(this,new_points) - % Add a collection of new points to the space. - - var = this.GetVariables(); - if nargin<=1 - new_points = this.GetParam(var); - end - - - % Assume that for an "row x col" matrix that represents the new points, - % each col represents a unique point. - num_points = size(new_points,2); - for ind = 1:num_points - thisPoint = new_points(:,ind); - this.AddPoint(thisPoint); - end - - end - - function [varargout] = AddPoint(this,new_point) - % Add a point to the data structure - - if nargout>0 - varargout{1} = true; - end - if nargout>1 - varargout{2} = []; - end - - if ~this.TestPointInRange(new_point) - %fprintf('\nPoint not in range, so it was not added.\n'); - if nargout>0 - varargout{1} = false; - end - return - end - - epsgridsize = this.epsgridsize; - deltagridsize = this.deltagridsize; - - if isrow(new_point) - new_point = new_point'; - end - - if nargout>1 - varargout{2} = new_point; - end - - % Identify the "lower, left" corner of the parameter set - lower_left_corner = this.LowerLeftCorner(); - - shifted_point = new_point - lower_left_corner; - - % First, identify corresponding eps grid element - shifted_grid_element = diag(epsgridsize)*floor(shifted_point./epsgridsize); - eps_grid_element = shifted_grid_element + lower_left_corner; - - % Also, identify delta grid element - shifted_grid_element = diag(deltagridsize)*floor(shifted_point./deltagridsize); - delta_grid_element = shifted_grid_element + lower_left_corner; - - if this.snap_to_grid - if this.EpsGridMapObj.isKey(mat2str(eps_grid_element)) - %fprintf('\nValue already present in grid element %s.\n',mat2str(eps_grid_element)); - if nargout>0 - varargout{1} = false; - end - if nargout>1 - varargout{2} = []; - end - else - if this.DeltaGridMapObj.isKey(mat2str(delta_grid_element)) - previous_grid_members = this.DeltaGridMapObj(mat2str(delta_grid_element)); - this.DeltaGridMapObj(mat2str(delta_grid_element)) = previous_grid_members + 1; - else - this.DeltaGridMapObj(mat2str(delta_grid_element)) = 1; - end - this.EpsGridMapObj(mat2str(eps_grid_element)) = 1; - if nargout>1 - varargout{2} = eps_grid_element; - end - end - else - % For this case, create a list for each grid - % element that is populated by at least one point. - if this.EpsGridMapObj.isKey(mat2str(eps_grid_element)) - previous_grid_members = this.EpsGridMapObj(mat2str(eps_grid_element)); - this.EpsGridMapObj(mat2str(eps_grid_element)) = [previous_grid_members new_point]; - else - this.EpsGridMapObj(mat2str(eps_grid_element)) = [new_point]; - end - - if this.DeltaGridMapObj.isKey(mat2str(delta_grid_element)) - previous_grid_members = this.DeltaGridMapObj(mat2str(delta_grid_element)); - this.DeltaGridMapObj(mat2str(delta_grid_element)) = previous_grid_members + 1; - else - this.DeltaGridMapObj(mat2str(delta_grid_element)) = 1; + sig = signals{is} ; + if this.AliasMap.isKey(sig) + aliases = union(aliases, this.AliasMap(sig),'stable'); end end - - end - - function varying_parameter_indices = VaryingParamList(this) - % Determine the indices into the SimulinkSystem parameters that - % correspond to parameters that are allowed to vary (i.e., that - % have a range assocaited with them). - - [~, varying_parameter_indices] = this.GetBoundedDomains(); - - end - - function num_points = NumPoints(this) - % Return number of points in the list - num_points = double(this.EpsGridMapObj.Count); - end - - function DisplayPoints(this) - % Display all points in the list - KeysSet = this.EpsGridMapObj.keys; - fprintf('\nCell: Epsi Occupancy\n'); - for ind = 1:length(KeysSet); - ValueTemp = this.EpsGridMapObj(KeysSet{ind}); - fprintf('%s: %s \n',KeysSet{ind}, mat2str(ValueTemp')); - end - - KeysSet = this.DeltaGridMapObj.keys; - fprintf('\nCell: Delta Occupancy\n'); - for ind = 1:length(KeysSet); - ValueTemp = this.DeltaGridMapObj(KeysSet{ind}); - fprintf('%s: %s \n',KeysSet{ind}, mat2str(ValueTemp')); - end - - - end - - function total_cells = TotalCellCount(this) - % Total number of cells in the parameter space - % - % First, compute the width of the range for each dimension in - % the parameter space. - varying_parameter_indices = this.VaryingParamList; - rg = []; - for ip = varying_parameter_indices - rg = [rg; this.Domains(ip).domain(2)-this.Domains(ip).domain(1)]; - end - total_cells = prod(ceil(rg./this.epsgridsize)); - end - - function coverage = ComputeLogCellOccupancyCoverage(this) - % Compute the log of the cell occupancy - % for the parameter space - - % Total number of cells in the parameter space - total_cells = this.TotalCellCount(); - % Next, obtain the total number of populated cells - pop_cells = this.NumPoints(); - coverage = log(pop_cells)/log(total_cells); - end - - function coverage = ComputeCellOccupancyCoverage(this) - % Compute the cell occupancy for the parameter space - - % Total number of cells in the parameter space - total_cells = this.TotalCellCount(); - % Next, obtain the total number of populated cells - pop_cells = this.NumPoints(); - coverage = pop_cells/total_cells; - end - - function coverage = ComputeEntropyCoverage(this) - % Compute the combinatorial entropy (also known as the - % coarse-grained Boltzmann entropy). - - % The entropy measure is given by the following: - % - % G(D) = p!/(n_1!\cdots n_c!) - % - % S_B(D) = log(G(D)), - % - % where p is the total number of points, and n_i is the number - % of points in cell i. - - % Collect the number of points in each cell and compute the - % denominator of G(D). At the same time, compute the total - % number of points p. - delta_cell_labels = this.DeltaGridMapObj.keys; - numpoints = 0; - GD_denominator = 1; - for ind = 1:length(delta_cell_labels) - n_i = double(this.DeltaGridMapObj(delta_cell_labels{ind})); - numpoints = numpoints + n_i; - GD_denominator = GD_denominator*(factorial(n_i)); - end - - GD_numerator = factorial(numpoints); - - GD = GD_numerator/GD_denominator; - - coverage = log(GD); + end - + end - - - - methods (Access=protected) - + +%% Old coverage code +% The following commented code is for future potential coverage measure, +% courtesy of Jim K. +% +% function coverage = ComputeLogCellOccupancyCoverage(this) +% % Compute the log of the cell occupancy +% % for the parameter space +% +% % Total number of cells in the parameter space +% total_cells = this.TotalCellCount(); +% % Next, obtain the total number of populated cells +% pop_cells = this.NumPoints(); +% coverage = log(pop_cells)/log(total_cells); +% end +% +% function coverage = ComputeCellOccupancyCoverage(this) +% % Compute the cell occupancy for the parameter space +% +% % Total number of cells in the parameter space +% total_cells = this.TotalCellCount(); +% % Next, obtain the total number of populated cells +% pop_cells = this.NumPoints(); +% coverage = pop_cells/total_cells; +% end +% +% function coverage = ComputeEntropyCoverage(this) +% % Compute the combinatorial entropy (also known as the +% % coarse-grained Boltzmann entropy). +% +% % The entropy measure is given by the following: +% % +% % G(D) = p!/(n_1!\cdots n_c!) +% % +% % S_B(D) = log(G(D)), +% % +% % where p is the total number of points, and n_i is the number +% % of points in cell i. +% +% % Collect the number of points in each cell and compute the +% % denominator of G(D). At the same time, compute the total +% % number of points p. +% delta_cell_labels = this.DeltaGridMapObj.keys; +% numpoints = 0; +% GD_denominator = 1; +% for ind = 1:length(delta_cell_labels) +% n_i = double(this.DeltaGridMapObj(delta_cell_labels{ind})); +% numpoints = numpoints + n_i; +% GD_denominator = GD_denominator*(factorial(n_i)); +% end +% +% GD_numerator = factorial(numpoints); +% +% GD = GD_numerator/GD_denominator; +% +% coverage = log(GD); +% end + + + + methods (Access=protected) + function Xp = get_signals_from_traj(this, traj, names) idx = FindParam(this.P, names); % not fool proof, but not supposed to be used by fools Xp = traj.X(idx,:); end - + function traj = set_signals_in_traj(this, traj, names, Xp) idx = FindParam(this.P, names); % not fool proof, but not supposed to be used by fools traj.X(idx,:) = Xp; end - + %% Compare (FIXME) function cmp = compare(this, other) % Compares two BreachSet. Goes through a series of tests, logs @@ -2786,16 +4267,16 @@ function DisplayPoints(this) % (structural) difference, 0 means identical, large negative % means potiential bug, other can be anything, though structure % should be the same. - + cmp = BreachStatus; - + % checks if the handles are the same is_same_obj = (this==other); if is_same_obj cmp.addStatus(0, 'The two sets are the same objects in memory.') return; end - + % checks presence of P if (isempty(this.P))&&((isempty(other.P))) cmp.addStatus(0, 'The two objects have an empty parameter set.') @@ -2807,18 +4288,18 @@ function DisplayPoints(this) cmp.addStatus(10000, 'Parameter set is empty in second object.'); return; end - + % Checks signals and parameters thisParamList = this.P.ParamList; otherParamList = other.P.ParamList; - + diff_pl = ~(isequal(thisParamList,otherParamList)); if diff_pl % Checks signals sigthis = this.GetSignalNames(); sigother = other.GetSignalNames(); diff_signal = ~(isequal(sigthis,sigother)); - + if diff_signal cmp.addStatus(1, 'The two sets have different signals.') return; @@ -2833,51 +4314,51 @@ function DisplayPoints(this) cmp.addStatus(10, 'The two sets have different property parameter lists.') end end - + end - + % checks if parameter sets are equal if (isequal(this.P,other.P)) cmp.addStatus(0,'The two sets parameters and traces are strictly identical.'); return; end - + % checks pts nb nb_pts_this = this.GetNbParamVectors(); nb_pts_other = other.GetNbParamVectors(); - + if nb_pts_this ~= nb_pts_other - cmp.addStatus(10,'The two sets have different number of parameter vectors.'); + cmp.addStatus(10,'The two sets have different number of parameter samples.'); return; end - + % Checks system parameters rg_sys = this.P.DimX+1:this.P.DimP; % at this point, this is the same as other sys_pts_this = this.P.pts(rg_sys,:); sys_pts_other = other.P.pts(rg_sys,:); - + diff_sys_pts = norm(sys_pts_this-sys_pts_other); if (diff_sys_pts == 0) - cmp.addStatus(0, 'The two sets have the same system parameter vectors.'); + cmp.addStatus(0, 'The two sets have the same system parameter samples.'); else - cmp.addStatus(1, ['Distance between the two system parameter vectors: ' num2str(diff_sys_pts)]) + cmp.addStatus(1, ['Distance between the two system parameter samples: ' num2str(diff_sys_pts)]) end - + % Checks spec. parameters rg_pspec = this.P.DimP+1:size(this.P.pts,1); % at this point, this is the same as other if ~isempty(rg_pspec) spec_pts_this = this.P.pts(rg_pspec,:); spec_pts_other = other.P.pts(rg_pspec,:); - + diff_spec_pts = norm(spec_pts_this-spec_pts_other); if (diff_spec_pts == 0) - cmp.addStatus(0, 'The two objects have the same spec. parameter vectors.'); + cmp.addStatus(0, 'The two objects have the same spec. parameter samples.'); else - cmp.addStatus(1, ['Distance between the two spec. parameter vectors: ' num2str(diff_spec_pts)]) + cmp.addStatus(1, ['Distance between the two spec. parameter samples: ' num2str(diff_spec_pts)]) end end - - + + % Checks trajs field if (isfield(this.P, 'traj'))&&(~isfield(other.P, 'traj')) cmp.addStatus(1000, 'This has computed trajectories or traces but not other'); @@ -2894,7 +4375,7 @@ function DisplayPoints(this) cmp.addStatus(100, 'Different numbers of traces.'); return; end - + if isequal(this.P.traj, other.P.traj) cmp.addStatus(0, 'Traces are identical.'); else @@ -2919,9 +4400,9 @@ function DisplayPoints(this) cmp.addStatus(1,['Max difference between trajectories: p:' num2str(max_diff_param) ' time:' num2str(max_diff_time) ' X:' num2str(max_diff_X)]); end end - + % Checks props fields - + if (isfield(this.P, 'props'))&&(~isfield(other.P, 'props')) cmp.addStatus(100, 'This has properties evaluated but not other'); return; @@ -2933,17 +4414,17 @@ function DisplayPoints(this) if (isfield(this.P, 'props_names'))&&(isfield(other.P, 'props_names')) if isequal(this.P.props_names,other.P.props_names) if (isfield(this.P, 'props_values'))&&(isfield(other.P, 'props_values')) - + % both have traces, compare them - + this_props_vals = this.P.props_values; other_props_vals = other.P.props_values; - + if ~isequal(size(this_props_vals), size(other_props_vals)) cmp.addStatus(-100, 'Different numbers of property evaluations.'); return; end - + if isequal(this_props_vals, other_props_vals) cmp.addStatus(0, 'Property evaluations are identical.'); else @@ -2966,26 +4447,26 @@ function DisplayPoints(this) if diff_val cmp.addStatus(1, [pre_status '-- Overall difference in quantitative satisfactions: ' num2str(diff_val)]); end - + end end end end else cmp.addStatus(-100,'BUG: field props is present but not field props_values'); % probably dead code... - + end else cmp.addStatus(100,'Property names evaluated in this and other are different.'); end - + else cmp.addStatus(-100,'BUG: field props is present but not field props_names'); % probably dead code... end end - - + + end - + end end diff --git a/Core/BreachSimulinkSystem.m b/Core/BreachSimulinkSystem.m index e042a6eb..bc6d6151 100644 --- a/Core/BreachSimulinkSystem.m +++ b/Core/BreachSimulinkSystem.m @@ -1187,6 +1187,25 @@ function Sim(this, tspan, U) end end + %% Disp + + + + function varargout = disp(this) + + str = sprintf('BreachSimulink object for model %s.\n',this.mdl.name); + str = [str this.disp_short_description()]; + + if nargout == 0 + varargout = {}; + fprintf(str); + else + varargout{1} = str; + end + + end + + %% Misc function S = GetSignature(this, varargin) S = GetSignature@BreachOpenSystem(this, varargin{:}); @@ -1276,8 +1295,8 @@ function ClearDiskCache(this) function hash = get_hash(this) st.signals = this.GetSignalsList(); - st.params = this.GetParamList(); - + st.params = this.GetParamList(); + hash= DataHash(st); end %% Export result @@ -1502,7 +1521,7 @@ function ExportToExcel(this, excel_file) % TODO: specialize to Simulink ? or us end - function st = disp(this) + function st = disp_old(this) if isfield(this.P, 'traj') nb_traj = numel(this.P.traj); else diff --git a/Core/BreachSimulinkWizard.m b/Core/BreachSimulinkWizard.m index 4e618119..41b63373 100644 --- a/Core/BreachSimulinkWizard.m +++ b/Core/BreachSimulinkWizard.m @@ -174,7 +174,7 @@ evalin('base', [opt.VariableName '=BreachSimulinkSystem(''' Name ''', params__,[],sigs__,IG__,opt__ );']); Br = evalin('base',opt.VariableName); gu = Br.SetInputGenGUI(); -uiwait(gu); +uiwait(gu.main); clean; diff --git a/Core/BreachSystem.m b/Core/BreachSystem.m index 2a41a9c8..793ea96e 100644 --- a/Core/BreachSystem.m +++ b/Core/BreachSystem.m @@ -55,6 +55,7 @@ otherwise % creates an extern system if ~(exist(varargin{1})==4) % tests if the first argument is a Simulink model this.Sys = CreateExternSystem(varargin{:}); + else warning('First argument is the name of a Simulink model - consider using BreachSimulinkSystem instead'); end @@ -63,7 +64,9 @@ this.SignalRanges = []; if (isaSys(this.Sys)) this.P = CreateParamSet(this.Sys); - end + this.Domains = repmat(BreachDomain(), 1, this.Sys.DimP); + end + end function SetupParallel(this, NumWorkers) @@ -157,6 +160,44 @@ function StopParallel(this) end end + function Bout = parSim(this, varargin) + % ALPHA tentative simpler parallel implementation. + + if isempty(gcp('nocreate')) + gcp; + end + if ~isempty(this.InitFn) + pctRunOnAll(this.InitFn); + end + + num_sim = this.GetNbParamVectors(); + num_task = num_sim; + if num_sim == 1 + this.Sim(varargin{:}) + else + % create tasks systems + + for i = 1:num_sim + Btask(i) = this.ExtractSubset(i); + end + + fprintf(2,[repmat('|',1,num_task) ' 100%%\n\n']); + parfor i = 1:num_task + Btask(i).Sim(varargin{:}); + Bres{i} = Btask(i).copy(); % need to actually copy to get trace data + fprintf('\b|\n'); + end + fprintf('\n'); + Bout = Bres{1}.copy(); + for i = 2:num_task + Bout.Concat(Bres{i},1) + end + this.P = Bout.P; + end + + end + + %% Parameters % Get and set default parameter values (defined in Sys) function values = GetDefaultParam(this, params) @@ -193,7 +234,7 @@ function SetTime(this,tspan) error('BreachSystem:SetTime:undef', 'Cannot evaluate time expression %s', tspan); end else - this.P.Sys.tspan = check_sim_time(tspan); + this.Sys.tspan = check_sim_time(tspan); end end function time = GetTime(this) @@ -240,10 +281,7 @@ function PlotEnveloppe(this, signals) end - end - - - + end %% Specs function phi = AddSpec(this, varargin) @@ -402,17 +440,6 @@ function SetSpec(this,varargin) Sim(this); this.CheckinDomainTraj(); - % JOHAN ADDED - - % This was used previously to save trajectory info to the - % folder 'trajectories' -% filesInTrajFolder = length(dir('trajectories')) - 2; -% tmpP = this.P; -% paramValues = values; -% load('nextReqToBeFalsified'); % Loads currentReq -% save(['trajectories/' num2str(filesInTrajFolder + 1) '.mat'],'tmpP','params','paramValues', 'currentReq'); - % END JOHAN ADDED - % FIXME: this is going to break with multiple trajectories with % some of them containing NaN - @@ -460,16 +487,6 @@ function SetSpec(this,varargin) Sim(this); this.CheckinDomainTraj(); - % JOHAN ADDED - % This was used previously to save trajectory info to the - % folder 'trajectories' -% filesInTrajFolder = length(dir('trajectories')) - 2; -% tmpP = this.P; -% paramValues = values; -% load('nextReqToBeFalsified'); % Loads currentReq -% save(['trajectories/' num2str(filesInTrajFolder + 1) '.mat'],'tmpP','params','paramValues', 'currentReq'); - % END JOHAN ADDED - % FIXME: this is going to break with multiple trajectories with % some of them containing NaN - if any(isnan(this.P.traj{1}.X)) @@ -1035,24 +1052,23 @@ function AddOutput(this, output) end end + function varargout = disp(this) - if isfield(this.P, 'traj') - nb_traj = numel(this.P.traj); - else - nb_traj = 0; - end - - st = ['BreachSystem ' this.Sys.name '. It contains ' num2str(this.GetNbParamVectors()) ' samples and ' num2str(nb_traj) ' unique traces.\n']; + + str = 'BreachSystem object.\n'; + str = [str this.disp_short_description()]; if nargout == 0 varargout = {}; - fprintf(st); + fprintf(str); else - varargout{1} = st; + varargout{1} = str; end end - + + + function assignin_ws_p0(this) ip = 0; for p = this.Sys.ParamList diff --git a/Core/BreachTraceSystem.m b/Core/BreachTraceSystem.m index fe3e61c4..3f8d854f 100644 --- a/Core/BreachTraceSystem.m +++ b/Core/BreachTraceSystem.m @@ -136,7 +136,18 @@ function AddTrace(this, trace, params) traj.X(isig,:) = trace.inputs.values(idx_sig,:); end end - + elseif all(isfield(trace,{'time', 'signals','params'})) % reading one struct obtained from a SaveResult + traj.time= trace.time; + signals = this.GetSignalNames(); + traj.X = zeros(numel(signals),numel(traj.time)); + for isig = 1:numel(signals) + idx_sig = find(strcmp(trace.signals.names, signals{isig}),1); + if isempty(idx_sig) + error('BreachTraceSystem:signal_not_found', 'Signal %s not found', signals{isig}); + end + traj.X(isig,:) = trace.signals.values(idx_sig,:); + end + params = trace.params.values; elseif all(isfield(trace,{'time', 'signals'})) % reading one struct obtained from a SaveResult traj.time= trace.time; signals = this.GetSignalNames(); @@ -177,7 +188,7 @@ function AddTrace(this, trace, params) if numel(params)~= num_p error('BreachTraceSystem:wrong_param', 'Wrong number of parameters (last argument), should be %d', this.P.DimP-this.P.DimX); else - traj.param(this.DimX+1:end) = reshape(params, 1, num_p); + traj.param(this.Sys.DimX+1:end) = reshape(params, 1, num_p); end end diff --git a/Core/ComputeTraj.m b/Core/ComputeTraj.m index 9f81fef8..c9995477 100644 --- a/Core/ComputeTraj.m +++ b/Core/ComputeTraj.m @@ -109,7 +109,9 @@ for ii = 1:numel(Pf.traj) Pf.traj{ii}.param = Pf.pts(1:Pf.DimP,ii)'; end - elseif(isfield(P0,'traj_to_compute') &&... + Pf = Preset_traj_ref(Pf); % also checks consistency of initial signal values in pts and traj + +elseif(isfield(P0,'traj_to_compute') &&... ~isempty(P0.traj_to_compute) && ~isequal(P0.traj_to_compute,1:size(P0.pts,2))&&... % some traces have already be computed isfield(P0, 'traj')&&~isempty(P0.traj)&&isequal(P0.traj{1}.time, tspan)) % some traces have been computed on the same tspan % Here, we assume: @@ -169,6 +171,7 @@ end [traj.time, traj.X] = Sys.sim(Sys, tspan, P0.pts(:,ii)); + %[traj.time, traj.X] = Sys.sim(Sys, tspan, P0.pts(Sys.DimX+1:end,ii)); traj.param = P0.pts(1:P0.DimP,ii)'; if isfield(Sys, 'output_gens') @@ -338,6 +341,18 @@ end +% enforce consistency of pts and init traj values + +use_caching = isfield(Sys,'DiskCachingFolder')&&(~isempty(Sys.DiskCachingFolder)); +if ~use_caching % maybe too slow with large traces in cache. else warning ? or make sure this is done somewhere else TODO + for ii = 1:size(Pf.pts, 2) + itraj = Pf.traj_ref(ii); + X0 = Pf.traj{itraj}.X(:,1); + Pf.pts(1:Pf.DimX,ii) = X0; + Pf.traj{itraj}.param(1:numel(X0)) = X0'; + end +end + if(isfield(Sys,'time_mult') && ~isfield(Pf,'time_mult')) Pf.time_mult = Sys.time_mult; end diff --git a/Core/Gui/BreachGuiClass.m b/Core/Gui/BreachGuiClass.m index 2b5d295d..55253486 100644 --- a/Core/Gui/BreachGuiClass.m +++ b/Core/Gui/BreachGuiClass.m @@ -43,11 +43,20 @@ hs2= this.create_separator('hsep.1'); hs2.h = 0.1; + hs2bis= this.create_separator('hsep.2'); + hs2bis.h = 0.2; + hs25 = this.create_separator('hsep.25'); hs25.h = 0.25; - hs3= this.create_separator('hsep.5'); - hs3.h = 0.5; + hs3 = this.create_separator('hsep.3'); + hs3.h = 0.3; + + hs4 = this.create_separator('hsep.4'); + hs4.h = 0.4; + + hs5= this.create_separator('hsep.5'); + hs5.h = 0.5; ws1= this.create_separator('wsep.05'); ws1.w = 0.05; @@ -58,12 +67,12 @@ this.create_separator('wsep.5', .5,1); this.create_separator('fullsep', 1, 1); - %% create scaling invisible button + %% create scaling invisible button button_scale = this.create_button('button_scale'); this.set_by_id('button_scale', 'enable', 'off', 'Position', [0 0 this.wunit this.hunit]); - %% Creates default ok and cancel buttons + %% Creates default ok and cancel buttons button_ok = this.create_button('button_ok','Ok',@(o,e)(this.button_ok_callback(o))); button_cancel = this.create_button('button_cancel','Cancel',@(o,e)(this.button_cancel_callback())); @@ -165,7 +174,7 @@ this.uimap(id)= e; end - + function e = create_slider(this, id, string, callback, w,h) if ~exist('callback','var')||isempty(callback) callback = @(o,e)(this.zcallback(id,o,e)); @@ -312,9 +321,7 @@ if ~exist('callback','var')||isempty(callback) callback = @(o,e)(this.zcallback(id,o,e)); end - - - + g = uicontrol('Parent',this.hdle,... 'Style','listbox',... @@ -438,8 +445,9 @@ this.uimap(id)= e; end - + function e = create_panel(this, id, string, layout) + if nargin<3 string = [id ' panel']; end @@ -450,6 +458,10 @@ 'FontSize', this.font_size,... 'Units', 'pixel',... 'visible', 'off'); + + + + layout = [{{'hsep.5'}}; layout; {{'hsep.2'}}]; [w, h] = get_layout_sz(this, layout); e = panel_elem(p, layout); @@ -467,6 +479,306 @@ this.uimap(id)= e; end + %% Advanced elements + function e = create_changing_button(this, id,names, callback ) + % button cycling through a list of options + + e = create_button(this,id, names{1},@(o,e)(callback_changing)); + + this.data_gui.(id).Value= 1; + this.data_gui.(id).String = names{1}; + this.data_gui.(id).StringList =names; + num_options = numel(names); + + function callback_changing() + idx = this.data_gui.(id).Value; + if idx==num_options + idx = 1; + else + idx=idx+1; + end + this.data_gui.(id).Value=idx; + this.data_gui.(id).String = this.data_gui.(id).StringList{idx}; + update(); + end + + function update() + set(e.hdle, 'String', this.data_gui.(id).String); + callback(); + end + end + + function e = create_domain_slider(this, id, name, domain, value, callback) + % Creates a slider from a BreachDomain + id_min = [id '_min']; + id_val = [id '_val']; + id_max = [id '_max']; + id_slider = [id '_slider']; + + e_min = this.create_edit(id_min, num2str(0), @(o,e)(callback_edit()), 1/4, 1/2); + e_min.htop = e_min.htop/2; + e_min.wright=0; + + e_val =this.create_edit(id_val, num2str(0.5),@(o,e)(callback_edit()), 1/2, 1/2); + e_val.htop = e_val.htop/2; + e_val.wleft = e_val.wleft/2; + e_val.wright = e_val.wright/2; + + e_max = this.create_edit(id_max, num2str(1),@(o,e)(callback_edit()), 1/4, 1/2); + e_max.wleft = 0; + e_max.htop = e_max.htop/2; + + e_slider= this.create_slider(id_slider, name, @(o,e)(callback_slider())); + + this.set_h(id_slider, 1/2); + this.set_w(id_slider, 1); + + %% create panel + layout_panel = { ... + {id_min, id_val, id_max}; ... + {id_slider};... + }; + + string = name; + e = this.create_panel(id,string, layout_panel); + + %% Setup values of stuff + value = domain.checkin(value); + this.data_gui.(id).name =name; + this.data_gui.(id).domain =domain; + this.data_gui.(id).value = value; + this.data_gui.(id).callback = callback; + + function callback_edit() + + bug= 0; + dom = this.data_gui.(id).domain.domain; + if isempty(dom) + old_min = -inf; + old_max = inf; + else + old_min = dom(1); + old_max =dom(2); + end + old_value = this.data_gui.(id).value; + + min_value = str2double(get(e_min.hdle, 'String')); + if isnan(min_value) + bug= 1; + min_value = old_min; + end + max_value = str2double(get(e_max.hdle, 'String')); + if isnan(max_value) + bug= 1; + max_value = old_max; + end + val = str2double(get(e_val.hdle, 'String')); + if isnan(val) + bug= 1; + val = old_value; + end + + if max_value-min_value<0 + bug = 1; + if old_max ~= max_value + max_value = min_value; + else + min_value= max_value; + end + end + + if bug||~(isequal(old_min,min_value)&&isequal(old_max,max_value)&&isequal(old_value,val)) + if min_value == -inf && max_value== inf + this.data_gui.(id).domain.domain = []; + else + this.data_gui.(id).domain.domain = [min_value, max_value]; + end + this.data_gui.(id).value = this.data_gui.(id).domain.checkin(val); + update(~bug&&~isequal(this.data_gui.(id).value,old_value )); + end + end + + function callback_slider() + + dom = this.data_gui.(id).domain; + val= get(e_slider.hdle,'Value'); + if strcmp(dom.type,'enum') + m =str2double( get(e_min.hdle,'String')); + M =str2double( get(e_max.hdle,'String')); + enum = dom.enum; + enum = enum(enum>=m); + enum = enum(enum<=M); + val = enum(val); + else + val = this.data_gui.(id).domain.checkin(val); + end + + old_value = this.data_gui.(id).value; + + if ~isequal(old_value , val) + this.data_gui.(id).value = val; + update(true); + end + end + + function update(call) + this.update_domain_slider(id, call); + end + + end + + function update_domain_slider(this, id, call) + % if call is true, call user provided callback of the slider. + % reads Breach Domain and value. value is in domain already. + name = this.data_gui.(id).name; + dom = this.data_gui.(id).domain.domain; + val = this.data_gui.(id).value; + + id_min = [id '_min']; + id_val = [id '_val']; + id_max = [id '_max']; + id_slider = [id '_slider']; + + e = this.uimap(id); + e_slider = this.uimap(id_slider); + e_min = this.uimap(id_min); + e_max = this.uimap(id_max); + e_val = this.uimap(id_val); + + if isempty(dom) + min = '-inf'; + max = 'inf'; + min_slider= val-10; + max_slider = val+10; + elseif (dom(1) ==-inf) && dom(2)-inf + min = num2str(dom(1)); + max = 'inf'; + min_slider= dom(1); + max_slider = val+100; + else + min = num2str(dom(1)); + max = num2str(dom(2)); + min_slider= dom(1); + max_slider = dom(2); + end + + switch this.data_gui.(id).domain.type + case 'bool' + min = 0; + max = 1; + + set(e_slider.hdle,'Max',1); + set(e_slider.hdle,'Min',0); + set(e_slider.hdle,'SliderStep',[1 1]); + set(e_slider.hdle,'Value', val); + + + case 'int' + + min = ceil(str2num(min)); + max = floor(str2num(max)); + min_slider= ceil(min_slider); + max_slider = floor(max_slider); + if min_slider == max_slider + max_slider = max_slider+1; + end + + set(e_slider.hdle,'Min', min_slider); + set(e_slider.hdle,'Max', max_slider); + set(e_slider.hdle,'Value', val); + set(e_slider.hdle,'SliderStep',[1 10]/(max_slider - min_slider)); + + case 'enum' + enum = this.data_gui.(id).domain.enum; + enum = enum(enum>=str2double(min)); + enum = enum(enum<=str2double(max)); + if isempty(enum) + enum = nan; + end + min_slider = 1; + max_slider = numel(enum); + val_slider = find(val==enum,1); + if isempty(val_slider) + val_slider = 1; + val = nan; + end + if min_slider==max_slider + max_slider=min_slider+1; + end + + set(e_slider.hdle,'Min', min_slider); + set(e_slider.hdle,'Max', max_slider); + set(e_slider.hdle,'Value', val_slider); + set(e_slider.hdle,'SliderStep',[1 10]/(max_slider - min_slider)); + otherwise + if min_slider == max_slider + max_slider = max_slider+10*eps; + end + + set(e_slider.hdle,'Min', min_slider); + set(e_slider.hdle,'Max', max_slider); + set(e_slider.hdle,'Value', val); + end + set(e_min.hdle,'String', num2str(min)); + set(e_max.hdle,'String', num2str(max)); + set(e_val.hdle,'String', num2str(val)); + + set(e.hdle,'Title', [name ' (' this.data_gui.(id).domain.short_disp() ')']); + if call + this.data_gui.(id).callback(); + end + end + + function e = create_radio_panel(this,id,title, list, cb_fun) + this.data_gui.(id).list = list; + num_radios = numel(list); + + this.data_gui.(id).idx_selected = zeros(1,num_radios); + this.data_gui.(id).selected=''; + + + layout_panel = cell(num_radios,1); + id_radios = cell(num_radios); + for ir = 1:num_radios + id_radios{ir} = [id '_radio' num2str(ir)]; + this.create_radio(id_radios{ir}, list{ir}, @(o,e)(callback_radio(ir))); + layout_panel{ir,1} = id_radios(ir); + end + + e = this.create_panel(id,title,layout_panel); + callback_radio(1); + + function callback_radio(idx_r) + old_selected = this.data_gui.(id).idx_selected; + this.data_gui.(id).idx_selected = zeros(1,num_radios); + this.data_gui.(id).idx_selected(idx_r) = 1; + this.data_gui.(id).selected= list{idx_r}; + if ~isequal(old_selected, this.data_gui.(id).idx_selected) + update(); + drawnow; + cb_fun(idx_r); + end + end + + function update() + for ir = 1:num_radios + id_radio =[id '_radio' num2str(ir)]; + if this.data_gui.(id).idx_selected(ir) + this.set_by_id(id_radio, 'Value',1); + else + this.set_by_id(id_radio, 'Value',0); + end + end + + end + + end + %% Callbacks function button_ok_callback(this, hobj) @@ -479,7 +791,7 @@ function button_cancel_callback(this) this.output=[]; close(this.hdle); end - + end %% Layout @@ -487,13 +799,13 @@ function button_cancel_callback(this) layout % cell of cells of dimension n x 1 where n is number of rows font_name = 'Arial' - font_size = 11 + font_size = 10 max_char = 80 hdle % handle to GUI top dialog window - wunit = 500 - hunit = 50 + wunit = 400 + hunit = 40 end @@ -711,6 +1023,10 @@ function set_h(this,id,h) methods + + + + %% A template for a button. Replace truc with some label. function this = button_template(this, mode,name,string,callback,w,h) % Names diff --git a/Core/Gui/BreachGuiEditParams.m b/Core/Gui/BreachGuiEditParams.m new file mode 100644 index 00000000..e7dce5d7 --- /dev/null +++ b/Core/Gui/BreachGuiEditParams.m @@ -0,0 +1,149 @@ +classdef BreachGuiEditParams < BreachGuiClass + % Defines a Gui class that takes a BreachSet as argument and implements: + % - a listbox_params + % - a slider_samples + % - a panel_params + % - a slider_edit_params connected to the above + methods + function this =BreachGuiEditParams(B) + + if nargin==0 + % default BreachSet for testing purposes + B = BreachSet({'p1','p2', 'Some_other_param'}); + B.SetParamRanges('p1', [0 12]); + B.SetParamRanges('p2', [-1 5]); + B.QuasiRandomSample(10); + end + % + + this.data_gui.BrSet =B; + this.data_gui.all_params = B.GetParamList(); + this.data_gui.current_sample = 1; + + % listbox + id_listbox = 'listbox_params'; + string = this.get_string_params_and_values(this.data_gui.all_params, this.data_gui.current_sample); + c = @(o,e)(this.callback_listbox(id_listbox)); + this.create_listbox(id_listbox,string, c); + + + % slider for sample idx + max_slider= B.GetNumSamples(); + %if max_slider>1 + min_slider = 1; + + id_slider_samples= 'slider_samples' ; + c_slider_samples = @(o,e)(this.callback_slider_samples()); + e_slider = this.create_slider(id_slider_samples, 'what', c_slider_samples); + e_slider.h = 0.5; + + set(e_slider.hdle,'Min', min_slider); + set(e_slider.hdle,'Max', max_slider); + set(e_slider.hdle,'Value', 1); + set(e_slider.hdle,'SliderStep',[1 10]/(max(max_slider, min_slider+1) - min_slider)); + + panel_layout = { {id_slider_samples}; {id_listbox}}; + %else + % panel_layout = {{id_listbox}}; + % end + % embed into a panel (nicer) + this.create_panel('panel_params', 'Parameters',panel_layout); + + this.update_panel_params(); + % slider + id_slider = 'slider_edit_param'; + name = this.data_gui.all_params{1}; + domain = B.GetDomain(name); + value= B.GetParam(name, 1); + c_slider = @this.callback_slider; + + this.create_domain_slider(id_slider, name, domain,value,c_slider); + + % layout + layout = {{'panel_params'} ;... + {id_slider} }; + + % update + this.update_domain_slider('slider_edit_param', false); + + this.set_layout(layout); + this.enable_resizable(); + end + + function callback_listbox(this,id) + e = this.uimap(id); % listbox elem + idx_param = get(e.hdle,'Value'); + param = this.data_gui.all_params{idx_param}; + + domain = this.data_gui.BrSet.GetDomain(param); + + this.data_gui.slider_edit_param.name = param; + this.data_gui.slider_edit_param.domain = domain; + this.data_gui.slider_edit_param.value = this.data_gui.BrSet.GetParam(param,this.data_gui.current_sample); + this.update_domain_slider('slider_edit_param', false); + end + + + function callback_slider_samples(this) + e = this.uimap('slider_samples'); + val = round(get(e.hdle, 'Value')); + this.data_gui.current_sample=val; + set(e.hdle,'Value', val); + % + e_listbox = this.uimap('listbox_params'); % listbox elem + idx_param = get(e_listbox.hdle,'Value'); + param = this.data_gui.all_params{idx_param}; + + % update slider edit param + this.data_gui.('slider_edit_param').value = this.data_gui.BrSet.GetParam(param,this.data_gui.current_sample); + this.update_domain_slider('slider_edit_param', false); + + this.update_panel_params(); + + + end + + + + function callback_slider(this) + e = this.uimap('slider_edit_param_slider'); + + val = get(e.hdle, 'Value'); + param = this.data_gui.slider_edit_param.name; + all_params_values = this.data_gui.BrSet.GetParam(param); + all_params_values(this.data_gui.current_sample) = val; + this.data_gui.BrSet.SetParam(param, all_params_values); + this.update_panel_params(); + + end + + + function update_panel_params(this) + e = this.uimap('panel_params'); + title_panel = ['Parameters for sample ' num2str(this.data_gui.current_sample) '/' num2str(this.data_gui.BrSet.GetNumSamples())]; + set(e.hdle, 'Title', title_panel); + + string_listbox = this.get_string_params_and_values(this.data_gui.all_params, this.data_gui.current_sample); + e_listbox = this.uimap('listbox_params'); + set(e_listbox.hdle,'String', string_listbox); + + end + + function update_samples_slider(this) + val = this.data_gui.current_sample; + num_samples =this.data_gui.BrSet.GetNumSamples(); + end + + + function str = get_string_params_and_values(this,params, idx_sample) + + str = cell(1, numel(params)); + for idx_param = 1:numel(params) + param = params{idx_param}; + val = this.data_gui.BrSet.GetParam(param, idx_sample); + str{idx_param}= [param ': ' num2str(val)]; + end + end + + end +end diff --git a/Core/Gui/elems/panel_elem.m b/Core/Gui/elems/panel_elem.m index 5d85585a..5c0598dc 100644 --- a/Core/Gui/elems/panel_elem.m +++ b/Core/Gui/elems/panel_elem.m @@ -1,10 +1,7 @@ classdef panel_elem < gui_elem properties - layout - margin_top = .02 - margin_bot = .025 - + layout end methods @@ -14,8 +11,8 @@ this.layout = layout; this.wleft =.025; % default internal margins smaller for panels this.wright =.025; - this.htop =.025; - this.hbot = .025; + this.htop =.05; + this.hbot = .05; end diff --git a/Core/OutputGen/expr_output_gen.m b/Core/OutputGen/expr_output_gen.m index e56f64c8..d13b216c 100644 --- a/Core/OutputGen/expr_output_gen.m +++ b/Core/OutputGen/expr_output_gen.m @@ -4,7 +4,16 @@ function this = expr_output_gen(name, expr) formula = STL_Formula(name, [expr '>0']); this = this@stl_monitor(formula); + this.signals ={name}; end + + function [time, Xout] = computeSignals(this, time, X, p) + this.init_tXp(time,X,p); + [time, Xout] = this.get_standard_rob(this.formula, time); + end + + end + end \ No newline at end of file diff --git a/Core/ParamGen/equal_param_gen.m b/Core/ParamGen/equal_param_gen.m index 62f26348..804cb475 100644 --- a/Core/ParamGen/equal_param_gen.m +++ b/Core/ParamGen/equal_param_gen.m @@ -1,20 +1,28 @@ classdef equal_param_gen < param_gen -% equal_param_gen enforces one param_out to be always equal to param_in - + % equal_param_gen enforces one param_out to be always equal to param_in + + properties + n_out + end + methods function this = equal_param_gen(param_in, param_out, p0) this.params = {param_in}; - this.params_out = {param_out}; - + if ~iscell(param_out) + param_out={param_out}; + end + this.params_out = param_out; + this.n_out= numel(this.params_out); + if nargin<3 p0= 0; end - this.p0 =p0; + this.p0 =p0; end - + function p_out = computeParams(this, p_in) - p_out = p_in; + p_out = repmat(p_in,this.n_out,1); end end - + end \ No newline at end of file diff --git a/Core/STL_ReadFile.m b/Core/STL_ReadFile.m index 6d85f30b..75c5a67b 100644 --- a/Core/STL_ReadFile.m +++ b/Core/STL_ReadFile.m @@ -41,6 +41,10 @@ if(fid==-1) error('STL_ReadFile:OpeningError',['Couldn''t open file ' fname]); +else + if nargout==0 + fprintf('---\nReading file %s for STL formulas\n---\n', fname); + end end % JOHAN ADDED @@ -161,6 +165,10 @@ phi = wrap_up(current_id, current_formula, new_params, in_signal_names, out_signal_names); props = [props, {phi}]; %#ok<*AGROW> props_names = [props_names, {current_id}]; + if nargout==0 + fprintf('%s = %s\n', current_id, current_formula); + end + catch err assignin('base', 'row_to_replace', num_line); fprintf(['ERROR: Problem with formula ' current_id ' at line ' ... @@ -174,7 +182,7 @@ current_id = tokens{1}{1}; try assignin('base', current_id, 0); - assignin('caller', current_id,0); + assignin('caller', current_id,0); catch error('STL_ReadFile:IdError',[current_id ' on line ' int2str(num_line) ' is not a valid id.']); end @@ -198,6 +206,10 @@ phi = wrap_up(current_id, current_formula, new_params, in_signal_names, out_signal_names); props = [props, {phi}]; props_names = [props_names, {current_id}]; + if nargout==0 + fprintf('%s = %s\n', current_id, current_formula); + end + catch err assignin('base', 'row_to_replace', num_line); fprintf(['ERROR: Problem with formula ' current_id ' at line ' ... diff --git a/Core/SignalGen/BreachSignalGen.m b/Core/SignalGen/BreachSignalGen.m index e33a7f8a..46b2d2bc 100644 --- a/Core/SignalGen/BreachSignalGen.m +++ b/Core/SignalGen/BreachSignalGen.m @@ -111,12 +111,14 @@ function InitSignalGen(this, signalGenerators) for isg = 1:numel(this.signalGenerators) sg = this.signalGenerators{isg}; - idx_psg = FindParam(this.P, sg.params); + idx_psg = FindParam(this.P, sg.params)-this.Sys.DimX; p_isg = p(idx_psg); ns = numel(sg.signals); [X(cur_is:cur_is+ns-1, :), tspan]= sg.computeSignals(p_isg, tspan); cur_is = cur_is+ ns; end + + end diff --git a/Core/SignalGen/cp_signal_gen.m b/Core/SignalGen/cp_signal_gen.m index 8c56f254..fcb75e95 100644 --- a/Core/SignalGen/cp_signal_gen.m +++ b/Core/SignalGen/cp_signal_gen.m @@ -64,8 +64,7 @@ if isempty(this.p0) this.p0=p0; end - - + this.params_domain = repmat(BreachDomain(), 1, numel(this.params)); this.signals_domain = repmat(BreachDomain(), 1, numel(this.signals)); @@ -101,7 +100,12 @@ end function type = getType(this) - type = 'varstep'; + type = 'cp'; end + + function args = getSignalGenArgs(this) + args = {'cp','method'}; + end + end end diff --git a/Core/SignalGen/random_signal_gen.m b/Core/SignalGen/random_signal_gen.m index e6e74e4f..7bf018b3 100644 --- a/Core/SignalGen/random_signal_gen.m +++ b/Core/SignalGen/random_signal_gen.m @@ -62,7 +62,7 @@ end if isempty(this.p0) - this.p0 = repmat( [0 0.1 1 -1 1] , 1, numel(signals)); + this.p0 = repmat([0 0.1 1 -1 1],1,numel(signals)); end % domains @@ -77,8 +77,8 @@ X = zeros(numel(this.signals),numel(time)); for isg = 1:numel(this.signals) - - rng(p((isg-1)*5+1), 'twister'); + this_seed= p((isg-1)*5+1)+1e9*(isg-1); + rng(this_seed, 'twister'); dt_min = p((isg-1)*5+2); dt_max = p((isg-1)*5+3); cp_min = p((isg-1)*5+4); diff --git a/Core/SignalGen/signal_gen_gui.m b/Core/SignalGen/signal_gen_gui.m index 4f2c4c90..b692b86a 100644 --- a/Core/SignalGen/signal_gen_gui.m +++ b/Core/SignalGen/signal_gen_gui.m @@ -1,5 +1,5 @@ function varargout = signal_gen_gui(varargin) -% SIGNAL_GEN_GUI MATLAB code for signal_gen_gui.fig +% SIGNL_GEN_GUI MATLAB code for signal_gen_gui.fig % SIGNAL_GEN_GUI, by itself, creates a new SIGNAL_GEN_GUI or raises the existing % singleton*. % @@ -129,7 +129,8 @@ function signal_gen_gui_OpeningFcn(hObject, eventdata, handles, varargin) % signal_types= { 'constant_signal_gen',... - 'step_signal_gen',... + 'step_signal_gen',... + 'cp_signal_gen',... 'fixed_cp_signal_gen',... 'var_cp_signal_gen',... 'pulse_signal_gen',... diff --git a/Core/SignalGen/sinusoid_signal_gen.m b/Core/SignalGen/sinusoid_signal_gen.m index 038a17cf..a8af0929 100644 --- a/Core/SignalGen/sinusoid_signal_gen.m +++ b/Core/SignalGen/sinusoid_signal_gen.m @@ -41,7 +41,7 @@ for i_s = 0:numel(this.signals)-1 % with the variable order: base, amp, decay, freq pi_s = p(4*i_s+1:4*i_s+4); - X(i_s+1,:) = pi_s(1) + pi_s(2) * exp(pi_s(3)*time) .* sin(pi_s(4)*time); + X(i_s+1,:) = pi_s(1) + pi_s(2) * exp(pi_s(3)*time) .* sin(2*pi*pi_s(4)*time); end end diff --git a/Core/m_src/append_or_linebreak.m b/Core/m_src/append_or_linebreak.m new file mode 100644 index 00000000..d3c6a40c --- /dev/null +++ b/Core/m_src/append_or_linebreak.m @@ -0,0 +1,19 @@ +function st= append_or_linebreak(st, st_new, prefix, max_char) + + if nargin <3 + prefix = ''; + end + if nargin <4 + max_char = 100; + end + + + lines = strsplit(st,'\n'); + len_last_line = numel(lines{end}); + if len_last_line+numel(st_new)< max_char + st = [st st_new]; + else + st = sprintf([st '\n' prefix st_new]); + end + +end \ No newline at end of file diff --git a/Core/m_src/create_breachset.m b/Core/m_src/create_breachset.m new file mode 100644 index 00000000..5100a70f --- /dev/null +++ b/Core/m_src/create_breachset.m @@ -0,0 +1,40 @@ +function B = create_breachset(dim, varargin) +% create_breachset(dim, varargin) +% +% returns a BreachSet object of various dimensions with default names for +% parameters ('p1', 'p2', etc), with domains [0,1] +% +% dim is the number of parameters or a string. If it is a string, tries to +% match it to 'load' a previous specific instance. +% +% reloading can also be done using the 'load' option +% Bfav = create_breachset(2,'load', 'fav') + +if nargin==0 + dim = 3; +end +opts.dim=dim; + +if ischar(dim) + opts.load = dim; +else + opts.load = 'default'; % used to 'save specific previous configurations' +end + +opts.prefix_params = 'p'; +opts.ranges = [0 1]; +opts.samples = 1; +opts= varargin2struct_breach(opts,varargin{:}); + + +switch opts.load + case 'default' + params = arrayfun(@(c)([opts.prefix_params num2str(c)]),1:dim, 'UniformOutput',false); + B = BreachSet(params); + B.SetParamRanges(params,opts.ranges); + if opts.samples > 1 + B.SampleDomain(opts.samples) + end + +end + diff --git a/Core/m_src/create_breachsignalgen.m b/Core/m_src/create_breachsignalgen.m new file mode 100644 index 00000000..9ad862e0 --- /dev/null +++ b/Core/m_src/create_breachsignalgen.m @@ -0,0 +1,47 @@ +function B = create_breachsignalgen(dim, varargin) +% create_breachset(dim, varargin) +% +% returns a BreachSet object of various dimensions with default names for +% parameters ('p1', 'p2', etc), with domains [0,1] +% +% dim is the number of parameters or a string. If it is a string, tries to +% match it to 'load' a previous specific instance. +% +% reloading can also be done using the 'load' option +% Bfav = create_breachsignalgen(2,'load', 'fav') + +if nargin==0 + dim = 3; +end +opts.dim=dim; + +if ischar(dim) + opts.load = dim; +else + opts.load = 'default'; % used to 'save specific previous configurations' +end + +opts.prefix_signals = 'sig'; +opts.ranges = [-1 1]; +opts.samples = 1; +opts= varargin2struct_breach(opts,varargin{:}); + + +switch opts.load + case 'default' + signals = arrayfun(@(c)([opts.prefix_signals num2str(c)]),1:dim, 'UniformOutput',false); + + sg = random_signal_gen(signals); + B = BreachSignalGen(sg); + + seeds_params = B.expand_param_name('_seed'); + global_seed_param_gen = equal_param_gen('seed', seeds_params); + B.SetParamGen(global_seed_param_gen); + + B.SetDomain('seed', 'int') + if opts.samples > 1 + B.SetParam('seed',1:opts.samples) + end + +end + diff --git a/Core/m_src/disp_cover_opts.m b/Core/m_src/disp_cover_opts.m new file mode 100644 index 00000000..06a5847c --- /dev/null +++ b/Core/m_src/disp_cover_opts.m @@ -0,0 +1,164 @@ +function disp_cover_opts(opts) + +st = ''; +if isfield(opts,'params') + params = fieldnames(opts.params); + st = sprintf('Parameters:\n-------------\n'); + for ip= 1:numel(params) + param_ip= params{ip}; + range_ip = opts.params.(param_ip).range; + grid_ip = opts.params.(param_ip).grid; + st_ip = [param_ip ' in [' num2str(range_ip(1)) ', ' num2str(range_ip(2)) '] with ' num2str(numel(grid_ip)) ' bins.']; + st = sprintf([st '%s\n'], st_ip); + end +end + +if isfield(opts, 'projections') + st_proj = sprintf('\nProjections:\n-------------\n'); + num_dim = numel(opts.projections); + for dim = 1:num_dim + st_dim=sprintf([num2str(dim) 'd: ']); + pref= repmat(' ', 1, numel(st_dim)+3); + this_dim_proj = opts.projections{dim}; + % first projection + this_proj = this_dim_proj{1}; + this_proj_st = this_proj{1}; + for j = 2:dim + this_proj_st = sprintf([this_proj_st '__x__%s'],this_proj{j}); + end + st_dim = append_or_linebreak(st_dim, this_proj_st,pref); + + % rest of it + for i = 2:numel(opts.projections{dim}) + this_proj = this_dim_proj{i}; + this_proj_st = this_proj{1}; + for j = 2:dim + this_proj_st = sprintf([this_proj_st '__x__%s'],this_proj{j}); + end + st_dim = append_or_linebreak([st_dim ', '], this_proj_st,pref); + end + st_proj = sprintf([st_proj st_dim '\n']); + end + st = [st st_proj]; +end + +if isfield(opts,'signals') + signals = fieldnames(opts.signals); + st_sigs = sprintf('\nSignals:\n-------\n'); + for ip= 1:numel(signals) + signal_ip= signals{ip}; + range_ip = opts.signals.(signal_ip).range; + grid_ip = opts.signals.(signal_ip).grid; + st_ip = [signal_ip ' in [' num2str(range_ip(1)) ', ' num2str(range_ip(2)) '] with ' num2str(numel(grid_ip)) ' bins.']; + st_sigs = sprintf([st_sigs '%s\n'], st_ip); + end + st = [st st_sigs]; +end + +if isfield(opts, 'signals_freqs') + signals_freqs = fieldnames(opts.signals_freqs); + if ~exist('st_sigs','var') + + st_sigs = sprintf('\nSignals:\n-------\n'); + end + for ip= 1:numel(signals_freqs) + signal_ip= signals_freqs{ip}; + range_ip = opts.signals_freqs.(signal_ip).range; + grid_ip = opts.signals_freqs.(signal_ip).grid; + st_ip = [signal_ip ' in [' num2str(range_ip(1)) ', ' num2str(range_ip(2)) '] with ' num2str(numel(grid_ip)) ' bins.']; + st_sigs = sprintf([st_sigs '%s\n'], st_ip); + end + st = [st st_sigs]; +end + +if isfield(opts, 'signals_projections') + st_proj = sprintf('\nSignals Projections:\n-------------\n'); + num_dim = numel(opts.signals_projections); + for dim = 1:num_dim + st_dim=sprintf([num2str(dim) 'd: ']); + pref= repmat(' ', 1, numel(st_dim)+3); + this_dim_proj = opts.signals_projections{dim}; + % first projection + this_proj = this_dim_proj{1}; + this_proj_st = this_proj{1}; + for j = 2:dim + this_proj_st = sprintf([this_proj_st '__x__%s'],this_proj{j}); + end + st_dim = append_or_linebreak(st_dim, this_proj_st,pref); + + % rest of it + for i = 2:numel(opts.signals_projections{dim}) + this_proj = this_dim_proj{i}; + this_proj_st = this_proj{1}; + for j = 2:dim + this_proj_st = sprintf([this_proj_st '__x__%s'],this_proj{j}); + end + st_dim = append_or_linebreak([st_dim ', '], this_proj_st,pref); + end + st_proj = sprintf([st_proj st_dim '\n']); + end +st = [st st_proj]; +end + + +if isfield(opts, 'time_signals_projections') + st_proj = sprintf('\nTime Signals Projections:\n--------------------------\n'); + num_dim = numel(opts.time_signals_projections); + for dim = 2:num_dim+1 + st_dim=sprintf([num2str(dim-1) 'd: ']); + pref= repmat(' ', 1, numel(st_dim)+3); + this_dim_proj = opts.time_signals_projections{dim-1}; + % first projection + this_proj = this_dim_proj{1}; + this_proj_st = this_proj{1}; + for j = 2:dim + this_proj_st = sprintf([this_proj_st '__x__%s'],this_proj{j}); + end + st_dim = append_or_linebreak(st_dim, this_proj_st,pref); + + % rest of it + for i = 2:numel(opts.time_signals_projections{dim-1}) + this_proj = this_dim_proj{i}; + this_proj_st = this_proj{1}; + for j = 2:dim + this_proj_st = sprintf([this_proj_st '__x__%s'],this_proj{j}); + end + st_dim = append_or_linebreak([st_dim ', '], this_proj_st,pref); + end + st_proj = sprintf([st_proj st_dim '\n']); + end +st = [st st_proj]; +end + +if isfield(opts, 'signals_freqs_projections') + st_proj = sprintf('\nSignals Frequencies Projections:\n-----------------------------\n'); + num_dim = numel(opts.signals_freqs_projections); + for dim = 1:num_dim + st_dim=sprintf([num2str(dim) 'd: ']); + pref= repmat(' ', 1, numel(st_dim)+3); + this_dim_proj = opts.signals_freqs_projections{dim}; + % first projection + this_proj = this_dim_proj{1}; + this_proj_st = this_proj{1}; + for j = 2:dim + this_proj_st = sprintf([this_proj_st '__x__%s'],this_proj{j}); + end + st_dim = append_or_linebreak(st_dim, this_proj_st,pref); + + % rest of it + for i = 2:numel(opts.signals_freqs_projections{dim}) + this_proj = this_dim_proj{i}; + this_proj_st = this_proj{1}; + for j = 2:dim + this_proj_st = sprintf([this_proj_st '__x__%s'],this_proj{j}); + end + st_dim = append_or_linebreak([st_dim ', '], this_proj_st,pref); + end + st_proj = sprintf([st_proj st_dim '\n']); + end +st = [st st_proj]; +end + + +disp(st); +end \ No newline at end of file diff --git a/Core/m_src/list_manip.m b/Core/m_src/list_manip.m index bf680d3a..99840e35 100644 --- a/Core/m_src/list_manip.m +++ b/Core/m_src/list_manip.m @@ -81,6 +81,19 @@ end end end + + function st = to_string_values(l, v, sep) + % display list of name: value + if nargin <3 + sep= '\n'; + end + st = [l{1} ': ' num2str(v(1))]; + if numel(l)>1 + for n = 2:numel(l) + st= [st sep [l{n} ': ' num2str(v(n))]]; + end + end + end end end \ No newline at end of file diff --git a/Core/m_src/sim_breach.m b/Core/m_src/sim_breach.m deleted file mode 100644 index 387389f7..00000000 --- a/Core/m_src/sim_breach.m +++ /dev/null @@ -1,36 +0,0 @@ -function [tout, X] = sim_breach(Sys, tspan, pts) -% -% Generic wrapper function that runs a Simulink model and collect signal -% data in Breach format (called by ComputeTraj) -% - - mdl = Sys.mdl; - load_system(mdl); - num_signals = Sys.DimX; - - params = Sys.ParamList; - for i = 1:numel(params)-num_signals - assignin('base',params{i+num_signals},pts(i+num_signals)); - end - - assignin('base','tspan',tspan); - - if numel(tspan)>2 - set_param(mdl, 'OutputTimes', 'tspan',... - 'OutputOption','SpecifiedOutputTimes'); - else - set_param(mdl, 'OutputTimes', 'tspan',... - 'OutputOption','RefineOutput'); - end - - try - simout= sim(mdl); - catch - s= lasterror; - warning(['An error was returned from Simulink:' s.message '\n Returning a null trajectory']); - tout = tspan; - X = zeros(Sys.DimX, numel(tspan)); - return; - end - - [tout, X] = simout2X(simout); \ No newline at end of file diff --git a/Core/m_src/sim_breach_old_ver.m b/Core/m_src/sim_breach_old_ver.m deleted file mode 100644 index 4cc821d9..00000000 --- a/Core/m_src/sim_breach_old_ver.m +++ /dev/null @@ -1,51 +0,0 @@ -function [tout X] = sim_breach(Sys, tspan, pts) -% -% SIM_BREACH breach interface with sim Simulink command -% - - mdl = Sys.mdl; - load_system(mdl); - num_signals = Sys.DimX; - params = Sys.ParamList; - - for i = 1:numel(params)-num_signals - assignin('base',params{i+num_signals},pts(i+num_signals)); - end - - assignin('base','tspan',tspan); - - set_param(mdl, 'OutputTimes', 'tspan',... - 'OutputOption', 'SpecifiedOutputTimes'); - - try - simout= sim(mdl); - catch - simout= sim(mdl, 'SimulationMode', 'normal'); - end - lg = simout.get('logsout'); - - tout = simout.get('tout')'; - Y = simout.get('yout'); - X = []; - if ~isempty(Y) - for i=1:numel(Y.signals) - xx = interp1(tout', Y.signals(i).values, tspan')'; - X = [X; xx]; - end - end - - if ~isempty(lg) - lg.unpack('all'); - end - - for i = Sys.DimY+1:num_signals - - sig = Sys.ParamList{i}; - xdata = eval([sig '.Data']); - xtime = eval([sig '.Time']); - xdata = interp1(xtime',xdata(:,1),tspan, 'linear','extrap'); - X = [X ; xdata(1,:)]; %% FIXME: SUPPORT FOR MULTIDIMENSIONAL SIGNALS - - end; - - tout = tspan; diff --git a/Core/m_src/varargin2struct_breach.m b/Core/m_src/varargin2struct_breach.m index 07e020cc..db164367 100644 --- a/Core/m_src/varargin2struct_breach.m +++ b/Core/m_src/varargin2struct_breach.m @@ -1,8 +1,17 @@ function strct = varargin2struct_breach(strct, varargin) % varargin2struct_breach(default_strct, varargin) Convert varargin given as pair % of name, values into a struct. Default is provided as strct. - +% +% +% % read the acceptable names + +% case when no default struct is provided +if ischar(strct) + varargin = [{strct} varargin]; + strct = struct(); +end + optionNames = fieldnames(strct); % count arguments @@ -13,7 +22,8 @@ opt_flds = fieldnames(opt); s = setdiff(opt_flds,optionNames); if ~isempty(s) - error('varargin2struct_breach:invalid_options', [ s{1} ' is not a valid option.']); + warning('varargin2struct_breach:invalid_options', [ s{1} ' is not a valid option.']); + fprintf('Possible options are: %s', list_manip.to_string(optionNames)); end for io = 1:length(opt_flds) @@ -34,7 +44,8 @@ % you can use "if strcmp(inpName,'problemOption'),testMore,end"-statements strct.(optionNames{idx}) = pair{2}; else - error('%s is not a recognized option name',pair{1}) + warning('%s is not a recognized option name',pair{1}); + fprintf('Valid options are: %s', list_manip.to_string(optionNames)); end end end diff --git a/Core/turbo_wrapper.m b/Core/turbo_wrapper.m new file mode 100755 index 00000000..49a40f50 --- /dev/null +++ b/Core/turbo_wrapper.m @@ -0,0 +1,5 @@ +function res = turbo_wrapper(x, brProblem) + +res = brProblem.objective(x); + +end diff --git a/Examples/Wordgen/SimpleUppaalTA.xml b/Examples/Wordgen/SimpleUppaalTA.xml new file mode 100644 index 00000000..8da5bba5 --- /dev/null +++ b/Examples/Wordgen/SimpleUppaalTA.xml @@ -0,0 +1,47 @@ + + + + // Place global declarations here. +clock c; +chan phase1,phase2; + + // Place template instantiations here. +Process = Template(); +// List one or more processes to be composed into a system. +system Process; + + + + + + + + diff --git a/Examples/Wordgen/Wordgen_Breach_Demo.m b/Examples/Wordgen/Wordgen_Breach_Demo.m new file mode 100644 index 00000000..f823231e --- /dev/null +++ b/Examples/Wordgen/Wordgen_Breach_Demo.m @@ -0,0 +1,58 @@ +%% Import a Timed Automaton as a Breach Signal Generator +% First we import a timed automaton defined with Wordgen web interface or prism, or Uppal. +% To configure the import, we need to specify the TA file, the desired +% length of time words, and options for wordgen. Then we need to inform +% Breach about how to convert labels into real values. + +TA_filename = 'driving_TA.prism'; +num_evts = 10; +signals = {'throttle','brake'}; + +%% Define ranges for the signals for all labels +% The signals will take values in these ranges unless specified otherwise +% on a per label basis. +labels_ranges.all__.throttle = [0 100]; +labels_ranges.all__.brake = [0 350]; + + +%% Definie specific ranges for each label, always starting with init label +% label init: Throttle takes default range but brake is 0 +labels_ranges.init.brake = 0; + +% label a: no acceleration and no braking +labels_ranges.a.throttle =0; +labels_ranges.a.brake =0; + +% label b: no acceleration, braking in range +labels_ranges.b.throttle =0; + +% label c: no braking +labels_ranges.c.brake =0; + +% label d: no acceleration +labels_ranges.d.throttle =0; + +% label e: no acceleration and no braking +labels_ranges.e.throttle =0; + +% label f: no acceleration and no braking +labels_ranges.f.throttle =0; +labels_ranges.f.brake =0; + +% label g: no braking +labels_ranges.g.brake =0; + +% label h: no braking +labels_ranges.h.brake =0; + +%% Create BreachSignalGen object which can generate signals from the TA +B_ta = BreachTASignalGen(signals, TA_filename,labels_ranges,num_evts); + +%% Uniform sampling +B_ta.SampleDomain(3) +B_ta.Sim(0:0.01:50); +B_ta.PlotSignals() + + + + diff --git a/Examples/Wordgen/driving_TA.prism b/Examples/Wordgen/driving_TA.prism new file mode 100644 index 00000000..ce077288 --- /dev/null +++ b/Examples/Wordgen/driving_TA.prism @@ -0,0 +1,23 @@ +pta +module m +s : [0..3] init 0; +x : clock; +y : clock; + +invariant + y<15 +endinvariant + +//Coasting to see, save fuel +[a] (s=0) & (y>2) -> (s'=3) & (x'=0) & (y'=0); //acceleration to coasting 00 +[b] (s=3) & (y>1) -> (s'=2) & (x'=0) & (y'=0); //coasting to brake 01 +[c] (s=3) & (y>1) -> (s'=0) & (x'=0) & (y'=0); //coasting to acceleration 10 + +//Braking cycle +[d] (s=0) & (y>2) -> (s'=2) & (x'=0) & (y'=0); //acceleration to braking 01 +[e] (s=1) & (y>1) & (y<2) -> (s'=2) & (y'=0); //coasting to brake 01 +[f] (s=2) & (y>1) & (y<2) -> (s'=1) & (y'=0); //brake to coasting 00 +[g] (s=2) & (y>1) & (x>3) -> (s'=0) & (x'=0) & (y'=0); //brake to acceleration 10 +[h] (s=1) & (y>1) & (x>3) -> (s'=0) & (x'=0) & (y'=0); //coasting to acceleration 10 + +endmodule diff --git a/Examples/Wordgen/test_SimpleUppaalTA.m b/Examples/Wordgen/test_SimpleUppaalTA.m new file mode 100644 index 00000000..18084c4f --- /dev/null +++ b/Examples/Wordgen/test_SimpleUppaalTA.m @@ -0,0 +1,22 @@ +TA_filename = 'SimpleUppaalTA.xml'; +num_evts = 10; +signals = {'sig1','sig2'}; + + +labels_ranges.all__.sig1 = [5 20]; +labels_ranges.all__.sig2 = [-10 0]; + +labels_ranges.phase1.sig2 = -4; % sig2 fixed in phase1 + +labels_ranges.phase2.sig1 = 10; % sig1 constant in phase2 + +B_uppaal = BreachTASignalGen( ... + signals, ... % signal names + TA_filename, ... % automata file + labels_ranges, ... % mapping between labels of time words and ranges/values for signals + num_evts); % length of words in number of labels or events + +B_uppaal.SampleDomain(10); +B_uppaal.Sim(0:.01:50); + +B_uppaal.PlotSignals() \ No newline at end of file diff --git a/Examples/reachcover/test_rand_cover_filter.m b/Examples/reachcover/test_rand_cover_filter.m new file mode 100644 index 00000000..c5698057 --- /dev/null +++ b/Examples/reachcover/test_rand_cover_filter.m @@ -0,0 +1,39 @@ +% In this experiment, we try to establish a template for input-output +% mapping coverage. +% +% The goal is to find a minimal input set that guarantees coverage of +% outputs. +% +% First we look at the reachable outputs, by bombarding with input signals, +% then we use basic filtering. +% + +BrDemo.InitAFC + +% setup input generation + +B = BrAFC.copy(); +B.SetParamRanges('Pedal_Angle_pulse_period',[.1 10]); +B.SetParamRanges('Pedal_Angle_pulse_amp',[0 100]); +B.SetParamRanges('Pedal_Angle_pulse_width',[0 1]); +B.SetParamRanges('Pedal_Angle_pulse_delay',[0 10]); + +Engine_params = B.expand_param_name('Engine_Speed'); +B.SetParamRanges(Engine_params, [900 1100]); + +% Generate +B.QuasiRandomSample(100); +B.Sim() +B.PlotSignals('AF') + +%% Compute coverage +Bc = B.copy(); +opts = struct; +opts = B.SetCoverageOptions(opts,'ParamsMode','off'); +B.GetCoverage(); + +%% +Bp = B.copy(); +opc = Bp.SetCoverageOptions('ParamsMode','off', 'IncludeSignals',{'AF','MAF'}); +disp_cover_opts(opc) +Bp.GetCoverage(opc) \ No newline at end of file diff --git a/Ext/Specs/AFC_simple_spec.stl b/Ext/Specs/AFC_simple_spec.stl index 14196699..406ff1ee 100644 --- a/Ext/Specs/AFC_simple_spec.stl +++ b/Ext/Specs/AFC_simple_spec.stl @@ -1,3 +1,4 @@ +signal AF, AFref # We declare parameters with default values param tol=0.01, af_ref=14.7 AF_abs_ok := abs(AF[t]- AFref[t]) < tol*af_ref diff --git a/Ext/Toolboxes/turbo/CONTRIBUTORS.md b/Ext/Toolboxes/turbo/CONTRIBUTORS.md new file mode 100755 index 00000000..4fd0bc8a --- /dev/null +++ b/Ext/Toolboxes/turbo/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +Code written by: +- David Eriksson diff --git a/Ext/Toolboxes/turbo/LICENSE.md b/Ext/Toolboxes/turbo/LICENSE.md new file mode 100755 index 00000000..488547e4 --- /dev/null +++ b/Ext/Toolboxes/turbo/LICENSE.md @@ -0,0 +1,41 @@ +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by the text below. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under this License. + +This License governs use of the accompanying Work, and your use of the Work constitutes acceptance of this License. + +You may use this Work for any non-commercial purpose, subject to the restrictions in this License. Some purposes which can be non-commercial are teaching, academic research, and personal experimentation. You may also distribute this Work with books or other teaching materials, or publish the Work on websites, that are intended to teach the use of the Work. + +You may not use or distribute this Work, or any derivative works, outputs, or results from the Work, in any form for commercial purposes. Non-exhaustive examples of commercial purposes would be running business operations, licensing, leasing, or selling the Work, or distributing the Work for use with commercial products. + +You may modify this Work and distribute the modified Work for non-commercial purposes, however, you may not grant rights to the Work or derivative works that are broader than or in conflict with those provided by this License. For example, you may not distribute modifications of the Work under terms that would permit commercial use, or under terms that purport to require the Work or derivative works to be sublicensed to others. + +In return, we require that you agree: + +1. Not to remove any copyright or other notices from the Work. + +2. That if you distribute the Work in Source or Object form, you will include a verbatim copy of this License. + +3. That if you distribute derivative works of the Work in Source form, you do so only under a license that includes all of the provisions of this License and is not in conflict with this License, and if you distribute derivative works of the Work solely in Object form you do so only under a license that complies with this License. + +4. That if you have modified the Work or created derivative works from the Work, and distribute such modifications or derivative works, you will cause the modified files to carry prominent notices so that recipients know that they are not receiving the original Work. Such notices must state: (i) that you have changed the Work; and (ii) the date of any changes. + +5. If you publicly use the Work or any output or result of the Work, you will provide a notice with such use that provides any person who uses, views, accesses, interacts with, or is otherwise exposed to the Work (i) with information of the nature of the Work, (ii) with a link to the Work, and (iii) a notice that the Work is available under this License. + +6. THAT THE WORK COMES "AS IS", WITH NO WARRANTIES. THIS MEANS NO EXPRESS, IMPLIED OR STATUTORY WARRANTY, INCLUDING WITHOUT LIMITATION, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR ANY WARRANTY OF TITLE OR NON-INFRINGEMENT. ALSO, YOU MUST PASS THIS DISCLAIMER ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +7. THAT NEITHER UBER TECHNOLOGIES, INC. NOR ANY OF ITS AFFILIATES, SUPPLIERS, SUCCESSORS, NOR ASSIGNS WILL BE LIABLE FOR ANY DAMAGES RELATED TO THE WORK OR THIS LICENSE, INCLUDING DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL OR INCIDENTAL DAMAGES, TO THE MAXIMUM EXTENT THE LAW PERMITS, NO MATTER WHAT LEGAL THEORY IT IS BASED ON. ALSO, YOU MUST PASS THIS LIMITATION OF LIABILITY ON WHENEVER YOU DISTRIBUTE THE WORK OR DERIVATIVE WORKS. + +8. That if you sue anyone over patents that you think may apply to the Work or anyone's use of the Work, your license to the Work ends automatically. + +9. That your rights under the License end automatically if you breach it in any way. + +10. Uber Technologies, Inc. reserves all rights not expressly granted to you in this License. diff --git a/Ext/Toolboxes/turbo/README.md b/Ext/Toolboxes/turbo/README.md new file mode 100755 index 00000000..a49c27a3 --- /dev/null +++ b/Ext/Toolboxes/turbo/README.md @@ -0,0 +1,95 @@ +## Overview + +This is the code-release for the TuRBO algorithm from ***Scalable Global Optimization via Local Bayesian Optimization*** appearing in NeurIPS 2019. This is an implementation for the noise-free case and may not work well if observations are noisy as the center of the trust region should be chosen based on the posterior mean in this case. + +Note that TuRBO is a **minimization** algorithm, so please make sure you reformulate potential maximization problems. + +## Benchmark functions + +### Robot pushing +The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We have made the following changes to the code when running our experiments: + +1. We turned off the visualization, which speeds up the function evaluations. +2. We replaced all instances of ```np.random.normal(0, 0.01)``` by ```np.random.normal(0, 1e-6)``` in ```push_utils.py```. This makes the function close to noise-free. Another option is to average over several evaluations using the original code +3. We flipped the sign of the objective function to turn this into a minimization problem. + +Dependencies: ```numpy ```, ```pygame```, ```box2d-py``` + +### Rover +The original code for the robot pushing problem is available at https://github.com/zi-w/Ensemble-Bayesian-Optimization. We used the large version of the problem, which has 60 dimensions. We have flipped the sign of the objective function to turn this into a minimization problem. + +Dependencies: ```numpy```, ```scipy``` + +### Lunar + +The lunar code is available in the OpenAI gym: https://github.com/openai/gym. The goal of the problem is to learn the parameter values of a controller for the lunar lander. The controller we learn is a modification of the original heuristic controller which takes the form: + +``` +def heuristic_Controller(s, w): + angle_targ = s[0] * w[0] + s[2] * w[1] + if angle_targ > w[2]: + angle_targ = w[2] + if angle_targ < -w[2]: + angle_targ = -w[2] + hover_targ = w[3] * np.abs(s[0]) + + angle_todo = (angle_targ - s[4]) * w[4] - (s[5]) * w[5] + hover_todo = (hover_targ - s[1]) * w[6] - (s[3]) * w[7] + + if s[6] or s[7]: + angle_todo = w[8] + hover_todo = -(s[3]) * w[9] + + a = 0 + if hover_todo > np.abs(angle_todo) and hover_todo > w[10]: + a = 2 + elif angle_todo < -w[11]: + a = 3 + elif angle_todo > +w[11]: + a = 1 + return a +``` + +We use the constraints 0 <= w_i <= 2 for all parameters. We use ```INITIAL_RANDOM = 1500.0``` to make the problem more challenging. + +For more information about the logic behind this controller and how to integrate it with ```gym```, take a look at the original heuristic controller source code: https://github.com/openai/gym/blob/master/gym/envs/box2d/lunar_lander.py#L364 + +Dependencies: ```gym```, ```box2d-py``` + +### Cosmological constant +The code for the cosmological constant problem is available here: https://ascl.net/1306.012. You need to follow the instructions and compile the FORTRAN code. This gives you an executable ```CAMB``` that you can call to run the simulation. + +The parameter names and bounds that we tune are the following: + +``` +ombh2: [0.01, 0.25] +omch2: [0.01, 0.25] +omnuh2: [0.01, 0.25] +omk: [0.01, 0.25] +hubble: [52.5, 100] +temp_cmb: [2.7, 2.8] +hefrac: [0.2, 0.3] +mneu: [2.9, 3.09] +scalar_amp: [1.5e-9, 2.6e-8] +scalar_spec_ind: [0.72, 5] +rf_fudge: [0, 100] +rf_fudge_he: [0, 100] +``` + +## Examples +Check the examples folder for two examples on how to use Turbo-1 and Turbo-n. + +## Citing us + +The final version of the paper is available at: http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization. + +``` +@inproceedings{eriksson2019scalable, + title = {Scalable Global Optimization via Local {Bayesian} Optimization}, + author = {Eriksson, David and Pearce, Michael and Gardner, Jacob and Turner, Ryan D and Poloczek, Matthias}, + booktitle = {Advances in Neural Information Processing Systems}, + pages = {5496--5507}, + year = {2019}, + url = {http://papers.nips.cc/paper/8788-scalable-global-optimization-via-local-bayesian-optimization.pdf}, +} +``` diff --git a/Ext/Toolboxes/turbo/SavingAllDataInJsonFolderTurbo.m b/Ext/Toolboxes/turbo/SavingAllDataInJsonFolderTurbo.m new file mode 100755 index 00000000..85beb702 --- /dev/null +++ b/Ext/Toolboxes/turbo/SavingAllDataInJsonFolderTurbo.m @@ -0,0 +1,41 @@ +function SavingAllDataInJsonFolderTurbo(lower_bound, upper_bound, maxNumberOfIterations) + +%% Initialize the scenario: Input parameterization for falsification problem in a "json" file +inputsRange = [lower_bound, upper_bound]; +numberOfInputs = length(inputsRange(:,1)); + +%% Creating the scenario +scenario = {}; +scenario.optimization_iterations = maxNumberOfIterations; +scenario.number_dimensions = numberOfInputs; + +scenario.input_parameters = {}; + +%% Generating input parameters: x1,x2,...,xn and filling their values +for i = 1: numberOfInputs + eval(['x' num2str(i) ' = ' mat2str(inputsRange(i,:)) ';']) +end + +%% Adding input parametrs to main scenario +for i = 1:numberOfInputs + eval(['scenario.input_parameters.x' num2str(i) ' = x' num2str(i) ';']) +end + +%% Creating "json" file and save all assigned values +jsonFileName = sprintf('scenario.json'); +openJSONFile = fopen(jsonFileName,'w'); +encodedJSON = jsonencode(scenario); + +%% Cleaning the "json" file to make it more readable and every data to be in one line +encodedJSONCommaClean = strrep(encodedJSON, ',"', sprintf(',\r"')); +encodedJSONOpenBracketClean = strrep(encodedJSONCommaClean, '{"', sprintf('{\r"')); +encodedJSONCloseBracketClean = strrep(encodedJSONOpenBracketClean, '}', sprintf('\r}')); +encodedJSONColonBracketClean = strrep(encodedJSONCloseBracketClean, ':{', sprintf(':\r{')); +encodedJSONColonBracketCleanBracket_begin = strrep(encodedJSONColonBracketClean, '["', sprintf('"')); +encodedJSONColonBracketCleanBracket_end = strrep(encodedJSONColonBracketCleanBracket_begin, '"]', sprintf('"')); + +fprintf(openJSONFile, encodedJSONColonBracketCleanBracket_end); +fclose(openJSONFile); + + +end \ No newline at end of file diff --git a/Ext/Toolboxes/turbo/examples/Turbo1.ipynb b/Ext/Toolboxes/turbo/examples/Turbo1.ipynb new file mode 100755 index 00000000..7226a00c --- /dev/null +++ b/Ext/Toolboxes/turbo/examples/Turbo1.ipynb @@ -0,0 +1,254 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple example of TuRBO-1" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from turbo import Turbo1\n", + "import numpy as np\n", + "import torch\n", + "import math\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up an optimization problem class" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class Levy:\n", + " def __init__(self, dim=10):\n", + " self.dim = dim\n", + " self.lb = -5 * np.ones(dim)\n", + " self.ub = 10 * np.ones(dim)\n", + " \n", + " def __call__(self, x):\n", + " assert len(x) == self.dim\n", + " assert x.ndim == 1\n", + " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", + " w = 1 + (x - 1.0) / 4.0\n", + " val = np.sin(np.pi * w[0]) ** 2 + \\\n", + " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", + " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", + " return val\n", + "\n", + "f = Levy(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Turbo optimizer instance" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using dtype = torch.float64 \n", + "Using device = cpu\n" + ] + } + ], + "source": [ + "turbo1 = Turbo1(\n", + " f=f, # Handle to objective function\n", + " lb=f.lb, # Numpy array specifying lower bounds\n", + " ub=f.ub, # Numpy array specifying upper bounds\n", + " n_init=20, # Number of initial bounds from an Latin hypercube design\n", + " max_evals = 1000, # Maximum number of evaluations\n", + " batch_size=10, # How large batch size TuRBO uses\n", + " verbose=True, # Print information from each batch\n", + " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", + " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", + " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", + " min_cuda=1024, # Run on the CPU for small datasets\n", + " device=\"cpu\", # \"cpu\" or \"cuda\"\n", + " dtype=\"float64\", # float64 or float32\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run the optimization process" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting from fbest = 20.98\n", + "50) New best: 15.65\n", + "80) New best: 11.27\n", + "90) New best: 9.325\n", + "100) New best: 8.288\n", + "110) New best: 6.944\n", + "120) New best: 5.974\n", + "140) New best: 5.951\n", + "160) New best: 5.905\n", + "170) New best: 5.905\n", + "180) New best: 5.822\n", + "190) New best: 5.785\n", + "200) New best: 5.759\n", + "220) New best: 5.738\n", + "230) New best: 5.683\n", + "240) Restarting with fbest = 5.683\n", + "Starting from fbest = 32.5\n", + "320) New best: 5.526\n", + "330) New best: 3.95\n", + "350) New best: 1.736\n", + "370) New best: 1.229\n", + "410) New best: 1.206\n", + "420) New best: 1.193\n", + "430) New best: 1.191\n", + "440) New best: 1.163\n", + "450) New best: 1.145\n", + "460) New best: 1.06\n", + "480) New best: 1.024\n", + "490) New best: 1.01\n", + "500) New best: 1.001\n", + "530) Restarting with fbest = 1.001\n", + "Starting from fbest = 12.85\n", + "730) Restarting with fbest = 8.634\n", + "Starting from fbest = 9.62\n", + "890) Restarting with fbest = 5.87\n", + "Starting from fbest = 25.71\n" + ] + } + ], + "source": [ + "turbo1.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract all evaluations from Turbo and print the best" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best value found:\n", + "\tf(x) = 1.001\n", + "Observed at:\n", + "\tx = [-3.006 0.914 3.659 0.853 0.033 -0.203 1.199 0.812 -0.301 2.42 ]\n" + ] + } + ], + "source": [ + "X = turbo1.X # Evaluated points\n", + "fX = turbo1.fX # Observed values\n", + "ind_best = np.argmin(fX)\n", + "f_best, x_best = fX[ind_best], X[ind_best, :]\n", + "\n", + "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot the progress\n", + "Each trust region is independent and finds different solutions" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure(figsize=(7, 5))\n", + "matplotlib.rcParams.update({'font.size': 16})\n", + "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", + "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", + "plt.xlim([0, len(fX)])\n", + "plt.ylim([0, 30])\n", + "plt.title(\"10D Levy function\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Ext/Toolboxes/turbo/examples/TurboM.ipynb b/Ext/Toolboxes/turbo/examples/TurboM.ipynb new file mode 100755 index 00000000..c5a632cc --- /dev/null +++ b/Ext/Toolboxes/turbo/examples/TurboM.ipynb @@ -0,0 +1,247 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple example of TuRBO-m" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from turbo import TurboM\n", + "import numpy as np\n", + "import torch\n", + "import math\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up an optimization problem class" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class Levy:\n", + " def __init__(self, dim=10):\n", + " self.dim = dim\n", + " self.lb = -5 * np.ones(dim)\n", + " self.ub = 10 * np.ones(dim)\n", + " \n", + " def __call__(self, x):\n", + " assert len(x) == self.dim\n", + " assert x.ndim == 1\n", + " assert np.all(x <= self.ub) and np.all(x >= self.lb)\n", + " w = 1 + (x - 1.0) / 4.0\n", + " val = np.sin(np.pi * w[0]) ** 2 + \\\n", + " np.sum((w[1:self.dim - 1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[1:self.dim - 1] + 1) ** 2)) + \\\n", + " (w[self.dim - 1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[self.dim - 1])**2)\n", + " return val\n", + "\n", + "f = Levy(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Turbo optimizer instance" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using dtype = torch.float64 \n", + "Using device = cpu\n" + ] + } + ], + "source": [ + "turbo_m = TurboM(\n", + " f=f, # Handle to objective function\n", + " lb=f.lb, # Numpy array specifying lower bounds\n", + " ub=f.ub, # Numpy array specifying upper bounds\n", + " n_init=10, # Number of initial bounds from an Symmetric Latin hypercube design\n", + " max_evals=1000, # Maximum number of evaluations\n", + " n_trust_regions=5, # Number of trust regions\n", + " batch_size=10, # How large batch size TuRBO uses\n", + " verbose=True, # Print information from each batch\n", + " use_ard=True, # Set to true if you want to use ARD for the GP kernel\n", + " max_cholesky_size=2000, # When we switch from Cholesky to Lanczos\n", + " n_training_steps=50, # Number of steps of ADAM to learn the hypers\n", + " min_cuda=1024, # Run on the CPU for small datasets\n", + " device=\"cpu\", # \"cpu\" or \"cuda\"\n", + " dtype=\"float64\", # float64 or float32\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run the optimization process" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TR-0 starting from: 24.79\n", + "TR-1 starting from: 20.77\n", + "TR-2 starting from: 14.87\n", + "TR-3 starting from: 27.97\n", + "TR-4 starting from: 23.89\n", + "80) New best @ TR-2: 12.43\n", + "90) New best @ TR-2: 6.42\n", + "110) New best @ TR-2: 5.467\n", + "180) New best @ TR-2: 2.888\n", + "230) New best @ TR-1: 1.944\n", + "280) New best @ TR-1: 1.54\n", + "310) New best @ TR-1: 1.052\n", + "340) New best @ TR-1: 1.038\n", + "390) New best @ TR-1: 0.9689\n", + "410) New best @ TR-1: 0.877\n", + "420) New best @ TR-1: 0.7794\n", + "460) New best @ TR-1: 0.7509\n", + "470) New best @ TR-1: 0.7264\n", + "480) New best @ TR-1: 0.7238\n", + "530) New best @ TR-1: 0.7044\n", + "540) New best @ TR-1: 0.695\n", + "550) New best @ TR-1: 0.6823\n", + "560) New best @ TR-1: 0.6656\n", + "590) New best @ TR-1: 0.6614\n", + "600) New best @ TR-1: 0.6604\n", + "640) TR-1 converged to: : 0.6604\n", + "640) TR-1 is restarting from: : 23.66\n" + ] + } + ], + "source": [ + "turbo_m.optimize()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extract all evaluations from Turbo and print the best" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best value found:\n", + "\tf(x) = 0.660\n", + "Observed at:\n", + "\tx = [-2.968 1.072 0.173 0.973 3.698 0.883 0.946 0.872 0.006 0.927]\n" + ] + } + ], + "source": [ + "X = turbo_m.X # Evaluated points\n", + "fX = turbo_m.fX # Observed values\n", + "ind_best = np.argmin(fX)\n", + "f_best, x_best = fX[ind_best], X[ind_best, :]\n", + "\n", + "print(\"Best value found:\\n\\tf(x) = %.3f\\nObserved at:\\n\\tx = %s\" % (f_best, np.around(x_best, 3)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plot the progress\n", + "\n", + "TuRBO-5 converges to a solution close to the global optimum" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure(figsize=(7, 5))\n", + "matplotlib.rcParams.update({'font.size': 16})\n", + "plt.plot(fX, 'b.', ms=10) # Plot all evaluated points as blue dots\n", + "plt.plot(np.minimum.accumulate(fX), 'r', lw=3) # Plot cumulative minimum as a red line\n", + "plt.xlim([0, len(fX)])\n", + "plt.ylim([0, 30])\n", + "plt.title(\"10D Levy function\")\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Ext/Toolboxes/turbo/scenario.json b/Ext/Toolboxes/turbo/scenario.json new file mode 100755 index 00000000..cb5e8bf0 --- /dev/null +++ b/Ext/Toolboxes/turbo/scenario.json @@ -0,0 +1 @@ +{ "optimization_iterations":23, "number_dimensions":2, "input_parameters": { "x1":[10,12], "x2":[10,40] } } \ No newline at end of file diff --git a/Ext/Toolboxes/turbo/setup.py b/Ext/Toolboxes/turbo/setup.py new file mode 100755 index 00000000..bd5afd5c --- /dev/null +++ b/Ext/Toolboxes/turbo/setup.py @@ -0,0 +1,8 @@ +from setuptools import setup, find_packages + +setup( + name="turbo", + version="0.0.1", + packages=find_packages(), + install_requires=["numpy>=1.17.3", "torch>=1.3.0", "gpytorch>=0.3.6"], +) diff --git a/Ext/Toolboxes/turbo/turbo/__init__.py b/Ext/Toolboxes/turbo/turbo/__init__.py new file mode 100755 index 00000000..6d17f201 --- /dev/null +++ b/Ext/Toolboxes/turbo/turbo/__init__.py @@ -0,0 +1,2 @@ +from .turbo_1 import Turbo1 +from .turbo_m import TurboM diff --git a/Ext/Toolboxes/turbo/turbo/gp.py b/Ext/Toolboxes/turbo/turbo/gp.py new file mode 100755 index 00000000..873df9df --- /dev/null +++ b/Ext/Toolboxes/turbo/turbo/gp.py @@ -0,0 +1,98 @@ +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math + +import gpytorch +import numpy as np +import torch +from gpytorch.constraints.constraints import Interval +from gpytorch.distributions import MultivariateNormal +from gpytorch.kernels import MaternKernel, ScaleKernel +from gpytorch.likelihoods import GaussianLikelihood +from gpytorch.means import ConstantMean +from gpytorch.mlls import ExactMarginalLogLikelihood +from gpytorch.models import ExactGP + + +# GP Model +class GP(ExactGP): + def __init__(self, train_x, train_y, likelihood, lengthscale_constraint, outputscale_constraint, ard_dims): + super(GP, self).__init__(train_x, train_y, likelihood) + self.ard_dims = ard_dims + self.mean_module = ConstantMean() + base_kernel = MaternKernel(lengthscale_constraint=lengthscale_constraint, ard_num_dims=ard_dims, nu=2.5) + self.covar_module = ScaleKernel(base_kernel, outputscale_constraint=outputscale_constraint) + + def forward(self, x): + mean_x = self.mean_module(x) + covar_x = self.covar_module(x) + return MultivariateNormal(mean_x, covar_x) + + +def train_gp(train_x, train_y, use_ard, num_steps, hypers={}): + """Fit a GP model where train_x is in [0, 1]^d and train_y is standardized.""" + assert train_x.ndim == 2 + assert train_y.ndim == 1 + assert train_x.shape[0] == train_y.shape[0] + + # Create hyper parameter bounds + noise_constraint = Interval(5e-4, 0.2) + if use_ard: + lengthscale_constraint = Interval(0.005, 2.0) + else: + lengthscale_constraint = Interval(0.005, math.sqrt(train_x.shape[1])) # [0.005, sqrt(dim)] + outputscale_constraint = Interval(0.05, 20.0) + + # Create models + likelihood = GaussianLikelihood(noise_constraint=noise_constraint).to(device=train_x.device, dtype=train_y.dtype) + ard_dims = train_x.shape[1] if use_ard else None + model = GP( + train_x=train_x, + train_y=train_y, + likelihood=likelihood, + lengthscale_constraint=lengthscale_constraint, + outputscale_constraint=outputscale_constraint, + ard_dims=ard_dims, + ).to(device=train_x.device, dtype=train_x.dtype) + + # Find optimal model hyperparameters + model.train() + likelihood.train() + + # "Loss" for GPs - the marginal log likelihood + mll = ExactMarginalLogLikelihood(likelihood, model) + + # Initialize model hypers + if hypers: + model.load_state_dict(hypers) + else: + hypers = {} + hypers["covar_module.outputscale"] = 1.0 + hypers["covar_module.base_kernel.lengthscale"] = 0.5 + hypers["likelihood.noise"] = 0.005 + model.initialize(**hypers) + + # Use the adam optimizer + optimizer = torch.optim.Adam([{"params": model.parameters()}], lr=0.1) + + for _ in range(num_steps): + optimizer.zero_grad() + output = model(train_x) + loss = -mll(output, train_y) + loss.backward() + optimizer.step() + + # Switch to eval mode + model.eval() + likelihood.eval() + + return model diff --git a/Ext/Toolboxes/turbo/turbo/turbo_1.py b/Ext/Toolboxes/turbo/turbo/turbo_1.py new file mode 100755 index 00000000..4fcf176f --- /dev/null +++ b/Ext/Toolboxes/turbo/turbo/turbo_1.py @@ -0,0 +1,355 @@ +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math +import sys +from copy import deepcopy +import csv +import os + +import gpytorch +import numpy as np +import torch +from torch.quasirandom import SobolEngine + +from .gp import train_gp +from .utils import from_unit_cube, latin_hypercube, to_unit_cube + + +class Turbo1: + """The TuRBO-1 algorithm. + + Parameters + ---------- + f : function handle + lb : Lower variable bounds, numpy.array, shape (d,). + ub : Upper variable bounds, numpy.array, shape (d,). + n_init : Number of initial points (2*dim is recommended), int. + max_evals : Total evaluation budget, int. + batch_size : Number of points in each batch, int. + verbose : If you want to print information about the optimization progress, bool. + use_ard : If you want to use ARD for the GP kernel. + max_cholesky_size : Largest number of training points where we use Cholesky, int + n_training_steps : Number of training steps for learning the GP hypers, int + min_cuda : We use float64 on the CPU if we have this or fewer datapoints + device : Device to use for GP fitting ("cpu" or "cuda") + dtype : Dtype to use for GP fitting ("float32" or "float64") + + Example usage: + turbo1 = Turbo1(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals) + turbo1.optimize() # Run optimization + X, fX = turbo1.X, turbo1.fX # Evaluated points + """ + + def __init__( + self, + f, + lb, + ub, + n_init, + max_evals, + batch_size=1, + verbose=True, + use_ard=True, + max_cholesky_size=2000, + n_training_steps=50, + min_cuda=1024, + device="cpu", + dtype="float64", + ): + + # Very basic input checks + assert lb.ndim == 1 and ub.ndim == 1 + assert len(lb) == len(ub) + assert np.all(ub > lb) + assert max_evals > 0 and isinstance(max_evals, int) + assert n_init > 0 and isinstance(n_init, int) + assert batch_size > 0 and isinstance(batch_size, int) + assert isinstance(verbose, bool) and isinstance(use_ard, bool) + assert max_cholesky_size >= 0 and isinstance(batch_size, int) + assert n_training_steps >= 30 and isinstance(n_training_steps, int) + # Note: max_evals can be lower than n_init if we try to load initial samples from .csv + # file sent from MATLAB - hence we don't check that max_evals is "large enough" + # assert max_evals > n_init and max_evals > batch_size + assert device == "cpu" or device == "cuda" + assert dtype == "float32" or dtype == "float64" + if device == "cuda": + assert torch.cuda.is_available(), "can't use cuda if it's not available" + + # Save function information + self.f = f + self.dim = len(lb) + self.lb = lb + self.ub = ub + + # Settings + self.n_init = n_init + self.max_evals = max_evals + self.batch_size = batch_size + self.verbose = verbose + self.use_ard = use_ard + self.max_cholesky_size = max_cholesky_size + self.n_training_steps = n_training_steps + + # Hyperparameters + self.mean = np.zeros((0, 1)) + self.signal_var = np.zeros((0, 1)) + self.noise_var = np.zeros((0, 1)) + self.lengthscales = np.zeros((0, self.dim)) if self.use_ard else np.zeros((0, 1)) + + # Tolerances and counters + self.n_cand = min(100 * self.dim, 5000) + self.failtol = np.ceil(np.max([4.0 / batch_size, self.dim / batch_size])) + self.succtol = 3 + self.n_evals = 0 + + # Trust region sizes + self.length_min = 0.5 ** 7 + self.length_max = 1.6 + self.length_init = 0.8 + + # Save the full history + self.X = np.zeros((0, self.dim)) + self.fX = np.zeros((0, 1)) + + # Device and dtype for GPyTorch + self.min_cuda = min_cuda + self.dtype = torch.float32 if dtype == "float32" else torch.float64 + self.device = torch.device("cuda") if device == "cuda" else torch.device("cpu") + if self.verbose: + print("Using dtype = %s \nUsing device = %s" % (self.dtype, self.device)) + sys.stdout.flush() + + # Initialize parameters + self._restart() + + # Define a counter for how many times Turbo has reset + self.reset_counter = 1 + + def _restart(self): + self._X = [] + self._fX = [] + self.failcount = 0 + self.succcount = 0 + self.length = self.length_init + + def _adjust_length(self, fX_next): + if np.min(fX_next) < np.min(self._fX) - 1e-3 * math.fabs(np.min(self._fX)): + self.succcount += 1 + self.failcount = 0 + else: + self.succcount = 0 + self.failcount += 1 + + if self.succcount == self.succtol: # Expand trust region + self.length = min([2.0 * self.length, self.length_max]) + self.succcount = 0 + elif self.failcount == self.failtol: # Shrink trust region + self.length /= 2.0 + self.failcount = 0 + + def _create_candidates(self, X, fX, length, n_training_steps, hypers): + """Generate candidates assuming X has been scaled to [0,1]^d.""" + # Pick the center as the point with the smallest function values + # NOTE: This may not be robust to noise, in which case the posterior mean of the GP can be used instead + assert X.min() >= 0.0 and X.max() <= 1.0 + + # Standardize function values. + mu, sigma = np.median(fX), fX.std() + sigma = 1.0 if sigma < 1e-6 else sigma + fX = (deepcopy(fX) - mu) / sigma + + # Figure out what device we are running on + if len(X) < self.min_cuda: + device, dtype = torch.device("cpu"), torch.float64 + else: + device, dtype = self.device, self.dtype + + # We use CG + Lanczos for training if we have enough data + with gpytorch.settings.max_cholesky_size(self.max_cholesky_size): + X_torch = torch.tensor(X).to(device=device, dtype=dtype) + y_torch = torch.tensor(fX).to(device=device, dtype=dtype) + gp = train_gp( + train_x=X_torch, train_y=y_torch, use_ard=self.use_ard, num_steps=n_training_steps, hypers=hypers + ) + + # Save state dict + hypers = gp.state_dict() + + # Create the trust region boundaries + x_center = X[fX.argmin().item(), :][None, :] + weights = gp.covar_module.base_kernel.lengthscale.cpu().detach().numpy().ravel() + weights = weights / weights.mean() # This will make the next line more stable + weights = weights / np.prod(np.power(weights, 1.0 / len(weights))) # We now have weights.prod() = 1 + lb = np.clip(x_center - weights * length / 2.0, 0.0, 1.0) + ub = np.clip(x_center + weights * length / 2.0, 0.0, 1.0) + + # Draw a Sobolev sequence in [lb, ub] + seed = np.random.randint(int(1e6)) + sobol = SobolEngine(self.dim, scramble=True, seed=seed) + pert = sobol.draw(self.n_cand).to(dtype=dtype, device=device).cpu().detach().numpy() + pert = lb + (ub - lb) * pert + + # Create a perturbation mask + prob_perturb = min(20.0 / self.dim, 1.0) + mask = np.random.rand(self.n_cand, self.dim) <= prob_perturb + ind = np.where(np.sum(mask, axis=1) == 0)[0] + mask[ind, np.random.randint(0, self.dim - 1, size=len(ind))] = 1 + + # Create candidate points + X_cand = x_center.copy() * np.ones((self.n_cand, self.dim)) + X_cand[mask] = pert[mask] + + # Figure out what device we are running on + if len(X_cand) < self.min_cuda: + device, dtype = torch.device("cpu"), torch.float64 + else: + device, dtype = self.device, self.dtype + + # We may have to move the GP to a new device + gp = gp.to(dtype=dtype, device=device) + + # We use Lanczos for sampling if we have enough data + with torch.no_grad(), gpytorch.settings.max_cholesky_size(self.max_cholesky_size): + X_cand_torch = torch.tensor(X_cand).to(device=device, dtype=dtype) + y_cand = gp.likelihood(gp(X_cand_torch)).sample(torch.Size([self.batch_size])).t().cpu().detach().numpy() + + # Remove the torch variables + del X_torch, y_torch, X_cand_torch, gp + + # De-standardize the sampled values + y_cand = mu + sigma * y_cand + + return X_cand, y_cand, hypers + + def _select_candidates(self, X_cand, y_cand): + """Select candidates.""" + X_next = np.ones((self.batch_size, self.dim)) + for i in range(self.batch_size): + # Pick the best point and make sure we never pick it again + indbest = np.argmin(y_cand[:, i]) + X_next[i, :] = deepcopy(X_cand[indbest, :]) + y_cand[indbest, :] = np.inf + return X_next + + def optimize(self): + """Run the full optimization process.""" + while self.n_evals < self.max_evals: + if len(self._fX) > 0 and self.verbose: + n_evals, fbest = self.n_evals, self._fX.min() + print(f"{n_evals}) Restarting with fbest = {fbest:.4}") + self.reset_counter += 1 + sys.stdout.flush() + + # Initialize parameters + self._restart() + + matlabToTurboInitFileName = 'initDataFromMatlabToTurbo.csv' + readMatlabSamples = 1 + if self.reset_counter == 1 and os.path.isfile(matlabToTurboInitFileName) and readMatlabSamples: + # No resets have been done - read initial values from matlab + readMatlabSamples = 0 + half_number_init = int(self.n_init/2) + with open(matlabToTurboInitFileName, 'r') as f: + matlab_matrix = [] + reader = csv.reader(f) + for line in reader: + matlab_matrix.append(line) + matlab_matrix = np.vstack(matlab_matrix) + matlab_matrix = matlab_matrix.astype(np.float) ## Converting from string to float + + X_init_matlab = matlab_matrix[:,0:len(self.ub)] + fX_init_matlab = matlab_matrix[:,-1] + fX_init_matlab = fX_init_matlab.reshape(-1,1) # Make fX_init_matlab into column vector instead of row + + # Generate and evalute initial design points (if needed to reach n_init) + n_points_loaded_from_matlab = X_init_matlab.shape[0] + n_random_points_to_generate = max(self.n_init - n_points_loaded_from_matlab, 0) + if n_random_points_to_generate > 0: + print(f"Loaded {n_points_loaded_from_matlab} initial samples from MATLAB. Generating {n_random_points_to_generate} points in Turbo to reach n_init={self.n_init}.") + X_init_rand = latin_hypercube(n_random_points_to_generate, self.dim) + X_init_rand = from_unit_cube(X_init_rand, self.lb, self.ub) + fX_init_rand = np.array([[self.f(x)] for x in X_init_rand]) + + X_init = np.vstack((X_init_matlab, X_init_rand)) + fX_init = np.vstack((fX_init_matlab, fX_init_rand)) + else: + print(f"Loaded {n_points_loaded_from_matlab} initial samples from MATLAB. No need to generate more points in Turbo in order to reach n_init={self.n_init}.") + X_init = X_init_matlab + fX_init = fX_init_matlab + + else: + # Generate and evalute initial design points with random + n_random_points_to_generate = self.n_init + X_init = latin_hypercube(n_random_points_to_generate, self.dim) + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Update budget and set as initial data for this TR + self.n_evals += n_random_points_to_generate + self._X = deepcopy(X_init) + self._fX = deepcopy(fX_init) + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_init))) + self.fX = np.vstack((self.fX, deepcopy(fX_init))) + + if self.verbose: + fbest = self._fX.min() + print(f"Starting from fbest = {fbest:.4}") + sys.stdout.flush() + + # Thompson sample to get next suggestions + while self.n_evals < self.max_evals and self.length >= self.length_min: + print(f"n_evals = {self.n_evals} (max_evals = {self.max_evals})") + + # Change batch size in order to exit by max_evals + if self.max_evals - self.n_evals < self.batch_size: + self.batch_size = self.max_evals - self.n_evals + print(f"Computed {self.n_evals}/{self.max_evals} points. Changing batch_size to {self.batch_size} to not exceed max.") + + # Warp inputs + X = to_unit_cube(deepcopy(self._X), self.lb, self.ub) + + # Standardize values + fX = deepcopy(self._fX).ravel() + + # Create th next batch + X_cand, y_cand, _ = self._create_candidates( + X, fX, length=self.length, n_training_steps=self.n_training_steps, hypers={} + ) + X_next = self._select_candidates(X_cand, y_cand) + + # Undo the warping + X_next = from_unit_cube(X_next, self.lb, self.ub) + + # Evaluate batch + print(f"Starting batch (n_evals={self.n_evals}, X_next.shape={X_next.shape})") + fX_next = np.array([[self.f(x)] for x in X_next]) + print(f"Finished batch (n_evals={self.n_evals})") + + # Update trust region + self._adjust_length(fX_next) + + # Update budget and append data + self.n_evals += self.batch_size + self._X = np.vstack((self._X, X_next)) + self._fX = np.vstack((self._fX, fX_next)) + + if self.verbose and fX_next.min() < self.fX.min(): + n_evals, fbest = self.n_evals, fX_next.min() + print(f"{n_evals}) New best: {fbest:.4}") + sys.stdout.flush() + + # Append data to the global history + self.X = np.vstack((self.X, deepcopy(X_next))) + self.fX = np.vstack((self.fX, deepcopy(fX_next))) + print(f"End inner while loop") diff --git a/Ext/Toolboxes/turbo/turbo/turbo_m.py b/Ext/Toolboxes/turbo/turbo/turbo_m.py new file mode 100755 index 00000000..9e8a9001 --- /dev/null +++ b/Ext/Toolboxes/turbo/turbo/turbo_m.py @@ -0,0 +1,247 @@ +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import math +import sys +from copy import deepcopy + +import gpytorch +import numpy as np +import torch + +from .gp import train_gp +from .turbo_1 import Turbo1 +from .utils import from_unit_cube, latin_hypercube, to_unit_cube + + +class TurboM(Turbo1): + """The TuRBO-m algorithm. + + Parameters + ---------- + f : function handle + lb : Lower variable bounds, numpy.array, shape (d,). + ub : Upper variable bounds, numpy.array, shape (d,). + n_init : Number of initial points *FOR EACH TRUST REGION* (2*dim is recommended), int. + max_evals : Total evaluation budget, int. + n_trust_regions : Number of trust regions + batch_size : Number of points in each batch, int. + verbose : If you want to print information about the optimization progress, bool. + use_ard : If you want to use ARD for the GP kernel. + max_cholesky_size : Largest number of training points where we use Cholesky, int + n_training_steps : Number of training steps for learning the GP hypers, int + min_cuda : We use float64 on the CPU if we have this or fewer datapoints + device : Device to use for GP fitting ("cpu" or "cuda") + dtype : Dtype to use for GP fitting ("float32" or "float64") + + Example usage: + turbo5 = TurboM(f=f, lb=lb, ub=ub, n_init=n_init, max_evals=max_evals, n_trust_regions=5) + turbo5.optimize() # Run optimization + X, fX = turbo5.X, turbo5.fX # Evaluated points + """ + + def __init__( + self, + f, + lb, + ub, + n_init, + max_evals, + n_trust_regions, + batch_size=1, + verbose=True, + use_ard=True, + max_cholesky_size=2000, + n_training_steps=50, + min_cuda=1024, + device="cpu", + dtype="float64", + ): + self.n_trust_regions = n_trust_regions + super().__init__( + f=f, + lb=lb, + ub=ub, + n_init=n_init, + max_evals=max_evals, + batch_size=batch_size, + verbose=verbose, + use_ard=use_ard, + max_cholesky_size=max_cholesky_size, + n_training_steps=n_training_steps, + min_cuda=min_cuda, + device=device, + dtype=dtype, + ) + + self.succtol = 3 + self.failtol = max(5, self.dim) + + # Very basic input checks + assert n_trust_regions > 1 and isinstance(max_evals, int) + assert max_evals > n_trust_regions * n_init, "Not enough trust regions to do initial evaluations" + assert max_evals > batch_size, "Not enough evaluations to do a single batch" + + # Remember the hypers for trust regions we don't sample from + self.hypers = [{} for _ in range(self.n_trust_regions)] + + # Initialize parameters + self._restart() + + def _restart(self): + self._idx = np.zeros((0, 1), dtype=int) # Track what trust region proposed what using an index vector + self.failcount = np.zeros(self.n_trust_regions, dtype=int) + self.succcount = np.zeros(self.n_trust_regions, dtype=int) + self.length = self.length_init * np.ones(self.n_trust_regions) + + def _adjust_length(self, fX_next, i): + assert i >= 0 and i <= self.n_trust_regions - 1 + + fX_min = self.fX[self._idx[:, 0] == i, 0].min() # Target value + if fX_next.min() < fX_min - 1e-3 * math.fabs(fX_min): + self.succcount[i] += 1 + self.failcount[i] = 0 + else: + self.succcount[i] = 0 + self.failcount[i] += len(fX_next) # NOTE: Add size of the batch for this TR + + if self.succcount[i] == self.succtol: # Expand trust region + self.length[i] = min([2.0 * self.length[i], self.length_max]) + self.succcount[i] = 0 + elif self.failcount[i] >= self.failtol: # Shrink trust region (we may have exceeded the failtol) + self.length[i] /= 2.0 + self.failcount[i] = 0 + + def _select_candidates(self, X_cand, y_cand): + """Select candidates from samples from all trust regions.""" + assert X_cand.shape == (self.n_trust_regions, self.n_cand, self.dim) + assert y_cand.shape == (self.n_trust_regions, self.n_cand, self.batch_size) + assert X_cand.min() >= 0.0 and X_cand.max() <= 1.0 and np.all(np.isfinite(y_cand)) + + X_next = np.zeros((self.batch_size, self.dim)) + idx_next = np.zeros((self.batch_size, 1), dtype=int) + for k in range(self.batch_size): + i, j = np.unravel_index(np.argmin(y_cand[:, :, k]), (self.n_trust_regions, self.n_cand)) + assert y_cand[:, :, k].min() == y_cand[i, j, k] + X_next[k, :] = deepcopy(X_cand[i, j, :]) + idx_next[k, 0] = i + assert np.isfinite(y_cand[i, j, k]) # Just to make sure we never select nan or inf + + # Make sure we never pick this point again + y_cand[i, j, :] = np.inf + + return X_next, idx_next + + def optimize(self): + """Run the full optimization process.""" + # Create initial points for each TR + for i in range(self.n_trust_regions): + X_init = latin_hypercube(self.n_init, self.dim) + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Update budget and set as initial data for this TR + self.X = np.vstack((self.X, X_init)) + self.fX = np.vstack((self.fX, fX_init)) + self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) + self.n_evals += self.n_init + + if self.verbose: + fbest = fX_init.min() + print(f"TR-{i} starting from: {fbest:.4}") + sys.stdout.flush() + + # Thompson sample to get next suggestions + while self.n_evals < self.max_evals: + + # Generate candidates from each TR + X_cand = np.zeros((self.n_trust_regions, self.n_cand, self.dim)) + y_cand = np.inf * np.ones((self.n_trust_regions, self.n_cand, self.batch_size)) + for i in range(self.n_trust_regions): + idx = np.where(self._idx == i)[0] # Extract all "active" indices + + # Get the points, values the active values + X = deepcopy(self.X[idx, :]) + X = to_unit_cube(X, self.lb, self.ub) + + # Get the values from the standardized data + fX = deepcopy(self.fX[idx, 0].ravel()) + + # Don't retrain the model if the training data hasn't changed + n_training_steps = 0 if self.hypers[i] else self.n_training_steps + + # Create new candidates + X_cand[i, :, :], y_cand[i, :, :], self.hypers[i] = self._create_candidates( + X, fX, length=self.length[i], n_training_steps=n_training_steps, hypers=self.hypers[i] + ) + + # Select the next candidates + X_next, idx_next = self._select_candidates(X_cand, y_cand) + assert X_next.min() >= 0.0 and X_next.max() <= 1.0 + + # Undo the warping + X_next = from_unit_cube(X_next, self.lb, self.ub) + + # Evaluate batch + fX_next = np.array([[self.f(x)] for x in X_next]) + + # Update trust regions + for i in range(self.n_trust_regions): + idx_i = np.where(idx_next == i)[0] + if len(idx_i) > 0: + self.hypers[i] = {} # Remove model hypers + fX_i = fX_next[idx_i] + + if self.verbose and fX_i.min() < self.fX.min() - 1e-3 * math.fabs(self.fX.min()): + n_evals, fbest = self.n_evals, fX_i.min() + print(f"{n_evals}) New best @ TR-{i}: {fbest:.4}") + sys.stdout.flush() + self._adjust_length(fX_i, i) + + # Update budget and append data + self.n_evals += self.batch_size + self.X = np.vstack((self.X, deepcopy(X_next))) + self.fX = np.vstack((self.fX, deepcopy(fX_next))) + self._idx = np.vstack((self._idx, deepcopy(idx_next))) + + # Check if any TR needs to be restarted + for i in range(self.n_trust_regions): + if self.length[i] < self.length_min: # Restart trust region if converged + idx_i = self._idx[:, 0] == i + + if self.verbose: + n_evals, fbest = self.n_evals, self.fX[idx_i, 0].min() + print(f"{n_evals}) TR-{i} converged to: : {fbest:.4}") + sys.stdout.flush() + + # Reset length and counters, remove old data from trust region + self.length[i] = self.length_init + self.succcount[i] = 0 + self.failcount[i] = 0 + self._idx[idx_i, 0] = -1 # Remove points from trust region + self.hypers[i] = {} # Remove model hypers + + # Create a new initial design + X_init = latin_hypercube(self.n_init, self.dim) + X_init = from_unit_cube(X_init, self.lb, self.ub) + fX_init = np.array([[self.f(x)] for x in X_init]) + + # Print progress + if self.verbose: + n_evals, fbest = self.n_evals, fX_init.min() + print(f"{n_evals}) TR-{i} is restarting from: : {fbest:.4}") + sys.stdout.flush() + + # Append data to local history + self.X = np.vstack((self.X, X_init)) + self.fX = np.vstack((self.fX, fX_init)) + self._idx = np.vstack((self._idx, i * np.ones((self.n_init, 1), dtype=int))) + self.n_evals += self.n_init diff --git a/Ext/Toolboxes/turbo/turbo/utils.py b/Ext/Toolboxes/turbo/turbo/utils.py new file mode 100755 index 00000000..08068129 --- /dev/null +++ b/Ext/Toolboxes/turbo/turbo/utils.py @@ -0,0 +1,39 @@ +############################################################################### +# Copyright (c) 2019 Uber Technologies, Inc. # +# # +# Licensed under the Uber Non-Commercial License (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at the root directory of this project. # +# # +# See the License for the specific language governing permissions and # +# limitations under the License. # +############################################################################### + +import numpy as np + + +def to_unit_cube(x, lb, ub): + """Project to [0, 1]^d from hypercube with bounds lb and ub""" + assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 + xx = (x - lb) / (ub - lb) + return xx + + +def from_unit_cube(x, lb, ub): + """Project from [0, 1]^d to hypercube with bounds lb and ub""" + assert np.all(lb < ub) and lb.ndim == 1 and ub.ndim == 1 and x.ndim == 2 + xx = x * (ub - lb) + lb + return xx + + +def latin_hypercube(n_pts, dim): + """Basic Latin hypercube implementation with center perturbation.""" + X = np.zeros((n_pts, dim)) + centers = (1.0 + 2.0 * np.arange(0.0, n_pts)) / float(2 * n_pts) + for i in range(dim): # Shuffle the center locataions for each dimension. + X[:, i] = centers[np.random.permutation(n_pts)] + + # Add some perturbations within each box + pert = np.random.uniform(-1.0, 1.0, (n_pts, dim)) / float(2 * n_pts) + X += pert + return X diff --git a/Ext/Toolboxes/turbo/turbo_example.m b/Ext/Toolboxes/turbo/turbo_example.m new file mode 100755 index 00000000..40607cfe --- /dev/null +++ b/Ext/Toolboxes/turbo/turbo_example.m @@ -0,0 +1,33 @@ +% This is an example script of how to use Breach with the Turbo solver. + +%% Initialization +% The following script creates a default interface with the +% AbstractFuelControl model. +BrDemo.InitAFC(); +BrAFC + +%% Creating a Falsification Problem +% Given a requirement R and some parameter range, we want to find a +% parameter value for which the system violates R. + +%% +% First we create the parameter search domain and load specifications. +AFC_Falsify = BrAFC.copy(); +AFC_Falsify.SetParamRanges({'Pedal_Angle_pulse_period', 'Pedal_Angle_pulse_amp'}, [10 12; 10 40]); +STL_ReadFile('AFC_simple_spec.stl'); +req = BreachRequirement(AF_alw_ok); + +savingExampleName = fopen('currentExample.txt','wt'); +fprintf(savingExampleName, 'demo_example'); +fclose(savingExampleName); +%% +% Then we create the falsification problem and solve it. +falsif_pb = FalsificationProblem(AFC_Falsify, req); +falsif_pb.max_obj_eval = 23; + +%% Calling turbo +xHist = [10 12 10 + 10 40 40]; +fHist = [0.01992 0.11524 0.014005]; +falsif_pb.setup_turbo('start_sample', xHist, 'start_function_values', fHist); +falsif_pb.solve(); diff --git a/Ext/Toolboxes/turbo/turbo_optimization.py b/Ext/Toolboxes/turbo/turbo_optimization.py new file mode 100755 index 00000000..f08c3260 --- /dev/null +++ b/Ext/Toolboxes/turbo/turbo_optimization.py @@ -0,0 +1,133 @@ +import sys + +from turbo import Turbo1 # import Turbo +import numpy as np +import json +import os +import GPy +import scipy.io +from pyDOE import lhs +from turbo import Turbo1 +import time +import csv +from tqdm import tqdm # to track iterations +from torch.quasirandom import SobolEngine + + +# json file +def inputs_params(): + """ + In this function, we can get the information of the system from a json file, called "scenario.json". + """ + + parameters_file = os.getcwd() + "/scenario.json" + with open('scenario.json', 'r') as outputfile: + + outputdata = json.load(outputfile) + numberOfDimensions = outputdata['number_dimensions'] + inputParameters = outputdata['input_parameters'] + maxNumberOfIterations = outputdata['optimization_iterations'] + lowerBounds = []; + upperBounds = []; + + for i in range(1, numberOfDimensions+1): + readingInputs = {} + currentInput = "x" + str(i) + readingInputs["x" + str(i)] = inputParameters[currentInput] + readingInputsRanges = readingInputs[currentInput] + lowerBounds.append(readingInputsRanges[0]); + upperBounds.append(readingInputsRanges[1]); + + return numberOfDimensions, maxNumberOfIterations, lowerBounds, upperBounds + +# Check to see that objective output is calculated and saved in a csv file, waiting until the objective value is calculated from Breach + +def waiting_to_get_objectvalue_from_matlab(nameOfFileToWaitFor): + + while not os.path.exists (nameOfFileToWaitFor): + time.sleep(1) + + +class Levy: + # Reading system information saved in a json file by calling function "inputs_params" + numberOfDimensions, maxNumberOfIterations, lowerBounds, upperBounds = inputs_params() + def __init__(self, dim = numberOfDimensions): + self.dim = dim + self.lb = lowerBounds + self.ub = upperBounds + + def __call__(self, x): + assert len(x) == self.dim + assert x.ndim == 1 + assert np.all(x <= self.ub) and np.all(x >= self.lb) + + global numberOfIterations + nameOfCurrentInputFile = "turboToMatlab.csv" + + # Writing the input values in a csv file + with open(nameOfCurrentInputFile, 'w') as writingInput: + writingInputsToCSV = csv.writer(writingInput) + for i in range(1, len(x)+1): + convertingValuesToFloat = np.float(x[i-1]) + x[i-1] = convertingValuesToFloat + + + writingInputsToCSV.writerow(x) + writingInput.seek(0,2) + size=writingInput.tell() + writingInput.truncate(size-2) + + #writingInput.close() + + # Tell MATLAB that Turbo has finished writing + f = open('turboFinishedWriting.dummy', 'w') + f.close() + + nameOfCurrentOutputFile = "matlabToTurbo.csv" + nameOfFileToWaitFor = 'matlabFinishedWriting.dummy' + + # Waiting to get objective value from Breach by calling function "waiting_to_get_objectvalue_from_matlab" + waiting_to_get_objectvalue_from_matlab(nameOfFileToWaitFor) + + # Opening and reading value function saved in a csv file + outData = open(nameOfCurrentOutputFile, "r") + outObj = [] + for out in outData: + outObj.append(float(out)) + + outData.close() + + os.remove(nameOfCurrentOutputFile) + os.remove(nameOfFileToWaitFor) + numberOfIterations = numberOfIterations + 1 # Increasing numberOfIterations + + return outObj[0] + + + + +if __name__ == '__main__': + global numberOfIterations + numberOfDimensions, maxNumberOfIterations, lowerBounds, upperBounds = inputs_params() + numberOfIterations = 1 + + f = Levy(numberOfDimensions) + + turbo1 = Turbo1( + f=f, # Handle to objective function + lb=np.array(lowerBounds), # Numpy array specifying lower bounds + ub=np.array(upperBounds), # Numpy array specifying upper bounds + n_init=2*numberOfDimensions, # Number of initial bounds from an Latin hypercube design + max_evals = maxNumberOfIterations, # Maximum number of evaluations + batch_size=10, # How large batch size TuRBO uses + verbose=True, # Print information from each batch + use_ard=True, # Set to true if you want to use ARD for the GP kernel + max_cholesky_size=2000, # When we switch from Cholesky to Lanczos + n_training_steps=50, # Number of steps of ADAM to learn the hypers + min_cuda=1024, # Run on the CPU for small datasets + device="cpu", # "cpu" or "cuda" + dtype="float64", # float64 or float32 + ) + + turbo1.optimize() + sys.exit(0) \ No newline at end of file diff --git a/Ext/Toolboxes/wordgen/AutoTransmission_demo.m b/Ext/Toolboxes/wordgen/AutoTransmission_demo.m new file mode 100755 index 00000000..cefa39b3 --- /dev/null +++ b/Ext/Toolboxes/wordgen/AutoTransmission_demo.m @@ -0,0 +1,239 @@ +%%% Old Script for demo of Classification based method +bdclose all +close all +clear +InitBreach + + +%% Breach Interface Object Creation + +model_name = 'autotrans_mod04'; +fprintf('\n Creating breach interface with simulink model %s\n',model_name) + +simTime = 30 ; +fprintf('\n Simulation time horizon is %d seconds\n',simTime) + +fprintf('\n Press any key to continue') +pause + +BrSys = CoverageBreachSet(model_name,{}); +BrSys.SetTime([0 simTime]); + +%% Set input signals + +fprintf('\n Parametrizing input signals throttle and break....\n') +fprintf('\n Input signals are parametrized as piecewise constant\n ') +input_gen.type = 'UniStep'; + +N1 = 7; N2 = 3; +fprintf('Number of control points for throttle input is %d\n',N1) +fprintf('Number of control points for break input is %d\n',N2 ) +input_gen.cp = [N1 N2]; +input_gen.method = {'previous','previous'}; +BrSys.SetInputGen(input_gen); + +fprintf('\n Press any key to continue\n') +pause + +%% Specifying parameter names +for i=0:N1-1 + signal_u0{1,i+1}=strcat('In1_u',num2str(i)); +end + +for i=0:N2-1 + signal_u1{1,i+1}=strcat('In2_u',num2str(i)); +end +signal = [signal_u0,signal_u1]; + +%% Initializing CBS object parameters + +% Input ranges +fprintf('\n Range of throttle is [35,100]\n') +fprintf('Range of break is [0,40] \n') +fprintf('\n Grid discretization unit for both signal ranges is 4 units\n') +R1 = [35,100]; +R2 = [0,40]; +CBS = BrSys.copy; +CBS.SetParamRanges(signal,[ones(N1,1)*R1;ones(N2,1)*R2]); +CBS.SetEpsGridsize([4*ones(N1,1);4*ones(N2,1)]); +CBS.SetDeltaGridsize(2*CBS.epsgridsize); + +fprintf('\n Press any key to continue\n ') +pause + + +%% Specifying STL formula +fprintf('\n The STL formula is\n ') + f1 = STL_Formula('f1','alw(RPM[t]<2520)'); + f2 = STL_Formula('f2','ev_[0,10](speed[t]>50)'); + phi = STL_Formula('phi1','not(f1 and f2)'); + + % Can rewrite formula: [f1 => phitest2] where + phitest1 = STL_Formula('phitest1','ev(RPM[t]>2520)'); % not(f1) + phitest2 = STL_Formula('phitest2','alw_[0,10](speed[t]<50)'); % not(f2) +disp(phi) + +fprintf('\n Press any key to continue\n') +pause + +%% Setting falsification method and parameters +msg1 = sprintf('\nChoose a falsification method\n'); +msg2 = sprintf('Press 1 : Classification guided sampling\n'); +msg3 = sprintf('Press 2: Pseudo random sampling\n'); +msg4 = sprintf('Press 3: Global_nelder_mead\n'); +msg5 = sprintf('Press 4: CMA-ES\n'); +msg6 = sprintf('Press 5: simulannealbnd\n'); + +a = input([msg1,msg2,msg3,msg4,msg5,msg6]); + +switch a + + case 2 + time_lim = 2000; + fprintf('\n Time limit of computation is %d seconds\n',time_lim) + snap_grid = 'y'; + switch snap_grid + case 'y' + CBS.SetSnapToGrid(true); + case 'n' + CBS.SetSnapToGrid(false); + otherwise + error('no epsilon resolution specified') + end + max_sim = inf; + fprintf('\n Choose one of the following seeds for pseudorandom sampling:\n') + r = input('0, 5000, 10000 or 15000\n'); + rng(r,'twister'); + tic + Out = StatFalsify(CBS,phi,w_rob,max_sim,max_sim,time_lim); + time = toc; + fprintf('Computation time = %f seconds \n',time); + + case 1 + time_lim = inf; + snap_grid = 'y'; + switch snap_grid + case 'y' + CBS.SetSnapToGrid(true); + case 'n' + CBS.SetSnapToGrid(false); + otherwise + error('no epsilon resolution specified') + end + + w_rob = 0.5; + fprintf('\n Weightage to robustness information is %f\n',w_rob) + + max_sim = 1500; + fprintf('\n Limit on number of simulations during global search is %d.\n',max_sim) + + init_sim = 70; + fprintf('\n Threshold number of samples for classification is %d\n ',init_sim) + + + fprintf('\n Press any key to continue\n') + pause + + fprintf('\n Choose one of the following seeds for pseudorandom sampling:\n') + r = input('0, 5000, 10000 or 15000\n'); + rng(r,'twister'); + timervar_1 = tic; + Out = StatFalsify([],CBS,phitest1,w_rob,init_sim,max_sim,time_lim); + time_1 = toc(timervar_1); + fname = ['cl',num2str(r)]; + save(fname, 'AutoTrans_phitest1') + %% + if isempty(Out.falsifier) + % Resetting CBS + clear CBS; + CBS = BrSys.copy; + CBS.SetParamRanges(signal_u0,ones(N1,1)*R1); + CBS.SetParamRanges(signal_u1,ones(N2,1)*R2); + CBS.SetEpsGridsize([4*ones(N1,1);4*ones(N2,1)]); + CBS.SetDeltaGridsize(2*CBS.epsgridsize); + + + % Sort region indices in ascending order of lowest robustness values + [~,I] = sort(Out.lower_bounds.vals); + L = Out.lower_bounds; + X = L.pts; + avg = mean(L.vals); + Y = L.pts(L.vals50)'); +%phi = STL_Formula('phi','alw_[0,10](RPM[t]<2520)'); +%phi = STL_Formula('phi','not(f1 and f2)'); +%phi = STL_Formula('phi','alw_[0,10](speed[t]<50)'); + +% Can rewrite formula: [f1 => phitest2] where +% phitest1 = STL_Formula('phitest1','ev(RPM[t]>2520)'); % not(f1) +% phitest2 = STL_Formula('phitest2','alw_[0,10](speed[t]<50)'); % not(f2) + + +%% Checking reachable labels +STL_ReadFile('Autotrans_req.stl'); +phi = BreachRequirement(never_gear3_and_speed_low); + + +%% Set input signals + +fprintf('\n Parametrizing input signals throttle and break....\n') +fprintf('\n Input signals are parametrized as piecewise constant\n ') + +%nb_ctr_pts = [ 7; 3 ]; +nb_ctr_pts = [ 20; 10 ]; +timepoints = [ simTime/nb_ctr_pts(1,1) simTime/nb_ctr_pts(2,1)]; +input_ranges = [ 35 100; 0 40 ]; + +%signal_types = { 'UniStep', 'UniStep' }; +%%%% thao signal_types = { 'VarStep', 'VarStep' }; +input_signal_names = {'In1','In2'}; +signal_gen_method = {'previous','previous'}; + + +%%%% +% Insig = load(sigfilename, '-ascii'); +% scaling=0.65e-7; +% %%Passband 0.4e-7 Oui les deux -- 0.5e-7 Oui les deux -- +% %0.6e-7 discrepance 19 Oui, uni NON -- %0.8e-7 NON les deux +% +% %%Lowpass +% %%0.4e-4; +% +% Insig(:,1) = scaling*In1(:,1); +% time = Insig(:,1); +%%%% signal_gen_method = {'linear','linear'}; +%%%% In1_u0 = +%%%% In1_dt0 = time(2) - time(1) +%%%% pas de dernier interval dt + + +%from old code +%input_gen.type = 'UniStep'; +%input_gen.method = {'previous','previous'}; + +% grid size collumn on the range of each input signal +gridsize_vector = [ 4; 4 ]; + + +%%%% Once the above system specifications and falsification options are given, +%%%% the following part of the code need not be modified by the user +MetaObj = MetaFalsify(model_name,IO_signal_names); +% specify the simulation time +MetaObj.SimTimeSetUp(simTime); + +% specify the class of input signals +MetaObj.InputSignalFixedPatternSetUp(input_signal_names,signal_gen_method,nb_ctr_pts,input_ranges); + +% specify the property to falsify +MetaObj.STLFormulaSetUp(phi); + +% set up a grid on the input ranges, to estimate coverage +MetaObj.GridSetUp(gridsize_vector,nb_ctr_pts); + +fileID = fopen('OutFalsificationFixedPattern.txt','w'); +MetaObj.OutFileID = fileID; + +%% Start the falsification process +%%%[r,falsified,total_nb_sim,falsi_point] = MetaObj.MetaCall(); + +%%% Set up falsification options + + %%%% Search Monitoring Parameters + %% cov_epsilon = input('Specify coverage increase threshold : '); + MetaObj.cov_epsilon = 1e-3; + %% min robustness decrease in percentage + MetaObj.rob_epsilon_percent = 0.05; + %% min robustness stagnant monitoring window + MetaObj.rob_stagnant_win = 1 + %% coverage stagnant monitoring window + MetaObj.cov_monitoring_win = 1; + + %%% Options for picking initial conditions + MetaObj.re_init_strategy = 2; %2; + % re_init_strategy=0 to pick randomly from the whole space + % re_init_strategy=1 to pick randomly from xlog + % re_init_strategy=2 to pick randomly from xbest + + % re_init_num_xbest: window of choice from xbest, for picking initial point + MetaObj.re_init_num_xbest = 200; + + % num_solvers=nb of solvers %%other than pseudorandom sampling + % TODO add solver_list, and init num_solver as numel(solver_list) + MetaObj.num_solvers=4; + + %% limit on nb of solver calls + MetaObj.nb_solver_calls = 10 %1 %30 + + MetaObj.start_solver_index = 2; %3; %1; %PR 0, cmaes 1, SA 2, GNM 3 + + MetaObj.solver_time = [500 2000 500 900]; + MetaObj.max_obj_eval = [ 100 200 200 500 ]; + MetaObj.seed = 5000; + + +fprintf('\n The falsification problem by metaheuristics is\n ') +MetaObj + +%%%% Run the falsification +MetaObj.MetaCall(); + +%MetaObj.MetaSetupRun(Sys, phi) +%MetaObj.MetaSetupRun(MetaObj.Br, phi) + diff --git a/Ext/Toolboxes/wordgen/Autotrans_falsi_org.m b/Ext/Toolboxes/wordgen/Autotrans_falsi_org.m new file mode 100644 index 00000000..788ef2f9 --- /dev/null +++ b/Ext/Toolboxes/wordgen/Autotrans_falsi_org.m @@ -0,0 +1,140 @@ +%% STILL UNDER DEBUG + +%% This script set up the falsification problem +%% for the Autotransmission model + +close all +clear all +warning('off', 'ALL') + +thao =0; +if thao + addpath('/Users/thaodang/Metaheuristics/src') + addpath('/Users/thaodang/Metaheuristics/breach-dev') + addpath('.') + InitBreach('/Users/thaodang/Metaheuristics/breach-dev',true); % forces initialization from folder in Metaheuristics +else + addpath('../../Metaheuristics/src') +end +model_name = 'autotrans_mod04'; +fprintf('\n Creating breach interface with simulink model %s\n',model_name) + +simTime = 30 ; +fprintf('\n Simulation time horizon is %d seconds\n',simTime) + +IO_signal_names = {'In1','In2','RPM','gear','speed'}; + +%% Specifying STL formula +%fprintf('\n The STL formula is\n ') +f1 = STL_Formula('f1','alw(RPM[t]<2520)'); +f2 = STL_Formula('f2','ev_[0,10](speed[t]>50)'); +%phi = STL_Formula('phi','alw_[0,10](RPM[t]<2520)'); +phi = STL_Formula('phi','not(f1 and f2)'); +%phi = STL_Formula('phi','alw_[0,10](speed[t]<50)'); + +% Can rewrite formula: [f1 => phitest2] where +% phitest1 = STL_Formula('phitest1','ev(RPM[t]>2520)'); % not(f1) +% phitest2 = STL_Formula('phitest2','alw_[0,10](speed[t]<50)'); % not(f2) + + +%% Set input signals + +fprintf('\n Parametrizing input signals throttle and break....\n') +fprintf('\n Input signals are parametrized as piecewise constant\n ') + +nb_ctr_pts = [ 7; 3 ]; +input_ranges = [ 35 100; 0 40 ]; +%signal_types = { 'UniStep', 'UniStep' }; +%%%% thao signal_types = { 'VarStep', 'VarStep' }; +input_signal_names = {'In1','In2'}; +signal_gen_method = {'previous','previous'}; + + +%%%% +% Insig = load(sigfilename, '-ascii'); +% scaling=0.65e-7; +% %%Passband 0.4e-7 Oui les deux -- 0.5e-7 Oui les deux -- +% %0.6e-7 discrepance 19 Oui, uni NON -- %0.8e-7 NON les deux +% +% %%Lowpass +% %%0.4e-4; +% +% Insig(:,1) = scaling*In1(:,1); +% time = Insig(:,1); +%%%% signal_gen_method = {'linear','linear'}; +%%%% In1_u0 = +%%%% In1_dt0 = time(2) - time(1) +%%%% pas de dernier interval dt + + +%from old code +%input_gen.type = 'UniStep'; +%input_gen.method = {'previous','previous'}; + +% grid size collumn on the range of each input signal +gridsize_vector = [ 4; 4 ]; + + +%%%% Once the above system specifications and falsification options are given, +%%%% the following part of the code need not be modified by the user +MetaObj = MetaFalsify(model_name,IO_signal_names); +% specify the simulation time +MetaObj.SimTimeSetUp(simTime); + +% specify the class of input signals +MetaObj.InputSignalSetUp(input_signal_names,signal_gen_method,nb_ctr_pts,input_ranges); + +% specify the property to falsify +MetaObj.STLFormulaSetUp(phi); + +% set up a grid on the input ranges, to estimate coverage +MetaObj.GridSetUp(gridsize_vector,nb_ctr_pts); + + +%% Start the falsification process +%%%[r,falsified,total_nb_sim,falsi_point] = MetaObj.MetaCall(); + +%%% Set up falsification options + + %%%% Search Monitoring Parameters + %% cov_epsilon = input('Specify coverage increase threshold : '); + MetaObj.cov_epsilon = 1e-3; + %% min robustness decrease in percentage + MetaObj.rob_epsilon_percent = 0.05; + %% min robustness stagnant monitoring window + MetaObj.rob_stagnant_win = 1 + %% coverage stagnant monitoring window + MetaObj.cov_monitoring_win = 1; + + %%% Options for picking initial conditions + MetaObj.re_init_strategy = 2; %2; + % re_init_strategy=0 to pick randomly from the whole space + % re_init_strategy=1 to pick randomly from xlog + % re_init_strategy=2 to pick randomly from xbest + + % re_init_num_xbest: window of choice from xbest, for picking initial point + MetaObj.re_init_num_xbest = 200; + + % num_solvers=nb of solvers %%other than pseudorandom sampling + % TODO add solver_list, and init num_solver as numel(solver_list) + MetaObj.num_solvers=4; + + %% limit on nb of solver calls + MetaObj.nb_solver_calls = 2 %1 %30 + + MetaObj.start_solver_index = 2; %3; %1; %PR 0, cmaes 1, SA 2, GNM 3 + + MetaObj.solver_time = [500 2000 500 900]; + MetaObj.max_obj_eval = [ 1000 20000 1000 50000 ]; + MetaObj.seed = 5000; + + +fprintf('\n The falsification problem by metaheuristics is\n ') +MetaObj + +%%%% Run the falsification +MetaObj.MetaCall(); + +%MetaObj.MetaSetupRun(Sys, phi) +%MetaObj.MetaSetupRun(MetaObj.Br, phi) + diff --git a/Ext/Toolboxes/wordgen/Autotrans_setup.m b/Ext/Toolboxes/wordgen/Autotrans_setup.m new file mode 100644 index 00000000..d538794c --- /dev/null +++ b/Ext/Toolboxes/wordgen/Autotrans_setup.m @@ -0,0 +1,124 @@ +%% This script set up the falsification problem +%% for the Autotransmission model + +close all +clear all +%warning('off', 'ALL') + +addpath('/Users/thaodang/Metaheuristics/supp_code') +addpath('/Users/thaodang/Metaheuristics/src') +addpath('/Users/thaodang/Metaheuristics/breach-dev') +addpath('.') + +InitBreach('/Users/thaodang/Metaheuristics/breach-dev',true); % forces initialization from folder in Metaheuristics + +model_name = 'autotrans_mod04'; +fprintf('\n Creating breach interface with simulink model %s\n',model_name) + +simTime = 30 ; +fprintf('\n Simulation time horizon is %d seconds\n',simTime) + +IO_signal_names = {'In1','In2','RMP','gear','speed'}; +BrSys = CoverageBreachSet(model_name,{},[],IO_signal_names); +%BrSys = CoverageBreachSet(model_name,{}); +BrSys.SetTime([0 simTime]); + +%% Set input signals + +fprintf('\n Parametrizing input signals throttle and break....\n') +fprintf('\n Input signals are parametrized as piecewise constant\n ') +input_gen.type = 'UniStep'; + +N1 = 7; N2 = 3; +fprintf('Number of control points for throttle input is %d\n',N1) +fprintf('Number of control points for break input is %d\n',N2 ) +input_gen.cp = [N1 N2]; +input_gen.method = {'previous','previous'}; +BrSys.SetInputGen(input_gen); + +%% Specifying parameter names +for i=0:N1-1 + signal_u0{1,i+1}=strcat('In1_u',num2str(i)); +end + +for i=0:N2-1 + signal_u1{1,i+1}=strcat('In2_u',num2str(i)); +end +signal = [signal_u0,signal_u1]; + +%% Initializing CBS object parameters + +% Input ranges +% fprintf('\n Range of throttle is [35,100]\n') +% fprintf('Range of break is [0,40] \n') +% fprintf('\n Grid discretization unit for both signal ranges is 4 units\n') +R1 = [35,100]; +R2 = [0, 40]; +Sys = BrSys.copy(); +%signal +Sys.SetParamRanges(signal,[ones(N1,1)*R1;ones(N2,1)*R2]); +Sys.SetEpsGridsize([4*ones(N1,1);4*ones(N2,1)]); +Sys.SetDeltaGridsize(2*Sys.epsgridsize); + +%% Specifying STL formula +%fprintf('\n The STL formula is\n ') +f1 = STL_Formula('f1','alw(RPM[t]<2520)'); +f2 = STL_Formula('f2','ev_[0,10](speed[t]>50)'); +phi = STL_Formula('phi','not(f1 and f2)'); +%phi = STL_Formula('phi','alw_[0,10](speed[t]<50)'); + +% Can rewrite formula: [f1 => phitest2] where +% phitest1 = STL_Formula('phitest1','ev(RPM[t]>2520)'); % not(f1) +% phitest2 = STL_Formula('phitest2','alw_[0,10](speed[t]<50)'); % not(f2) + + + +MetaObj = MetaFalsify(); + + + + +%%% Set up falsification options + + %% limit on nb of solver calls + %this.nb_solver_calls = input('Specify Max Nb of Solver Calls: '); + MetaObj.nb_solver_calls = 30 %30 %1 %30 + %fprintf('\n Max Nb of Solver Calls: ',this.nb_solver_calls) + + %%%% Search Monitoring Parameters + %% cov_epsilon = input('Specify coverage increase threshold : '); + MetaObj.cov_epsilon = 1e-3; + %% min robustness decrease in percentage + MetaObj.rob_epsilon_percent = 0.05; + %% min robustness stagnant monitoring window + MetaObj.rob_stagnant_win = 1 + %% coverage stagnant monitoring window + MetaObj.cov_monitoring_win = 1; + + %%% Options for picking initial conditions + MetaObj.re_init_strategy = 1; + % re_init_strategy=0 to pick randomly from the whole space + % re_init_strategy=1 to pick randomly from xlog + % re_init_strategy=2 to pick randomly from xbest + + % re_init_num_xbest: window of choice from xbest, for picking initial point + MetaObj.re_init_num_xbest = 1; + + % num_solvers=nb of solvers %%other than pseudorandom sampling + % TODO add solver_list, and init num_solver as numel(solver_list) + MetaObj.num_solvers=4; + + MetaObj.start_solver_index = 3; %1; %PR 0, cmaes 1, SA 2, GNM 3 + + MetaObj.solver_time = [200 1200 200 200]; + MetaObj.max_obj_eval = [ 2000 2500 2000 2000 ]; + MetaObj.seed = 5000; + + +fprintf('\n The falsification problem by metaheuristics is\n ') +Sys + +%%%% Run the falsification +MetaObj.MetaSetupRun(Sys, phi); + + diff --git a/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi.m b/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi.m new file mode 100644 index 00000000..a56290a8 --- /dev/null +++ b/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi.m @@ -0,0 +1,55 @@ + +addpath('/Users/thaodang/Metaheuristics/src') +addpath('/Users/thaodang/Metaheuristics/wordgen') +addpath('/Users/thaodang/Metaheuristics/breach-dev') +addpath('.') +InitBreach('/Users/thaodang/Metaheuristics/breach-dev',true); % forces initialization from folder in Metaheuristics + + +%% TA based signal generator setup +% + +num_evt=5 + +init_TA_signal_gen; + + +%% Checking reachable labels +STL_ReadFile('Autotrans_req.stl'); + +%% + +S2 = S0.copy(); +mdl = 'Autotrans_wordgen'; +%mdl = 'autotrans_mod04'; +Ba = BreachSimulinkSystem(mdl); +Ba.SetTime(time); +Ba.SetInputGen(S2); +Ba.SetParamRanges([pevts pbranching], [0 1]); +Ba.Sim(); + + +%% +R = BreachRequirement(never_gear3_and_speed_low); +falsif_pb = FalsificationProblem(Ba, R); +falsif_pb.solver_options.num_corners = 100; +falsif_pb.solver_options.num_quasi_rand_samples = 100; +falsif_pb.max_obj_eval = 1000; +%falsif_pb.SetupDiskCaching(); + +%% +falsif_pb.solve(); + +%% Initial Counter-example +BFalse = falsif_pb.BrSet_False; +%BFalse = falsif_pb.GetFalse();; + +%% Fix Specification +param_pb = ParamSynthProblem(BFalse, never_gear3_and_speed_low, 'v_low', [0 30]); +param_pb.solver_options.monotony = -1; +param_pb.solve(); + + +%% Requirement mining: Iterate +%mining_pb = ReqMiningProblem(param_pb, falsif_pb); +%mining_pb.solve(); diff --git a/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi_optim.m b/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi_optim.m new file mode 100644 index 00000000..64d4bd7d --- /dev/null +++ b/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi_optim.m @@ -0,0 +1,145 @@ +addpath('/Users/dang/Metaheuristics/breach-dev') + +addpath('/Users/dang/Metaheuristics/src') +addpath('.') + +addpath('/Users/dang/Metaheuristics/wordgen') +InitBreach('/Users/dang/Metaheuristics/breach-dev',true); + +%% TA based signal generator setup +% +num_evt=25; +init_TA_signal_gen; + + +%% Checking reachable labels +STL_ReadFile('Autotrans_req.stl'); + +%% + +S2 = S0.copy(); +mdl = 'Autotrans_wordgen'; +Ba = BreachSimulinkSystem(mdl); +Ba.SetTime(time); +Ba.SetInputGen(S2); +Ba.SetParamRanges([pevts pbranching], [0.000001 0.999999]); +% Ba.Sim(); + + +%% +R = BreachRequirement(never_gear3_and_speed_low); +falsif_pb = FalsificationProblem(Ba, R); + +nb_varying_params = size(Ba.VaryingParamList(),2); + +% falsif_pb.solver_options.num_corners = 100; +% falsif_pb.solver_options.num_quasi_rand_samples = 100; +% falsif_pb.max_obj_eval = 1000; +%falsif_pb.SetupDiskCaching(); + +%falsif_pb.solve(); + +%% Initial Counter-example +%%BFalse = falsif_pb.BrSet_False; +%BFalse = falsif_pb.GetFalse();; + +%% Fix Specification +% param_pb = ParamSynthProblem(BFalse, never_gear3_and_speed_low, 'v_low', [0 30]); +% param_pb.solver_options.monotony = -1; +% param_pb.solve(); + + +%% Requirement mining: Iterate +%mining_pb = ReqMiningProblem(param_pb, falsif_pb); +%mining_pb.solve(); + + + +% grid size collumn on the range of each var param +gridnb_vector = []; +for ii = 1:nb_varying_params + gridnb_vector = [ gridnb_vector 10 ]; +end + +%%%% Once the above system specifications and falsification options are given, +%%%% the following part of the code need not be modified by the user +%MetaObj = MetaFalsify(B, R, params, ranges); +MetaObj = MetaFalsify(Ba, R, falsif_pb); + + +% fprintf('\n The falsification problem by metaheuristics is\n ') +% MetaObj.Pbs + +% set up a grid on the input ranges, to estimate coverage +MetaObj.GridNbSetUp(gridnb_vector); + + +%% Start the falsification process +%%%[r,falsified,total_nb_sim,falsi_point] = MetaObj.MetaCall(); + +%%% Set up falsification options + + %%%% Search Monitoring Parameters + %% cov_epsilon = input('Specify coverage increase threshold : '); + MetaObj.cov_epsilon = 1e-3; %1e-3; + %% min robustness decrease in percentage + MetaObj.rob_epsilon_percent = 1e-2; %0.05; + %% min robustness stagnant monitoring window + MetaObj.rob_stagnant_win = 1 + %% coverage stagnant monitoring window + MetaObj.cov_monitoring_win = 2; + + %%%% Problem-specific computation parameters + %%% Options for picking initial conditions + MetaObj.re_init_strategy = 2; + % re_init_strategy=0 to pick randomly from the whole space + % re_init_strategy=1 to pick randomly from xlog + % re_init_strategy=2 to pick randomly from xbest + + MetaObj.re_init_num_xbest = 10; + MetaObj.re_init_num_xlog = 200; + MetaObj.re_init_num_rand = 100; + + % num_solvers=nb of solvers %%other than pseudorandom sampling + % TODO add solver_list, and init num_solver as numel(solver_list) + MetaObj.num_solvers = 4; + + %% limit on nb of solver calls + MetaObj.nb_solver_calls = 2; + + MetaObj.start_solver_index = 3; %PR 0, cmaes 1, SA 2, GNM 3 + MetaObj.solver_time = [100 100 100 100]; + MetaObj.max_obj_eval = [200 1200 0 70]; + MetaObj.seed = 100; + +Plot_signal_names = {'brake','throttle','speed','gear','RPM'}; +MetaObj.Plot_signal_names = Plot_signal_names; + +% fprintf('\n The falsification problem by metaheuristics is\n ') +% MetaObj +% Open file to save intermediate results +fileID = fopen('OutFalsification.txt','w'); +MetaObj.OutFileID = fileID; + +fprintf(1,'\n STL_Formula %s', formuleSt); +fprintf(fileID,'\n STL_Formula %', formuleSt); +fprintf(1, '\n Model name is %s \n', model_name); +fprintf(fileID, '\n Model name is %s \n', model_name); + +MetaObj.MetaShortFilePrint(fileID); + +%%%% Run the falsification +MetaObj.MetaCall(); + +%MetaObj.MetaSetupRun(Sys, phi) +%MetaObj.MetaSetupRun(MetaObj.Br, phi) + + +%% Plotting Log +% Rpb= pb.GetLog(); +% Fpb = BreachSamplesPlot(Rpb); +% Fpb.set_y_axis('notsaturation'); + + +%% closing output file +fclose(fileID); \ No newline at end of file diff --git a/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi_optim_19_76_CMAES.m b/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi_optim_19_76_CMAES.m new file mode 100644 index 00000000..f51b83d4 --- /dev/null +++ b/Ext/Toolboxes/wordgen/Autotrans_wordgen_falsi_optim_19_76_CMAES.m @@ -0,0 +1,145 @@ +addpath('/Users/dang/Metaheuristics/breach-dev') + +addpath('/Users/dang/Metaheuristics/src') +addpath('.') + +addpath('/Users/dang/Metaheuristics/wordgen') +InitBreach('/Users/dang/Metaheuristics/breach-dev',true); + +%% TA based signal generator setup +% +num_evt=25; +init_TA_signal_gen; + + +%% Checking reachable labels +STL_ReadFile('Autotrans_req.stl'); + +%% + +S2 = S0.copy(); +mdl = 'Autotrans_wordgen'; +Ba = BreachSimulinkSystem(mdl); +Ba.SetTime(time); +Ba.SetInputGen(S2); +Ba.SetParamRanges([pevts pbranching], [0.000001 0.999999]); +% Ba.Sim(); + + +%% +R = BreachRequirement(never_gear3_and_speed_low); +falsif_pb = FalsificationProblem(Ba, R); + +nb_varying_params = size(Ba.VaryingParamList(),2); + +% falsif_pb.solver_options.num_corners = 100; +% falsif_pb.solver_options.num_quasi_rand_samples = 100; +% falsif_pb.max_obj_eval = 1000; +%falsif_pb.SetupDiskCaching(); + +%falsif_pb.solve(); + +%% Initial Counter-example +%%BFalse = falsif_pb.BrSet_False; +%BFalse = falsif_pb.GetFalse();; + +%% Fix Specification +% param_pb = ParamSynthProblem(BFalse, never_gear3_and_speed_low, 'v_low', [0 30]); +% param_pb.solver_options.monotony = -1; +% param_pb.solve(); + + +%% Requirement mining: Iterate +%mining_pb = ReqMiningProblem(param_pb, falsif_pb); +%mining_pb.solve(); + + + +% grid size collumn on the range of each var param +gridnb_vector = []; +for ii = 1:nb_varying_params + gridnb_vector = [ gridnb_vector 10 ]; +end + +%%%% Once the above system specifications and falsification options are given, +%%%% the following part of the code need not be modified by the user +%MetaObj = MetaFalsify(B, R, params, ranges); +MetaObj = MetaFalsify(Ba, R, falsif_pb); + + +% fprintf('\n The falsification problem by metaheuristics is\n ') +% MetaObj.Pbs + +% set up a grid on the input ranges, to estimate coverage +MetaObj.GridNbSetUp(gridnb_vector); + + +%% Start the falsification process +%%%[r,falsified,total_nb_sim,falsi_point] = MetaObj.MetaCall(); + +%%% Set up falsification options + + %%%% Search Monitoring Parameters + %% cov_epsilon = input('Specify coverage increase threshold : '); + MetaObj.cov_epsilon = 1e-3; %1e-3; + %% min robustness decrease in percentage + MetaObj.rob_epsilon_percent = 1e-2; %0.05; + %% min robustness stagnant monitoring window + MetaObj.rob_stagnant_win = 1 + %% coverage stagnant monitoring window + MetaObj.cov_monitoring_win = 2; + + %%%% Problem-specific computation parameters + %%% Options for picking initial conditions + MetaObj.re_init_strategy = 2; + % re_init_strategy=0 to pick randomly from the whole space + % re_init_strategy=1 to pick randomly from xlog + % re_init_strategy=2 to pick randomly from xbest + + MetaObj.re_init_num_xbest = 10; + MetaObj.re_init_num_xlog = 200; + MetaObj.re_init_num_rand = 100; + + % num_solvers=nb of solvers %%other than pseudorandom sampling + % TODO add solver_list, and init num_solver as numel(solver_list) + MetaObj.num_solvers = 4; + + %% limit on nb of solver calls + MetaObj.nb_solver_calls = 1; + + MetaObj.start_solver_index = 1; %PR 0, cmaes 1, SA 2, GNM 3 + MetaObj.solver_time = [100 100 100 100]; + MetaObj.max_obj_eval = [200 1200 0 1000]; + MetaObj.seed = 100; + +Plot_signal_names = {'brake','throttle','speed','gear','RPM'}; +MetaObj.Plot_signal_names = Plot_signal_names; + +% fprintf('\n The falsification problem by metaheuristics is\n ') +% MetaObj +% Open file to save intermediate results +fileID = fopen('OutFalsification.txt','w'); +MetaObj.OutFileID = fileID; + +%fprintf(1,'\n STL_Formula %', formuleSt); +%fprintf(fileID,'\n STL_Formula %', formuleSt); +%fprintf(1, '\n Model name is %s \n', model_name); +%fprintf(fileID, '\n Model name is %s \n', model_name); + +MetaObj.MetaShortFilePrint(fileID); + +%%%% Run the falsification +MetaObj.MetaCall(); + +%MetaObj.MetaSetupRun(Sys, phi) +%MetaObj.MetaSetupRun(MetaObj.Br, phi) + + +%% Plotting Log +% Rpb= pb.GetLog(); +% Fpb = BreachSamplesPlot(Rpb); +% Fpb.set_y_axis('notsaturation'); + + +%% closing output file +fclose(fileID); \ No newline at end of file diff --git a/Ext/Toolboxes/wordgen/BreachTASignalGen.m b/Ext/Toolboxes/wordgen/BreachTASignalGen.m new file mode 100644 index 00000000..28810224 --- /dev/null +++ b/Ext/Toolboxes/wordgen/BreachTASignalGen.m @@ -0,0 +1,87 @@ +classdef BreachTASignalGen < BreachSignalGen + + methods + function this = BreachTASignalGen(signals, TA_filename, labels_ranges, num_evts, time_scale) + + TA_filename = which(TA_filename); + if isempty(TA_filename) + error('File %s not found.', TA_filename); + end + + + + % creates signal_gen and BreachSignalGen + labels= setdiff(fieldnames(labels_ranges), {'all__'}); + sg = TA_signal_gen2(signals,TA_filename,labels, num_evts); + + + + this = this@BreachSignalGen({sg}); + + + % timescale parameter + if ~exist('time_scale', 'var')||isempty('time_scale') + time_scale =1; + end + + % some defaults for time steps, etc, may need adjusting + % later + this.SetParam('time_scale', time_scale); + sg.min_dt = time_scale/100; + dt = time_scale/200; + time = 0:dt:100*time_scale; + this.SetTime(time); + + % Adjust ranges. TODO: allow for enum and int... + for idx_label = 1:numel(labels) + label = labels{idx_label}; + + + for isig = 1:numel(signals) + sig = signals{isig}; + % Assign ranges for this specific label, if any + if isfield(labels_ranges.(label), sig) + range = labels_ranges.(label).(sig); + params = this.expand_param_name([sig '_' label '_val']); + if isscalar(range) % single value + this.SetParam(params, range); + else + this.SetParamRanges(params, range) + end + % assign range for all labels to this label + % otherwise + + elseif isfield(labels_ranges, 'all__')&&isfield(labels_ranges.all__, sig) + range = labels_ranges.all__.(sig); + params = this.expand_param_name([sig '_' label '_val']); + if isscalar(range) % single value + this.SetParam(params, range); + else + this.SetParamRanges(params, range) + end + end + + + end + end + + % 0,1 ranges for random parameters (branching and time for + % wordgen + pevts = this.expand_param_name('e.*_dt'); + pbranching = this.expand_param_name('e.*_branching'); + + this.SetParamRanges([pevts pbranching], [0 1]); + + + + + + + + + + end + + end + +end \ No newline at end of file diff --git a/Ext/Toolboxes/wordgen/HSCC_Paper_autotrans_time_duration_example_traces.m b/Ext/Toolboxes/wordgen/HSCC_Paper_autotrans_time_duration_example_traces.m new file mode 100644 index 00000000..0ab6c6c2 --- /dev/null +++ b/Ext/Toolboxes/wordgen/HSCC_Paper_autotrans_time_duration_example_traces.m @@ -0,0 +1,43 @@ +%% TA based signal generator setup +% +init_TA_signal_gen; + +S1 = S0.copy(); +S1.SetParamRanges([pevts pbranching], [0 1]); +pvar = S1.GetVariables(); + +S1.Sim(); +figure; +S1.PlotSignals(); + +mdl = 'Autotrans_shift'; +Ba = BreachSimulinkSystem(mdl); +Ba.SetTime(time); +Ba.SetInputGen(S1); +Ba.SetParamRanges([pevts pbranching], [0 1]); +Ba.SampleDomain(pvar,10); +Ba.Sys.Verbose=0; +%% +Ba.Sim(); + +%% Plotting +Ga = BreachSignalsPlot(Ba); + +%% +Ga.AddSignals('throttle', 1, 'all'); + +%% + +Ga.AddAxes +Ga.AddSignals('brake', 2, 'all'); + +Ga.AddAxes +Ga.AddSignals('speed', 3, 'all'); + +Ga.AddAxes +Ga.AddSignals('gear', 4, 'all'); +xlabel('Time') + +%save2pdf('Autotrans_TA_traces.pdf'); + +% alw (inSafeRegion[t] == 1) \ No newline at end of file diff --git a/Ext/Toolboxes/wordgen/NFM_Paper_autotrans_example_trace.m b/Ext/Toolboxes/wordgen/NFM_Paper_autotrans_example_trace.m new file mode 100644 index 00000000..0250d131 --- /dev/null +++ b/Ext/Toolboxes/wordgen/NFM_Paper_autotrans_example_trace.m @@ -0,0 +1,72 @@ +%% TA based signal generator setup +% +init_TA_signal_gen; + +S1 = S0.copy(); +S1.SetParamRanges([pevts pbranching], [0 1]); +pvar = S1.GetVariables(); + +S1.Sim(); +figure; +S1.PlotSignals(); + +mdl = 'Autotrans_wordgen'; +Ba = BreachSimulinkSystem(mdl); +Ba.SetTime(time); +Ba.SetInputGen(S1); +Ba.SetParamRanges([pevts pbranching], [0 1]); +Ba.SampleDomain(pvar,4); +Ba.Sim(); + +%% Plotting +Ga = BreachSignalsPlot(Ba); + +close all; +figure; +ax = subplot(4,1,1); +grid on; +Ga.AddSignals('throttle', ax, 'all'); + +ax = subplot(4,1,2); +grid on; +Ga.AddSignals('brake', ax, 'all'); + +ax = subplot(4,1,3); +grid on; +Ga.AddSignals('speed', ax, 'all'); + + +ax = subplot(4,1,4); +grid on; +Ga.AddSignals('gear', ax, 'all'); +xlabel('Time') + +save2pdf('Autotrans_TA_traces.pdf'); + +%% +%% Plotting +Ga = BreachSignalsPlot(Ba); + +it = 2; +figure; +ax = subplot(3,1,1); +grid on; +Ga.AddSignals('throttle', ax, it ); +set(gca, 'FontSize', 14, 'LineWidth',2); +set(get(gca, 'Children'), 'LineWidth',2) + +ax = subplot(3,1,2); +grid on; +Ga.AddSignals('brake', ax, it); +set(gca, 'FontSize', 14, 'LineWidth',2); +set(get(gca, 'Children'), 'LineWidth',2) + + +ax = subplot(3,1,3); +grid on; +Ga.AddSignals('timeword', ax, it); +xlabel('Time') +set(gca, 'FontSize', 14, 'LineWidth',2); +set(get(gca, 'Children'), 'LineWidth',2) + +save2pdf('Autotrans_Wordgen_Traces.pdf') \ No newline at end of file diff --git a/Ext/Toolboxes/wordgen/TA_param_gen.m b/Ext/Toolboxes/wordgen/TA_param_gen.m new file mode 100644 index 00000000..ff242340 --- /dev/null +++ b/Ext/Toolboxes/wordgen/TA_param_gen.m @@ -0,0 +1,99 @@ +classdef TA_param_gen < param_gen + properties + TA_file + num_events + wordgen_exe = './wordgen' + poly = 5 + template_in + template_out + val_map + p0_out + verbose=0 + min_dt = 1e-9 + end + + methods + function this= TA_param_gen(sig, TA_file, num_events) + this.TA_file = TA_file; + this.num_events = num_events; + this.params = repmat({'params_e'}, 1, num_events); + this.params_out = repmat({'params_dt'}, 1, num_events); + + this.val_map= containers.Map(); + + this.val_map('a') = 0; + this.val_map('b') = 1; + this.val_map('c') = 2; + this.val_map('d') = 3; + this.val_map('e') = 4; + + for ie = 0:num_events-1 + this.params{ie+1} = [sig '_e' num2str(ie)]; + this.params_out{2*ie+1} = [sig '_u' num2str(ie)]; + this.params_out{2*ie+2} = [sig '_dt' num2str(ie)]; + end + this.params{end+1} = 'time_scale'; + this.params{end+1} = 'alpha'; % linear transform of mapping + this.params{end+1} = 'beta'; + + this.params_out{2*num_events+1} = [sig '_u' num2str(num_events)]; + + this.domain = repmat(BreachDomain(),1,numel(this.params)); + this.domain_out = repmat(BreachDomain(),1,numel(this.params_out)); + + this.p0 = [.5*ones(num_events,1); ... + [1 .5 0]']; % time_scale, alpha, beta + this.template_out = [repmat('%g ', 1, num_events-1) '%g']; + + end + + function set_template_in(this, template_in) + [~,~,~, matches, tokens] = regexp(template_in, '\[(\w+)\]'); + this.p0_out = zeros(2*this.num_events+1,1); + this.template_in = template_in; + for im = 1:numel(matches) + this.p0_out(2*im-1,1) = this.val_map(tokens{im}{1}); + end + end + + function p_out = computeParams(this, p_in) + + exe = this.wordgen_exe; + in_file = this.TA_file; + num_pts = size(p_in,2); + + p_out = repmat(this.p0_out, 1, num_pts); + + for ipt = 1:num_pts + + time_scale = p_in(end-2,ipt); + alpha= p_in(end-1, ipt); + beta = p_in(end, ipt); + p = p_in(1:end-3, ipt); + p_out(1:2:end,ipt) = alpha*(p_out(1:2:end,ipt) +beta); + + cmd = [exe ' ' in_file ' --template "' sprintf(this.template_in, p) ... + '" --poly ' num2str(this.poly) ' --traj 1 -v 0 res.txt']; + if this.verbose + disp(cmd); + end + stat = system(cmd); + + if stat + warning('wordgen returned error, or incorrect command: %s', cmd); + p_out(2:2:end, ipt) = 1; + else + f= fopen('res.txt'); + dts = fscanf(f,this.template_out)*time_scale; + dts(dts==0) = this.min_dt; + p_out(2:2:end, ipt) = dts; + fclose(f); + end + + end + end + + end + + +end diff --git a/Ext/Toolboxes/wordgen/TA_signal_gen.m b/Ext/Toolboxes/wordgen/TA_signal_gen.m new file mode 100644 index 00000000..0cfd1274 --- /dev/null +++ b/Ext/Toolboxes/wordgen/TA_signal_gen.m @@ -0,0 +1,154 @@ +classdef TA_signal_gen < var_cp_signal_gen + properties + TA_file + num_evts + labels + wordgen_exe = './wordgen' + poly = 5 + template_in + template_out + params_cp + verbose=0 + min_dt = 1e-9 + end + + methods + function this= TA_signal_gen(sigs, TA_file, labels, num_evt, method) + + if ~exist('method', 'var') + method = 'previous'; + end + + if ~iscell(sigs) + sigs= {sigs}; + end + if ~iscell(labels) + labels= {labels}; + end + + this = this@var_cp_signal_gen(sigs, repmat(num_evt+2,1, numel(sigs)), method); + + this.params_cp = this.params; + this.TA_file = TA_file; + + + this.labels=labels; + + this.num_evts = num_evt; + this.params = repmat({'params_e'}, 1, num_evt); + + for ie = 0:num_evt-1 + this.params{ie+1} = ['d_e' num2str(ie)]; + end + this.params{end+1} = 'time_scale'; + + for i_l=1:numel(labels) + for i_sig=1:numel(sigs) + this.params{end+1} = [sigs{i_sig} '_' labels{i_l} '_val']; + end + end + + this.params = this.params; + + this.params_domain = repmat(BreachDomain(),1,numel(this.params)); + this.p0 = zeros(numel(this.params),1); + this.p0(1:num_evt,1) = 0.5; + + this.template_in = repmat('%g[0.5]', 1, num_evt); + this.template_out = [repmat('%g[%s] ', 1, num_evt-1) '%g[%s]']; + + end + + function X = computeSignals(this,p, time) % compute the signals + p_cp = computeParams(this, p); + X = computeSignals@var_cp_signal_gen(this, p_cp, time); + end + + function p_cp = computeParams(this, p_evt) + + exe = this.wordgen_exe; + in_file = this.TA_file; + num_pts = size(p_evt,2); + + p_cp = zeros(numel(this.params_cp),1); + + for ipt = 1:num_pts + + time_scale = p_evt(this.num_evts+1,ipt); + p = p_evt(1:this.num_evts, ipt); + + cmd = [exe ' ' in_file ' --template "' sprintf(this.template_in, p) ... + '" --poly ' num2str(this.poly) ' --traj 1 -v 0 --output-format timeword res.txt']; + if this.verbose + disp(cmd); + end + stat = system(cmd); + + if stat + warning('wordgen returned error, or incorrect command: %s', cmd); + p_cp(2:2:end, ipt) = 1; + else + fid= fopen('res.txt'); + tline = fgetl(fid); + tok = regexp(tline, '(.*?)\[(\w+)\] ', 'tokens'); + + for itok=1:numel(tok) + idx = find(strcmp(tok{itok}{2},this.labels), 1); + if isempty(idx) + error('Unknown label %s',tok{itok}{1}); + else + dt(itok) = str2num(tok{itok}{1})*time_scale; + idx_label(itok) = idx; + end + end + + idx = 1; + for isig = 1:numel(this.signals) % assign values from label to signals and control points + for i_tok = 1:this.num_cp(isig)-1 + if i_tok<= numel(dt) + p_cp(idx, ipt) = p_evt(this.num_evts... % events + +1 ... % time_scale + + (numel(this.signals)*(idx_label(i_tok)-1)+isig) ... + ,1) ; + p_cp(idx+1, ipt) = max(dt(i_tok), this.min_dt); + end + idx = idx+2; % jump over dt.. + end + if i_tok<= numel(dt) + p_cp(idx, ipt) = p_evt(this.num_evts+1+(numel(this.signals)*(idx_label(i_tok)-1)+isig),1) ; + end + idx = idx+1; + + end + + fclose(fid); + end + + end + end + + function type = getType(this) + type = 'varstep'; + end + + function args = getSignalGenArgs(this) + args = {'num_cp','TA_file','labels','method'}; + end + + end + + methods (Access=private) + + function i_evt = get_evt_idx(this) + i_evt = 1:this.num_evts; + end + + function i_labels = get_labels_idx(this) + i_labels= this.num_evts+1:this.num_evts+numel(this.signals)*numel(this.labels); + end + + + end + + +end diff --git a/Ext/Toolboxes/wordgen/TA_signal_gen2.m b/Ext/Toolboxes/wordgen/TA_signal_gen2.m new file mode 100644 index 00000000..aa911648 --- /dev/null +++ b/Ext/Toolboxes/wordgen/TA_signal_gen2.m @@ -0,0 +1,212 @@ +classdef TA_signal_gen2 < var_cp_signal_gen + properties + TA_file + num_evts + labels + wordgen_exe = './wordgen' + poly = 5 + expected_duration = 0 + template_in + template_out + params_cp + verbose=0 + min_dt = 1e-9 + end + + methods + function this= TA_signal_gen2(sigs, TA_file, labels, num_evt, method) + + if ~exist('method', 'var') + method = 'previous'; + end + + if ~iscell(sigs) + sigs= {sigs}; + end + if ~iscell(labels) + labels= {labels}; + end + + this = this@var_cp_signal_gen(sigs, repmat(num_evt+2,1, numel(sigs)), method); + + this.signals = [this.signals 'timeword']; + this.signals_domain(end+1) = BreachDomain('enum',[], 1:numel(labels)); + + this.params_cp = this.params; + this.TA_file = TA_file; + + if ispc + this.wordgen_exe = which('wordgen.exe'); + else + this.wordgen_exe = which('wordgen'); + end + + if isempty(this.wordgen_exe) + warning('wordgen executable not found.'); + end + + this.labels=labels; + + this.num_evts = num_evt; + this.params = {'time_scale'}; + + for ie = 0:num_evt-1 + this.params{end+1} = ['e' num2str(ie) '_dt']; + this.params{end+1} = ['e' num2str(ie) '_branching' ]; + for i_l=1:numel(labels) + for i_sig=1:numel(sigs) + this.params{end+1} = ['e' num2str(ie) '_' sigs{i_sig} '_' labels{i_l} '_val']; + end + end + end + + this.params = this.params; + this.params_domain = repmat(BreachDomain(),1,numel(this.params)); + this.p0 = zeros(numel(this.params),1); + this.p0(1) = 1; % timescale + idx_dt_branching = this.get_idx_dt_branching(); + this.p0(idx_dt_branching) = 0.5; + + this.template_in = repmat('%g[%g]', 1, num_evt); + this.template_out = [repmat('%g[%s] ', 1, num_evt-1) '%g[%s]']; + + end + + function [X,time] = computeSignals(this,p, time) % compute the signals + [p_cp, dts, labels_idx] = computeParams(this, p); + X = computeSignals@var_cp_signal_gen(this, p_cp, time); + dts(dts 0 + cmd_template = [cmd_template ... + '--expected-duration' num2str(this.expected_duration)]; + end + + cmd_template = [cmd_template ... + ' -v ' num2str(this.verbose)]; + + if ispc % for some reason caml cannot write cache file on windows + cmd_template = [cmd_template ... + ' --no-cache ']; + end + + cmd_template = [cmd_template ... + ' --traj 1 --exact-rational --output-format timeword res.txt']; + + + for ipt = 1:num_pts + + time_scale = p(1,ipt); + idx_dt_branching = this.get_idx_dt_branching(); + p_wordgen = p(idx_dt_branching,ipt); + + word_template = sprintf(this.template_in, p_wordgen); % form wordgen template for this run + + cmd = sprintf(cmd_template, word_template); + + +% cmd = [exe ' ' in_file ' --template "' sprintf(this.template_in, p_wordgen) ... +% '" --poly ' num2str(this.poly) ... +% ' --traj 1 --exact-rational --output-format timeword res.txt']; + + if this.verbose + disp(cmd); + end + stat = system(cmd); + + if stat + warning('wordgen returned error, or incorrect command: %s', cmd); + p_cp(2:2:end, ipt) = 1; + else + fid= fopen('res.txt'); + tline = fgetl(fid); + fclose(fid); + tok = regexp(tline, '(.*?)\[(\w+)\] ', 'tokens'); + + for itok=1:numel(tok) + idx = find(strcmp(tok{itok}{2},this.labels), 1); + if isempty(idx) + error('Unknown label %s',tok{itok}{2}); + else + dts(itok) = str2num(tok{itok}{1})*time_scale; + labels_idx(itok) = idx; + end + end + %fprintf('%g \n', sum(dts)/time_scale); + + idx = 1; + for isig = 1:numel(this.signals)-1 % assign values from label to signals and control points + for i_tok = 1:this.num_cp(isig)-1 + if i_tok<= numel(dts) + ilabel = labels_idx(i_tok); + iv = this.get_cp_idx(i_tok, isig, ilabel); + p_cp(idx, ipt) = p(iv); + p_cp(idx+1, ipt) = max(dts(i_tok), this.min_dt); + time_durations(idx) = max(dts(i_tok), this.min_dt); + end + idx = idx+2; % jump over dt.. + end + if i_tok<= numel(dts) + p_cp(idx, ipt) = p(this.num_evts+1+(numel(this.signals)*(labels_idx(i_tok)-1)+isig),1) ; + end + idx = idx+1; + end + + end + + end + end + + function type = getType(this) + type = 'varstep'; + end + + function args = getSignalGenArgs(this) + args = {'num_cp','TA_file','labels','method'}; + end + + end + + methods (Access=private) + + function idx = get_idx_dt_branching(this) + num_param_per_evt = 2+(numel(this.signals)-1)*numel(this.labels); + idx_dt = [0 rem((1:numel(this.params)-1)-1,num_param_per_evt)==0]; + idx_branching = [0 rem((1:numel(this.params)-1)-1,num_param_per_evt)==1]; + idx= idx_dt|idx_branching; + end + + function iv = get_cp_idx(this, ie, isig, ilabel) + num_param_per_evt = 2+(numel(this.signals)-1)*numel(this.labels); + iv = 1+... % timescale + (ie-1)*num_param_per_evt+... % num events + 2+... % dt,branching + (ilabel-1)*(numel(this.signals)-1)+... % skips labels + isig; + end + + function i_labels = get_labels_idx(this) + i_labels= this.num_evts+1:this.num_evts+numel(this.signals)*numel(this.labels); + end + + + end + + +end diff --git a/Ext/Toolboxes/wordgen/driving_HSCC21_v1.pcmp b/Ext/Toolboxes/wordgen/driving_HSCC21_v1.pcmp new file mode 100644 index 00000000..d427b21c Binary files /dev/null and b/Ext/Toolboxes/wordgen/driving_HSCC21_v1.pcmp differ diff --git a/Ext/Toolboxes/wordgen/driving_HSCC21_v1.prism b/Ext/Toolboxes/wordgen/driving_HSCC21_v1.prism new file mode 100644 index 00000000..25527194 --- /dev/null +++ b/Ext/Toolboxes/wordgen/driving_HSCC21_v1.prism @@ -0,0 +1,19 @@ +pta +module m +s : [0..3] init 0; +x : clock; +y : clock; + +//Coasting to see, save fuel +[a] (s=0) & (y>2) -> (s'=3) & (x'=0) & (y'=0); //acceleration to coasting 00 +[b] (s=3) & (y>1) -> (s'=2) & (x'=0) & (y'=0); //coasting to brake 01 +[c] (s=3) & (y>1) -> (s'=0) & (x'=0) & (y'=0); //coasting to acceleration 10 + +//Braking cycle +[d] (s=0) & (y>2) -> (s'=2) & (x'=0) & (y'=0); //acceleration to braking 01 +[e] (s=1) & (y>1) & (y<2) -> (s'=2) & (y'=0); //coasting to brake 01 +[f] (s=2) & (y>1) & (y<2) -> (s'=1) & (y'=0); //brake to coasting 00 +[g] (s=2) & (y>1) & (x>3) -> (s'=0) & (x'=0) & (y'=0); //brake to acceleration 10 +[h] (s=1) & (y>1) & (x>3) -> (s'=0) & (x'=0) & (y'=0); //coasting to acceleration 10 + +endmodule diff --git a/Ext/Toolboxes/wordgen/init_TA_signal_gen.m b/Ext/Toolboxes/wordgen/init_TA_signal_gen.m new file mode 100644 index 00000000..56d74ff8 --- /dev/null +++ b/Ext/Toolboxes/wordgen/init_TA_signal_gen.m @@ -0,0 +1,83 @@ +InitBreach + +%% TA based signal generator setup +num_evt = 10; +%sg2 = TA_signal_gen2({'throttle', 'brake'},'driving_NFM_v2.prism',{'a','b','c','d','e','f','g','h'}, num_evt); +sg2 = TA_signal_gen2({'throttle', 'brake'},'driving_HSCC21_v1.prism',{'a','b','c','d','e','f','g','h'}, num_evt); +sg2.verbose = true; +S0 = BreachSignalGen(sg2); +ts = 1; +time = 0:.005:100; +sg2.min_dt = .01; + +S0.SetParam('time_scale',ts); +S0.SetTime(time); + +%% Assign values +p_throttle_init = S0.expand_param_name('.*throttle_init_val'); +p_brake_init = S0.expand_param_name('.*brake_init_val'); + +p_throttle_a = S0.expand_param_name('.*throttle_a_val'); +p_brake_a = S0.expand_param_name('.*brake_a_val'); + +p_throttle_b = S0.expand_param_name('.*throttle_b_val'); +p_brake_b = S0.expand_param_name('.*brake_b_val'); + +p_throttle_c = S0.expand_param_name('.*throttle_c_val'); +p_brake_c = S0.expand_param_name('.*brake_c_val'); + +p_throttle_d = S0.expand_param_name('.*throttle_d_val'); +p_brake_d = S0.expand_param_name('.*brake_d_val'); + +p_throttle_e = S0.expand_param_name('.*throttle_e_val'); +p_brake_e = S0.expand_param_name('.*brake_e_val'); + +p_throttle_f = S0.expand_param_name('.*throttle_f_val'); +p_brake_f = S0.expand_param_name('.*brake_f_val'); + +p_throttle_g = S0.expand_param_name('.*throttle_g_val'); +p_brake_g = S0.expand_param_name('.*brake_g_val'); + +p_throttle_h = S0.expand_param_name('.*throttle_h_val'); +p_brake_h = S0.expand_param_name('.*brake_h_val'); + + +%% +acc_range = [0 100]; +brake_range = [100 325]; + +%% +S0.SetParamRanges(p_throttle_init, acc_range); +S0.SetParam(p_brake_init, 0); + +%% Ranges for label a +S0.SetParam(p_throttle_a, 0); +S0.SetParam(p_brake_a, 0); + +%% Ranges for label b +S0.SetParam(p_throttle_b, 0); +S0.SetParamRanges(p_brake_b, brake_range); + +%% +S0.SetParamRanges(p_throttle_c, acc_range); +S0.SetParam(p_brake_c, 0); + +S0.SetParam(p_throttle_d, 0); +S0.SetParamRanges(p_brake_d, brake_range); + +S0.SetParam(p_throttle_e, 0); +S0.SetParamRanges(p_brake_e, brake_range); + +S0.SetParam(p_throttle_f, 0); +S0.SetParam(p_brake_f, 0); + +S0.SetParamRanges(p_throttle_g, acc_range); +S0.SetParam(p_brake_g, 0); + +S0.SetParamRanges(p_throttle_h, acc_range); +S0.SetParam(p_brake_h, 0); + +pvar = S0.GetVariables(); +pevts = S0.expand_param_name('e.*_dt'); +pbranching = S0.expand_param_name('e.*_branching'); + diff --git a/Ext/Toolboxes/wordgen/test_TA_signal_gen.m b/Ext/Toolboxes/wordgen/test_TA_signal_gen.m new file mode 100644 index 00000000..8c3e84b4 --- /dev/null +++ b/Ext/Toolboxes/wordgen/test_TA_signal_gen.m @@ -0,0 +1,36 @@ +%% TA based signal generator setup +% +init_TA_signal_gen; + +S1 = S0.copy(); +S1.SetParamRanges([pevts pbranching], [0 1]); +pvar = S1.GetVariables(); +S1.SampleDomain(pvar,100); + +S1.Sim(); +figure; +S1.PlotSignals(); + +%% Checking reachable labels +STL_ReadFile('Autotrans_spec.stl'); +Rreach_labels = BreachRequirement({req_a, req_b, req_c, req_d, req_e, req_f, req_g, req_h}); +Rreach_labels.Eval(phi1); +BreachSamplesPlot(Rreach_labels); + +%% +S2 = S0.copy(); +mdl = 'Autotrans_wordgen'; +Ba = BreachSimulinkSystem(mdl); +Ba.SetTime(time); +Ba.SetInputGen(S2); +Ba.SetParamRanges([pevts pbranching], [0 1]); +Ba.SampleDomain(pvar,10); +Ba.Sim(); + + +Rreach_labels = BreachRequirement({req_a, req_b, req_c, req_d, req_e, req_f, req_g, req_h}); +Rreach_labels.Eval(Ba) + +%% +BreachSamplesPlot(Rreach_labels); + diff --git a/Ext/Toolboxes/wordgen/test_TA_signal_gen_corners.m b/Ext/Toolboxes/wordgen/test_TA_signal_gen_corners.m new file mode 100644 index 00000000..eac53b12 --- /dev/null +++ b/Ext/Toolboxes/wordgen/test_TA_signal_gen_corners.m @@ -0,0 +1,13 @@ +%% testing corners with events +% +num_evt = 5; +init_TA_signal_gen; + +S1 = S0.copy(); +S1.SetParamRanges([pevts pbranching], [0.01 0.99]); +pvar = S1.GetVariables(); +S1.CornerSample(2); + +S1.Sim(); +figure; +S1.PlotSignals(); diff --git a/Ext/Toolboxes/wordgen/test_duration_TA_signal_gen.m b/Ext/Toolboxes/wordgen/test_duration_TA_signal_gen.m new file mode 100644 index 00000000..9124b39a --- /dev/null +++ b/Ext/Toolboxes/wordgen/test_duration_TA_signal_gen.m @@ -0,0 +1,20 @@ +%% TA based signal generator setup +% +init_TA_signal_gen; + +S1 = S0.copy(); +S1.SetParamRanges([pevts pbranching], [0 1]); +pvar = S1.GetVariables(); +%S1.SampleDomain(pvar,100); + +S1.Sim(); +figure; +S1.PlotSignals(); + +%% Checking time duration + +dt_evt = S1.GetParam(pevts); + +%% +T_evt = sum(dt_evt, 1); +mean(T_evt) \ No newline at end of file diff --git a/Ext/Toolboxes/wordgen/wordgen b/Ext/Toolboxes/wordgen/wordgen new file mode 100755 index 00000000..e1bfc0bf Binary files /dev/null and b/Ext/Toolboxes/wordgen/wordgen differ diff --git a/InitBreach.m b/InitBreach.m index 5c937502..f35a62be 100644 --- a/InitBreach.m +++ b/InitBreach.m @@ -115,6 +115,8 @@ function InitBreach(br_dir, force_init, varargin) [br_dir filesep 'Ext' filesep 'Toolboxes' filesep 'YAMLmatlab' filesep 'extras'], ... [br_dir filesep 'Ext' filesep 'Toolboxes' filesep 'snobfit'], ... [br_dir filesep 'Ext' filesep 'Toolboxes' filesep 'minq'], ... + [br_dir filesep 'Ext' filesep 'Toolboxes' filesep 'wordgen'], ... + [br_dir filesep 'Ext' filesep 'Toolboxes' filesep 'turbo'], ... }; addpath(list_path{:}); diff --git a/Params/HaltonRefine.m b/Params/HaltonRefine.m index 967da346..28d533f4 100644 --- a/Params/HaltonRefine.m +++ b/Params/HaltonRefine.m @@ -36,7 +36,7 @@ %See also QuasiRefine Refine RandomLogRefine LogNRefine % -if(nb<=1) +if(nb<1) return; end diff --git a/Params/Pcheck_traj_ref.m b/Params/Pcheck_traj_ref.m index a33cedf5..428043ed 100644 --- a/Params/Pcheck_traj_ref.m +++ b/Params/Pcheck_traj_ref.m @@ -1,9 +1,9 @@ function P = Pcheck_traj_ref(P) -% Pcheck_traj_ref(P) checks traj_ref consistency with +% Pcheck_traj_ref(P) checks traj_ref consistency if isfield(P,'traj') pts_row = P.pts(1:P.DimP,:)'; - for ir = 1:numel(P.traj_ref); + for ir = 1:numel(P.traj_ref) if ~isequal(pts_row(ir,:), P.traj{P.traj_ref(ir)}.param) warning('inconsistent traj_ref'); end diff --git a/Params/Preset_traj_ref.m b/Params/Preset_traj_ref.m index e7b6f2c1..faf9b6c5 100644 --- a/Params/Preset_traj_ref.m +++ b/Params/Preset_traj_ref.m @@ -31,6 +31,7 @@ num_traj=0; end + pts_indices_to_compute = find(~logical(P.traj_ref)); if isempty(pts_indices_to_compute) P.traj_to_compute = []; diff --git a/Params/QuasiRefine.m b/Params/QuasiRefine.m index d558b8a1..e5220c42 100644 --- a/Params/QuasiRefine.m +++ b/Params/QuasiRefine.m @@ -37,7 +37,7 @@ % % process inputs -if(nb<=1) +if(nb<1) return; end nb = floor(nb); diff --git a/Plots/BreachSamplesPlot.m b/Plots/BreachSamplesPlot.m index 7ac2e609..f2ea8292 100644 --- a/Plots/BreachSamplesPlot.m +++ b/Plots/BreachSamplesPlot.m @@ -179,7 +179,7 @@ function update_data(this) vals_cum_pos = vals_pos>=0; num_req = size(vals_pos,1); - for ir = 1:num_req % for each requirequirement + for ir = 1:num_req % for each requirement ifalse = find(vals_pos(ir,:)==0,1); if ifalse vals_cum_pos(ir, ifalse:end)=0; diff --git a/Plots/BreachSignalsPlot.m b/Plots/BreachSignalsPlot.m index c5a28fb4..6b478b10 100644 --- a/Plots/BreachSignalsPlot.m +++ b/Plots/BreachSignalsPlot.m @@ -5,11 +5,12 @@ Fig Axes Summary + annot end properties(SetAccess=protected, GetAccess=public) ipts - zero_rob_line_name = 'zero robustness line' + zero_rob_line_name = 'zero robustness line' end methods @@ -132,6 +133,19 @@ function DeleteAxes(this, pos) end end + function ClearAxes(this,pos) + if nargin<2 + pos=1; + end + ax= this.Axes.ax(pos); + cla(ax); + axes(ax); + this.Axes(pos).signals = {}; + title(''); + legend off; + end + + function AddSignals(this,sigs,ax,itraces) if ischar(sigs) sigs = {sigs}; @@ -241,7 +255,33 @@ function PlotDiagnostics(this, req) this.update_legend(ax); end - + + function set_disp_param(this,params) + this.annot.show_params = params; + this.update_annot() + end + + function update_annot(this) + if isstruct(this.annot)&&~isempty(this.Axes) + ax = this.Axes(1).ax; + l = legend(ax); + if isfield(this.annot,'show_params')&&~isempty(this.annot.show_params) + params = this.annot.show_params; + values = this.BrSet.GetParam(params, this.ipts); + sta = list_manip.to_string_values(params, values); + pos = get(l,'Position'); + posa = pos + [0 -pos(4) 0 0]; + if ~isfield(this.annot,'params_textbox')||isempty(this.annot.params_textbox) + this.annot.params_textbox = annotation('textbox',posa, 'String', sprintf(sta), 'FitBoxToText', 'on', 'Interpreter', 'None', 'EdgeColor',[1 1 1]); + else + delete(this.annot.params_textbox); + this.annot.params_textbox = annotation('textbox',posa, 'String', sprintf(sta), 'FitBoxToText', 'on', 'Interpreter', 'None', 'EdgeColor',[1 1 1]); + end + + end + end + end + function update_legend(this, ax) l = legend('-DynamicLegend'); c = flipud(get(ax, 'Children')); @@ -275,7 +315,8 @@ function update_legend(this, ax) st{end+1} = lst; end end - + + end if num_patch>0 @@ -326,8 +367,9 @@ function update_axes(this, ax) end function set_ipts(this,ipts) - this.ipts= ipts; + this.ipts= ipts; this.update_axes(); + this.update_annot(); this.update_title(); end @@ -338,6 +380,7 @@ function next_ipts(this, num) end this.ipts = min(this.ipts+num, this.Summary.num_traces); this.update_axes(); + this.update_annot(); this.update_title(); end @@ -347,6 +390,7 @@ function prev_ipts(this,num) end this.ipts = max(1,this.ipts-num); this.update_axes(); + this.update_annot(); this.update_title(); end @@ -463,7 +507,7 @@ function update_title(this) uimenu(cm, 'Label', 'Add axes below', 'Callback', @(o,e)ctxtfn_add_axes_below(ax, o,e)); uimenu(cm, 'Label', 'Reset axes','Separator', 'on', 'Callback', @(o,e)ctxtfn_reset_axes(ax, o,e)); uimenu(cm, 'Label', 'Delete axes', 'Callback', @(o,e)ctxtfn_delete_axes(ax, o,e)); - + function ctxtfn_add_axes_above(ax, ~,~) for ia = 1:numel(this.Axes) if isequal(ax,this.Axes(ia).ax) @@ -525,7 +569,7 @@ function get_mnu(this,ax,mnu_parent, phi,ir,o,e) i0 = 1; end - [st_phis, phi_ids, not_expanded] =tree_disp(phi,0,2); + [st_phis, ~, not_expanded] =tree_disp(phi,0,2); for i = i0:numel(st_phis) phi_str = strtrim(st_phis{i}); @@ -612,60 +656,65 @@ function do_nothing() l = []; return end - + pos = this.get_pos_from_ax(ax); - - this.Axes(pos).signals = union(this.Axes(pos).signals, sig); + + this.Axes(pos).signals = union(this.Axes(pos).signals, sig); if ~exist('ipts', 'var') ipts= this.ipts; end - + axes(ax); hold on; - itraj = unique(this.BrSet.P.traj_ref(ipts), 'stable'); - + itraj_list = unique(this.BrSet.P.traj_ref(ipts), 'stable'); + if ~iscell(sig) sig= {sig}; - end - time = this.BrSet.P.traj{itraj}.time; - ch = get(ax,'Children'); - if isempty(ch) - set(ax, 'XLimMode', 'auto', 'YLimMode', 'auto'); end - - for s = sig - % find out if it's a robustness signal or not - if isa(this.BrSet,'BreachRequirement') - [~, b, ~, bb]= this.BrSet.FindSignalsIdx(s{1}); - else - b=true; + + + for itraj=itraj_list + time = this.BrSet.P.traj{itraj}.time; + ch = get(ax,'Children'); + if isempty(ch) + set(ax, 'XLimMode', 'auto', 'YLimMode', 'auto'); end - if b||bb - sig_values = this.BrSet.GetSignalValues(s{1}, itraj); - l = this.get_line_from_signal_name(ax, s{1}); - if isempty(l) - l = plot(time , sig_values, 'DisplayName', s{1}); + + for s = sig + % find out if it's a robustness signal or not + if isa(this.BrSet,'BreachRequirement') + [~, b, ~, bb]= this.BrSet.FindSignalsIdx(s{1}); else - set(l, 'XData',time,'YData',sig_values); + b=true; end - - else % try robustnes... should be obsolete - warning('Not sure what I am doing here, plot_signal but trying robustness signal ?'); - l = this.get_line_from_signal_name(ax, s{1}); - [t,r]= this.BrSet.GetRobustSat(1, itraj, s{1}); - if isempty(l) - l = plot(t, r, 'DisplayName', s{1}); - else - set(l,'XData',t,'YData',r); - end - if isempty(this.get_line_from_signal_name(ax,this.zero_rob_line_name)) - plot(time, 0*time, 'DisplayName', this.zero_rob_line_name, 'LineStyle', '--', 'Color','r'); + if b||bb + sig_values = this.BrSet.GetSignalValues(s{1}, itraj); + l = this.get_line_from_signal_name(ax, s{1}); + if isempty(l) + l = plot(time , sig_values, 'DisplayName', s{1}); + else + set(l, 'XData',time,'YData',sig_values); + end + + else % try robustnes... should be obsolete + warning('Not sure what I am doing here, plot_signal but trying robustness signal ?'); + l = this.get_line_from_signal_name(ax, s{1}); + [t,r]= this.BrSet.GetRobustSat(1, itraj, s{1}); + if isempty(l) + l = plot(t, r, 'DisplayName', s{1}); + else + set(l,'XData',t,'YData',r); + end + if isempty(this.get_line_from_signal_name(ax,this.zero_rob_line_name)) + plot(time, 0*time, 'DisplayName', this.zero_rob_line_name, 'LineStyle', '--', 'Color','r'); + end + end - end end - + end end -end \ No newline at end of file +end + diff --git a/Plots/SplotBoxPts.m b/Plots/SplotBoxPts.m index 5d54eb5e..41d42d71 100644 --- a/Plots/SplotBoxPts.m +++ b/Plots/SplotBoxPts.m @@ -37,6 +37,7 @@ function SplotBoxPts(P, proj, ipts, opt, col, alph) if ~isnumeric(proj) proj = FindParam(P,proj); end + proj = proj(proj>0); proj = proj(proj<=size(P.pts,1)); if isempty(proj) diff --git a/Plots/m_src/fig_resize.m b/Plots/m_src/fig_resize.m index 44fdd275..f17dafc0 100644 --- a/Plots/m_src/fig_resize.m +++ b/Plots/m_src/fig_resize.m @@ -20,6 +20,10 @@ function fig_resize(h, x_scal, y_scal) % fig_resize(gcf, 2, 0.5) % +if nargin==2 + y_scal=x_scal; +end + old_pos = get(h,'Position'); %pos([1 2]) = [0 0]; pos = old_pos; diff --git a/Plots/m_src/flatbar3.m b/Plots/m_src/flatbar3.m new file mode 100644 index 00000000..c0d4d7fd --- /dev/null +++ b/Plots/m_src/flatbar3.m @@ -0,0 +1,42 @@ +function flatbar3(X,Y,Z,widthx,widthy) + +[n_y, n_x]=size(Z); + +if isscalar(widthx) + widthx = ones(1,n_x)*widthx; +end + +if isscalar(widthy) + widthy = ones(1,n_y)*widthy; +end + +for j=1:n_y + for k=1:n_x + if ~isnan(Z(j,k)) + drawbar(X(j,k),Y(j,k),Z(j,k),widthx(k)/2,widthy(j)/2) + end + end +end + +zlim=[min(Z(:)) max(Z(:))]; +if zlim(1)>0 + zlim(1)=0; +end +if zlim(2)<0 + zlim(2)=0; +end + +axis([min(X(:))-widthx(1) max(X(:))+widthx(end) min(Y(:))-widthy(1) max(Y(:))+widthy(end) zlim]) +Zmin = min(Z(:)); +Zmax = max(Z(:)); +if Zmin==Zmax + Zmax = Zmin+min(abs(Zmax),1)/1e3; +end +caxis([Zmin Zmax]); + +end + +function drawbar(x,y,z,widthx,widthy) +h(1)=patch([-widthx -widthx widthx widthx]+x,[-widthy widthy widthy -widthy]+y,[0 0 0 0],'b'); +set(h,'facecolor','flat','FaceVertexCData',z) +end \ No newline at end of file diff --git a/Plots/m_src/scatterbar3.m b/Plots/m_src/scatterbar3.m new file mode 100644 index 00000000..02ae9820 --- /dev/null +++ b/Plots/m_src/scatterbar3.m @@ -0,0 +1,42 @@ +function scatterbar3(X,Y,Z,widthx,widthy) + +[n_y, n_x]=size(Z); + +if isscalar(widthx) + widthx = ones(1,n_x)*widthx; +end + +if isscalar(widthy) + widthy = ones(1,n_y)*widthy; +end + +for j=1:n_y + for k=1:n_x + if ~isnan(Z(j,k)) + drawbar(X(j,k),Y(j,k),Z(j,k),widthx(k)/2,widthy(j)/2) + end + end +end + +zlim=[min(Z(:)) max(Z(:))]; +if zlim(1)>0 + zlim(1)=0; +end +if zlim(2)<0 + zlim(2)=0; +end + +axis([min(X(:))-widthx(1) max(X(:))+widthx(end) min(Y(:))-widthy(1) max(Y(:))+widthy(end) zlim]) +caxis([min(Z(:)) max(Z(:))]) + +end + +function drawbar(x,y,z,widthx,widthy) +h(1)=patch([-widthx -widthx widthx widthx]+x,[-widthy widthy widthy -widthy]+y,[0 0 0 0],'b'); +h(2)=patch(widthx.*[-1 -1 1 1]+x,widthy.*[-1 -1 -1 -1]+y,z.*[0 1 1 0],'b'); +h(3)=patch(widthx.*[-1 -1 -1 -1]+x,widthy.*[-1 -1 1 1]+y,z.*[0 1 1 0],'b'); +h(4)=patch([-widthx -widthx widthx widthx]+x,[-widthy widthy widthy -widthy]+y,[z z z z],'b'); +h(5)=patch(widthx.*[-1 -1 1 1]+x,widthy.*[1 1 1 1]+y,z.*[0 1 1 0],'b'); +h(6)=patch(widthx.*[1 1 1 1]+x,widthy.*[-1 -1 1 1]+y,z.*[0 1 1 0],'b'); +set(h,'facecolor','flat','FaceVertexCData',z) +end \ No newline at end of file diff --git a/README.md b/README.md index 62a1613f..b30e96d5 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,10 @@ - [Going further](#going-further-3) - [Interfacing a generic simulator](#interfacing-a-generic-simulator) -[link](#section) - # Introduction Breach is a Matlab toolbox for time series analysis and simulation-based analysis of dynamical/CPS/Hybrid systems. It can be useful to the prospective user (you) in the following situations: + - You have **time series data** and wants to check whether it satisfies some property - You need **signal temporal logic (STL) monitoring capability**, e.g., to check formal requirements on your data - You have a **Simulink models** and wants to perform **extensive testing** by running multiple simulations (e.g., parameter sweep) and quickly browse through the results, and/or assert whether some (STL) property is satisfied by simulations (random/Monte-Carlo testing) diff --git a/VERSION b/VERSION index 9ab8337f..ed21137e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.1 +1.10.0 \ No newline at end of file