From e9b01aa45f14f85654fa938a552f8bb72ca9229f Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Thu, 14 Aug 2025 12:33:14 +0100 Subject: [PATCH 1/2] Ensures the plot event always calculates SLD --- API/events/eventManager.m | 11 +++++++ API/events/hasPlotHandler.m | 32 ++++++++++++++++++++ API/events/triggerEvent.m | 5 ---- cppDeploy.m | 4 +-- minimisers/DE/deopt.m | 10 +++++-- minimisers/simplex/fMinSearch.m | 52 ++++++++++++++++++++++----------- 6 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 API/events/hasPlotHandler.m diff --git a/API/events/eventManager.m b/API/events/eventManager.m index 28155865b..37243a033 100644 --- a/API/events/eventManager.m +++ b/API/events/eventManager.m @@ -26,6 +26,8 @@ function setEvents(value) persistent events if isempty(events) + % Events are stored in a 3 column cell containing the ID, + % event type and callback for the event. events = cell(0, 3); end @@ -36,6 +38,15 @@ function setEvents(value) value = events; end + function state=hasEvent(event_type) + + state = false; + events = eventManager.getEvents(); + if any(find([events{:, 2}] == event_type, 1)) + state = true; + end + end + function funcID = getCallbackID(callback) % Generates a id for a callback handle function % diff --git a/API/events/hasPlotHandler.m b/API/events/hasPlotHandler.m new file mode 100644 index 000000000..10b56fa9d --- /dev/null +++ b/API/events/hasPlotHandler.m @@ -0,0 +1,32 @@ +function state = hasPlotHandler() + % Checks if a function is listening for the plot event + % + % state = hasPlotHandler(); + persistent helper; + + initialised = false; + state = false; + coder.extrinsic('eventManager.hasEvent') + if coder.target('MATLAB') || coder.target('MEX') + if eventManager.hasEvent(coderEnums.eventTypes.Plot) + state = true; + end + else + coder.cinclude('eventHelper.hpp'); + coder.updateBuildInfo('addLinkFlags','-ldl'); + if isempty(helper) + % Declaration for coder + helper = coder.opaque('eventHelper','NULL','HeaderFile','eventHelper.hpp'); + + % Make an instance + helper = coder.ceval('eventHelper'); + path = [getenv('RAT_PATH'), 0]; + coder.ceval('std::mem_fn(&eventHelper::init)', helper, path); + end + + initialised = coder.ceval('std::mem_fn(&eventHelper::isInitialised)', helper); + if initialised + state = coder.ceval('std::mem_fn(&eventHelper::hasPlotHandler)', helper); + end + end +end diff --git a/API/events/triggerEvent.m b/API/events/triggerEvent.m index 4e458be4a..7fe8ecd49 100644 --- a/API/events/triggerEvent.m +++ b/API/events/triggerEvent.m @@ -60,11 +60,6 @@ function triggerEvent(eventType, varargin) elseif eventType == coderEnums.eventTypes.Progress coder.ceval('std::mem_fn(&eventHelper::updateProgress)', helper, [varargin{1},0], varargin{2}); elseif eventType == coderEnums.eventTypes.Plot - hasPlotHandler = coder.ceval('std::mem_fn(&eventHelper::hasPlotHandler)', helper); - if ~hasPlotHandler - return; - end - result = varargin{1}; problemStruct = varargin{2}; subRoughs = result.contrastParams.subRoughs; diff --git a/cppDeploy.m b/cppDeploy.m index acaf49d54..25fa0678d 100644 --- a/cppDeploy.m +++ b/cppDeploy.m @@ -12,5 +12,5 @@ % Clean up delete 'compile/fullCompile/deploy.zip' 'compile/fullCompile/cppDeploy/buildInfo.mat'... - 'compile/fullCompile/cppDeploy/rtw_proj.tmw' 'compile/fullCompile/cppDeploy/defines.txt' - 'compile/fullCompile/cppDeploy/RATMain.a'; + 'compile/fullCompile/cppDeploy/rtw_proj.tmw' 'compile/fullCompile/cppDeploy/defines.txt'... + 'compile/fullCompile/cppDeploy/RATMain.a' 'compile/fullCompile/cppDeploy/RATMain.lib'; diff --git a/minimisers/DE/deopt.m b/minimisers/DE/deopt.m index a49e9e708..026cd8a34 100644 --- a/minimisers/DE/deopt.m +++ b/minimisers/DE/deopt.m @@ -112,6 +112,8 @@ I_refresh = S_struct.I_refresh; I_plotting = S_struct.I_plotting; +doPlotEvent = hasPlotHandler(); + %-----Check input variables--------------------------------------------- if (I_NP < 5) I_NP=5; @@ -343,8 +345,10 @@ end % Trigger the output event... - if rem(I_iter, controls.updatePlotFreq) == 0 + if doPlotEvent && rem(I_iter, controls.updatePlotFreq) == 0 + controls.calcSLD = true; [~,result] = fname(FVr_bestmem,problem,controls); + controls.calcSLD = false; triggerEvent(coderEnums.eventTypes.Plot, result, problem); end @@ -366,9 +370,11 @@ triggerEvent(coderEnums.eventTypes.Message, ... sprintf('Iteration: %g, Best: %f, fWeight: %f, F_CR: %f, I_NP: %g\n\n', I_iter-1,S_bestval.FVr_oa(1),fWeight,F_CR,I_NP)); end -if rem(I_iter-1, controls.updatePlotFreq) ~= 0 +if doPlotEvent && rem(I_iter-1, controls.updatePlotFreq) ~= 0 % This should ensure the final result is always plotted irrespective of update frequency + controls.calcSLD = true; [~,result] = fname(FVr_bestmem,problem,controls); + controls.calcSLD = false; triggerEvent(coderEnums.eventTypes.Plot, result, problem); end end diff --git a/minimisers/simplex/fMinSearch.m b/minimisers/simplex/fMinSearch.m index 2df5cd6ae..19cc93e67 100644 --- a/minimisers/simplex/fMinSearch.m +++ b/minimisers/simplex/fMinSearch.m @@ -63,7 +63,7 @@ tolf = optimget(options,'TolFun',defaultopt,'fast'); maxfun = optimget(options,'MaxFunEvals',defaultopt,'fast'); maxiter = optimget(options,'MaxIter',defaultopt,'fast'); -funValCheck = strcmp(optimget(options,'FunValCheck',defaultopt,'fast'),'on'); +% funValCheck = strcmp(optimget(options,'FunValCheck',defaultopt,'fast'),'on'); switch dis % Changed from TMW fminsearch case {'notify','notify-detailed'} @@ -85,9 +85,11 @@ % Convert to function handle as needed. % funfcn = fcnchk(funfcn,length(varargin)); % Add a wrapper function to check for Inf/NaN/complex values -controls = varargin{2}; problemStruct = varargin{1}; -if funValCheck +controls = varargin{2}; +params = varargin{3}; +doPlotEvent = hasPlotHandler(); +% if funValCheck % Add a wrapper function, CHECKFUN, to check for NaN/complex values without % having to change the calls that look like this: % f = funfcn(x,varargin{:}); @@ -95,9 +97,9 @@ % then the elements of varargin. To accomplish this we need to add the % user's function to the beginning of varargin, and change funfcn to be % CHECKFUN. - varargin = [{funfcn}, varargin]; - funfcn = @checkfun; -end +% varargin = [{funfcn}, varargin]; +% funfcn = @checkfun; +% end % n = numel(x); @@ -112,7 +114,11 @@ v = zeros(n,n+1); fv = zeros(1,n+1); v(:,1) = xin; % Place input guess in the simplex! (credit L.Pfeffer at Stanford) x(:) = xin; % Change x to the form expected by funfcn -[fv(:,1), result] = funfcn(x,varargin{:}); +if doPlotEvent + controls.calcSLD = true; +end +[fv(:,1), result] = funfcn(x, problemStruct, controls, params); +controls.calcSLD = false; func_evals = 1; itercount = 0; coder.varsize('how',[1 Inf],[0 1]); @@ -164,7 +170,9 @@ % fprintf('%g \n', func_evals) end -triggerEvent(coderEnums.eventTypes.Plot, result, problemStruct); +if doPlotEvent + triggerEvent(coderEnums.eventTypes.Plot, result, problemStruct); +end % OutputFcn and PlotFcns call % if haveoutputfcn || haveplotfcn @@ -191,7 +199,11 @@ y(j) = zero_term_delta; end v(:,j+1) = y; - x(:) = y; [f, result] = funfcn(x,varargin{:}); + if doPlotEvent + controls.calcSLD = true; + end + x(:) = y; [f, result] = funfcn(x, problemStruct, controls, params); + controls.calcSLD = false; fv(1,j+1) = f; end @@ -215,7 +227,7 @@ % fprintf('%s \n', 'func_evals = ') % fprintf('%g \n', func_evals) end -if rem(itercount, controls.updatePlotFreq) == 0 +if doPlotEvent && rem(itercount, controls.updatePlotFreq) == 0 triggerEvent(coderEnums.eventTypes.Plot, result, problemStruct); end if isRATStopped(controls.IPCFilePath) @@ -247,6 +259,9 @@ % The iteration stops if the maximum number of iterations or function evaluations % are exceeded while func_evals < maxfun && itercount < maxiter + if doPlotEvent + controls.calcSLD = true; + end if max(abs(fv(1)-fv(2:n+1))) <= max(tolf,10*eps(fv(1))) && ... max(max(abs(v(:,2:n+1)-v(:,onesn)))) <= max(tolx,10*eps(max(v(:,1)))) break @@ -257,13 +272,15 @@ % xbar = average of the n (NOT n+1) best points xbar = sum(v(:,1:n), 2)/n; xr = (1 + rho)*xbar - rho*v(:,end); - x(:) = xr; [fxr, result] = funfcn(x,varargin{:}); + x(:) = xr; [fxr, result] = funfcn(x, problemStruct, controls, params); func_evals = func_evals+1; if fxr < fv(:,1) % Calculate the expansion point xe = (1 + rho*chi)*xbar - rho*chi*v(:,end); - x(:) = xe; [fxe, result] = funfcn(x,varargin{:}); + + x(:) = xe; [fxe, result] = funfcn(x, problemStruct, controls, params); + controls.calcSLD = false; func_evals = func_evals+1; if fxe < fxr v(:,end) = xe; @@ -284,7 +301,7 @@ if fxr < fv(:,end) % Perform an outside contraction xc = (1 + psi*rho)*xbar - psi*rho*v(:,end); - x(:) = xc; [fxc, result] = funfcn(x,varargin{:}); + x(:) = xc; [fxc, result] = funfcn(x, problemStruct, controls, params); func_evals = func_evals+1; if fxc <= fxr @@ -298,7 +315,7 @@ else % Perform an inside contraction xcc = (1-psi)*xbar + psi*v(:,end); - x(:) = xcc; [fxcc, result] = funfcn(x,varargin{:}); + x(:) = xcc; [fxcc, result] = funfcn(x, problemStruct, controls, params); func_evals = func_evals+1; if fxcc < fv(:,end) @@ -313,7 +330,7 @@ if strcmp(how,'shrink') for j=2:n+1 v(:,j)=v(:,1)+sigma*(v(:,j) - v(:,1)); - x(:) = v(:,j); [fv(:,j), result] = funfcn(x,varargin{:}); + x(:) = v(:,j); [fv(:,j), result] = funfcn(x, problemStruct, controls, params); end func_evals = func_evals + n; end @@ -334,7 +351,8 @@ % fprintf('%s \n', 'func_evals = ') % fprintf('%s \n', num2str(func_evals)) end - if rem(itercount, controls.updatePlotFreq) == 0 + controls.calcSLD = false; + if doPlotEvent && rem(itercount, controls.updatePlotFreq) == 0 triggerEvent(coderEnums.eventTypes.Plot, result, problemStruct); end if isRATStopped(controls.IPCFilePath) @@ -362,7 +380,7 @@ % This should ensure the final result is printed at the end of a run irrespective of update frequency triggerEvent(coderEnums.eventTypes.Message, sprintf(' %5.0f %5.0f %12.6g %s\n', itercount, func_evals, fv(1), how)); end -if rem(itercount, controls.updatePlotFreq) ~= 0 +if doPlotEvent && rem(itercount, controls.updatePlotFreq) ~= 0 % This should ensure the final result is always plotted irrespective of update frequency triggerEvent(coderEnums.eventTypes.Plot, result, problemStruct); end From b3734e33f6011dd78d4e1770a37a2227004f2bc6 Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Tue, 19 Aug 2025 12:11:49 +0100 Subject: [PATCH 2/2] Addresses review comment --- minimisers/simplex/fMinSearch.m | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/minimisers/simplex/fMinSearch.m b/minimisers/simplex/fMinSearch.m index 19cc93e67..d843d86c2 100644 --- a/minimisers/simplex/fMinSearch.m +++ b/minimisers/simplex/fMinSearch.m @@ -63,7 +63,6 @@ tolf = optimget(options,'TolFun',defaultopt,'fast'); maxfun = optimget(options,'MaxFunEvals',defaultopt,'fast'); maxiter = optimget(options,'MaxIter',defaultopt,'fast'); -% funValCheck = strcmp(optimget(options,'FunValCheck',defaultopt,'fast'),'on'); switch dis % Changed from TMW fminsearch case {'notify','notify-detailed'} @@ -89,18 +88,6 @@ controls = varargin{2}; params = varargin{3}; doPlotEvent = hasPlotHandler(); -% if funValCheck - % Add a wrapper function, CHECKFUN, to check for NaN/complex values without - % having to change the calls that look like this: - % f = funfcn(x,varargin{:}); - % x is the first argument to CHECKFUN, then the user's function, - % then the elements of varargin. To accomplish this we need to add the - % user's function to the beginning of varargin, and change funfcn to be - % CHECKFUN. -% varargin = [{funfcn}, varargin]; -% funfcn = @checkfun; -% end -% n = numel(x); % Initialize parameters @@ -199,14 +186,13 @@ y(j) = zero_term_delta; end v(:,j+1) = y; - if doPlotEvent + if doPlotEvent && j==n controls.calcSLD = true; end x(:) = y; [f, result] = funfcn(x, problemStruct, controls, params); - controls.calcSLD = false; fv(1,j+1) = f; end - +controls.calcSLD = false; % sort so v(1,:) has the lowest function value [fv,j] = sort(fv); v = v(:,j); @@ -280,7 +266,6 @@ xe = (1 + rho*chi)*xbar - rho*chi*v(:,end); x(:) = xe; [fxe, result] = funfcn(x, problemStruct, controls, params); - controls.calcSLD = false; func_evals = func_evals+1; if fxe < fxr v(:,end) = xe;