partitioning introduced to main loop

This commit is contained in:
2025-11-12 18:13:43 -08:00
parent 9e948072e8
commit 3d35179579
6 changed files with 75 additions and 36 deletions

View File

@@ -58,18 +58,19 @@ classdef agent
obj.index = index; obj.index = index;
obj.label = label; obj.label = label;
end end
function obj = run(obj, objectiveFunction, domain) function obj = run(obj, sensingObjective, domain, partitioning)
arguments (Input) arguments (Input)
obj (1, 1) {mustBeA(obj, 'agent')}; obj (1, 1) {mustBeA(obj, 'agent')};
objectiveFunction (1, 1) {mustBeA(objectiveFunction, 'function_handle')}; sensingObjective (1, 1) {mustBeA(sensingObjective, 'sensingObjective')};
domain (1, 1) {mustBeGeometry}; domain (1, 1) {mustBeGeometry};
partitioning (:, :) double;
end end
arguments (Output) arguments (Output)
obj (1, 1) {mustBeA(obj, 'agent')}; obj (1, 1) {mustBeA(obj, 'agent')};
end end
% Do sensing % Do sensing
[sensedValues, sensedPositions] = obj.sensorModel.sense(objectiveFunction, domain, obj.pos); [sensedValues, sensedPositions] = obj.sensorModel.sense(obj, sensingObjective, domain, partitioning);
% Determine next planned position % Determine next planned position
nextPos = obj.guidanceModel(sensedValues, sensedPositions, obj.pos); nextPos = obj.guidanceModel(sensedValues, sensedPositions, obj.pos);

View File

@@ -1,14 +1,26 @@
function nextPos = gradientAscent(sensedValues, sensedPositions, pos) function nextPos = gradientAscent(sensedValues, sensedPositions, pos, rate)
arguments (Input) arguments (Input)
sensedValues (:, 1) double; sensedValues (:, 1) double;
sensedPositions (:, 3) double; sensedPositions (:, 3) double;
pos (1, 3) double; pos (1, 3) double;
rate (1, 1) double = 0.1;
end end
arguments (Output) arguments (Output)
nextPos(1, 3) double; nextPos(1, 3) double;
end end
% As a default, maintain current position
if size(sensedValues, 1) == 0 && size(sensedPositions, 1) == 0
nextPos = pos;
return;
end
% Select next position by maximum sensed value % Select next position by maximum sensed value
nextPos = sensedPositions(sensedValues == max(sensedValues), :); nextPos = sensedPositions(sensedValues == max(sensedValues), :);
nextPos = [nextPos(1, 1:2), pos(3)]; % just in case two get selected, simply pick one nextPos = [nextPos(1, 1:2), pos(3)]; % just in case two get selected, simply pick one
% rate-limit motion
v = nextPos - pos;
nextPos = pos + (v / norm(v, 2)) * rate;
end end

30
miSim.m
View File

