Skip to content

Commit

Permalink
Added Event Updates to DE (#231)
Browse files Browse the repository at this point in the history
* Added  64bit Apple silicon compile files

* Fixed problem with events compile on Apple Silicon

* Added 'useUpdatePlot.m' in Utilities

* DE now talks to live updating plots

* Made some changes to controls class so that updatePlotFreq now appears in DE and simplex, and modified some defaults

* Added some docs for events

* Fixed failing tests (caused by changing controlsCLass defaults)

* Removes unnecessary files and correct typo

* Refactors eventManager and adds new useLivePlot class

* Addresses review comments

---------

Co-authored-by: Stephen Nneji <[email protected]>
  • Loading branch information
arwelHughes and StephenNneji authored Jun 5, 2024
1 parent 3fb3de1 commit 7e96acd
Show file tree
Hide file tree
Showing 16 changed files with 314 additions and 118 deletions.
14 changes: 7 additions & 7 deletions API/controlsClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
resampleParams = [0.9 50]
% Display Option (Default: displayOptions.Iter)
display = displayOptions.Iter.value
updateFreq = 1
updatePlotFreq = 20

% optimization tolerance for simplex (Default: 1e-6)
xTolerance = 1e-6
Expand All @@ -18,8 +20,6 @@
maxFuncEvals = 10000
% Maximum number of iterations for simplex (Default: 1000)
maxIterations = 1000
updateFreq = -1
updatePlotFreq = 1

% Differential Evolution population size (Default: 20)
populationSize = 20
Expand Down Expand Up @@ -338,8 +338,7 @@
'funcTolerance',...
'maxFuncEvals',...
'maxIterations',...
'updateFreq',...
'updatePlotFreq'};
};

