diff --git a/AmoebotSim.pro b/AmoebotSim.pro index 2eda0dfc..7572bbd4 100644 --- a/AmoebotSim.pro +++ b/AmoebotSim.pro @@ -13,6 +13,7 @@ HEADERS += \ alg/demo/pulldemo.h \ alg/demo/tokendemo.h \ alg/compression.h \ + alg/improvedleaderelection.h \ alg/infobjcoating.h \ alg/shapeformation.h \ core/amoebotparticle.h \ @@ -40,6 +41,7 @@ SOURCES += \ alg/demo/pulldemo.cpp \ alg/demo/tokendemo.cpp \ alg/compression.cpp \ + alg/improvedleaderelection.cpp \ alg/infobjcoating.cpp \ alg/shapeformation.cpp \ core/amoebotparticle.cpp \ diff --git a/alg/improvedleaderelection.cpp b/alg/improvedleaderelection.cpp new file mode 100644 index 00000000..51e33b95 --- /dev/null +++ b/alg/improvedleaderelection.cpp @@ -0,0 +1,1186 @@ +#include +#include + +#include +#include + +#include "alg/improvedleaderelection.h" + +//----------------------------BEGIN PARTICLE CODE---------------------------- + +LeaderElectionParticle::LeaderElectionParticle(const Node head, + const int globalTailDir, + const int orientation, + AmoebotSystem& system, + State state) + : AmoebotParticle(head, globalTailDir, orientation, system), + state(state), + currentAgent(0) { + borderColorLabels.fill(-1); + borderPointColorLabels.fill(-1); + leaderSelected = false; +} + +void LeaderElectionParticle::activate() { + if (leaderSelected) { + if (state != State::Finished && state != State::Leader) { + for (unsigned i = 0; i < agents.size(); i++) { + LeaderElectionAgent* agent = agents.at(i); + agent->agentState = State::Finished; + agent->setStateColor(); + agent->cleanAllTokens(); + agent->paintBackSegment(-1); + agent->paintFrontSegment(-1); + } + state = State::Finished; + } + for (int i = 0; i < 6; i++) { + if (hasNbrAtLabel(i)) { + nbrAtLabel(i).leaderSelected = true; + } + } + } + + if (state == State::Idle) { + // Determine the number of neighbors of the current particle. + // If there are no neighbors, then that means the particle is the only + // one in the system and should declare itself as the leader. + // If it is surrounded by 6 neighbors, then it cannot participate in + // leader election. + // Otherwise, the particle may participate in leader election and must + // generate agents to do so. + int numNbrs = getNumberOfNbrs(); + if (numNbrs == 0) { + state = State::Leader; + return; + } else if (numNbrs == 6) { + state = State::Finished; + } else { + int agentId = 0; + for (int dir = 0; dir < 6; dir++) { + if (!hasNbrAtLabel(dir) && hasNbrAtLabel((dir + 1) % 6)) { + Q_ASSERT(agentId < 3); + + LeaderElectionAgent* agent = new LeaderElectionAgent(); + agent->candidateParticle = this; + agent->localId = agentId + 1; + agent->agentDir = dir; + agent->nextAgentDir = getNextAgentDir(dir); + agent->prevAgentDir = getPrevAgentDir(dir); + agent->agentState = State::Candidate; + agent->subPhase = LeaderElectionAgent::SubPhase::SegmentSetup; + agent->setStateColor(); + + agent->paintBackSegment(0x696969); + agent->paintFrontSegment(0x696969); + + agents.push_back(agent); + agentId++; + } + } + state = State::Candidate; + return; + } + } else if (state == State::Candidate) { + agents.at(currentAgent)->activate(); + currentAgent = (currentAgent + 1) % agents.size(); + + // The following is used by a particle in the candidate state to determine + // whether or not to declare itself as the Leader or declare itself to be in + // the Finished state depending on the state of its agents. + bool allFinished = true; + for (unsigned i = 0; i < agents.size(); i++) { + LeaderElectionAgent* agent = agents.at(i); + if (agent->agentState != State::Finished) { + allFinished = false; + } + if (agent->agentState == State::Leader) { + state = State::Leader; + return; + } + } + + if (allFinished) { + state = State::Finished; + } + } else if (state == State::Finished) { + for (unsigned i = 0; i < agents.size(); i++) { + LeaderElectionAgent* agent = agents.at(i); + agent->cleanAllTokens(); + agent->paintBackSegment(-1); + agent->paintFrontSegment(-1); + } + } else if (state == State::Leader) { + for (int i = 0; i < 6; i++) { + if (hasNbrAtLabel(i)) { + nbrAtLabel(i).leaderSelected = true; + } + } + leaderSelected = true; + for (unsigned i = 0; i < agents.size(); i++) { + LeaderElectionAgent* agent = agents.at(i); + agent->cleanAllTokens(); + agent->paintBackSegment(-1); + agent->paintFrontSegment(-1); + } + } + + return; +} + +int LeaderElectionParticle::headMarkColor() const { + if (state == State::Leader) { + return 0x00ff00; + } + + return -1; +} + +int LeaderElectionParticle::tailMarkColor() const { + return headMarkColor(); +} + +QString LeaderElectionParticle::inspectionText() const { + QString text; + QString indent = " "; + text += "head: (" + QString::number(head.x) + ", " + QString::number(head.y) + + ")\n"; + text += "orientation: " + QString::number(orientation) + "\n"; + text += "globalTailDir: " + QString::number(globalTailDir) + "\n"; + text += "state: "; + text += [this](){ + switch(state) { + case State::Idle: return "idle"; + case State::Candidate: return "candidate"; + case State::SoleCandidate: return "sole candidate"; + case State::Demoted: return "demoted"; + case State::Leader: return "leader"; + case State::Finished: return "finished"; + default: return "no state"; + } + }(); + text += "\n"; + text += "number of agents: " + QString::number(agents.size()) + "\n"; + for (LeaderElectionAgent* agent : agents) { + int nextAgentDir = agent->nextAgentDir; + int prevAgentDir = agent->prevAgentDir; + text += [agent, indent](){ + switch(agent->agentState) { + case State::Demoted: return indent + "demoted\n"; + case State::Candidate: + switch(agent->subPhase) { + case LeaderElectionParticle::LeaderElectionAgent:: + SubPhase::SegmentSetup: return indent + "segment setup\n"; + case LeaderElectionParticle::LeaderElectionAgent:: + SubPhase::IdentifierSetup: return indent + "identifier setup\n"; + case LeaderElectionParticle::LeaderElectionAgent:: + SubPhase::IdentifierComparison: + return indent + "identifer comparison\n"; + case LeaderElectionParticle::LeaderElectionAgent:: + SubPhase::SolitudeVerification: + return indent + "solitude verification\n"; + } + case State::SoleCandidate: return indent + "sole candidate\n"; + default: return indent + "invalid\n"; + } + }(); + text += indent + indent + "agent dir: " + QString::number(agent->agentDir) + + "\n"; + text += indent + indent + "next agent dir: " + + QString::number(nextAgentDir) + "\n"; + text += indent + indent + "prev agent dir: " + + QString::number(prevAgentDir) + "\n"; + text += indent + indent + "number of digit tokens: " + + QString::number(agent->countAgentTokens(nextAgentDir)) + + "\n"; + text += indent + indent + "number of delimiter tokens: " + + QString::number(agent->countAgentTokens(nextAgentDir)) + + "\n"; + text += indent + indent + "has generated reverse tokens: " + + QString::number(agent->hasGeneratedReverseToken) + "\n"; + text += indent + indent + "number of setup tokens: " + + QString::number(agent->countAgentTokens(nextAgentDir) + + agent->countAgentTokens(prevAgentDir)) + + "\n"; + text += indent + indent + "id value: " + QString::number(agent->idValue) + + "\n"; + text += indent + indent + "compare status: " + + QString::number(agent->compareStatus) + "\n"; + } + text += "has leader election tokens: " + + QString::number(hasToken()) + "\n"; + text += "has solitude active token: " + + QString::number(hasToken()) + "\n"; + text += "has " + QString::number(countTokens()) + + " positive x tokens\n"; + text += "has " + QString::number(countTokens()) + + " negative x tokens\n"; + text += "has " + QString::number(countTokens()) + + " positive y tokens\n"; + text += "has " + QString::number(countTokens()) + + " negative y tokens\n"; + text += "leader has been selected: " + QString::number(leaderSelected) + "\n"; + text += "\n"; + + return text; +} + +std::array LeaderElectionParticle::borderColors() const { + return borderColorLabels; +} + +std::array LeaderElectionParticle::borderPointColors() const { + return borderPointColorLabels; +} + +LeaderElectionParticle& LeaderElectionParticle::nbrAtLabel(int label) const { + return AmoebotParticle::nbrAtLabel(label); +} + +int LeaderElectionParticle::getNextAgentDir(const int agentDir) const { + Q_ASSERT(!hasNbrAtLabel(agentDir)); + + for (int dir = 1; dir < 6; dir++) { + if (hasNbrAtLabel((agentDir - dir + 6) % 6)) { + return (agentDir - dir + 6) % 6; + } + } + + Q_ASSERT(false); + return -1; +} + +int LeaderElectionParticle::getPrevAgentDir(const int agentDir) const { + Q_ASSERT(!hasNbrAtLabel(agentDir)); + for (int dir = 1; dir < 6; dir++) { + if (hasNbrAtLabel((agentDir + dir) % 6)) { + return (agentDir + dir) % 6; + } + } + + Q_ASSERT(false); + return -1; +} + +int LeaderElectionParticle::getNumberOfNbrs() const { + int count = 0; + for (int dir = 0; dir < 6; dir++) { + if (hasNbrAtLabel(dir)) { + count++; + } + } + return count; +} +//----------------------------END PARTICLE CODE---------------------------- + +//----------------------------BEGIN AGENT CODE---------------------------- + +LeaderElectionParticle::LeaderElectionAgent::LeaderElectionAgent() : + localId(-1), + agentDir(-1), + nextAgentDir(-1), + prevAgentDir(-1), + agentState(State::Idle), + candidateParticle(nullptr) +{} + +void LeaderElectionParticle::LeaderElectionAgent::activate() { + passTokensDir = randInt(0, 2); + if (agentState == State::Candidate) { + LeaderElectionAgent* next = nextAgent(); + LeaderElectionAgent* prev = prevAgent(); + + if (subPhase == SubPhase::SegmentSetup) { + bool coinFlip = randBool(); + if (coinFlip) { + subPhase = SubPhase::IdentifierSetup; + } else { + agentState = State::Demoted; + } + setStateColor(); + return; + } + + // Identifier Setup + if (passTokensDir == 1 && hasAgentToken(prevAgentDir) && + prev != nullptr && prev->idValue != -1 && + !prev->hasGeneratedReverseToken) { + takeAgentToken(prevAgentDir); + passAgentToken(prevAgentDir, + std::make_shared()); + } + + if (hasAgentToken(nextAgentDir) && + !hasGeneratedReverseToken) { + if (next != nullptr && (next->agentState == State::Candidate || + next->demotedFromComparison)) { + takeAgentToken(nextAgentDir); + candidateParticle->putToken( + std::make_shared(nextAgentDir, comparisonColor, + idValue)); + hasGeneratedReverseToken = true; + paintFrontSegment(comparisonColor); + paintBackSegment(0x696969); + } else if (passTokensDir == 0 && next != nullptr) { + Q_ASSERT(next->idValue != -1); + Q_ASSERT(!next->hasGeneratedReverseToken); + + std::shared_ptr token = + takeAgentToken(nextAgentDir); + candidateParticle->putToken( + std::make_shared(nextAgentDir, token->val)); + std::shared_ptr nextToken = + std::make_shared(-1, idValue, comparisonColor); + passAgentToken(nextAgentDir, nextToken); + hasGeneratedReverseToken = true; + paintFrontSegment(0x696969); + paintBackSegment(0x696969); + } + } + + // Identifier Comparison + if (hasAgentToken(nextAgentDir)) { + if (isActive && peekAgentToken(nextAgentDir)->isActive) { + isActive = false; + peekAgentToken(nextAgentDir)->isActive = false; + demotedFromComparison = true; + } + if (canPassComparisonToken(false)) { + peekAgentToken(nextAgentDir)->isActive = true; + passAgentToken(prevAgentDir, + takeAgentToken(nextAgentDir)); + } + } + + if (hasAgentToken(nextAgentDir)) { + if (isActive && peekAgentToken(nextAgentDir)->isActive) { + int tokenValue = peekAgentToken(nextAgentDir)->value; + int status = peekAgentToken(nextAgentDir)->compare; + compareStatus = idValue - tokenValue; + isActive = false; + peekAgentToken(nextAgentDir)->isActive = false; + if (compareStatus == -1 || (status == -1 && compareStatus == 0)) { + demotedFromComparison = true; + } else if (compareStatus == 0 && status == 0) { + subPhase = SubPhase::SolitudeVerification; + setStateColor(); + } + } + if (canPassComparisonToken(true)) { + int color = + peekAgentToken(nextAgentDir)->comparisonColor; + peekAgentToken(nextAgentDir)->isActive = true; + passAgentToken(prevAgentDir, + takeAgentToken + (nextAgentDir)); + if (prev->agentState == State::Candidate) { + prev->paintFrontSegment(color); + } else { + prev->paintFrontSegment(color); + prev->paintBackSegment(color); + } + paintFrontSegment(0x696969); + isActive = true; + } + } + + // Solitude Verification + // Here we check whether or not the SolitudeActiveToken has a set local_id + // to avoid the a possibility that the current agent might incorrectly + // assume that the SolitudeActiveToken owned by the particle is meant for + // the agent to pass back (and set the local_id value for); for example, in + // the case where a candidate agent's nextAgentDir and prevAgentDir are the + // same. + if (passTokensDir == 1 && + hasAgentToken(prevAgentDir) && + peekAgentToken(prevAgentDir)->local_id == -1) { + peekAgentToken(prevAgentDir)->local_id = localId; + passAgentToken + (prevAgentDir, takeAgentToken(prevAgentDir)); + paintBackSegment(0x696969); + } + + if (hasAgentToken(nextAgentDir)) { + peekAgentToken(nextAgentDir)->isSettled = true; + } + + if (hasAgentToken(nextAgentDir)) { + peekAgentToken(nextAgentDir)->isSettled = true; + } + + if (hasAgentToken(nextAgentDir)) { + peekAgentToken(nextAgentDir)->isSettled = true; + } + + if (hasAgentToken(nextAgentDir)) { + peekAgentToken(nextAgentDir)->isSettled = true; + } + + if (subPhase == SubPhase::IdentifierSetup) { + if (!hasGeneratedSetupToken && passTokensDir == 0) { + passAgentToken(nextAgentDir, + std::make_shared + (comparisonColor)); + idValue = randInt(0,2); + hasGeneratedSetupToken = true; + paintFrontSegment(0xffa500); + return; + } else if (hasGeneratedReverseToken) { + subPhase = SubPhase::IdentifierComparison; + setStateColor(); + return; + } + } else if (subPhase == SubPhase::IdentifierComparison) { + if (demotedFromComparison) { + agentState = State::Demoted; + setStateColor(); + return; + } + } else if (subPhase == SubPhase::SolitudeVerification) { + if (!createdLead && passTokensDir == 0) { + passAgentToken + (nextAgentDir, std::make_shared()); + candidateParticle->putToken + (std::make_shared(nextAgentDir, true)); + paintFrontSegment(0x00bfff); + createdLead = true; + hasGeneratedTokens = true; + } else if (hasAgentToken(nextAgentDir) && + peekAgentToken + (nextAgentDir)->local_id != -1) { + int checkX = checkSolitudeXTokens(); + int checkY = checkSolitudeYTokens(); + bool isSole = + peekAgentToken(nextAgentDir)->isSoleCandidate; + int id = peekAgentToken(nextAgentDir)->local_id; + if (isSole && localId == id && checkX == 2 && checkY == 2) { + agentState = State::SoleCandidate; + } else if (checkX == 1 || checkY == 1) { + // It should never reach this state since all solitude vector tokens + // that reach a candidate agent are settled + Q_ASSERT(false); + return; + } else if (!demotedFromComparison) { + subPhase = SubPhase::IdentifierComparison; + } else { + agentState = State::Demoted; + } + takeAgentToken(nextAgentDir); + createdLead = false; + cleanSolitudeVerificationTokens(); + setStateColor(); + return; + } + } + } else if (agentState == State::Demoted) { + LeaderElectionAgent* next = nextAgent(); + LeaderElectionAgent* prev = prevAgent(); + + if (passTokensDir == 0 && hasAgentToken(prevAgentDir)) { + std::shared_ptr token = + takeAgentToken(prevAgentDir); + token->borderSum = addNextBorder(token->borderSum); + passAgentToken(nextAgentDir, token); + cleanAllTokens(); + paintBackSegment(-1); + paintFrontSegment(-1); + agentState = State::Finished; + setStateColor(); + } + + // Identifier Setup + if (hasAgentToken(prevAgentDir)) { + if (demotedFromComparison && passTokensDir == 1 && prev != nullptr) { + Q_ASSERT(prev->idValue != -1); + Q_ASSERT(!prev->hasGeneratedReverseToken); + + takeAgentToken(prevAgentDir); + passAgentToken(prevAgentDir, + std::make_shared()); + } else if (!demotedFromComparison && passTokensDir == 0) { + Q_ASSERT(idValue == -1); + + idValue = randInt(0, 2); + passAgentToken(nextAgentDir, + takeAgentToken(prevAgentDir)); + paintFrontSegment(0xffa500); + paintBackSegment(0xffa500); + } + } + + if (hasAgentToken(nextAgentDir) && + !hasGeneratedReverseToken) { + Q_ASSERT(!demotedFromComparison); + + if (prev != nullptr && prev->hasGeneratedReverseToken) { + if (next != nullptr && next->hasGeneratedReverseToken) { + takeAgentToken(nextAgentDir); + candidateParticle->putToken( + std::make_shared(nextAgentDir, idValue)); + hasGeneratedReverseToken = true; + paintFrontSegment(0x696969); + paintBackSegment(0x696969); + } else if (next != nullptr && passTokensDir == 0) { + std::shared_ptr token = + takeAgentToken(nextAgentDir); + candidateParticle->putToken( + std::make_shared(nextAgentDir, token->val)); + std::shared_ptr nextToken = + std::make_shared(); + nextToken->val = idValue; + hasGeneratedReverseToken = true; + passAgentToken(nextAgentDir, nextToken); + paintFrontSegment(0x696969); + paintBackSegment(0x696969); + } + } else if (passTokensDir == 1 && prev != nullptr) { + std::shared_ptr token = + takeAgentToken(nextAgentDir); + if (token->val == -1) { + token->val = idValue; + } + passAgentToken(prevAgentDir, token); + } + } + + if (hasAgentToken(prevAgentDir) && + !hasGeneratedReverseToken) { + if (next != nullptr && prev != nullptr && passTokensDir == 1 && + (next->agentState == State::Candidate || + next->hasGeneratedReverseToken || next->demotedFromComparison)) { + std::shared_ptr token = + takeAgentToken(prevAgentDir); + if (next->agentState == State::Candidate || + next->demotedFromComparison) { + candidateParticle->putToken( + std::make_shared(nextAgentDir, + token->comparisonColor, + token->val)); + paintFrontSegment(token->comparisonColor); + paintBackSegment(0x696969); + } else { + candidateParticle->putToken( + std::make_shared(nextAgentDir, token->val)); + paintFrontSegment(0x696969); + paintBackSegment(0x696969); + } + hasGeneratedReverseToken = true; + if (!prev->hasGeneratedReverseToken) { + passAgentToken(prevAgentDir, + std::make_shared()); + } + } else if (next != nullptr && passTokensDir == 0 && + !(next->agentState == State::Candidate || + next->hasGeneratedReverseToken || + next->demotedFromComparison)) { + passAgentToken(nextAgentDir, + takeAgentToken + (prevAgentDir)); + } + } + + // Identifier Comparison + if (hasAgentToken(nextAgentDir)) { + if (demotedFromComparison) { + if (canPassComparisonToken(false)) { + peekAgentToken(nextAgentDir)->isActive = true; + passAgentToken(prevAgentDir, + takeAgentToken(nextAgentDir)); + } + } else { + if (isActive && peekAgentToken(nextAgentDir)->isActive) { + isActive = false; + peekAgentToken(nextAgentDir)->isActive = false; + int tokenValue = peekAgentToken(nextAgentDir)->value; + compareStatus = idValue - tokenValue; + } + if (canPassComparisonToken(false)) { + passAgentToken(prevAgentDir, + takeAgentToken(nextAgentDir)); + } + } + } + + if (hasAgentToken(nextAgentDir)) { + if (demotedFromComparison) { + if (canPassComparisonToken(true)) { + int color = + peekAgentToken(nextAgentDir)->comparisonColor; + peekAgentToken(nextAgentDir)->isActive = true; + peekAgentToken(nextAgentDir)->compare = 0; + passAgentToken(prevAgentDir, + takeAgentToken + (nextAgentDir)); + prev->paintFrontSegment(color); + prev->paintBackSegment(color); + paintFrontSegment(0x696969); + paintBackSegment(0x696969); + } + } else { + if (isActive && + peekAgentToken(nextAgentDir)->isActive) { + isActive = false; + peekAgentToken(nextAgentDir)->isActive = false; + } + if (compareStatus != 0) { + peekAgentToken(nextAgentDir)->compare = compareStatus; + } + if (canPassComparisonToken(true)) { + int color = + peekAgentToken(nextAgentDir)->comparisonColor; + isActive = true; + passAgentToken(prevAgentDir, + takeAgentToken + (nextAgentDir)); + compareStatus = 0; + prev->paintFrontSegment(color); + prev->paintBackSegment(color); + paintFrontSegment(0x696969); + paintBackSegment(0x696969); + } + } + } + + // Solitude Verification Tokens + if (passTokensDir == 0 && + hasAgentToken(prevAgentDir) && + peekAgentToken(prevAgentDir)->local_id == -1 && + !hasGeneratedTokens) { + std::shared_ptr token = + takeAgentToken(prevAgentDir); + std::pair generatedPair = augmentDirVector(token->vector); + generateSolitudeVectorTokens(generatedPair); + token->vector = generatedPair; + paintBackSegment(0x00bfff); + paintFrontSegment(0x00bfff); + passAgentToken(nextAgentDir, token); + hasGeneratedTokens = true; + } else if (passTokensDir == 1 && + hasAgentToken(nextAgentDir) && + peekAgentToken + (nextAgentDir)->local_id != -1 && + hasGeneratedTokens) { + int checkX = checkSolitudeXTokens(); + int checkY = checkSolitudeYTokens(); + if ((checkX == 2 && checkY == 2) || + !peekAgentToken(nextAgentDir)->isSoleCandidate) { + passAgentToken + (prevAgentDir, takeAgentToken(nextAgentDir)); + cleanSolitudeVerificationTokens(); + } else if (checkX == 0 || checkY == 0) { + std::shared_ptr token = + takeAgentToken(nextAgentDir); + token->isSoleCandidate = false; + passAgentToken(prevAgentDir, token); + cleanSolitudeVerificationTokens(); + } + } + + if (hasAgentToken(nextAgentDir) && + !peekAgentToken(nextAgentDir)->isSettled && + hasGeneratedTokens) { + if (passTokensDir == 1 && prev != nullptr && + !prev->hasAgentToken(prev->nextAgentDir)) { + passAgentToken + (prevAgentDir, + takeAgentToken(nextAgentDir)); + } else if (prev != nullptr && + prev->hasAgentToken + (prev->nextAgentDir) && + prev->peekAgentToken + (prev->nextAgentDir)->isSettled) { + peekAgentToken(nextAgentDir)->isSettled = true; + } + } + + if (hasAgentToken(nextAgentDir) && + !peekAgentToken(nextAgentDir)->isSettled && + hasGeneratedTokens) { + if (passTokensDir == 1 && prev != nullptr && + !prev->hasAgentToken(prev->nextAgentDir)) { + passAgentToken + (prevAgentDir, + takeAgentToken(nextAgentDir)); + } else if (prev != nullptr && + prev->hasAgentToken + (prev->nextAgentDir) && + prev->peekAgentToken + (prev->nextAgentDir)->isSettled) { + peekAgentToken(nextAgentDir)->isSettled = true; + } + } + + if (hasAgentToken(nextAgentDir) && + !peekAgentToken(nextAgentDir)->isSettled && + hasGeneratedTokens) { + if (passTokensDir == 1 && prev != nullptr && + !prev->hasAgentToken(prev->nextAgentDir)) { + passAgentToken + (prevAgentDir, + takeAgentToken(nextAgentDir)); + } else if (prev != nullptr && + prev->hasAgentToken + (prev->nextAgentDir) && + prev->peekAgentToken + (prev->nextAgentDir)->isSettled) { + peekAgentToken(nextAgentDir)->isSettled = true; + } + } + + if (hasAgentToken(nextAgentDir) && + !peekAgentToken(nextAgentDir)->isSettled && + hasGeneratedTokens) { + if (passTokensDir == 1 && prev != nullptr && + !prev->hasAgentToken(prev->nextAgentDir)) { + passAgentToken + (prevAgentDir, + takeAgentToken(nextAgentDir)); + } else if (prev != nullptr && + prev->hasAgentToken + (prev->nextAgentDir) && + prev->peekAgentToken + (prev->nextAgentDir)->isSettled) { + peekAgentToken(nextAgentDir)->isSettled = true; + } + } + + } else if (agentState == State::SoleCandidate) { + if (!testingBorder) { + std::shared_ptr token = + std::make_shared(prevAgentDir, addNextBorder(0)); + passAgentToken(nextAgentDir, token); + paintFrontSegment(-1); + testingBorder = true; + } else if (hasAgentToken(prevAgentDir) && + peekAgentToken(prevAgentDir)->borderSum != -1) { + int borderSum = takeAgentToken(prevAgentDir)->borderSum; + paintBackSegment(-1); + if (borderSum == 1) { + agentState = State::Leader; + } else if (borderSum == 4) { + agentState = State::Finished; + } else { + Q_ASSERT(false); + } + setStateColor(); + testingBorder = false; + return; + } + } else if (agentState == State::Finished) { + cleanAllTokens(); + paintBackSegment(-1); + paintFrontSegment(-1); + } +} + +void LeaderElectionParticle::LeaderElectionAgent::cleanAllTokens() { + cleanSolitudeVerificationTokens(); + while (hasAgentToken(nextAgentDir)) { + takeAgentToken(nextAgentDir); + } + if (hasAgentToken(nextAgentDir)) { + takeAgentToken(nextAgentDir); + } +} + +bool LeaderElectionParticle::LeaderElectionAgent:: +canPassComparisonToken(bool isDelimiter) const { + LeaderElectionAgent* prev = prevAgent(); + if (prev == nullptr || !prev->hasGeneratedReverseToken || + passTokensDir != 1) { + return false; + } + int prevNextAgentDir = prev->nextAgentDir; + if (isDelimiter) { + if (!prev->hasAgentToken(prevNextAgentDir) && + !prev->hasAgentToken(prevNextAgentDir)) { + return true; + } + } else { + int prevCountDigit = prev->countAgentTokens(prevNextAgentDir); + if (prevCountDigit < 2 && + !prev->hasAgentToken(prevNextAgentDir)) { + return true; + } + } + return false; +} + +std::pair LeaderElectionParticle::LeaderElectionAgent:: +augmentDirVector(std::pair vector) { + unsigned int offset = (nextAgentDir - ((prevAgentDir + 3) % 6) + 6) % 6; + const std::array, 6> vectors = + { std::make_pair(1, 0), std::make_pair(0, 1), std::make_pair(-1, 1), + std::make_pair(-1, 0), std::make_pair(0, -1), std::make_pair(1, -1) }; + + for (unsigned i = 0; i < vectors.size(); ++i) { + if (vector == vectors.at(i)) { + return vectors.at((i + offset) % 6); + } + } + + Q_ASSERT(false); + return std::make_pair(0, 0); +} + +void LeaderElectionParticle::LeaderElectionAgent:: +generateSolitudeVectorTokens(std::pair vector) { + // We initialize the solitude vector tokens with an origin direction + // of nextAgentDir because this is the only direction from which these + // tokens can come from, so it does not matter whether or not these tokens + // were generated by the agent or if they were passed to the agent. + switch(vector.first) { + case -1: + candidateParticle->putToken + (std::make_shared(nextAgentDir, false)); + break; + case 0: + break; + case 1: + candidateParticle->putToken + (std::make_shared(nextAgentDir, false)); + break; + default: + Q_ASSERT(false); + break; + } + switch(vector.second) { + case -1: + candidateParticle->putToken + (std::make_shared(nextAgentDir, false)); + break; + case 0: + break; + case 1: + candidateParticle->putToken + (std::make_shared(nextAgentDir, false)); + break; + default: + Q_ASSERT(false); + break; + } +} + +int LeaderElectionParticle::LeaderElectionAgent::checkSolitudeXTokens() const { + if (hasAgentToken(nextAgentDir) && + hasAgentToken(nextAgentDir)) { + if (peekAgentToken(nextAgentDir)->isSettled && + peekAgentToken(nextAgentDir)->isSettled) { + return 2; + } else { + return 1; + } + } else if (hasAgentToken(nextAgentDir)) { + if (peekAgentToken(nextAgentDir)->isSettled) { + return 0; + } else { + return 1; + } + } else if (hasAgentToken(nextAgentDir)) { + if (peekAgentToken(nextAgentDir)->isSettled) { + return 0; + } else { + return 1; + } + } else { + return 2; + } +} + +int LeaderElectionParticle::LeaderElectionAgent::checkSolitudeYTokens() const { + if (hasAgentToken(nextAgentDir) && + hasAgentToken(nextAgentDir)) { + if (peekAgentToken(nextAgentDir)->isSettled && + peekAgentToken(nextAgentDir)->isSettled) { + return 2; + } else { + return 1; + } + } else if (hasAgentToken(nextAgentDir)) { + if (peekAgentToken(nextAgentDir)->isSettled) { + return 0; + } else { + return 1; + } + } else if (hasAgentToken(nextAgentDir)) { + if (peekAgentToken(nextAgentDir)->isSettled) { + return 0; + } else { + return 1; + } + } else { + return 2; + } +} + +void LeaderElectionParticle::LeaderElectionAgent:: +cleanSolitudeVerificationTokens() { + if (hasAgentToken(nextAgentDir)) { + takeAgentToken(nextAgentDir); + } + if (hasAgentToken(nextAgentDir)) { + takeAgentToken(nextAgentDir); + } + if (hasAgentToken(nextAgentDir)) { + takeAgentToken(nextAgentDir); + } + if (hasAgentToken(nextAgentDir)) { + takeAgentToken(nextAgentDir); + } + paintFrontSegment(0x696969); + paintBackSegment(0x696969); + hasGeneratedTokens = false; +} + +int LeaderElectionParticle::LeaderElectionAgent:: +addNextBorder(int currentSum) const { + // adjust offset in modulo 6 to be compatible with modulo 5 computations + int offsetMod6 = (prevAgentDir + 3) % 6 - nextAgentDir; + if(4 <= offsetMod6 && offsetMod6 <= 5) { + offsetMod6 -= 6; + } else if(-5 <= offsetMod6 && offsetMod6 <= -3) { + offsetMod6 += 6; + } + + return (currentSum + offsetMod6 + 5) % 5; +} + +template +bool LeaderElectionParticle::LeaderElectionAgent:: +hasAgentToken(int agentDir) const{ + auto prop = [agentDir](const std::shared_ptr token) { + return token->origin == agentDir; + }; + return candidateParticle->hasToken(prop); +} + +template +std::shared_ptr +LeaderElectionParticle::LeaderElectionAgent:: +peekAgentToken(int agentDir) const { + auto prop = [agentDir](const std::shared_ptr token) { + return token->origin == agentDir; + }; + return candidateParticle->peekAtToken(prop); +} + +template +std::shared_ptr +LeaderElectionParticle::LeaderElectionAgent::takeAgentToken(int agentDir) { + auto prop = [agentDir](const std::shared_ptr token) { + return token->origin == agentDir; + }; + return candidateParticle->takeToken(prop); +} + +template +void LeaderElectionParticle::LeaderElectionAgent:: +passAgentToken(int agentDir, std::shared_ptr token) { + LeaderElectionParticle* nbr = &candidateParticle->nbrAtLabel(agentDir); + int origin = -1; + for (int i = 0; i < 6; i++) { + if (nbr->hasNbrAtLabel(i) && &nbr->nbrAtLabel(i) == candidateParticle) { + origin = i; + break; + } + } + Q_ASSERT(origin != -1); + token->origin = origin; + nbr->putToken(token); +} + +template +int LeaderElectionParticle::LeaderElectionAgent:: +countAgentTokens(int agentDir) const { + auto prop = [agentDir](const std::shared_ptr token) { + return token->origin == agentDir; + }; + return candidateParticle->countTokens(prop); +} + +LeaderElectionParticle::LeaderElectionAgent* +LeaderElectionParticle::LeaderElectionAgent::nextAgent() const { + LeaderElectionParticle* nextNbr = + &candidateParticle->nbrAtLabel(nextAgentDir); + int originLabel = -1; + for (int i = 0; i < 6; i++) { + if (nextNbr->hasNbrAtLabel(i) && + &nextNbr->nbrAtLabel(i) == candidateParticle) { + originLabel = i; + break; + } + } + Q_ASSERT(originLabel != -1); + for (LeaderElectionAgent* agent : nextNbr->agents) { + if (agent->prevAgentDir == originLabel) { + return agent; + } + } + Q_ASSERT(nextNbr->agents.size() == 0); + return nullptr; +} + +LeaderElectionParticle::LeaderElectionAgent* +LeaderElectionParticle::LeaderElectionAgent::prevAgent() const { + LeaderElectionParticle* prevNbr = + &candidateParticle->nbrAtLabel(prevAgentDir); + int originLabel = -1; + for (int i = 0; i < 6; i++) { + if (prevNbr->hasNbrAtLabel(i) && + &prevNbr->nbrAtLabel(i) == candidateParticle) { + originLabel = i; + break; + } + } + Q_ASSERT(originLabel != -1); + for (LeaderElectionAgent* agent : prevNbr->agents) { + if (agent->nextAgentDir == originLabel) { + return agent; + } + } + Q_ASSERT(prevNbr->agents.size() == 0); + return nullptr; +} + +void LeaderElectionParticle::LeaderElectionAgent::setStateColor() { + int globalizedDir = candidateParticle->localToGlobalDir(agentDir); + switch (agentState) { + case State::Candidate: + setSubPhaseColor(); + break; + case State::Demoted: + candidateParticle->borderPointColorLabels.at(globalizedDir) = 0x696969; + break; + case State::SoleCandidate: + candidateParticle->borderPointColorLabels.at(globalizedDir) = 0x00ff00; + break; + case State::Leader: + candidateParticle->borderPointColorLabels.at(globalizedDir) = -1; + break; + case State::Finished: + candidateParticle->borderPointColorLabels.at(globalizedDir) = -1; + break; + default: + break; + } +} + +void LeaderElectionParticle::LeaderElectionAgent::setSubPhaseColor() { + int globalizedDir = candidateParticle->localToGlobalDir(agentDir); + switch (subPhase) { + case SubPhase::SegmentSetup: + candidateParticle->borderPointColorLabels.at(globalizedDir) = 0xff0000; + break; + case SubPhase::IdentifierSetup: + candidateParticle->borderPointColorLabels.at(globalizedDir) = 0xffa500; + break; + case SubPhase::IdentifierComparison: + candidateParticle->borderPointColorLabels.at(globalizedDir) = + comparisonColor; + break; + case SubPhase::SolitudeVerification: + candidateParticle->borderPointColorLabels.at(globalizedDir) = 0x00bfff; + break; + default: + Q_ASSERT(false); + break; + } +} + +void LeaderElectionParticle::LeaderElectionAgent::paintFrontSegment( + const int color) { + // Must use localToGlobalDir method to reconcile the difference between the + // local orientation of the particle and the global orientation used by + // drawing + int tempDir = candidateParticle->localToGlobalDir(agentDir); + int tempNextDir = candidateParticle->localToGlobalDir(nextAgentDir); + while (tempDir != (tempNextDir + 1) % 6) { + if ((tempDir + 5) % 6 != tempNextDir) { + candidateParticle->borderColorLabels.at((3 * tempDir + 17) % 18) = + color; + } + tempDir = (tempDir + 5) % 6; + } +} + +void LeaderElectionParticle::LeaderElectionAgent::paintBackSegment( + const int color) { + // Must use localToGlobalDir method to reconcile the difference between the + // local orientation of the particle and the global orientation used by + // drawing + candidateParticle->borderColorLabels.at( + 3 * candidateParticle->localToGlobalDir(agentDir) + 1) = color; +} + +//----------------------------END AGENT CODE---------------------------- + +//----------------------------BEGIN SYSTEM CODE---------------------------- + +ImprovedLeaderElectionSystem::ImprovedLeaderElectionSystem(int numParticles, + double holeProb) { + Q_ASSERT(numParticles > 0); + Q_ASSERT(0 <= holeProb && holeProb <= 1); + + // Insert the seed at (0,0). + insert(new LeaderElectionParticle(Node(0, 0), -1, randDir(), *this, + LeaderElectionParticle::State::Idle)); + std::set occupied; + occupied.insert(Node(0, 0)); + + std::set candidates; + for (int i = 0; i < 6; ++i) { + candidates.insert(Node(0, 0).nodeInDir(i)); + } + + // Add inactive particles. + int numNonStaticParticles = 0; + while (numNonStaticParticles < numParticles && !candidates.empty()) { + // Pick random candidate. + int randIndex = randInt(0, candidates.size()); + Node randomCandidate; + for (auto it = candidates.begin(); it != candidates.end(); ++it) { + if (randIndex == 0) { + randomCandidate = *it; + candidates.erase(it); + break; + } else { + randIndex--; + } + } + + occupied.insert(randomCandidate); + + // Add this candidate as a particle if not a hole. + if (randBool(1.0 - holeProb)) { + insert(new LeaderElectionParticle(randomCandidate, -1, randDir(), *this, + LeaderElectionParticle::State::Idle)); + ++numNonStaticParticles; + + // Add new candidates. + for (int i = 0; i < 6; ++i) { + auto neighbor = randomCandidate.nodeInDir(i); + if (occupied.find(neighbor) == occupied.end()) { + candidates.insert(neighbor); + } + } + } + } +} + +bool ImprovedLeaderElectionSystem::hasTerminated() const { + #ifdef QT_DEBUG + if (!isConnected(particles)) { + return true; + } + #endif + + for (auto p : particles) { + auto hp = dynamic_cast(p); + if (hp->state != LeaderElectionParticle::State::Leader && + hp->state != LeaderElectionParticle::State::Finished) { + return false; + } + } + + return true; +} diff --git a/alg/improvedleaderelection.h b/alg/improvedleaderelection.h new file mode 100644 index 00000000..f38c71a0 --- /dev/null +++ b/alg/improvedleaderelection.h @@ -0,0 +1,387 @@ +// Defines the particle system and composing particles for the Improved +// Leader Election Algorithm as alluded to in 'Improved Leader Election for +// Self-Organizing Programmable Matter' +// [https://arxiv.org/abs/1701.03616]. +// +// A side remark about the algorithm is about an additional condition that may +// cause the algorithm to fail to elect a leader (apart from the probability +// that all agents elect to demote themselves in the Segment Setup phase): +// In the Segment Setup phase, if the agents determine their agent states in +// such a way that each candidate agent is either 1 demoted agent away from +// one another, or directly next to each other, the algorithm will not progress +// beyond the Identifier Comparison phase and fail to elect a leader. This may +// be observed in cycles of smaller lengths (such as ones in the inner +// boundary). + +#ifndef AMOEBOTSIM_ALG_IMPROVEDLEADERELECTION_H +#define AMOEBOTSIM_ALG_IMPROVEDLEADERELECTION_H + +#include "core/amoebotparticle.h" +#include "core/amoebotsystem.h" + +#include +#include +#include + +class LeaderElectionParticle : public AmoebotParticle { + public: + enum class State { + Idle, + Candidate, + SoleCandidate, + Demoted, + Leader, + Finished + }; + + // Constructs a new particle with a node position for its head, a global + // compass direction from its head to its tail (-1 if contracted), an offset + // for its local compass, and a system which it belongs to. + LeaderElectionParticle(const Node head, const int globalTailDir, + const int orientation, AmoebotSystem& system, + State state); + + // Executes one particle activation. + virtual void activate(); + + // Functions for altering a particle's cosmetic appearance; headMarkColor + // (respectively, tailMarkColor) returns the color to be used for the ring + // drawn around the head (respectively, tail) node. Tail color is not shown + // when the particle is contracted. + virtual int headMarkColor() const; + virtual int tailMarkColor() const; + + // Returns the string to be displayed when this particle is inspected; used + // to snapshot the current values of this particle's memory at runtime. + virtual QString inspectionText() const; + + // Returns the borderColors and borderPointColors arrays associated with the + // particle to draw the boundaries for leader election. + virtual std::array borderColors() const; + virtual std::array borderPointColors() const; + + // Gets a reference to the neighboring particle incident to the specified port + // label. Crashes if no such particle exists at this label; consider using + // hasNbrAtLabel() first if unsure. + LeaderElectionParticle& nbrAtLabel(int label) const; + + // Returns the label associated with the direction which the next (resp. + // previous) agent is according to the cycle that the agent is on (which is + // determined by the provided agentDir parameter) + int getNextAgentDir(const int agentDir) const; + int getPrevAgentDir(const int agentDir) const; + + // Returns a count of the number of particle neighbors surrounding the calling + // particle. + int getNumberOfNbrs() const; + + protected: + // The LeaderElectionToken struct provides a general framework of what a + // token under the Improved Leader Election algorithm behaves. + struct LeaderElectionToken : public Token { + // origin is used to define the direction (label) from which a + // LeaderElectionToken has been sent from. + int origin; + }; + + // Tokens for Identifier Setup + struct SetUpToken : public LeaderElectionToken { + int comparisonColor = -1; + SetUpToken(int comparisonColor = -1, int origin = -1) { + this->comparisonColor = comparisonColor; + this->origin = origin; + } + }; + + struct NextIDPassToken : public LeaderElectionToken { + int val = -1; + int comparisonColor = -1; + NextIDPassToken(int origin = -1, int val = -1, int comparisonColor = -1) { + this->origin = origin; + this->val = val; + this->comparisonColor = comparisonColor; + } + }; + + struct PrevIDPassToken : public LeaderElectionToken { + int val = -1; + PrevIDPassToken(int origin = -1, int val = -1) { + this->origin = origin; + this->val = val; + } + }; + + // Tokens for Identifier Comparison + struct DigitToken : public LeaderElectionToken { + int value = -1; + bool isActive = false; + DigitToken(int origin = -1, int value = -1, bool isActive = false) { + this->origin = origin; + this->value = value; + this->isActive = isActive; + } + }; + + // DelimiterToken carries the comparison values from the previous comparisons + // of agents with DigitTokens to gauge the compare the id segments of the + // different candidate agnets. The value stored in compare follows the same + // guidelines as in compareStatus (see below). + struct DelimiterToken : public LeaderElectionToken { + int value = 0; + bool isActive = false; + int compare = 0; + int comparisonColor = -1; + DelimiterToken(int origin = -1, int comparisonColor = -1, + int value = -1, bool isActive = false, int compare = 0) { + this->origin = origin; + this->comparisonColor = comparisonColor; + this->value = value; + this->isActive = isActive; + this->compare = compare; + } + }; + + // Tokens for Solitude Verification + struct SolitudeActiveToken : public LeaderElectionToken { + bool isSoleCandidate; + std::pair vector; + int local_id; + SolitudeActiveToken(int origin = -1, + std::pair vector = std::make_pair(1, 0), + int local_id = -1, + bool isSole = true) { + this->origin = origin; + this->vector = vector; + this->local_id = local_id; + this->isSoleCandidate = isSole; + } + }; + struct SolitudeVectorToken : public LeaderElectionToken { + bool isSettled; + }; + + struct SolitudePositiveXToken : public SolitudeVectorToken { + SolitudePositiveXToken(int origin = -1, bool settled = false) { + this->origin = origin; + this->isSettled = settled; + } + }; + struct SolitudePositiveYToken : public SolitudeVectorToken { + SolitudePositiveYToken(int origin = -1, bool settled = false) { + this->origin = origin; + this->isSettled = settled; + } + }; + struct SolitudeNegativeXToken : public SolitudeVectorToken { + SolitudeNegativeXToken(int origin = -1, bool settled = false) { + this->origin = origin; + this->isSettled = settled; + } + }; + struct SolitudeNegativeYToken : public SolitudeVectorToken { + SolitudeNegativeYToken(int origin = -1, bool settled = false) { + this->origin = origin; + this->isSettled = settled; + } + }; + + // Token for Border Testing + struct BorderTestToken : public LeaderElectionToken { + int borderSum; + BorderTestToken(int origin = -1, int borderSum = -1) { + this->origin = origin; + this->borderSum = borderSum; + } + }; + private: + friend class ImprovedLeaderElectionSystem; + + // The nested class LeaderElectionAgent is used to define the behavior for the + // agents as described in the paper + class LeaderElectionAgent { + public: + enum class SubPhase { + SegmentSetup = 0, + IdentifierSetup, + IdentifierComparison, + SolitudeVerification + }; + + LeaderElectionAgent(); + + // General variables in agent memory: + // The particle emulating this agent assigns it a localId in [1,3] to + // distinguish it from the other agents it may be emulating. From the + // particle's perspective, this agent is in local direction/label agentDir. + // The neighboring particle emulating the next (respectively, previous) + // agent on this agent's boundary is in local direction nextAgentDir + // (respectively, prevAgentDir). passTokensDir is used to determine if the + // agent should pass tokens toward nextAgentDir (if 0) or prevAgentDir (if + // 1). This is done to maintain the rule from direct write communication + // that a particle can only write into the memory of one of its neighbors in + // a single activation. demotedFromComparison is used to determine whether or + // not the current agent was originally a candidate, which may impact its + // behavior for phases such as Identifier Comparison. + int localId; + int agentDir, nextAgentDir, prevAgentDir; + int passTokensDir = -1; + bool demotedFromComparison = false; + + State agentState; + SubPhase subPhase; + LeaderElectionParticle* candidateParticle; + + // Variables for Identifier Setup: + // hasGeneratedSetupToken is used to determine whether or not the candidate + // agent has generated a SetupToken. + // hasGeneratedReverseToken is used to determine whether or not the agent + // has generated an id token for the corresponding candidate's segment. + bool hasGeneratedSetupToken = false; + bool hasGeneratedReverseToken = false; + + // Variables for Identifier Comparison: + // compareStatus is used to store the difference between the current agent + // and the DigitToken that it has matched with. compareStatus is computed as + // the difference between the agent's digit value and the DigitToken's value, + // i.e., compareStatus = idValue - DigitToken.value, with the values + // representing the following relationships: + // -1 --> agent value less than token value + // 0 --> agent value equal to token value + // 1 --> agent value greater than token value + // idValue is used to store the agent's id value generated from the + // Identifier Setup Phase. + // comparisonColor is used to store the agent's identifying color for the + // Identifier Comparison Phase. + // isActive is used to determine whether or not the agent has matched with a + // DigitToken. + int compareStatus = -1; + int idValue = -1; + int comparisonColor = randInt(0, 16777216); + bool isActive = false; + + // canPassComparisonToken is a helper functino for the Identifier Comparison + // phase to determine whether or not the current agent may pass the + // Identifier Comparison token (Digit Token or Delimiter Token, determined + // by the boolean parameter). + bool canPassComparisonToken(bool isDelimiter) const; + + // Variables for Solitude Verification + // createdLead is true if this agent generated a solitude active token and + // passed it forward during Solitude Verification. + // hasGeneratedTokens is true if this agent generated solitude vector tokens + // using the solitude active token. This is used to avoid incorrectly mixing + // tokens of different agents on the same particle in Solitude Verification. + bool createdLead = false; + bool hasGeneratedTokens = false; + + // Variables for Boundary Testing + // testingBorder is true if this agent is the sole candidate and has begun + // the Boundary Testing subphase. + bool testingBorder = false; + + // The activate function is the LeaderElectionAgent equivalent of an + // Amoebot Particle's activate function. + void activate(); + + void cleanAllTokens(); + + // Solitude Verification Methods + // augmentDirVector takes a pair as a parameter, which represents + // the current vector stored in the solitude active token. This function + // then generates the next vector according to a local coordinate system + // (which is determined when a candidate agent in the Solitude Verification + // subphase generates the solitude active token) based on the vector stored + // in the solitude active token. + std::pair augmentDirVector(std::pair vector); + + // generateSolitudeVectorTokens generates the solitude vector tokens + // (SolitudePositiveXToken, SolitudeNegativeXToken, etc.) based on the given + // parameter vector. + void generateSolitudeVectorTokens(std::pair vector); + + // The checkSolitudeXTokens and checkSolitudeYTokens are used to determine + // the condition of the solitude vector tokens that an agent might own. + // The functions will return a value contained in [0,2] depending on what + // condition the solitude vector tokens are in: + // 0 --> tokens are settled and there is a mismatch, i.e., the agent might + // have a positive x token (which as settled), but no corresponding negative + // x token. + // 1 --> at least one of the tokens is not settled + // 2 --> tokens are settled and there is a match or neither tokens are + // present on the current agent. + int checkSolitudeXTokens() const; + int checkSolitudeYTokens() const; + + // The cleanSolitudeVerificationTokens function will clean the solitude + // vector tokens owned by a particular agent as well as paint the + // front and back segments gray. + void cleanSolitudeVerificationTokens(); + + // Boundary Testing methods + int addNextBorder(int currentSum) const; + + // Methods for passing, taking, and checking the ownership of tokens at the + // agent level. + template + bool hasAgentToken(int agentDir) const; + template + std::shared_ptr peekAgentToken(int agentDir) const; + template + std::shared_ptr takeAgentToken(int agentDir); + template + void passAgentToken(int agentDir, std::shared_ptr token); + + // Method for counting the number of tokens at the agent level. + template + int countAgentTokens(int agentDir) const; + + LeaderElectionAgent* nextAgent() const; + LeaderElectionAgent* prevAgent() const; + + // Methods responsible for rendering the agents onto the simulator with their + // colors changing based on the state and the subphase of the current agent. + // Yellow --> Identifier Setup + // Random Color (determined by comparisonColor) --> Identifier Comparison + // Blue --> Solitude Verification Subphase + // Grey --> Demoted agent + // Green --> Sole candidate + void setStateColor(); + void setSubPhaseColor(); + + // Methods responsible for painting the borders which will act as physical + // representations of the cycle for leader election. + // Yellow --> Identifier Setup + // Random Color (determined by comparisonColor) --> DelimiterToken for + // Identifier Comparison + // Blue --> Solitude Verification Phase + // Grey --> No specific phase + void paintFrontSegment(const int color); + void paintBackSegment(const int color); + }; + + protected: + State state; + unsigned currentAgent; + std::vector agents; + std::array borderColorLabels; + std::array borderPointColorLabels; + + // leaderSelected is used to act as a signal for when a leader is selected to + // set all of the remaining particles to finished. + bool leaderSelected; +}; + +class ImprovedLeaderElectionSystem : public AmoebotSystem { + public: + // Constructs a system of LeaderElectionParticles with an optionally specified + // size (#particles), hole probability, and shape to form. holeProb in [0,1] + // controls how "spread out" the system is; closer to 0 is more compressed, + // closer to 1 is more expanded. + ImprovedLeaderElectionSystem(int numParticles = 100, double holeProb = 0.2); + + // Checks whether or not the system's run of the Leader Election algorithm has + // terminated (all particles in state Finished or Leader). + bool hasTerminated() const override; +}; + +#endif // AMOEBOTSIM_ALG_IMPROVEDLEADERELECTION_H diff --git a/core/amoebotparticle.h b/core/amoebotparticle.h index 62caae0e..8920903d 100644 --- a/core/amoebotparticle.h +++ b/core/amoebotparticle.h @@ -130,8 +130,7 @@ class AmoebotParticle : public LocalParticle, public RandomNumberGenerator { // for that must satisfy a particular property requirement template std::shared_ptr peekAtToken( - std::function)> - propertyCheck) const; + std::function)> propertyCheck) const; template std::shared_ptr takeToken( std::function)> propertyCheck); @@ -145,12 +144,15 @@ class AmoebotParticle : public LocalParticle, public RandomNumberGenerator { template bool hasToken() const; - // An overloaded function of hasToken has been provided in case there is a - // specific token of type TokenType that is being searched for that must - // satisfy a particular property requirement + // Overloaded functinos for countTokens and hasToken have been provided in + // case we wish to check or count the existence(s) of a particular token of + // TokenType satisfying a property requirement. template bool hasToken(std::function)> propertyCheck) const; + template + int countTokens(std::function)> + propertyCheck) const; AmoebotSystem& system; private: @@ -203,8 +205,7 @@ std::shared_ptr AmoebotParticle::takeToken() { std::shared_ptr token = std::dynamic_pointer_cast(tokens[i]); if (token != nullptr) { - std::swap(tokens[0], tokens[i]); - tokens.pop_front(); + tokens.erase(tokens.begin() + i); return token; } } @@ -231,8 +232,7 @@ std::shared_ptr AmoebotParticle::takeToken( std::shared_ptr token = std::dynamic_pointer_cast(tokens[i]); if (token != nullptr && propertyCheck(token)) { - std::swap(tokens[0], tokens[i]); - tokens.pop_front(); + tokens.erase(tokens.begin() + i); return token; } } @@ -252,6 +252,20 @@ int AmoebotParticle::countTokens() const { return count; } +template +int AmoebotParticle::countTokens( + std::function)> propertyCheck) const { + int count = 0; + for (unsigned int i = 0; i < tokens.size(); i++) { + std::shared_ptr token = + std::dynamic_pointer_cast(tokens[i]); + if (token != nullptr && propertyCheck(token)) { + count++; + } + } + return count; +} + template bool AmoebotParticle::hasToken() const { for (unsigned int i = 0; i < tokens.size(); i++) { diff --git a/script/scriptinterface.cpp b/script/scriptinterface.cpp index adb605e4..dc00abbb 100644 --- a/script/scriptinterface.cpp +++ b/script/scriptinterface.cpp @@ -11,6 +11,7 @@ #include "alg/demo/pulldemo.h" #include "alg/demo/tokendemo.h" #include "alg/compression.h" +#include "alg/improvedleaderelection.h" #include "alg/infobjcoating.h" #include "alg/leaderelection.h" #include "alg/shapeformation.h" @@ -155,6 +156,18 @@ void ScriptInterface::compression(const int numParticles, const double lambda) { } } +void ScriptInterface::improvedleaderelection(const int numParticles, + const double holeProb) { + if (numParticles <= 0) { + log("# particles must be > 0", true); + } else if (holeProb < 0 || holeProb > 1) { + log("holeProb in [0,1] required", true); + } else { + sim.setSystem(std::make_shared(numParticles, + holeProb)); + } +} + void ScriptInterface::infobjcoating(const int numParticles, const double holeProb) { if (numParticles <= 0) { diff --git a/script/scriptinterface.h b/script/scriptinterface.h index 19d90ec2..d8c59f9c 100644 --- a/script/scriptinterface.h +++ b/script/scriptinterface.h @@ -71,6 +71,8 @@ class ScriptInterface : public QObject { // Algorithm instance commands. Documentation for foo() can be found in // alg/foo.h. void compression(const int numParticles = 100, const double lambda = 4.0); + void improvedleaderelection(const int numParticles = 100, + const double holeProb = 0.2); void infobjcoating(const int numParticles = 100, const double holeProb = 0.2); void leaderelection(const int numParticles = 100, const double holeProb = 0.2); diff --git a/ui/alg.cpp b/ui/alg.cpp index ecd0cb58..2fb32e36 100644 --- a/ui/alg.cpp +++ b/ui/alg.cpp @@ -69,6 +69,11 @@ AlgorithmList::AlgorithmList() { _algorithms.back()->addParameter("# Particles", "100"); _algorithms.back()->addParameter("Lambda", "4.0"); + // Improved Leader Election. + _algorithms.push_back(new Algorithm("Improved Leader Election", "improvedleaderelection")); + _algorithms.back()->addParameter("# Particles", "100"); + _algorithms.back()->addParameter("Hole Prob.", "0.2"); + // Infinite Object Coating. _algorithms.push_back(new Algorithm("Infinite Object Coating", "infobjcoating")); _algorithms.back()->addParameter("# Particles", "100");