@@ -4,6 +4,7 @@ classdef miSim
% Simulation parameters % Simulation parameters
properties (SetAccess = private, GetAccess = public) properties (SetAccess = private, GetAccess = public)
timestep = NaN; % delta time interval for simulation iterations timestep = NaN; % delta time interval for simulation iterations
partitioningFreq = NaN; % number of simulation timesteps at which the partitioning routine is re-run
maxIter = NaN; % maximum number of simulation iterations maxIter = NaN; % maximum number of simulation iterations
domain = rectangularPrism; domain = rectangularPrism;
objective = sensingObjective; objective = sensingObjective;
@@ -27,13 +28,14 @@ classdef miSim
end end
methods (Access = public) methods (Access = public)
function [obj, f] = initialize(obj, domain, objective, agents, timestep, maxIter, obstacles) function [obj, f] = initialize(obj, domain, objective, agents, timestep, partitoningFreq, maxIter, obstacles)
arguments (Input) arguments (Input)
obj (1, 1) {mustBeA(obj, 'miSim')}; obj (1, 1) {mustBeA(obj, 'miSim')};
domain (1, 1) {mustBeGeometry}; domain (1, 1) {mustBeGeometry};
objective (1, 1) {mustBeA(objective, 'sensingObjective')}; objective (1, 1) {mustBeA(objective, 'sensingObjective')};
agents (:, 1) cell; agents (:, 1) cell;
timestep (:, 1) double = 0.05; timestep (:, 1) double = 0.05;
partitoningFreq (:, 1) double = 0.25
maxIter (:, 1) double = 1000; maxIter (:, 1) double = 1000;
obstacles (:, 1) cell {mustBeGeometry} = cell(0, 1); obstacles (:, 1) cell {mustBeGeometry} = cell(0, 1);
end end
@@ -48,6 +50,7 @@ classdef miSim
% Define domain % Define domain
obj.domain = domain; obj.domain = domain;
obj.partitioningFreq = partitoningFreq;
% Add geometries representing obstacles within the domain % Add geometries representing obstacles within the domain
obj.obstacles = obstacles; obj.obstacles = obstacles;
@@ -106,6 +109,7 @@ classdef miSim
% Set up times to iterate over % Set up times to iterate over
times = linspace(0, obj.timestep * obj.maxIter, obj.maxIter+1)'; times = linspace(0, obj.timestep * obj.maxIter, obj.maxIter+1)';
partitioningTimes = times(obj.partitioningFreq:obj.partitioningFreq:size(times, 1));
% Start video writer % Start video writer
v = setupVideoWriter(obj.timestep); v = setupVideoWriter(obj.timestep);
@@ -116,16 +120,23 @@ classdef miSim
t = times(ii); t = times(ii);
fprintf("Sim Time: %4.2f (%d/%d)\n", t, ii, obj.maxIter) fprintf("Sim Time: %4.2f (%d/%d)\n", t, ii, obj.maxIter)
% Check if it's time for new partitions
updatePartitions = false;
if ismember(t, partitioningTimes)
updatePartitions = true;
obj = obj.partition();
end
% Iterate over agents to simulate their motion % Iterate over agents to simulate their motion
for jj = 1:size(obj.agents, 1) for jj = 1:size(obj.agents, 1)
obj.agents{jj} = obj.agents{jj}.run(obj.objective.objectiveFunction, obj.domain); obj.agents{jj} = obj.agents{jj}.run(obj.objective, obj.domain, obj.partitioning);
end end
% Update adjacency matrix % Update adjacency matrix
obj = obj.updateAdjacency; obj = obj.updateAdjacency;
% Update plots % Update plots
[obj, f] = obj.updatePlots(f); [obj, f] = obj.updatePlots(f, updatePartitions);
% Write frame in to video % Write frame in to video
I = getframe(f); I = getframe(f);
@@ -160,10 +171,11 @@ classdef miSim
[i,j] = ndgrid(1:m, 1:n); [i,j] = ndgrid(1:m, 1:n);
obj.partitioning = agentInds(sub2ind(size(agentInds), i, j, idx)); obj.partitioning = agentInds(sub2ind(size(agentInds), i, j, idx));
end end
function [obj, f] = updatePlots(obj, f) function [obj, f] = updatePlots(obj, f, updatePartitions)
arguments (Input) arguments (Input)
obj (1, 1) {mustBeA(obj, 'miSim')}; obj (1, 1) {mustBeA(obj, 'miSim')};
f (1, 1) {mustBeA(f, 'matlab.ui.Figure')} = figure; f (1, 1) {mustBeA(f, 'matlab.ui.Figure')} = figure;
updatePartitions (1, 1) logical = false;
end end
arguments (Output) arguments (Output)
obj (1, 1) {mustBeA(obj, 'miSim')}; obj (1, 1) {mustBeA(obj, 'miSim')};
@@ -181,11 +193,17 @@ classdef miSim
% Update agent connections plot % Update agent connections plot
delete(obj.connectionsPlot); delete(obj.connectionsPlot);
[obj, f] = obj.plotConnections(f); [obj, f] = obj.plotConnections(obj.spatialPlotIndices, f);
% Update network graph plot % Update network graph plot
delete(obj.graphPlot); delete(obj.graphPlot);
[obj, f] = obj.plotGraph(f); [obj, f] = obj.plotGraph(obj.networkGraphIndex, f);
% Update partitioning plot
if updatePartitions
delete(obj.partitionPlot);
[obj, f] = obj.plotPartitions(obj.partitionGraphIndex, f);
end
drawnow; drawnow;
end end

View File

