From a68690a5cfb5f03d9df682e26b78e98c3823b95a Mon Sep 17 00:00:00 2001 From: Kevin D Date: Tue, 27 Jan 2026 16:39:22 -0800 Subject: [PATCH] random agent placement in parametric testing --- @miSim/miSim.m | 4 +- @miSim/teardown.m | 21 ++++-- @sensingObjective/initialize.m | 3 +- @sensingObjective/plot.m | 4 +- @sensingObjective/sensingObjective.m | 1 - test/parametricTestSuite.m | 99 ++++++++++++++++++++++++---- test/testIterations.csv | 6 +- 7 files changed, 108 insertions(+), 30 deletions(-) diff --git a/@miSim/miSim.m b/@miSim/miSim.m index d1d3f6c..e80b198 100644 --- a/@miSim/miSim.m +++ b/@miSim/miSim.m @@ -15,8 +15,8 @@ classdef miSim partitioning = NaN; perf; % sensor performance timeseries array performance = 0; % simulation performance timeseries vector - barrierGain = 100; % CBF gain parameter - barrierExponent = 3; % CBF exponent parameter + barrierGain = NaN; % CBF gain parameter + barrierExponent = NaN; % CBF exponent parameter artifactName = ""; fPerf; % performance plot figure end diff --git a/@miSim/teardown.m b/@miSim/teardown.m index ae83145..8b03822 100644 --- a/@miSim/teardown.m +++ b/@miSim/teardown.m @@ -11,13 +11,20 @@ function obj = teardown(obj) close(obj.fPerf); close(obj.f); - % Reset accumulators + % reset parameters + obj.timestep = NaN; + obj.timestepIndex = NaN; + obj.maxIter = NaN; + obj.domain = rectangularPrism; + obj.objective = sensingObjective; + obj.obstacles = cell(0, 1); + obj.agents = cell(0, 1); + obj.adjacency = NaN; + obj.constraintAdjacencyMatrix = NaN; + obj.partitioning = NaN; obj.performance = 0; - - % Reset agents - for ii = 1:size(obj.agents, 1) - obj.agents{ii} = agent; - end - + obj.barrierGain = NaN; + obj.barrierExponent = NaN; + obj.artifactName = ""; end \ No newline at end of file diff --git a/@sensingObjective/initialize.m b/@sensingObjective/initialize.m index 257f5e1..a0b2ed6 100644 --- a/@sensingObjective/initialize.m +++ b/@sensingObjective/initialize.m @@ -14,8 +14,7 @@ function obj = initialize(obj, objectiveFunction, domain, discretizationStep, pr obj.discretizationStep = discretizationStep; obj.sensorPerformanceMinimum = sensorPerformanceMinimum; - - obj.groundAlt = domain.minCorner(3); + obj.protectedRange = protectedRange; % Extract footprint limits diff --git a/@sensingObjective/plot.m b/@sensingObjective/plot.m index 9e48eeb..157cd05 100644 --- a/@sensingObjective/plot.m +++ b/@sensingObjective/plot.m @@ -14,13 +14,13 @@ function f = plot(obj, ind, f) % Plot gradient on the "floor" of the domain if isnan(ind) hold(f.CurrentAxes, "on"); - o = surf(f.CurrentAxes, obj.X, obj.Y, repmat(obj.groundAlt, size(obj.X)), obj.values ./ max(obj.values, [], "all"), 'EdgeColor', 'none'); + o = surf(f.CurrentAxes, obj.X, obj.Y, zeros(size(obj.X)), obj.values ./ max(obj.values, [], "all"), 'EdgeColor', 'none'); o.HitTest = 'off'; o.PickableParts = 'none'; hold(f.CurrentAxes, "off"); else hold(f.Children(1).Children(ind(1)), "on"); - o = surf(f.Children(1).Children(ind(1)), obj.X, obj.Y, repmat(obj.groundAlt, size(obj.X)), obj.values ./ max(obj.values, [], "all"), 'EdgeColor', 'none'); + o = surf(f.Children(1).Children(ind(1)), obj.X, obj.Y, zeros(size(obj.X)), obj.values ./ max(obj.values, [], "all"), 'EdgeColor', 'none'); o.HitTest = 'off'; o.PickableParts = 'none'; hold(f.Children(1).Children(ind(1)), "off"); diff --git a/@sensingObjective/sensingObjective.m b/@sensingObjective/sensingObjective.m index db10142..6456d07 100644 --- a/@sensingObjective/sensingObjective.m +++ b/@sensingObjective/sensingObjective.m @@ -2,7 +2,6 @@ classdef sensingObjective % Sensing objective definition parent class properties (SetAccess = private, GetAccess = public) label = ""; - groundAlt = NaN; groundPos = [NaN, NaN]; discretizationStep = NaN; objectiveFunction = @(x, y) NaN; % define objective functions over a grid in this manner diff --git a/test/parametricTestSuite.m b/test/parametricTestSuite.m index 4e0e768..e62457f 100644 --- a/test/parametricTestSuite.m +++ b/test/parametricTestSuite.m @@ -5,6 +5,9 @@ classdef parametricTestSuite < matlab.unittest.TestCase domain = rectangularPrism; obstacles = cell(1, 0); + % RNG control + seed = 1234567890; + %% Diagnostic Parameters % No effect on simulation dynamics makeVideo = true; % disable video writing for big performance increase @@ -16,6 +19,13 @@ classdef parametricTestSuite < matlab.unittest.TestCase csvPath = fullfile(matlab.project.rootProject().RootFolder, 'test', 'testIterations.csv'); end + methods (TestMethodSetup) + function rngSetup(tc) + % Allow for controlling the random seed for reproducibility + rng(tc.seed); + end + end + methods (Static) function params = readIterationsCsv(csvPath) arguments (Input) @@ -30,39 +40,102 @@ classdef parametricTestSuite < matlab.unittest.TestCase assert(endsWith(csvPath, '.csv'), "%s is not a CSV file."); % Read file - csv = readtable(csvPath); + csv = readtable(csvPath, 'TextType', 'string', 'NumHeaderLines', 0, "VariableNamingRule", "Preserve"); + csv.Properties.VariableNames = ["timestep", "maxIter", "minAlt", "discretizationStep", "sensorPerformanceMinimum", "initialStepSize", "barrierGain", "barrierExponent", "numAgents", "collisionRadius", "comRange", "alphaDist", "betaDist", "alphaTilt", "betaTilt"]; + + for ii = 1:size(csv.Properties.VariableNames, 2) + csv.(csv.Properties.VariableNames{ii}) = cell2mat(cellfun(@(x) str2num(x), csv.(csv.Properties.VariableNames{ii}), 'UniformOutput', false)); + end % Put params into standard structure params = struct('timestep', csv.timestep, 'maxIter', csv.maxIter, 'minAlt', csv.minAlt, 'discretizationStep', csv.discretizationStep, ... - 'sensorPerformanceMinimum', csv.sensorPerformanceMinimum, 'collisionRadius', csv.collisionRadius, 'alphaDist', csv.alphaDist, 'betaDist', csv.betaDist, ... - 'alphaTilt', csv.alphaTilt, 'betaTilt', csv.betaTilt, 'comRange', csv.comRange, 'initialStepSize', csv.initialStepSize, 'barrierGain', csv.barrierGain, 'barrierExponent', csv.barrierExponent); + 'sensorPerformanceMinimum', csv.sensorPerformanceMinimum, 'initialStepSize', csv.initialStepSize, 'barrierGain', csv.barrierGain, 'barrierExponent', csv.barrierExponent, ... + 'numAgents', csv.numAgents, 'collisionRadius', csv.collisionRadius, 'comRange', csv.comRange, 'alphaDist', csv.alphaDist, 'betaDist', csv.betaDist, 'alphaTilt', csv.alphaTilt, 'betaTilt', csv.betaTilt); end end - methods (Test, ParameterCombination = "exhaustive") + methods (Test) % Test cases - function single_agent_gradient_ascent(tc) + function csv_parametric_tests(tc) % Read in parameters to iterate over params = tc.readIterationsCsv(tc.csvPath); % Test case setup - l = 10; + l = 10; % domain size sensorModel = sigmoidSensor; - agentPos = [l/4, l/4, l/4]; collisionGeometry = spherical; - agents = {agent}; + % Iterate over test cases defined in CSV for ii = 1:size(params.timestep, 1) % Set up square domain tc.domain = tc.domain.initialize([zeros(1, 3); l * ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([.75 * l, 0.75 * l]), tc.domain, params.discretizationStep(ii), tc.protectedRange, params.sensorPerformanceMinimum(ii)); - % Set up agent - sensorModel = sensorModel.initialize(params.alphaDist(ii), params.betaDist(ii), params.alphaTilt(ii), params.betaTilt(ii)); - collisionGeometry = collisionGeometry.initialize(agentPos, params.collisionRadius(ii), REGION_TYPE.COLLISION, "Agent 1 Collision Region"); - agents{1} = agents{1}.initialize(agentPos, collisionGeometry, sensorModel, params.comRange(ii), params.maxIter(ii), params.initialStepSize(ii), "Agent 1", tc.plotCommsGeometry); - + % Initialize agents + agents = cell(params.numAgents(ii), 1); + [agents{:}] = deal(agent); + + % Initialize sensor model + sensorModel = sensorModel.initialize(params.alphaDist(ii, 1), params.betaDist(ii, 1), params.alphaTilt(ii, 1), params.betaTilt(ii, 1)); + + % Place first agent randomly in the quadrant opposite the objective + % not too close to the domain boundaries + bounds = [params.collisionRadius(ii, 1) * ones(1, 2), max([params.collisionRadius(ii, 1), params.minAlt(ii)]); l / 2 * ones(1, 2), l - params.collisionRadius(ii, 1)]; + agentPos = bounds(1, :) + (bounds(2, :) - bounds(1, :)) .* rand(1, 3); + + % Keep trying new positions until the greatest possible + % sensor performance clears the threshold (meaning this + % agent has the ability to make a partition) + while sensorModel.sensorPerformance(agentPos, [agentPos(1:2), 0]) < params.sensorPerformanceMinimum(ii) + agentPos = bounds(1, :) + (bounds(2, :) - bounds(1, :)) .* rand(1, 3); + end + + % Initialize agent + collisionGeometry = collisionGeometry.initialize(agentPos, params.collisionRadius(ii, 1), REGION_TYPE.COLLISION, "Agent 1 Collision Region"); + agents{1} = agents{1}.initialize(agentPos, collisionGeometry, sensorModel, params.comRange(ii, 1), params.maxIter(ii), params.initialStepSize(ii), "Agent 1", tc.plotCommsGeometry); + + % Set up remaining agents in random (valid) locations + for jj = 2:size(agents, 1) + % Initialize sensor model + sensorModel = sensorModel.initialize(params.alphaDist(ii, jj), params.betaDist(ii, jj), params.alphaTilt(ii, jj), params.betaTilt(ii, jj)); + + % Base next agent's location on random previous agent's location + baseAgentIdx = randi(jj - 1); + retry = true; + while retry + agentPos = agents{baseAgentIdx}.commsGeometry.random(); + + % Check that the agent's greatest sensor + % performance clears the threshold for partitioning + if sensorModel.sensorPerformance(agentPos, [agentPos(1:2), 0]) < params.sensorPerformanceMinimum(ii) + retry = true; + continue; + end + + % Check that candidate position is well inside the domain + bounds = [params.collisionRadius(ii, jj) * ones(1, 2), max([params.collisionRadius(ii, jj), params.minAlt(ii)]); l / 2 * ones(1, 2), l - params.collisionRadius(ii, jj)]; + if ~isequal(agentPos < bounds, [false, false, false; true, true, true]) + retry = true; + continue; + end + + % Check that candidate position does not collide with existing agents + for kk = 1:(jj - 1) + if norm(agents{kk}.pos - agentPos, 2) < agents{kk}.collisionGeometry.radius + params.collisionRadius(ii, jj) + retry = true; + continue; + end + end + retry = false; + end + + % Initialize agent + collisionGeometry = collisionGeometry.initialize(agentPos, params.collisionRadius(ii, jj), REGION_TYPE.COLLISION, sprintf("Agent %d Collision Region", jj)); + agents{jj} = agents{jj}.initialize(agentPos, collisionGeometry, sensorModel, params.comRange(ii, jj), params.maxIter(ii), params.initialStepSize(ii), sprintf("Agent %d", jj), tc.plotCommsGeometry); + end + % Set up simulation + agents = agents(randperm(numel(agents))); % randomly shuffle agents to make the network more interesting (probably) tc.testClass = tc.testClass.initialize(tc.domain, agents, params.barrierGain(ii), params.barrierExponent(ii), params.minAlt(ii), params.timestep(ii), params.maxIter(ii), tc.obstacles, tc.makePlots, tc.makeVideo); % Save simulation parameters to output file diff --git a/test/testIterations.csv b/test/testIterations.csv index e5f1942..4451b39 100644 --- a/test/testIterations.csv +++ b/test/testIterations.csv @@ -1,3 +1,3 @@ -timestep, maxIter, minAlt, barrierGain, barrierExponent, sensorPerformanceMinimum, discretizationStep, collisionRadius, initialStepSize, alphaDist, betaDist, alphaTilt, betaTilt, comRange -1, 25, 1, 100, 3, 1e-6, 0.01, 0.1, 0.2, 2.5, 3, 15, 3, 3 -1, 25, 1, 100, 3, 1e-6, 0.01, 0.1, 0.2, 5, 15, 30, 15, 3 +timestep, maxIter, minAlt, discretizationStep, sensorPerformanceMinimum, initialStepSize, barrierGain, barrierExponent, numAgents, collisionRadius, comRange, alphaDist, betaDist, alphaTilt, betaTilt +1, 10, 1, 0.01, 1e-6, 0.2, 100, 3, 5, "0.1, 0.1, 0.1, 0.1, 0.1", "2.5, 2.5, 2.5, 2.5, 2.5", "3, 3, 3, 3, 3", "15, 15, 15, 15, 15", "3, 3, 3, 3, 3", "3, 3, 3, 3, 3" +1, 10, 1, 0.01, 1e-6, 0.2, 100, 3, 5, "0.1, 0.1, 0.1, 0.1, 0.1", "3.5, 3.5, 3.5, 3.5, 3.5", "15, 15, 15, 15, 15", "30, 30, 30, 30, 30", "15, 15, 15, 15, 15", "3, 3, 3, 3, 3"