deCell = {'populationSize',...
'fWeight',...
Expand All @@ -363,15 +362,16 @@
if isscalar(obj)
dispPropList = masterPropList;
if strcmpi(obj.procedure, 'calculate')
dispPropList = rmfield(masterPropList, [deCell, simplexCell, nsCell, dreamCell]);
dispPropList = rmfield(masterPropList, [deCell, simplexCell, nsCell, dreamCell, {'updatePlotFreq','updateFreq'}]);
elseif strcmpi(obj.procedure, 'simplex')
dispPropList = rmfield(masterPropList, [deCell, nsCell, dreamCell]);
elseif strcmpi(obj.procedure, 'de')
dispPropList = rmfield(masterPropList, [simplexCell, nsCell, dreamCell]);
% Add the update back...
elseif strcmpi(obj.procedure, 'ns')
dispPropList = rmfield(masterPropList, [simplexCell, deCell, dreamCell]);
dispPropList = rmfield(masterPropList, [simplexCell, deCell, dreamCell, {'updatePlotFreq','updateFreq'}]);
elseif strcmpi(obj.procedure, 'dream')
dispPropList = rmfield(masterPropList, [simplexCell, deCell, nsCell]);
dispPropList = rmfield(masterPropList, [simplexCell, deCell, nsCell, {'updatePlotFreq','updateFreq'}]);
end
groups = matlab.mixin.util.PropertyGroup(dispPropList);
else
Expand Down
116 changes: 66 additions & 50 deletions API/events/eventManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function setEvents(value)
persistent events

if isempty(events)
events = {};
events = cell(0, 3);
end

switch op
Expand All @@ -36,62 +36,56 @@ function setEvents(value)
value = events;
end

function value = getHandlers()
% Gets the list of registered callbacks
function funcID = getCallbackID(callback)
% Generates a id for a callback handle function
%
% handlerList = eventManager.getHandlers();
value = eventManager.handlers('get');
% ID = getCallbackID(@disp)
funcID = func2str(callback);
funcDetails = functions(callback);
if strcmp(funcDetails.type, 'anonymous')
workspace = functions(callback).workspace{:};
if isfield(workspace, 'obj') && isprop(workspace.obj, 'callback') && isprop(workspace.obj, 'figureId')
funcID = sprintf('Function ''%s'' on figure %d', func2str(workspace.obj.callback), workspace.obj.figureId);
end
end
end

function setHandlers(value)
% Sets the list of registered callbacks
function callback = validateCallback(callback)
% Validate the given callback and return handle for the
% callback
%
% eventManager.setHandlers(handlerList);
eventManager.handlers('set', value);
end

function value=handlers(op, newValue)
% Helper to store the list of registered callbacks as static variable
%
% handlerList = eventManager.handlers('get');
% eventManager.handlers('set', handlerList);
persistent handlers

if isempty(handlers)
handlers = {};
% callback = validateCallback('disp');
if ~isa(callback, 'function_handle') && (~isText(callback) || isempty(char(callback)))
throw(exceptions.invalidType('callback must be function name (text) or handle'));
end

switch op
case 'set'
handlers = newValue;

if isText(callback)
callback = str2func(callback);
end
value = handlers;
end

function register(eventType, functionName)
function register(eventType, callback)
% Register a callback function for the given eventType. eventType
% should be an eventTypes enum and functionName should be name of the
% should be an eventTypes enum and callback should be name or handle of the
% callback function.
%
% eventManager.register(eventTypes.Plot, 'plotRefSLDHelper');
eventType = validateOption(eventType, 'eventTypes', 'Event type must be an eventTypes enum').value;

if ~isText(functionName) || isempty(char(functionName))
throw(exceptions.invalidType('Second value must be function name (text)'));
end
callback = eventManager.validateCallback(callback);

events = eventManager.getEvents();
handlers = eventManager.getHandlers();
funcID = eventManager.getCallbackID(callback);

if isempty(events) || ~any([events{:}] == eventType)
eventManagerInterface('register', eventType);
if ~isempty(events)
for i=1:size(events, 1)
if strcmp(events{i, 1}, funcID) && events{i, 2} == eventType
return
end
end
end

events{end + 1} = eventType;
handlers{end + 1} = functionName;

eventManager.setEvents(events)
eventManager.setHandlers(handlers)
events(end + 1, :) = {funcID, eventType, callback};
eventManager.setEvents(events);
end

function notify(eventType, data)
Expand All @@ -101,28 +95,50 @@ function notify(eventType, data)
%
% eventManager.notify(eventTypes.Message, 'wow');
events = eventManager.getEvents();
handlers = eventManager.getHandlers();

eventType = validateOption(eventType, 'eventTypes', 'Event type must be an eventTypes enum').value;
for i=1:length(events)
if (eventType == events{i})
for i=1:size(events, 1)
if eventType == events{i, 2}
try
funcHandle = str2func(handlers{i});
funcHandle = events{i, 3};
funcHandle(data);
catch ME
fprintf('EVENTMANAGER: calling %s function failed on line %d because: \n\n %s\n', handlers{i}, ME.stack(1).line, ME.message);
fprintf('EVENTMANAGER: calling %s function failed on line %d because: \n\n %s\n', events{i, 1}, ME.stack(1).line, ME.message);
end
end
end
end

function clear()
% Clear all register event callbacks
function unregister(eventType, callback)
% Remove specific event callbacks
%
% eventManager.unregister(eventTypes.Plot, 'plotRefSLDHelper');
eventType = validateOption(eventType, 'eventTypes', 'Event type must be an eventTypes enum').value;
callback = eventManager.validateCallback(callback);
events = eventManager.getEvents();
for i=size(events, 1):-1:1
if (eventType == events{i, 2}) && strcmp(eventManager.getCallbackID(callback), events{i, 1})
events(i, :) = [];
end
end
eventManager.setEvents(events);
end

function clear(varargin)
% Clear all register event callbacks or specific types
%
% eventManager.clear();
eventManager.setEvents({})
eventManager.setHandlers({})
eventManagerInterface('clear');
% eventManager.clear(eventTypes.Plot);
events = eventManager.getEvents();
if isempty(events)
return
end
if nargin == 0
events = cell(0, 3);
else
eventType = validateOption(varargin{1}, 'eventTypes', 'Event type must be an eventTypes enum').value;
events(eventType==[events{:, 2}], :) = [];
end
eventManager.setEvents(events);
end

end
Expand Down
5 changes: 0 additions & 5 deletions buildScript.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@
ratMainCompileScript;
ratMainCodeGen;

% Finally, change the paths back or the tests might fail...
compilePath = fullfile(thisPath, 'compile', 'events');
cd(compilePath);
eventCompileScript;

compilePath = fullfile(thisPath, 'compile', 'customWrapper');
cd(compilePath);
wrapperCompileScript;
Expand Down
12 changes: 10 additions & 2 deletions compile/events/eventCompileScript.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
clear eventManagerInterface;
args = {};
if ismac
mbuild eventManager.cpp CMDLINE150='' CMDLINE200='$LD $LDFLAGS $OBJS $LINKOPTIM $LINKEXPORT $CLIBS $LINKLIBS -o $EXE' ...
LDEXT='.dylib' LDTYPE='-dynamiclib' LDFLAGS='-lc++ -arch x86_64 -mmacosx-version-min=$SDKVER -Wl,-syslibroot,$ISYSROOT $LDTYPE $LINKEXPORT -framework CoreFoundation -install_name "@loader_path/$EXENAME$LDEXT"'
% Need to check whether it's Apple or Intel Silicon....
[~,result] = system('uname -v');
is_m1_mac = any(strfind(result,'ARM64'));
if is_m1_mac
mbuild eventManager.cpp CMDLINE150='' CMDLINE200='$LD $LDFLAGS $OBJS $LINKOPTIM $LINKEXPORT $CLIBS $LINKLIBS -o $EXE' ...
LDEXT='.dylib' LDTYPE='-dynamiclib' LDFLAGS='-lc++ -arch arm64 -mmacosx-version-min=$SDKVER -Wl,-syslibroot,$ISYSROOT $LDTYPE $LINKEXPORT -framework CoreFoundation -install_name "@loader_path/$EXENAME$LDEXT"'
else
mbuild eventManager.cpp CMDLINE150='' CMDLINE200='$LD $LDFLAGS $OBJS $LINKOPTIM $LINKEXPORT $CLIBS $LINKLIBS -o $EXE' ...
LDEXT='.dylib' LDTYPE='-dynamiclib' LDFLAGS='-lc++ -arch x86_64 -mmacosx-version-min=$SDKVER -Wl,-syslibroot,$ISYSROOT $LDTYPE $LINKEXPORT -framework CoreFoundation -install_name "@loader_path/$EXENAME$LDEXT"'
end
elseif isunix
mbuild eventManager.cpp CXXFLAGS="-fPIC $CXXFLAGS" LDTYPE="-shared" LDEXT=".so";
args = {'-ldl'};
Expand Down
Binary file added docs/source/images/misc/eventContents.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/images/misc/updateBreakPoint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/images/misc/updatePlotFreq.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion docs/source/incoherent.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ domain that the 'domainClass' is used. As with 'normal reflectivity', we can han
:maxdepth: 2

domainsStanlay
domainsCustomLayers
domainsCustomLayers
domainsCustomXY
64 changes: 61 additions & 3 deletions docs/source/livePlot.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,65 @@
.. _livePlot:


Live Updating Plots
...................
Events and Live Updating Plots
..............................

Sometimes it is useful to be able to monitor the progress / success of a fit in real time for long simulations. For Simplex and DE fits, RAT sends out 'events', which send out data concerning the
reflectivity, SLD's and so on as the fit progresses. By writing functions that 'listen' to these events, you can use this information to build various kinds of graphical updates to suit your needs.
In this section, we'll use this capability to build a live, updating plot of reflectivity and SLD, similar to that of the main RasCAL GUI.

.. note::
The code in this section already exists in the repo (utilities / plotting), and you can activate the full updating plot at any time by just typing 'useLivePlot()' at the Matlab command window. But we detail it here to illustrate how to interact with events.

**Registering Listeners**

On the Matlab side, the interaction with RAT event is via the 'eventManager' class. To register a listener, we use the 'register' method to associate a function with the event.

.. code-block:: MATLAB
eventManager.register(eventTypes.Plot, 'updatePlot');
In this line, we've done two things: we've registered a 'listener' for 'Plot' events, and defined the function 'updatePlot' as the function that runs when the event is triggered (known as a 'handler')
We need to define the handler function:

.. code-block:: MATLAB
function updatePlot(varargin)
h = figure(1000); % Select / open the figure
subplot(1,2,1); cla % Reflectivity plot panel
subplot(1,2,2); cla % SLD plot panel
plotRefSLDHelper(varargin{:}); % Use the standard RAT reflectivity plot
drawnow limitrate % Make sure it updates
end
We can put a breakpoint in our function to examine the contents of varargin

.. image:: images/misc/updateBreakPoint.png
:width: 300
:alt: breakpoint in update function

We see that it's a struct containing everything needed to make our custom plot:

.. image:: images/misc/eventContents.png
:width: 300
:alt: contents of events

In other words, RAT has packaged the current state of the reflectivity and SLD's, along with a number of other items that you can use to make a plot however you like.
For these purposes, we just make use of the existing RAT plot routine to make our plot. The result is the updating plot routine bundled with RAT.

**Frequency of events**

To control how often the event is triggered, we set the 'updatePlotFreq' parameter in the controls block, which defaults to 20:

.. image:: images/misc/updatePlotFreq.png
:width: 300
:alt: contents of events

.. note::
If you set the plot frequency too low (i.e. make the plot update too often), this will slow your fit as Matlab takes time out of the analysis to update the figure.
Updating every 20 iterations is a reasonable compromise between speed and utility.

TODO
2 changes: 1 addition & 1 deletion docs/source/plotFunsBayes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

==========================
Plotting Bayesian Analysis
===========================
==========================

A number of function exist for plotting the results of Bayesian analysis.

Expand Down
15 changes: 11 additions & 4 deletions minimisers/DE/deopt.m
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@

coder.varsize('I_best_index',[1 1],[0 0]);
I_best_index = 1; % start with first population member
S_val(1) = fname(FM_pop(I_best_index,:),problem,problemCells,problemLimits,controls);
[S_val(1),~] = fname(FM_pop(I_best_index,:),problem,problemCells,problemLimits,controls);
S_bestval = S_val(1); % best objective function value so far
I_nfeval = I_nfeval + 1;
for k=2:I_NP % check the remaining members
S_val(k) = fname(FM_pop(k,:),problem,problemCells,problemLimits,controls);
[S_val(k),~] = fname(FM_pop(k,:),problem,problemCells,problemLimits,controls);
I_nfeval = I_nfeval + 1;
if (leftWin(S_val(k),S_bestval) == 1)
I_best_index = k; % save its location
Expand Down Expand Up @@ -284,7 +284,7 @@
end
%=====End boundary constraints==========================================

S_tempval = fname(FM_ui(k,:),problem,problemCells,problemLimits,controls); % check cost of competitor
[S_tempval,~] = fname(FM_ui(k,:),problem,problemCells,problemLimits,controls); % check cost of competitor
I_nfeval = I_nfeval + 1;
if (leftWin(S_tempval,S_val(k)) == 1)
FM_pop(k,:) = FM_ui(k,:); % replace old vector with new one (for new iteration)
Expand Down Expand Up @@ -318,7 +318,14 @@
% end
stopflag = 0;

end
end

% Trigger the output event...
if rem(I_iter, controls.updatePlotFreq) == 0
[~,result] = fname(FVr_bestmem,problem,problemCells,problemLimits,controls);
triggerEvent(coderEnums.eventTypes.Plot, result, problem);
end

end
if stopflag == 0
I_iter = I_iter + 1;
Expand Down
2 changes: 1 addition & 1 deletion minimisers/DE/runDE.m
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
end


function S_MSE = intrafun(p,problemStruct,problemCells,problemLimits,controls)
function [S_MSE,result] = intrafun(p,problemStruct,problemCells,problemLimits,controls)

coder.varsize('S_MSE.I_nc',[1 1],[0 0]);
coder.varsize('S_MSE.FVr_ca',[1 1],[0 0]);
Expand Down
4 changes: 2 additions & 2 deletions tests/testControlsClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -518,8 +518,8 @@ function testSetProcedureWithSimplex(testCase)
testCase.verifyEqual(testCase.controls.funcTolerance, 1e-6, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.maxFuncEvals, 10000, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.maxIterations, 1000, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.updateFreq, -1, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.updatePlotFreq, 1, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.updateFreq, 1, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.updatePlotFreq, 20, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.parallel, parallelOptions.Single.value, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.calcSldDuringFit, false, 'setProcedure method is not working');
testCase.verifyEqual(testCase.controls.resampleParams, [0.9 50], 'setProcedure method is not working');
Expand Down
Loading

0 comments on commit 7e96acd

Please sign in to comment.