@@ -15,12 +15,13 @@ classdef fixedCardinalSensor
end end
obj.r = r; obj.r = r;
end end
function [neighborValues, neighborPos] = sense(obj, objectiveFunction, domain, pos) function [neighborValues, neighborPos] = sense(obj, agent, sensingObjective, domain, partitioning)
arguments (Input) arguments (Input)
obj (1, 1) {mustBeA(obj, 'fixedCardinalSensor')}; obj (1, 1) {mustBeA(obj, 'fixedCardinalSensor')};
objectiveFunction (1, 1) {mustBeA(objectiveFunction, 'function_handle')}; agent (1, 1) {mustBeA(agent, 'agent')};
sensingObjective (1, 1) {mustBeA(sensingObjective, 'sensingObjective')};
domain (1, 1) {mustBeGeometry}; domain (1, 1) {mustBeGeometry};
pos (1, 3) double; partitioning (:, :) double = NaN;
end end
arguments (Output) arguments (Output)
neighborValues (4, 1) double; neighborValues (4, 1) double;
@@ -28,7 +29,7 @@ classdef fixedCardinalSensor
end end
% Evaluate objective at position offsets +/-[r, 0, 0] and +/-[0, r, 0] % Evaluate objective at position offsets +/-[r, 0, 0] and +/-[0, r, 0]
currentPos = pos(1:2); currentPos = agent.pos(1:2);
neighborPos = [currentPos(1) + obj.r, currentPos(2); ... % (+x) neighborPos = [currentPos(1) + obj.r, currentPos(2); ... % (+x)
currentPos(1), currentPos(2) + obj.r; ... % (+y) currentPos(1), currentPos(2) + obj.r; ... % (+y)
currentPos(1) - obj.r, currentPos(2); ... % (-x) currentPos(1) - obj.r, currentPos(2); ... % (-x)
@@ -44,13 +45,13 @@ classdef fixedCardinalSensor
end end
% Replace out of bounds positions with inoffensive in-bounds positions % Replace out of bounds positions with inoffensive in-bounds positions
neighborPos(outOfBounds, 1:3) = repmat(pos, sum(outOfBounds), 1); neighborPos(outOfBounds, 1:3) = repmat(agent.pos, sum(outOfBounds), 1);
% Sense values at selected positions % Sense values at selected positions
neighborValues = [objectiveFunction(neighborPos(1, 1), neighborPos(1, 2)), ... % (+x) neighborValues = [sensingObjective.objectiveFunction(neighborPos(1, 1), neighborPos(1, 2)), ... % (+x)
objectiveFunction(neighborPos(2, 1), neighborPos(2, 2)), ... % (+y) sensingObjective.objectiveFunction(neighborPos(2, 1), neighborPos(2, 2)), ... % (+y)
objectiveFunction(neighborPos(3, 1), neighborPos(3, 2)), ... % (-x) sensingObjective.objectiveFunction(neighborPos(3, 1), neighborPos(3, 2)), ... % (-x)
objectiveFunction(neighborPos(4, 1), neighborPos(4, 2)), ... % (-y) sensingObjective.objectiveFunction(neighborPos(4, 1), neighborPos(4, 2)), ... % (-y)
]; ];
% Prevent out of bounds locations from ever possibly being selected % Prevent out of bounds locations from ever possibly being selected

View File

@@ -1,12 +1,12 @@
classdef sigmoidSensor classdef sigmoidSensor
properties (SetAccess = private, GetAccess = public) properties (SetAccess = private, GetAccess = public)
% Sensor parameters % Sensor parameters
alphaDist; alphaDist = NaN;
betaDist; betaDist = NaN;
alphaPan; alphaPan = NaN;
betaPan; betaPan = NaN;
alphaTilt; alphaTilt = NaN;
betaTilt; betaTilt = NaN;
end end
methods (Access = public) methods (Access = public)
@@ -31,19 +31,26 @@ classdef sigmoidSensor
obj.alphaTilt = alphaTilt; obj.alphaTilt = alphaTilt;
obj.betaTilt = betaTilt; obj.betaTilt = betaTilt;
end end
function [neighborValues, neighborPos] = sense(obj, objectiveFunction, domain, pos) function [values, positions] = sense(obj, agent, sensingObjective, domain, partitioning)
arguments (Input) arguments (Input)
obj (1, 1) {mustBeA(obj, 'sigmoidSensor')}; obj (1, 1) {mustBeA(obj, 'sigmoidSensor')};
objectiveFunction (1, 1) {mustBeA(objectiveFunction, 'function_handle')}; agent (1, 1) {mustBeA(agent, 'agent')};
sensingObjective (1, 1) {mustBeA(sensingObjective, 'sensingObjective')};
domain (1, 1) {mustBeGeometry}; domain (1, 1) {mustBeGeometry};
pos (1, 3) double; partitioning (:, :) double;
end end
arguments (Output) arguments (Output)
neighborValues (4, 1) double; values (:, 1) double;
neighborPos (4, 3) double; positions (:, 3) double;
end end
% Find positions for this agent's assigned partition in the domain
idx = partitioning == agent.index;
positions = [sensingObjective.X(idx), sensingObjective.Y(idx), zeros(size(sensingObjective.X(idx)))];
% Evaluate objective function at every point in this agent's
% assigned partiton
values = sensingObjective.values(idx);
end end
function value = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos) function value = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos)
arguments (Input) arguments (Input)

View File

@@ -6,6 +6,7 @@ classdef test_miSim < matlab.unittest.TestCase
domain = rectangularPrism; % domain geometry domain = rectangularPrism; % domain geometry
maxIter = 250; maxIter = 250;
timestep = 0.05 timestep = 0.05
partitoningFreq = 5;
% Obstacles % Obstacles
minNumObstacles = 1; % Minimum number of obstacles to be randomly generated minNumObstacles = 1; % Minimum number of obstacles to be randomly generated
@@ -242,7 +243,7 @@ classdef test_miSim < matlab.unittest.TestCase
end end
% Initialize the simulation % Initialize the simulation
[tc.testClass, f] = tc.testClass.initialize(tc.domain, tc.objective, tc.agents, tc.timestep, tc.maxIter, tc.obstacles); [tc.testClass, f] = tc.testClass.initialize(tc.domain, tc.objective, tc.agents, tc.timestep, tc.partitoningFreq, tc.maxIter, tc.obstacles);
end end
function misim_run(tc) function misim_run(tc)
% randomly create obstacles % randomly create obstacles
@@ -410,7 +411,7 @@ classdef test_miSim < matlab.unittest.TestCase
end end
% Initialize the simulation % Initialize the simulation
[tc.testClass, f] = tc.testClass.initialize(tc.domain, tc.objective, tc.agents, tc.timestep, tc.maxIter, tc.obstacles); [tc.testClass, f] = tc.testClass.initialize(tc.domain, tc.objective, tc.agents, tc.timestep, tc.partitoningFreq, tc.maxIter, tc.obstacles);
% Run simulation loop % Run simulation loop
[tc.testClass, f] = tc.testClass.run(f); [tc.testClass, f] = tc.testClass.run(f);
@@ -418,7 +419,6 @@ classdef test_miSim < matlab.unittest.TestCase
function test_basic_partitioning(tc) function test_basic_partitioning(tc)
% place agents a fixed distance +/- X from the domain's center % place agents a fixed distance +/- X from the domain's center
d = 1; d = 1;
c = 0.1;
% make basic domain % make basic domain
tc.domain = tc.domain.initialize([zeros(1, 3); 10 * ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); tc.domain = tc.domain.initialize([zeros(1, 3); 10 * ones(1, 3)], REGION_TYPE.DOMAIN, "Domain");
@@ -442,7 +442,7 @@ classdef test_miSim < matlab.unittest.TestCase
tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - [d, 0, 0], zeros(1,3), 0, 0, geometry2, sensor, @gradientAscent, 3*d, 2, sprintf("Agent %d", 2)); tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - [d, 0, 0], zeros(1,3), 0, 0, geometry2, sensor, @gradientAscent, 3*d, 2, sprintf("Agent %d", 2));
% Initialize the simulation % Initialize the simulation
[tc.testClass, f] = tc.testClass.initialize(tc.domain, tc.objective, tc.agents, tc.timestep, tc.maxIter); [tc.testClass, f] = tc.testClass.initialize(tc.domain, tc.objective, tc.agents, tc.timestep, tc.partitoningFreq, tc.maxIter);
end end
end end
end end