diff --git a/src/main/java/com/cemgokmen/particles/algorithms/AlignmentAlgorithm.java b/src/main/java/com/cemgokmen/particles/algorithms/AlignmentAlgorithm.java index 6a39389..395af06 100644 --- a/src/main/java/com/cemgokmen/particles/algorithms/AlignmentAlgorithm.java +++ b/src/main/java/com/cemgokmen/particles/algorithms/AlignmentAlgorithm.java @@ -18,22 +18,26 @@ package com.cemgokmen.particles.algorithms; -import com.cemgokmen.particles.util.RandomSelector; +import com.cemgokmen.particles.capabilities.*; import com.cemgokmen.particles.util.Utils; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; import com.cemgokmen.particles.models.amoebot.AmoebotGrid; -import com.cemgokmen.particles.models.amoebot.AmoebotParticle; import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; +import com.google.common.collect.ImmutableList; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import java.util.List; -public class AlignmentAlgorithm extends CompressionAlgorithm { +public class AlignmentAlgorithm extends ParticleAlgorithm { public static final double DEFAULT_ROTATION_BIAS = 20.0; public static final double DEFAULT_TRANSLATION_BIAS = 1.0; public static final double DEFAULT_FORWARD_BIAS = 1.1; + public static final double DEFAULT_LAMBDA = 4.0; + + public static final List> requiredCapabilities = ImmutableList.of( + MovementCapable.class, NeighborDetectionCapable.class, UniformRandomDirectionCapable.class, SpinCapable.class, WrappedNormalRandomDirectionCapable.class); protected final DoubleProperty rotationBias = new SimpleDoubleProperty(); protected final DoubleProperty translationBias = new SimpleDoubleProperty(); @@ -87,62 +91,61 @@ public AlignmentAlgorithm() { @Override public void onParticleActivation(Particle p) { - if (!(p instanceof DirectedAmoebotParticle)) { - throw new RuntimeException("This particle type is not compatible with AlignmentAlgorithm."); - } + double spinProbability = 0.1; - DirectedAmoebotParticle particle = (DirectedAmoebotParticle) p; + // Check if we're about to hit the wall + /* ParticleGrid.Direction faceDir = ((SpinCapable) p).getDirection(); + if (!((NeighborDetectionCapable) p).isDirectionInBounds(faceDir)) { + spinProbability = 0.5; + } */ - if (Utils.randomDouble() <= 0.5) { + if (Utils.randomDouble() <= spinProbability) { // With one half probability, we rotate - ParticleGrid.Direction randomDirection = particle.getRandomDirection(); + ParticleGrid.Direction randomDirection = ((UniformRandomDirectionCapable) p).getUniformRandomDirection(); - double moveProbability = this.getRotateMoveProbability(particle, randomDirection); + double moveProbability = this.getRotateMoveProbability(p, randomDirection); if (Utils.randomDouble() > moveProbability) { return; } - particle.setDirection(randomDirection); + ((SpinCapable) p).setDirection(randomDirection); } else { // With the rest probability, we translate // Pick a random direction using the correct weights - RandomSelector selector = RandomSelector.weighted(particle.compass.getDirections(), direction -> { - double angle = particle.compass.getAngleBetweenDirections(direction, particle.getDirection()); - return Math.pow(this.getForwardBias(), normalizeDotProduct(Math.cos(angle))); - }); - - ParticleGrid.Direction randomDirection = selector.next(Utils.random); + ParticleGrid.Direction currentDirection = ((SpinCapable) p).getDirection(); + ParticleGrid.Direction randomDirection = ((WrappedNormalRandomDirectionCapable) p).getWrappedNormalRandomDirection(currentDirection, this.getForwardBias()); // Run move validation - if (!this.isMoveValid(particle, randomDirection)) { + if (!((MovementCapable) p).isDirectionWithinBounds(randomDirection)) { return; } - double moveProbability = this.getTranslateMoveProbability(particle, randomDirection); + /*if (false && !RuleUtils.isMoveValidCompressionMove(particle, randomDirection, false, true)) { + return; + }*/ + + double moveProbability = this.getTranslateMoveProbability(p, randomDirection); if (Utils.randomDouble() > moveProbability) { return; } // Now make the move try { - particle.move(randomDirection, this.isSwapsAllowed(), this.isNonSwapsAllowed()); - //System.out.println("Moved."); - } catch (Exception ignored) { - - } + ((MovementCapable) p).move(randomDirection); + } catch (Exception ignored){} } } @Override - public boolean isParticleAllowed(Particle p) { - return p instanceof DirectedAmoebotParticle; + public List> getRequiredCapabilities() { + return requiredCapabilities; } - private double getRotateMoveProbability(DirectedAmoebotParticle p, ParticleGrid.Direction inDirection) { - List neighbors = p.getNeighborParticles(false, null); + private double getRotateMoveProbability(Particle p, ParticleGrid.Direction inDirection) { + List neighbors = ((NeighborDetectionCapable) p).getNeighborParticles(false, null); - double sumDotProducts = getDotProductSum(neighbors, p.getDirection()); + double sumDotProducts = getDotProductSum(neighbors, ((SpinCapable) p).getDirection()); double newSumDotProducts = getDotProductSum(neighbors, inDirection); return Math.pow(this.getRotationBias(), newSumDotProducts - sumDotProducts); @@ -152,40 +155,34 @@ private static double getDotProductSum(List particles, ParticleGrid.Di double sumDotProducts = 0; for (Particle nbrAnonymous : particles) { - DirectedAmoebotParticle nbr = (DirectedAmoebotParticle) nbrAnonymous; - sumDotProducts += normalizeDotProduct(Math.cos(nbr.compass.getAngleBetweenDirections(withDirection, nbr.getDirection()))); + SpinCapable nbr = (SpinCapable) nbrAnonymous; + sumDotProducts += normalizeDotProduct(Math.cos(nbr.getCompass().getAngleBetweenDirections(withDirection, nbr.getDirection()))); } return sumDotProducts; } - private double getTranslateMoveProbability(DirectedAmoebotParticle p, ParticleGrid.Direction inDirection) { - AmoebotGrid.AmoebotCompass compass = (AmoebotGrid.AmoebotCompass) p.compass; - ParticleGrid.Direction particleDirection = p.getDirection(); + private double getTranslateMoveProbability(Particle p, ParticleGrid.Direction inDirection) { + ParticleGrid.Direction particleDirection = ((SpinCapable) p).getDirection(); - List currentNeighbors = p.getNeighborParticles(false, null); - List futureNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, particle -> particle + List currentNeighbors = ((NeighborDetectionCapable) p).getNeighborParticles(false, null); + List futureNeighbors = ((NeighborDetectionCapable) p).getAdjacentPositionNeighborParticles(inDirection, false, particle -> particle != p); double translationBiasTerm = Math.pow(this.getTranslationBias(), futureNeighbors.size() - currentNeighbors.size()); - double rotationBiasExponent = getDotProductSum(futureNeighbors, p.getDirection()) - getDotProductSum(currentNeighbors, p.getDirection()); + double rotationBiasExponent = getDotProductSum(futureNeighbors, ((SpinCapable) p).getDirection()) - getDotProductSum(currentNeighbors, ((SpinCapable) p).getDirection()); double rotationBiasTerm = Math.pow(this.getRotationBias(), rotationBiasExponent); return translationBiasTerm * rotationBiasTerm; } - @Override - public double getCompressionBias(Particle p) { - throw new RuntimeException("getCompressionBias method should not be used in AlignmentAlgorithm."); + private static double normalizeDotProduct(double dot) { + return (dot + 1) / 2.0; } @Override - public double getMoveProbability(AmoebotParticle p, ParticleGrid.Direction inDirection) { - throw new RuntimeException("getMoveProbability method should not be used in AlignmentAlgorithm."); - } - - private static double normalizeDotProduct(double dot) { - return (dot + 1) / 2.0; + public boolean isGridValid(ParticleGrid grid) { + return true; } } diff --git a/src/main/java/com/cemgokmen/particles/algorithms/BobBotAlignmentAlgorithm.java b/src/main/java/com/cemgokmen/particles/algorithms/BobBotAlignmentAlgorithm.java new file mode 100644 index 0000000..e392816 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/algorithms/BobBotAlignmentAlgorithm.java @@ -0,0 +1,91 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.algorithms; + +import com.cemgokmen.particles.capabilities.*; +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.ParticleGrid; +import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; +import com.cemgokmen.particles.util.RandomSelector; +import com.cemgokmen.particles.util.Utils; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +public class BobBotAlignmentAlgorithm extends ParticleAlgorithm { + // TODO: FIX THIS + public static final List> requiredCapabilities = ImmutableList.of( + MovementCapable.class, NeighborDetectionCapable.class, UniformRandomDirectionCapable.class, SpinCapable.class); + + @Override + public void onParticleActivation(Particle p) { + // Do the rejection here + DirectedAmoebotParticle particle = (DirectedAmoebotParticle) p; + double dotProdSum = particle.getCompass().getDirections().stream() + .mapToDouble(d -> { + boolean occupied = particle.getNeighborInDirection(d, 0, null) != null; + if (!occupied) return 0; + + double angle = particle.getCompass().getAngleBetweenDirections(d, ((DirectedAmoebotParticle) p).getDirection()); + return -1 * Math.cos(angle); + }) + .sum(); + + double moveProb = 0.2 + 0.1 * dotProdSum; + if (Utils.randomDouble() > moveProb) return; + + if (Utils.randomDouble() <= 0.5) { + // With one half probability, we rotate + ParticleGrid.Direction randomDirection = particle.getUniformRandomDirection(); + + particle.setDirection(randomDirection); + } else { + // With the rest probability, we translate + + // Pick a random direction using the correct weights + RandomSelector selector = RandomSelector.uniform(particle.getCompass().getDirections()); + + ParticleGrid.Direction randomDirection = selector.next(Utils.random); + + // Run move validation + if (!particle.isDirectionWithinBounds(randomDirection)) { + return; + } + + // Now make the move + try { + particle.move(randomDirection); + //particle.setDirection(particle.compass.shiftDirectionCounterclockwise(particle.getDirection(), 3)); + //System.out.println("Moved."); + } catch (Exception ignored) { + + } + } + } + + @Override + public List> getRequiredCapabilities() { + return requiredCapabilities; + } + + @Override + public boolean isGridValid(ParticleGrid grid) { + return true; + } +} diff --git a/src/main/java/com/cemgokmen/particles/algorithms/CompressionAlgorithm.java b/src/main/java/com/cemgokmen/particles/algorithms/CompressionAlgorithm.java index d13a2b5..2647056 100644 --- a/src/main/java/com/cemgokmen/particles/algorithms/CompressionAlgorithm.java +++ b/src/main/java/com/cemgokmen/particles/algorithms/CompressionAlgorithm.java @@ -18,14 +18,23 @@ package com.cemgokmen.particles.algorithms; +import com.cemgokmen.particles.capabilities.MovementCapable; +import com.cemgokmen.particles.capabilities.NeighborDetectionCapable; +import com.cemgokmen.particles.capabilities.ParticleCapability; +import com.cemgokmen.particles.capabilities.UniformRandomDirectionCapable; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; import com.cemgokmen.particles.models.amoebot.AmoebotParticle; import com.cemgokmen.particles.util.Utils; +import com.google.common.collect.ImmutableList; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; +import java.util.List; + public class CompressionAlgorithm extends ParticleAlgorithm { + public static final List> requiredCapabilities = ImmutableList.of(MovementCapable.class, UniformRandomDirectionCapable.class, NeighborDetectionCapable.class); + public static final double DEFAULT_LAMBDA = 4.0; protected final DoubleProperty lambda = new SimpleDoubleProperty(); @@ -50,32 +59,26 @@ public CompressionAlgorithm() { this(DEFAULT_LAMBDA); } - public double getCompressionBias(Particle p) { - return this.getLambda(); - } - @Override public void onParticleActivation(Particle p) { - if (!(p instanceof AmoebotParticle)) { - throw new RuntimeException("This particle type is not compatible with CompressionAlgorithm."); - } - - AmoebotParticle particle = (AmoebotParticle) p; + UniformRandomDirectionCapable particle = (UniformRandomDirectionCapable) p; // Pick a random direction - ParticleGrid.Direction randomDirection = particle.getRandomDirection(); + ParticleGrid.Direction randomDirection = particle.getUniformRandomDirection(); //System.out.println("Time for move validation"); // Run move validation - if (!this.isMoveValid(particle, randomDirection)) { - //System.out.println("Invalid move, returning"); - return; + if (p instanceof AmoebotParticle) { + if (!RuleUtils.isMoveValidCompressionMove((AmoebotParticle) particle, randomDirection, false, true)) { + //System.out.println("Invalid move, returning"); + return; + } } //System.out.println("Time for probability calculation"); - double moveProbability = this.getMoveProbability(particle, randomDirection); + double moveProbability = this.getMoveProbability((NeighborDetectionCapable) p, randomDirection); //System.out.printf("Move probability: %.2f%%. Now filtering.", moveProbability * 100); @@ -87,24 +90,16 @@ public void onParticleActivation(Particle p) { // Now make the move try { - particle.move(randomDirection, this.isSwapsAllowed(), this.isNonSwapsAllowed()); + ((MovementCapable) particle).move(randomDirection); //System.out.println("Moved."); } catch (Exception ignored) { } } - public boolean isSwapsAllowed() { - return false; - } - - public boolean isNonSwapsAllowed() { - return true; - } - @Override - public boolean isParticleAllowed(Particle p) { - return p instanceof AmoebotParticle; + public List> getRequiredCapabilities() { + return requiredCapabilities; } @Override @@ -112,27 +107,10 @@ public boolean isGridValid(ParticleGrid grid) { return RuleUtils.checkParticleConnection(grid, particle -> true) && RuleUtils.checkParticleHoles(grid, particle -> true); } - public double getMoveProbability(AmoebotParticle p, ParticleGrid.Direction inDirection) { + public double getMoveProbability(NeighborDetectionCapable p, ParticleGrid.Direction inDirection) { int currentNeighbors = p.getNeighborParticles(false, null).size(); int futureNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, particle -> particle != p).size(); - return Math.pow(this.getCompressionBias(p), futureNeighbors - currentNeighbors); + return Math.pow(this.getLambda(), futureNeighbors - currentNeighbors); } - - public boolean isMoveValid(AmoebotParticle p, ParticleGrid.Direction d) { - if (!p.isDirectionWithinBounds(d)) { - return false; - } - boolean isOccupied = p.getNeighborInDirection(d, 0, null) != null; - if ((isOccupied && !this.isSwapsAllowed()) || (!isOccupied && !this.isNonSwapsAllowed())) { - return false; - } - - boolean cond1 = p.getNeighborParticles(false, null).size() < 5; - boolean cond2 = RuleUtils.checkProperty1(p, d, null); - boolean cond3 = RuleUtils.checkProperty2(p, d, null); - - return cond1 && (cond2 || cond3); - } - } diff --git a/src/main/java/com/cemgokmen/particles/algorithms/ContinuousAlignmentAlgorithm.java b/src/main/java/com/cemgokmen/particles/algorithms/ContinuousAlignmentAlgorithm.java new file mode 100644 index 0000000..4cb671c --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/algorithms/ContinuousAlignmentAlgorithm.java @@ -0,0 +1,244 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.algorithms; + +import com.cemgokmen.particles.capabilities.*; +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.ParticleGrid; +import com.cemgokmen.particles.models.amoebot.AmoebotGrid; +import com.cemgokmen.particles.models.amoebot.specializedparticles.ContinuousDirectedAmoebotParticle; +import com.cemgokmen.particles.util.RandomSelector; +import com.cemgokmen.particles.util.Utils; +import com.google.common.collect.ImmutableList; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class ContinuousAlignmentAlgorithm extends ParticleAlgorithm { + public static final double DEFAULT_ROTATION_BIAS = 20.0; + public static final double DEFAULT_TRANSLATION_BIAS = 1.0; + public static final double DEFAULT_FORWARD_BIAS = 1.1; + public static final double DEFAULT_LAMBDA = 4.0; + public static final List POSSIBLE_ANGLES = IntStream.range(0, 360).mapToDouble(x -> x * Math.PI / 180.0).boxed().collect(Collectors.toList()); + public static final List> requiredCapabilities = ImmutableList.of( + MovementCapable.class, NeighborDetectionCapable.class, UniformRandomDirectionCapable.class, SpinCapable.class); + + // TODO: SAMPLE FROM A WRAPPED CAUCHY DISTRIBUTION OR A TRUNCATED NORMAL OR A VON MISES + + protected final DoubleProperty rotationBias = new SimpleDoubleProperty(); + protected final DoubleProperty translationBias = new SimpleDoubleProperty(); + protected final DoubleProperty forwardBias = new SimpleDoubleProperty(); + + public double getRotationBias() { + return this.rotationBias.get(); + } + + public DoubleProperty rotationBiasProperty() { + return this.rotationBias; + } + + public void setRotationBias(double rotationBias) { + this.rotationBias.set(rotationBias); + } + + public double getTranslationBias() { + return this.translationBias.get(); + } + + public DoubleProperty translationBiasProperty() { + return this.translationBias; + } + + public void setTranslationBias(double translationBias) { + this.translationBias.set(translationBias); + } + + public double getForwardBias() { + return this.forwardBias.get(); + } + + public DoubleProperty forwardBiasProperty() { + return this.forwardBias; + } + + public void setForwardBias(double forwardBias) { + this.forwardBias.set(forwardBias); + } + + public ContinuousAlignmentAlgorithm(double rotationBias, double translationBias, double forwardBias) { + this.setRotationBias(rotationBias); + this.setTranslationBias(translationBias); + this.setForwardBias(forwardBias); + } + + public ContinuousAlignmentAlgorithm() { + this(DEFAULT_ROTATION_BIAS, DEFAULT_TRANSLATION_BIAS, DEFAULT_FORWARD_BIAS); + } + + @Override + public void onParticleActivation(Particle p) { + ContinuousDirectedAmoebotParticle particle = (ContinuousDirectedAmoebotParticle) p; + + if (Utils.randomDouble() <= 0.1) { + // With one half probability, we rotate + double randomDirection = Utils.randomDouble() * Math.PI * 2; + + double moveProbability = this.getRotateMoveProbability(particle, randomDirection); + if (Utils.randomDouble() > moveProbability) { + return; + } + + particle.setDirection(randomDirection); + + //RandomSelector selector = RandomSelector.weighted(POSSIBLE_ANGLES, direction -> this.getRotateMoveProbability(particle, direction)); + //particle.setDirection(selector.next(Utils.random)); + } else { + // With the rest probability, we translate + + // Pick a random direction using the correct weights + RandomSelector selector = RandomSelector.weighted(particle.compass.getDirections(), direction -> { + double angle = angleDifference(directionToRadians(direction, particle.compass), particle.getDirection()); + return Math.pow(this.getForwardBias(), normalizeDotProduct(Math.cos(angle))); + }); + + ParticleGrid.Direction randomDirection = selector.next(Utils.random); + + // Run move validation + if (!particle.isDirectionWithinBounds(randomDirection)) { + return; + } + + if (false && !RuleUtils.isMoveValidCompressionMove(particle, randomDirection, false, true)) { + return; + } + + double moveProbability = this.getTranslateMoveProbability(particle, randomDirection); + if (Utils.randomDouble() > moveProbability) { + return; + } + + // Now make the move + try { + particle.move(randomDirection); + //particle.setDirection(particle.compass.shiftDirectionCounterclockwise(particle.getDirection(), 3)); + //System.out.println("Moved."); + } catch (Exception ignored) { + + } + } + } + + @Override + public List> getRequiredCapabilities() { + return requiredCapabilities; + } + + private double getRotateMoveProbability(ContinuousDirectedAmoebotParticle p, double inDirection) { + List neighbors = p.getNeighborParticles(false, null); + + double sumDotProducts = getDotProductSum(neighbors, p.getDirection()); + double newSumDotProducts = getDotProductSum(neighbors, inDirection); + + return Math.pow(this.getRotationBias(), newSumDotProducts - sumDotProducts); + } + + private static double getDotProductSum(List particles, double withDirection) { + double sumDotProducts = 0; + + for (Particle nbrAnonymous : particles) { + ContinuousDirectedAmoebotParticle nbr = (ContinuousDirectedAmoebotParticle) nbrAnonymous; + sumDotProducts += normalizeDotProduct(Math.cos(angleDifference(withDirection, nbr.getDirection()))); + } + + return sumDotProducts; + } + + private double getTranslateMoveProbability(ContinuousDirectedAmoebotParticle p, ParticleGrid.Direction inDirection) { + AmoebotGrid.AmoebotCompass compass = (AmoebotGrid.AmoebotCompass) p.compass; + double particleDirection = p.getDirection(); + double inDirectionRadians = directionToRadians(inDirection, p.compass); + + List currentNeighbors = p.getNeighborParticles(false, null); + List futureNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, particle -> particle + != p); + + double translationBiasTerm = Math.pow(this.getTranslationBias(), futureNeighbors.size() - currentNeighbors.size()); + + double rotationBiasExponent = getDotProductSum(futureNeighbors, p.getDirection()) - getDotProductSum(currentNeighbors, p.getDirection()); + double rotationBiasTerm = Math.pow(this.getRotationBias(), rotationBiasExponent); + + return translationBiasTerm * rotationBiasTerm; + } + + private static double oldDotProductSum(List particles, ParticleGrid.Direction withDirection) { + double sumDotProducts = 0; + + for (Particle nbrAnonymous : particles) { + ContinuousDirectedAmoebotParticle nbr = (ContinuousDirectedAmoebotParticle) nbrAnonymous; + sumDotProducts += normalizeDotProduct(Math.cos(nbr.compass.getAngleBetweenDirections(withDirection, discretize((AmoebotGrid.AmoebotCompass) nbr.compass, nbr.getDirection())))); + } + + return sumDotProducts; + } + + private double oldTranslateMoveProbability(ContinuousDirectedAmoebotParticle p, ParticleGrid.Direction inDirection) { + AmoebotGrid.AmoebotCompass compass = (AmoebotGrid.AmoebotCompass) p.compass; + + List currentNeighbors = p.getNeighborParticles(false, null); + List futureNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, particle -> particle + != p); + + double translationBiasTerm = Math.pow(this.getTranslationBias(), futureNeighbors.size() - currentNeighbors.size()); + + double rotationBiasExponent = oldDotProductSum(futureNeighbors, discretize(compass, p.getDirection())) - oldDotProductSum(currentNeighbors, discretize(compass, p.getDirection())); + double rotationBiasTerm = Math.pow(this.getRotationBias(), rotationBiasExponent); + + return translationBiasTerm * rotationBiasTerm; + } + + private static double normalizeDotProduct(double dot) { + return (dot + 1) / 2.0; + } + + @Override + public boolean isGridValid(ParticleGrid grid) { + return true; + } + + public static ParticleGrid.Direction discretize(AmoebotGrid.AmoebotCompass c, double d) { + double val = d / (Math.PI * 2) * (c.getDirections().size() - 1); + return c.getDirections().get(Math.round((float) val)); + } + + private static double directionToRadians(ParticleGrid.Direction d, ParticleGrid.Compass c) { + // Assumes that the first element in compass is the 0 angle. + // For now, also assumes uniform angles. + double step = 360.0 / c.getDirections().size(); + return step * c.getDirections().indexOf(d); + } + + private static double angleDifference(double a, double b) { + double diff = Math.abs(a - b) % (Math.PI * 2); + if (diff > Math.PI) diff = Math.PI * 2 - diff; + return diff; + } +} diff --git a/src/main/java/com/cemgokmen/particles/algorithms/ForagingAlgorithm.java b/src/main/java/com/cemgokmen/particles/algorithms/ForagingAlgorithm.java index fc14f42..260a7b9 100644 --- a/src/main/java/com/cemgokmen/particles/algorithms/ForagingAlgorithm.java +++ b/src/main/java/com/cemgokmen/particles/algorithms/ForagingAlgorithm.java @@ -18,26 +18,36 @@ package com.cemgokmen.particles.algorithms; +import com.cemgokmen.particles.capabilities.MovementCapable; +import com.cemgokmen.particles.capabilities.NeighborDetectionCapable; +import com.cemgokmen.particles.capabilities.ParticleCapability; +import com.cemgokmen.particles.capabilities.UniformRandomDirectionCapable; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; import com.cemgokmen.particles.models.amoebot.AmoebotParticle; import com.cemgokmen.particles.models.amoebot.specializedparticles.FoodAmoebotParticle; import com.cemgokmen.particles.models.amoebot.specializedparticles.ForagingAmoebotParticle; +import com.cemgokmen.particles.util.Utils; +import com.google.common.collect.ImmutableList; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; import java.util.Comparator; +import java.util.List; import java.util.Map; -public class ForagingAlgorithm extends CompressionAlgorithm { +public class ForagingAlgorithm extends ParticleAlgorithm { + public static final List> requiredCapabilities = ImmutableList.of(MovementCapable.class, UniformRandomDirectionCapable.class, NeighborDetectionCapable.class); + public static final double DEFAULT_FED_LAMBDA = 4.0; public static final double DEFAULT_HUNGRY_LAMBDA = 1.0; public static final int DEFAULT_FOOD_LIFETIME = Integer.MAX_VALUE; public static final int DEFAULT_FOOD_TOKEN_LIFETIME = 4; public static final int DEFAULT_PARTICLE_MAXIMUM_FED_ACTIVATIONS = 500; + public static final double DEFAULT_LAMBDA = 4.0; protected final DoubleProperty fedLambda = new SimpleDoubleProperty(); protected final DoubleProperty hungryLambda = new SimpleDoubleProperty(); @@ -45,6 +55,7 @@ public class ForagingAlgorithm extends CompressionAlgorithm { protected final IntegerProperty foodLifetime = new SimpleIntegerProperty(); protected final IntegerProperty foodTokenLifetime = new SimpleIntegerProperty(); protected final IntegerProperty particleMaximumFedActivations = new SimpleIntegerProperty(); + protected final DoubleProperty lambda = new SimpleDoubleProperty(); public ForagingAlgorithm(double fedLambda, double hungryLambda, int foodLifetime, int foodTokenLifetime, int particleMaximumFedActivations) { @@ -119,7 +130,6 @@ public void setParticleMaximumFedActivations(int particleMaximumFedActivations) this.particleMaximumFedActivations.set(particleMaximumFedActivations); } - @Override public double getCompressionBias(Particle p) { if (p instanceof FoodAmoebotParticle) { return 1; @@ -133,9 +143,23 @@ public void onParticleActivation(Particle p) { if (p instanceof FoodAmoebotParticle) { ((FoodAmoebotParticle) p).getNeighborParticles(false, particle -> particle instanceof ForagingAmoebotParticle).forEach(particle -> ((ForagingAmoebotParticle) particle).giveFoodToken(this.getFoodTokenLifetime(), this.getParticleMaximumFedActivations())); ((FoodAmoebotParticle) p).decrementLifetime(this.getFoodLifetime()); + + // Does p have neighbors? + //boolean pConnected = ((FoodAmoebotParticle) p).getNeighborParticles(false, x -> !(x instanceof FoodAmoebotParticle)).size() > 0; + + // Message passing hack right here. First, clear everyone. + /*ParticleGrid g = p.getGrid(); + if (pConnected) + g.getAllParticles().filter(x -> x instanceof ForagingAmoebotParticle).forEach(x -> ((ForagingAmoebotParticle) x).giveFoodToken(this.getFoodTokenLifetime(), this.getParticleMaximumFedActivations())); + else + g.getAllParticles().filter(x -> x instanceof ForagingAmoebotParticle).forEach(x -> ((ForagingAmoebotParticle) x).giveFoodToken(0, 0));*/ + + // Then, force the message onto everyone important. + // TODO: Maybe do a search here? } else if (p instanceof ForagingAmoebotParticle) { // Do the feeding first ForagingAmoebotParticle particle = (ForagingAmoebotParticle) p; + /*if (particle.getNeighborParticles(false, particle1 -> particle1 instanceof FoodAmoebotParticle).size() > 0) particle.feed(); else particle.decrementFedActivations();*/ @@ -144,7 +168,7 @@ public void onParticleActivation(Particle p) { int token = particle.getFoodToken(); --token; if (token > 0) { - ParticleGrid.Direction randomDirection = particle.getRandomDirection(); + ParticleGrid.Direction randomDirection = particle.getUniformRandomDirection(); ForagingAmoebotParticle nbr = (ForagingAmoebotParticle) particle.getNeighborInDirection(randomDirection, 0, particle1 -> particle1 instanceof ForagingAmoebotParticle); if (nbr != null) { nbr.giveFoodToken(token, this.getParticleMaximumFedActivations()); @@ -155,34 +179,50 @@ public void onParticleActivation(Particle p) { particle.incrementLastFedActivationsAgo(); particle.decrementFedActivations(); - super.onParticleActivation(p); + // TODO: CALL COMPRESSION'S METHOD + // Pick a random direction + ParticleGrid.Direction randomDirection = particle.getUniformRandomDirection(); + + // Run move validation + if (!this.isMoveValid(particle, randomDirection)) { + return; + } + + double moveProbability = this.getMoveProbability(particle, randomDirection); + if (Utils.randomDouble() > moveProbability) { + return; + } + + // Now make the move + try { + particle.move(randomDirection); + } catch (Exception ignored) { + + } } } @Override + public List> getRequiredCapabilities() { + return requiredCapabilities; + } + public boolean isSwapsAllowed() { return true; } - @Override public boolean isNonSwapsAllowed() { return true; } - @Override - public boolean isParticleAllowed(Particle p) { - return p instanceof ForagingAmoebotParticle || p instanceof FoodAmoebotParticle; - } - - @Override public boolean isMoveValid(AmoebotParticle p, ParticleGrid.Direction d) { // Do not swap with food particles - Particle nbr = p.getNeighborInDirection(d, 0, null); - if (nbr != null && !nbr.getClass().equals(p.getClass())) { + Particle nbr = p.getNeighborInDirection(d, 0, x -> x instanceof FoodAmoebotParticle); + if (nbr != null) { return false; } - return super.isMoveValid(p, d); + return RuleUtils.isMoveValidCompressionMove(p, d, false, true, x -> x instanceof ForagingAmoebotParticle); } @Override @@ -198,4 +238,17 @@ public Map getInformation(ParticleGrid g) { info.put("Longest un-fed wait so far", longestWaiting + ""); return info; } + + + @Override + public boolean isGridValid(ParticleGrid grid) { + return RuleUtils.checkParticleConnection(grid, particle -> true) && RuleUtils.checkParticleHoles(grid, particle -> true); + } + + public double getMoveProbability(AmoebotParticle p, ParticleGrid.Direction inDirection) { + int currentNeighbors = p.getNeighborParticles(false, null).size(); + int futureNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, particle -> particle + != p).size(); + return Math.pow(this.getCompressionBias(p), futureNeighbors - currentNeighbors); + } } diff --git a/src/main/java/com/cemgokmen/particles/algorithms/ParticleAlgorithm.java b/src/main/java/com/cemgokmen/particles/algorithms/ParticleAlgorithm.java index e70685b..125b479 100644 --- a/src/main/java/com/cemgokmen/particles/algorithms/ParticleAlgorithm.java +++ b/src/main/java/com/cemgokmen/particles/algorithms/ParticleAlgorithm.java @@ -18,16 +18,35 @@ package com.cemgokmen.particles.algorithms; +import com.cemgokmen.particles.capabilities.ParticleCapability; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; +import com.cemgokmen.particles.models.amoebot.AmoebotGrid; +import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.util.HashMap; +import java.util.List; import java.util.Map; public abstract class ParticleAlgorithm { + public static final List> IMPLEMENTATIONS = ImmutableList.of( + CompressionAlgorithm.class, + SeparationAlgorithm.class, + ForagingAlgorithm.class, + AlignmentAlgorithm.class + //ContinuousAlignmentAlgorithm.class, + //BobBotAlignmentAlgorithm.class + ); + public abstract void onParticleActivation(Particle p); - public abstract boolean isParticleAllowed(Particle p); + public boolean isParticleAllowed(Particle p) { + return this.getRequiredCapabilities().stream().allMatch(req -> req.isInstance(p)); + } + + public abstract List> getRequiredCapabilities(); public abstract boolean isGridValid(ParticleGrid grid); diff --git a/src/main/java/com/cemgokmen/particles/algorithms/RuleUtils.java b/src/main/java/com/cemgokmen/particles/algorithms/RuleUtils.java index 707de5a..937fa57 100644 --- a/src/main/java/com/cemgokmen/particles/algorithms/RuleUtils.java +++ b/src/main/java/com/cemgokmen/particles/algorithms/RuleUtils.java @@ -25,10 +25,36 @@ import org.la4j.Vector; import java.util.*; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; public class RuleUtils { + protected static boolean isMoveValidCompressionMove(AmoebotParticle p, ParticleGrid.Direction d, boolean swapsAllowed, boolean nonswapsAllowed) { + return isMoveValidCompressionMove(p, d, swapsAllowed, nonswapsAllowed, null); + } + + protected static boolean isMoveValidCompressionMove(AmoebotParticle p, ParticleGrid.Direction d, boolean swapsAllowed, boolean nonswapsAllowed, Predicate filter) { + if (!p.isDirectionWithinBounds(d)) { + return false; + } + + boolean isOccupied = p.getNeighborInDirection(d, 0, null) != null; + if (isOccupied && swapsAllowed) return true; + + if (isOccupied || !nonswapsAllowed) { + return false; + } + + boolean cond1 = p.getNeighborParticles(false, filter).size() < 5; + boolean cond2 = RuleUtils.checkProperty1(p, d, filter); + boolean cond3 = RuleUtils.checkProperty2(p, d, filter); + + return cond1 && (cond2 || cond3); + } + protected static boolean checkProperty1(AmoebotParticle p, ParticleGrid.Direction d, Predicate filter) { Particle n1 = p.getNeighborInDirection(d, 5, filter); // WE SHOULD NOT HAVE ACCESS TO THESE! Particle n2 = p.getNeighborInDirection(d, 1, filter); @@ -85,7 +111,7 @@ protected static boolean checkProperty2(AmoebotParticle p, ParticleGrid.Directio protected static boolean checkParticleConnection(ParticleGrid grid, Predicate filter) { return checkConnected(grid, position -> { - boolean a = grid.isPositionValid(position); + boolean a = grid.isPositionValid(position, null); boolean b = grid.isPositionOccupied(position); return a && b; }); @@ -93,45 +119,66 @@ protected static boolean checkParticleConnection(ParticleGrid grid, Predicate filter) { return checkConnected(grid, position -> { - boolean a = grid.isPositionValid(position); + boolean a = grid.isPositionValid(position, null); boolean b = !grid.isPositionOccupied(position); return a && b; }); } private static boolean checkConnected(ParticleGrid grid, Predicate inclusionFilter) { - int eligibleCount = 0; - Vector start = null; - - for (Vector v: grid.getValidPositions()) { - if (inclusionFilter.test(v)) { - eligibleCount++; - start = v; - } - } + int eligibleCount = (int) grid.getValidPositions().filter(inclusionFilter).count(); + Vector start = grid.getValidPositions().filter(inclusionFilter).findAny().orElse(null); //System.out.println("Eligible: " + eligibleCount); - if (eligibleCount == 0) return true; + Function> neighborGetter = (p -> grid.getAdjacentPositions(p).filter(inclusionFilter)); - Set searched = search(grid, start, inclusionFilter); + Set searched = search(start, neighborGetter); //System.out.println("Searched: " + searched.size()); return eligibleCount == searched.size(); } - private static Set search(ParticleGrid grid, Vector start, Predicate inclusionFilter) { - Set visited = Sets.newHashSet(); - Queue queue = new LinkedList<>(); + public static Set getComponent(Particle origin, ParticleGrid grid) { + Function> neighborGetter = (p -> grid.getParticleNeighbors(p, false).stream()); + + return search(origin, neighborGetter); + } + + public static Set getLargestComponent(ParticleGrid grid) { + Set largestComponent = Sets.newHashSet(); + Set foundParticles = Sets.newHashSet(); + + List particles = grid.getAllParticles().collect(Collectors.toList()); + + for (Particle p: particles) { + if (!foundParticles.contains(p)) { + // New component! Find it + Set component = getComponent(p, grid); + + // Is it the largest? + if (component.size() > largestComponent.size()) largestComponent = component; + + // Mark the particles in the component as found + foundParticles.addAll(component); + } + } + + return largestComponent; + } + + private static Set search(T start, Function> getNeighbors) { + Set visited = Sets.newHashSet(); + Queue queue = new LinkedList<>(); queue.add(start); while (!queue.isEmpty()) { - Vector vertex = queue.remove(); + T vertex = queue.remove(); if (!visited.contains(vertex)) { visited.add(vertex); - Set nextUp = Arrays.stream(grid.getAdjacentPositions(vertex)).filter(inclusionFilter).collect(Collectors.toSet()); + Set nextUp = getNeighbors.apply(vertex).collect(Collectors.toSet()); queue.addAll(Sets.difference(nextUp, visited)); } } diff --git a/src/main/java/com/cemgokmen/particles/algorithms/SeparationAlgorithm.java b/src/main/java/com/cemgokmen/particles/algorithms/SeparationAlgorithm.java index c3e55f8..97bcc12 100644 --- a/src/main/java/com/cemgokmen/particles/algorithms/SeparationAlgorithm.java +++ b/src/main/java/com/cemgokmen/particles/algorithms/SeparationAlgorithm.java @@ -18,18 +18,25 @@ package com.cemgokmen.particles.algorithms; +import com.cemgokmen.particles.capabilities.*; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; import com.cemgokmen.particles.models.amoebot.AmoebotParticle; import com.cemgokmen.particles.models.amoebot.specializedparticles.SeparableAmoebotParticle; +import com.cemgokmen.particles.util.Utils; +import com.google.common.collect.ImmutableList; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; +import java.util.List; import java.util.function.Predicate; -public class SeparationAlgorithm extends CompressionAlgorithm { +public class SeparationAlgorithm extends ParticleAlgorithm { + public static final List> requiredCapabilities = ImmutableList.of( + MovementCapable.class, NeighborDetectionCapable.class, UniformRandomDirectionCapable.class, SwapMovementCapable.class); + public static final double DEFAULT_LAMBDA = 4.0; public static final double DEFAULT_ALPHA = 4.0; public static final boolean DEFAULT_SWAPS = true; @@ -38,15 +45,20 @@ public class SeparationAlgorithm extends CompressionAlgorithm { protected final DoubleProperty alpha = new SimpleDoubleProperty(); protected final BooleanProperty swapsAllowed = new SimpleBooleanProperty(); protected final BooleanProperty nonSwapsAllowed = new SimpleBooleanProperty(); + protected final DoubleProperty lambda = new SimpleDoubleProperty(); public SeparationAlgorithm(double lambda, double alpha, boolean swapsAllowed, boolean nonSwapsAllowed) { - super(lambda); + this.setLambda(lambda); this.setAlpha(alpha); this.setSwapsAllowed(swapsAllowed); this.setNonSwapsAllowed(nonSwapsAllowed); } + public SeparationAlgorithm() { + this(DEFAULT_LAMBDA, DEFAULT_ALPHA, DEFAULT_SWAPS, DEFAULT_NONSWAPS); + } + public double getAlpha() { return this.alpha.get(); } @@ -59,7 +71,6 @@ public void setAlpha(double alpha) { this.alpha.set(alpha); } - @Override public boolean isSwapsAllowed() { return this.swapsAllowed.get(); } @@ -72,7 +83,6 @@ public void setSwapsAllowed(boolean swapsAllowed) { this.swapsAllowed.set(swapsAllowed); } - @Override public boolean isNonSwapsAllowed() { return this.nonSwapsAllowed.get(); } @@ -85,56 +95,104 @@ public void setNonSwapsAllowed(boolean nonSwapsAllowed) { this.nonSwapsAllowed.set(nonSwapsAllowed); } - public SeparationAlgorithm() { - this(DEFAULT_LAMBDA, DEFAULT_ALPHA, DEFAULT_SWAPS, DEFAULT_NONSWAPS); + public double getLambda() { + return this.lambda.get(); } - private class ClassNumberPredicate implements Predicate { - private final int classNumber; + public DoubleProperty lambdaProperty() { + return this.lambda; + } - ClassNumberPredicate(int classNumber) { - this.classNumber = classNumber; + public void setLambda(double lambda) { + this.lambda.set(lambda); + } + + @Override + public void onParticleActivation(Particle p) { + AmoebotParticle particle = (AmoebotParticle) p; + + // Pick a random direction + ParticleGrid.Direction randomDirection = particle.getUniformRandomDirection(); + + //System.out.println("Time for move validation"); + + // Run move validation + if (!RuleUtils.isMoveValidCompressionMove(particle, randomDirection, this.isSwapsAllowed(), this.isNonSwapsAllowed())) { + //System.out.println("Invalid move, returning"); + return; } - @Override - public boolean test(Particle particle) { - return particle != null && ((SeparableAmoebotParticle) particle).getClassNumber() == this.classNumber; + //System.out.println("Time for probability calculation"); + + double moveProbability = this.getMoveProbability(particle, randomDirection); + + //System.out.printf("Move probability: %.2f%%. Now filtering.", moveProbability * 100); + + if (Utils.randomDouble() > moveProbability) { + return; } + + // Now make the move + if (this.isSwapsAllowed()) + particle.swapMove(randomDirection); + else + particle.move(randomDirection); } @Override - public double getMoveProbability(AmoebotParticle p, ParticleGrid.Direction inDirection) { - int classNumber = ((SeparableAmoebotParticle) p).getClassNumber(); - ClassNumberPredicate filter = new ClassNumberPredicate(classNumber); + public List> getRequiredCapabilities() { + return requiredCapabilities; + } - int currentHomogeneousNeighbors = p.getNeighborParticles(false, filter).size(); - int futureHomogeneousNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, particle -> - particle != p && filter.test(particle)).size(); + @Override + public boolean isGridValid(ParticleGrid grid) { + return RuleUtils.checkParticleConnection(grid, particle -> true) && RuleUtils.checkParticleHoles(grid, particle -> true); + } - double probability = Math.pow(this.getAlpha(), futureHomogeneousNeighbors - currentHomogeneousNeighbors); + private class ClassNumberPredicate implements Predicate { + private final Particle particle; + + ClassNumberPredicate(Particle particle) { + this.particle = particle; + } + + @Override + public boolean test(Particle p) { + return p != null && + p != this.particle && + ((SeparableAmoebotParticle) p).getClassNumber() == ((SeparableAmoebotParticle) this.particle).getClassNumber(); + } + } + + public double getMoveProbability(AmoebotParticle p, ParticleGrid.Direction inDirection) { + ClassNumberPredicate filter = new ClassNumberPredicate(p); Particle nbr = p.getNeighborInDirection(inDirection, 0, null); if (nbr == null) { + // This is a regular move + int currentHomogeneousNeighbors = p.getNeighborParticles(false, filter).size(); + int futureHomogeneousNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, filter).size(); + int currentNeighbors = p.getNeighborParticles(false, null).size(); int futureNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, particle -> particle != p).size(); - probability *= Math.pow(this.getCompressionBias(p), futureNeighbors - currentNeighbors); + + return Math.pow(this.getAlpha(), futureHomogeneousNeighbors - currentHomogeneousNeighbors) * // Separation + Math.pow(this.getLambda(), futureNeighbors - currentNeighbors); // Compression } else { - int nbrClassNumber = ((SeparableAmoebotParticle) nbr).getClassNumber(); - ClassNumberPredicate nbrFilter = new ClassNumberPredicate(nbrClassNumber); + // This is a swap move + ClassNumberPredicate nbrFilter = new ClassNumberPredicate(nbr); + + int currentHomogeneousNeighbors = p.getNeighborParticles(false, filter).size(); + int futureHomogeneousNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, filter).size(); + futureHomogeneousNeighbors += (filter.test(nbr)) ? 1 : 0; // Consider the neighbor too since we swap with it int nbrCurrentHomogeneousNeighbors = p.getAdjacentPositionNeighborParticles(inDirection, false, nbrFilter).size(); - int nbrFutureHomogeneousNeighbors = p.getNeighborParticles(false, particle -> particle != p - && nbrFilter.test(particle)).size(); + int nbrFutureHomogeneousNeighbors = p.getNeighborParticles(false, nbrFilter).size(); + nbrFutureHomogeneousNeighbors += (nbrFilter.test(p)) ? 1 : 0; // Consider p too since we swap with it - probability *= Math.pow(this.getAlpha(), nbrFutureHomogeneousNeighbors - nbrCurrentHomogeneousNeighbors); + return Math.pow(this.getAlpha(), futureHomogeneousNeighbors - currentHomogeneousNeighbors) * // For this particle + Math.pow(this.getAlpha(), nbrFutureHomogeneousNeighbors - nbrCurrentHomogeneousNeighbors); // For the swapped particle } - - return probability; - } - - @Override - public boolean isParticleAllowed(Particle p) { - return p instanceof SeparableAmoebotParticle; } } diff --git a/src/main/java/com/cemgokmen/particles/capabilities/DirectedNeighborDetectionCapable.java b/src/main/java/com/cemgokmen/particles/capabilities/DirectedNeighborDetectionCapable.java new file mode 100644 index 0000000..d3c77f4 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/capabilities/DirectedNeighborDetectionCapable.java @@ -0,0 +1,30 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.capabilities; + +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.ParticleGrid; +import org.la4j.Vector; + +import java.util.function.Predicate; + +public interface DirectedNeighborDetectionCapable extends ParticleCapability { + Particle getNeighborInDirection(ParticleGrid.Direction d, int counterclockwiseShift, Predicate filter); + Particle getAdjacentPositionNeighborInDirection(ParticleGrid.Direction d, int counterclockwiseShift, Predicate filter); +} diff --git a/src/main/java/com/cemgokmen/particles/capabilities/MovementCapable.java b/src/main/java/com/cemgokmen/particles/capabilities/MovementCapable.java new file mode 100644 index 0000000..3df2f5e --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/capabilities/MovementCapable.java @@ -0,0 +1,26 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.capabilities; + +import com.cemgokmen.particles.models.ParticleGrid; + +public interface MovementCapable extends ParticleCapability { + void move(ParticleGrid.Direction d); + boolean isDirectionWithinBounds(ParticleGrid.Direction d); +} diff --git a/src/main/java/com/cemgokmen/particles/capabilities/NeighborDetectionCapable.java b/src/main/java/com/cemgokmen/particles/capabilities/NeighborDetectionCapable.java new file mode 100644 index 0000000..59e5116 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/capabilities/NeighborDetectionCapable.java @@ -0,0 +1,31 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.capabilities; + +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.ParticleGrid; + +import java.util.List; +import java.util.function.Predicate; + +public interface NeighborDetectionCapable extends ParticleCapability { + List getNeighborParticles(boolean includeNulls, Predicate filter); + List getAdjacentPositionNeighborParticles(ParticleGrid.Direction d, boolean includeNulls, Predicate filter); + boolean isDirectionInBounds(ParticleGrid.Direction d); +} diff --git a/src/main/java/com/cemgokmen/particles/capabilities/ParticleCapability.java b/src/main/java/com/cemgokmen/particles/capabilities/ParticleCapability.java new file mode 100644 index 0000000..05b2a49 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/capabilities/ParticleCapability.java @@ -0,0 +1,22 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.capabilities; + +public interface ParticleCapability { +} diff --git a/src/main/java/com/cemgokmen/particles/capabilities/SpinCapable.java b/src/main/java/com/cemgokmen/particles/capabilities/SpinCapable.java new file mode 100644 index 0000000..03f24f6 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/capabilities/SpinCapable.java @@ -0,0 +1,27 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.capabilities; + +import com.cemgokmen.particles.models.ParticleGrid; + +public interface SpinCapable extends ParticleCapability { + ParticleGrid.Direction getDirection(); + void setDirection(ParticleGrid.Direction d); + ParticleGrid.Compass getCompass(); +} diff --git a/src/main/java/com/cemgokmen/particles/capabilities/SwapMovementCapable.java b/src/main/java/com/cemgokmen/particles/capabilities/SwapMovementCapable.java new file mode 100644 index 0000000..c4c3f89 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/capabilities/SwapMovementCapable.java @@ -0,0 +1,25 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.capabilities; + +import com.cemgokmen.particles.models.ParticleGrid; + +public interface SwapMovementCapable extends ParticleCapability { + void swapMove(ParticleGrid.Direction d); +} diff --git a/src/main/java/com/cemgokmen/particles/capabilities/UniformRandomDirectionCapable.java b/src/main/java/com/cemgokmen/particles/capabilities/UniformRandomDirectionCapable.java new file mode 100644 index 0000000..c68b2fd --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/capabilities/UniformRandomDirectionCapable.java @@ -0,0 +1,25 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.capabilities; + +import com.cemgokmen.particles.models.ParticleGrid; + +public interface UniformRandomDirectionCapable extends ParticleCapability { + ParticleGrid.Direction getUniformRandomDirection(); +} diff --git a/src/main/java/com/cemgokmen/particles/capabilities/WrappedNormalRandomDirectionCapable.java b/src/main/java/com/cemgokmen/particles/capabilities/WrappedNormalRandomDirectionCapable.java new file mode 100644 index 0000000..8f54b76 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/capabilities/WrappedNormalRandomDirectionCapable.java @@ -0,0 +1,26 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.capabilities; + +import com.cemgokmen.particles.models.ParticleGrid; + +public interface WrappedNormalRandomDirectionCapable extends ParticleCapability { + // Note that the support of this is between -Pi and Pi. Implementations need to keep the scale of the stdev same. + ParticleGrid.Direction getWrappedNormalRandomDirection(ParticleGrid.Direction mean, double standardDeviation); +} diff --git a/src/main/java/com/cemgokmen/particles/generators/RandomSystemGenerator.java b/src/main/java/com/cemgokmen/particles/generators/RandomSystemGenerator.java index ca83a93..fe2d880 100644 --- a/src/main/java/com/cemgokmen/particles/generators/RandomSystemGenerator.java +++ b/src/main/java/com/cemgokmen/particles/generators/RandomSystemGenerator.java @@ -29,49 +29,55 @@ import java.util.List; import java.util.Map; +import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class RandomSystemGenerator { + public static void addParticles(ParticleGrid grid, Stream particleStream, Predicate positionPredicate, int count) { + particleStream.limit(count).forEach(particle -> { + boolean inserted = false; + while (!inserted) { + try { + // Get a valid position + Vector position = grid.getRandomPosition(particle); + + if (positionPredicate != null && !positionPredicate.test(position)) continue; + + // Check if it is occupied + if (grid.isPositionOccupied(position)) continue; + + // Add the particle + grid.addParticle(particle, position); + + // Validate the grid + // The particle only has an algorithm for this purpose. + ParticleAlgorithm algorithm = particle.getAlgorithm(); + if (algorithm != null) { + if (!algorithm.isGridValid(grid)) { + grid.removeParticle(particle); + + continue; + } else { + // Delete the algorithm once we're done. + particle.setAlgorithm(null); + } + } + inserted = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + public static void addParticles(ParticleGrid grid, Map, Double> suppliers, int count) { int addedCount = 0; // Make the supplier randomizer RandomSelector> randomSupplier = RandomSelector.weighted(suppliers.keySet(), suppliers::get); - RandomSelector positionSupplier = RandomSelector.uniform(grid.getValidPositions()); - - while (addedCount < count) { - try { - // Get a valid position - Vector position = positionSupplier.next(Utils.random); - - // Check if it is occupied - if (grid.isPositionOccupied(position)) continue; - - // Get a particle supplier - Supplier supplier = randomSupplier.next(Utils.random); - - // Get a particle - Particle particle = supplier.get(); - - // Add the particle - grid.addParticle(particle, position); - - // Validate the grid - ParticleAlgorithm algorithm = particle.getAlgorithm(); - if (algorithm != null) { - if (!algorithm.isGridValid(grid)) { - grid.removeParticle(particle); - - continue; - } - } - addedCount++; - - System.out.println(addedCount); - } catch (Exception e) { - e.printStackTrace(); - } - } + addParticles(grid, Stream.generate(() -> randomSupplier.next(Utils.random).get()), null, count); } public static void addUniformWeightedParticles(ParticleGrid grid, List> suppliers, int count) { diff --git a/src/main/java/com/cemgokmen/particles/graphics/GridGraphics.java b/src/main/java/com/cemgokmen/particles/graphics/GridGraphics.java index 2d49482..480e9e8 100644 --- a/src/main/java/com/cemgokmen/particles/graphics/GridGraphics.java +++ b/src/main/java/com/cemgokmen/particles/graphics/GridGraphics.java @@ -23,6 +23,7 @@ import com.cemgokmen.particles.util.Utils; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; +import com.google.common.collect.Multimap; import com.orsonpdf.PDFDocument; import com.orsonpdf.PDFGraphics2D; import com.orsonpdf.Page; @@ -39,6 +40,8 @@ import java.io.IOException; import java.util.*; import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; public class GridGraphics { public static final int EMPTY_POSITION_RADIUS = 3; @@ -66,14 +69,32 @@ public class GridGraphics { public static final int PAGE_SIZE = 300; - public static Dimension getGridImageDimensions(ParticleGrid grid) { - List extremities = grid.getBoundaryVertices(); + public static Matrix getExtremityMatrix(ParticleGrid grid) { + Stream positions = grid.getBoundaryVertices().stream(); - // Store the extremities in a matrix - Matrix m = Matrix.zero(2, extremities.size()); - for (int i = 0; i < extremities.size(); i++) { - m.setColumn(i, grid.getUnitPixelCoordinates(extremities.get(i)).multiply(EDGE_LENGTH)); - } + // Temporary hack. TODO: Fix this. + positions = Stream.concat(grid.getValidPositions(), positions); + + Stream extremities = positions.map(grid::getUnitPixelCoordinates).map(v -> v.multiply(EDGE_LENGTH)); + + Matrix m = extremities.collect(() -> Matrix.zero(2, 2), (rolling, elem) -> { + // First row to get x, second row to get y. First col is min, second col is max. + for (int i = 0; i < 2; i++) { + rolling.set(i, 0, Math.min(rolling.get(i, 0), elem.get(i))); + rolling.set(i, 1, Math.max(rolling.get(i, 1), elem.get(i))); + } + }, (good, other) -> { + for (int i = 0; i < 2; i++) { + good.set(i, 0, Math.min(good.get(i, 0), other.get(i, 0))); + good.set(i, 1, Math.max(good.get(i, 1), other.get(i, 1))); + } + }); + + return m; + } + + public static Dimension getGridImageDimensions(ParticleGrid grid) { + Matrix m = getExtremityMatrix(grid); // Get the x and y coordinates as vectors Vector x = m.getRow(0); @@ -102,7 +123,8 @@ public static void saveGridAsVectorImage(ParticleGrid grid, File file) { Page page = pdfDoc.createPage(new Rectangle(PAGE_SIZE, PAGE_SIZE)); PDFGraphics2D graphics = page.getGraphics2D(); //g2.setRenderingHint(PDFHints.KEY_DRAW_STRING_TYPE, PDFHints.VALUE_DRAW_STRING_TYPE_VECTOR); - drawGridInfoOntoGraphics(grid, graphics, PAGE_SIZE); + + //drawGridInfoOntoGraphics(grid, graphics, PAGE_SIZE); drawGridOntoGraphics(grid, graphics, PAGE_SIZE); pdfDoc.writeToFile(file); @@ -114,7 +136,7 @@ public static MultipagePDFHandler createMultipagePDF(File file) throws FileNotFo public static void drawGridOntoMultipagePDF(ParticleGrid grid, MultipagePDFHandler multipagePDFHandler) throws IOException { multipagePDFHandler.runOnNewPage(grid.getActivationsRun() + "", graphics -> { - drawGridInfoOntoGraphics(grid, graphics, PAGE_SIZE); + //drawGridInfoOntoGraphics(grid, graphics, PAGE_SIZE); drawGridOntoGraphics(grid, graphics, multipagePDFHandler.getSize()); }); } @@ -162,13 +184,7 @@ public static void drawGridOntoGraphics(ParticleGrid grid, Graphics2D graphics, } public static void drawGridOntoGraphics(ParticleGrid grid, Graphics2D graphics, double size) { - List extremities = grid.getBoundaryVertices(); - - // Store the extremities in a matrix - Matrix m = Matrix.zero(2, extremities.size()); - for (int i = 0; i < extremities.size(); i++) { - m.setColumn(i, grid.getUnitPixelCoordinates(extremities.get(i)).multiply(EDGE_LENGTH)); - } + Matrix m = getExtremityMatrix(grid); // Get the x and y coordinates as vectors Vector x = m.getRow(0); @@ -188,14 +204,14 @@ public static void drawGridOntoGraphics(ParticleGrid grid, Graphics2D graphics, // Center the shorter edge AffineTransform secondTransform = new AffineTransform(); if (maxRange == xRange) { - secondTransform.translate(0, (maxRange - yRange / 2.0)); + secondTransform.translate(0, (maxRange - yRange) / 2.0); } else { secondTransform.translate((maxRange - xRange) / 2.0, 0); } // Now scale the thing so that it fits in the graphics context. AffineTransform lastTransform = new AffineTransform(); - lastTransform.scale(size / maxRange, size / maxRange); + lastTransform.scale(size / (maxRange + 50), size / (maxRange + 50)); firstTransform.preConcatenate(secondTransform); firstTransform.preConcatenate(lastTransform); @@ -206,42 +222,26 @@ public static void drawGridOntoGraphics(ParticleGrid grid, Graphics2D graphics, private static void drawGrid(Graphics2D graphics, ParticleGrid grid) { drawBackground(graphics, grid); - drawBorders(graphics, grid); + grid.drawBoundary(graphics); drawEdges(graphics, grid); drawParticles(graphics, grid); + drawPath(graphics, grid); //drawCenterOfMass(graphics, grid); } private static void drawBackground(Graphics2D graphics, ParticleGrid grid) { graphics.setColor(EMPTY_POSITION_COLOR); - for (Vector v : grid.getValidPositions()) { + grid.getValidPositions().forEach(v -> { Vector screenPosition = grid.getUnitPixelCoordinates(v).multiply(EDGE_LENGTH); Vector topLeft = screenPosition.add(EMPTY_POSITION_TOP_LEFT_VECTOR); - Ellipse2D.Double circle = new Ellipse2D.Double(topLeft.get(0), topLeft.get(1), - 2 * EMPTY_POSITION_RADIUS, 2 * EMPTY_POSITION_RADIUS); - graphics.fill(circle); - } - } - - private static void drawBorders(Graphics2D graphics, ParticleGrid grid) { - List extremities = grid.getBoundaryVertices(); - Path2D.Double polygon = new Path2D.Double.Double(); - - Vector firstPosition = grid.getUnitPixelCoordinates(extremities.get(0).multiply(EDGE_LENGTH)); - polygon.moveTo(firstPosition.get(0), firstPosition.get(1)); - - for (Vector position : extremities) { - Vector screenPosition = grid.getUnitPixelCoordinates(position).multiply(EDGE_LENGTH); - polygon.lineTo(screenPosition.get(0), screenPosition.get(1)); - } - - polygon.closePath(); - - graphics.setColor(BORDER_COLOR); - graphics.setStroke(BORDER_STROKE); - graphics.draw(polygon); + if (!grid.isPositionOccupied(v)) { + Ellipse2D.Double circle = new Ellipse2D.Double(topLeft.get(0), topLeft.get(1), + 2 * EMPTY_POSITION_RADIUS, 2 * EMPTY_POSITION_RADIUS); + graphics.fill(circle); + } + }); } private static void drawEdges(Graphics2D graphics, ParticleGrid grid) { @@ -253,12 +253,14 @@ private static void drawEdges(Graphics2D graphics, ParticleGrid grid) { grid.getAllParticles().forEach(p -> { Vector position = grid.getUnitPixelCoordinates(grid.getParticlePosition(p)).multiply(EDGE_LENGTH); - for (Particle nbr : grid.getParticleNeighbors(p, false)) { - if (!drawnParticles.contains(nbr)) { - Vector nbrPosition = grid.getUnitPixelCoordinates(grid.getParticlePosition(nbr)).multiply(EDGE_LENGTH); + if (p.shouldDrawEdges()) { + for (Particle nbr : grid.getParticleNeighbors(p, false)) { + if (nbr.shouldDrawEdges() && !drawnParticles.contains(nbr)) { + Vector nbrPosition = grid.getUnitPixelCoordinates(grid.getParticlePosition(nbr)).multiply(EDGE_LENGTH); - Line2D.Double line = new Line2D.Double(position.get(0), position.get(1), nbrPosition.get(0), nbrPosition.get(1)); - graphics.draw(line); + Line2D.Double line = new Line2D.Double(position.get(0), position.get(1), nbrPosition.get(0), nbrPosition.get(1)); + graphics.draw(line); + } } } @@ -267,9 +269,11 @@ private static void drawEdges(Graphics2D graphics, ParticleGrid grid) { } private static void drawParticles(Graphics2D graphics, ParticleGrid grid) { + Function gridToScreenCoords = (v) -> grid.getUnitPixelCoordinates(v).multiply(EDGE_LENGTH); + grid.getAllParticles().forEach(p -> { - Vector position = grid.getUnitPixelCoordinates(grid.getParticlePosition(p)).multiply(EDGE_LENGTH); - p.drawParticle(graphics, position, EDGE_LENGTH); + Vector position = gridToScreenCoords.apply(grid.getParticlePosition(p)); + p.drawParticle(graphics, position, EDGE_LENGTH, gridToScreenCoords); }); } @@ -287,4 +291,33 @@ private static void drawCenterOfMass(Graphics2D graphics, ParticleGrid grid) { 2 * COM_RADIUS, 2 * COM_RADIUS); graphics.fill(circle); } + + private static void drawPath(Graphics2D graphics, ParticleGrid grid) { + final List plotPoints = grid.getAdditionalPlotPoints(); + + float hue = 0.66f; + float bri = 1f; + + // Start at move 1000 + for (int i = 1000; i < plotPoints.size() - 1; i++) { + ParticleGrid.DataPoint from = plotPoints.get(i); + ParticleGrid.DataPoint to = plotPoints.get(i + 1); + + // Are they more than 2 units apart? + Vector diff = from.position.subtract(to.position); + if (diff.euclideanNorm() > 2) continue; + + Vector fromScreen = grid.getUnitPixelCoordinates(from.position).multiply(EDGE_LENGTH); + Vector toScreen = grid.getUnitPixelCoordinates(to.position).multiply(EDGE_LENGTH); + + double weight = (from.weight + to.weight) / 2; + + Color c = Color.getHSBColor(hue, (float) (0.1 + 0.8 * weight), 1); + graphics.setColor(c); + + Stroke stroke = new BasicStroke(1); + Line2D line = new Line2D.Double(fromScreen.get(0), fromScreen.get(1), toScreen.get(0), toScreen.get(1)); + graphics.draw(line); + } + } } \ No newline at end of file diff --git a/src/main/java/com/cemgokmen/particles/io/GridIO.java b/src/main/java/com/cemgokmen/particles/io/GridIO.java index 9c3351f..524db75 100644 --- a/src/main/java/com/cemgokmen/particles/io/GridIO.java +++ b/src/main/java/com/cemgokmen/particles/io/GridIO.java @@ -69,8 +69,8 @@ public static ParticleGrid importParticlesFromInputStream(InputStream in, Class< Scanner input = new Scanner(in); ParticleGrid grid; - if (GRID_LOADER_MAP.containsKey(gridClass)) { - grid = GRID_LOADER_MAP.get(gridClass).apply(input); + if (GridLoaders.GRID_LOADER_MAP.containsKey(gridClass)) { + grid = GridLoaders.GRID_LOADER_MAP.get(gridClass).apply(input); } else { throw new InvalidParticleClassException(gridClass); } @@ -84,8 +84,8 @@ public static ParticleGrid importParticlesFromInputStream(InputStream in, Class< while (input.hasNextLine()) { try { - if (PARTICLE_TYPE_LOADER_MAP.containsKey(particleClass)) { - PARTICLE_TYPE_LOADER_MAP.get(particleClass).accept(grid, input); + if (ParticleLoaders.PARTICLE_TYPE_LOADER_MAP.containsKey(particleClass)) { + ParticleLoaders.PARTICLE_TYPE_LOADER_MAP.get(particleClass).accept(grid, input); } else { throw new InvalidParticleClassException(particleClass); } @@ -101,23 +101,8 @@ public static ParticleGrid importParticlesFromInputStream(InputStream in, Class< return grid; } - private static final ImmutableMap, BiConsumer> PARTICLE_TYPE_LOADER_MAP = - new ImmutableMap.Builder, BiConsumer>() - .put(AmoebotParticle.class, ParticleLoaders::loadAmoebotParticle) - .put(SeparableAmoebotParticle.class, ParticleLoaders::loadSeparableAmoebotParticle) - .put(DirectedAmoebotParticle.class, ParticleLoaders::loadDirectedAmoebotParticle) - .put(ForagingAmoebotParticle.class, ParticleLoaders::loadForagingAmoebotParticle) - .build(); - - private static final ImmutableMap, Function> GRID_LOADER_MAP = - new ImmutableMap.Builder, Function>() - .put(HexagonalAmoebotGrid.class, GridLoaders::loadHexagonalAmoebotGrid) - .put(QuadrilateralAmoebotGrid.class, GridLoaders::loadQuadrilateralAmoebotGrid) - .put(ToroidalAmoebotGrid.class, GridLoaders::loadToroidalAmoebotGrid) - .build(); - - public static final ImmutableList> ALLOWED_PARTICLE_TYPES = ImmutableList.copyOf(PARTICLE_TYPE_LOADER_MAP.keySet()); - public static final ImmutableList> ALLOWED_GRID_TYPES = ImmutableList.copyOf(GRID_LOADER_MAP.keySet()); + public static final ImmutableList> ALLOWED_PARTICLE_TYPES = ImmutableList.copyOf(ParticleLoaders.PARTICLE_TYPE_LOADER_MAP.keySet()); + public static final ImmutableList> ALLOWED_GRID_TYPES = ImmutableList.copyOf(GridLoaders.GRID_LOADER_MAP.keySet()); public static class InvalidParticleClassException extends Exception { public InvalidParticleClassException() { diff --git a/src/main/java/com/cemgokmen/particles/io/GridLoaders.java b/src/main/java/com/cemgokmen/particles/io/GridLoaders.java index 018678d..ae8492f 100644 --- a/src/main/java/com/cemgokmen/particles/io/GridLoaders.java +++ b/src/main/java/com/cemgokmen/particles/io/GridLoaders.java @@ -19,13 +19,26 @@ package com.cemgokmen.particles.io; import com.cemgokmen.particles.models.ParticleGrid; -import com.cemgokmen.particles.models.amoebot.gridshapes.HexagonalAmoebotGrid; -import com.cemgokmen.particles.models.amoebot.gridshapes.QuadrilateralAmoebotGrid; -import com.cemgokmen.particles.models.amoebot.gridshapes.ToroidalAmoebotGrid; +import com.cemgokmen.particles.models.amoebot.gridshapes.*; +import com.cemgokmen.particles.models.continuous.ContinuousParticleGrid; +import com.cemgokmen.particles.models.continuous.boundary.CircularBoundary; +import com.google.common.collect.ImmutableMap; import java.util.Scanner; +import java.util.function.Function; public class GridLoaders { + protected static final ImmutableMap, Function> GRID_LOADER_MAP = + new ImmutableMap.Builder, Function>() + .put(HexagonalAmoebotGrid.class, GridLoaders::loadHexagonalAmoebotGrid) + .put(QuadrilateralAmoebotGrid.class, GridLoaders::loadQuadrilateralAmoebotGrid) + .put(ToroidalAmoebotGrid.class, GridLoaders::loadToroidalAmoebotGrid) + .put(LinearAmoebotGrid.class, GridLoaders::loadLinearAmoebotGrid) + .put(CircularAmoebotGrid.class, GridLoaders::loadCircularAmoebotGrid) + .put(ContinuousParticleGrid.class, GridLoaders::loadContinuousGrid) + .build(); + + static ParticleGrid loadHexagonalAmoebotGrid(Scanner input) { int radius = input.nextInt(); @@ -43,4 +56,24 @@ static ParticleGrid loadToroidalAmoebotGrid(Scanner input) { return new ToroidalAmoebotGrid(sideHalfLength); } + + static ParticleGrid loadLinearAmoebotGrid(Scanner input) { + int halfLength = input.nextInt(); + + return new LinearAmoebotGrid(halfLength); + } + + static ParticleGrid loadCircularAmoebotGrid(Scanner input) { + int halfLength = input.nextInt(); + + return new CircularAmoebotGrid(halfLength); + } + + static ParticleGrid loadContinuousGrid(Scanner input) { + double boundaryRadius = input.nextDouble(); + + CircularBoundary boundary = new CircularBoundary(boundaryRadius); + + return new ContinuousParticleGrid(boundary); + } } diff --git a/src/main/java/com/cemgokmen/particles/io/ParticleLoaders.java b/src/main/java/com/cemgokmen/particles/io/ParticleLoaders.java index fad54a3..1934d0c 100644 --- a/src/main/java/com/cemgokmen/particles/io/ParticleLoaders.java +++ b/src/main/java/com/cemgokmen/particles/io/ParticleLoaders.java @@ -18,18 +18,30 @@ package com.cemgokmen.particles.io; +import com.cemgokmen.particles.models.amoebot.specializedparticles.*; +import com.cemgokmen.particles.models.continuous.ContinuousParticle; +import com.cemgokmen.particles.models.continuous.ContinuousParticleGrid; import com.cemgokmen.particles.util.Utils; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; import com.cemgokmen.particles.models.amoebot.*; -import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; -import com.cemgokmen.particles.models.amoebot.specializedparticles.FoodAmoebotParticle; -import com.cemgokmen.particles.models.amoebot.specializedparticles.ForagingAmoebotParticle; -import com.cemgokmen.particles.models.amoebot.specializedparticles.SeparableAmoebotParticle; +import com.google.common.collect.ImmutableMap; +import org.la4j.Vector; import java.util.Scanner; +import java.util.function.BiConsumer; public class ParticleLoaders { + protected static final ImmutableMap, BiConsumer> PARTICLE_TYPE_LOADER_MAP = + new ImmutableMap.Builder, BiConsumer>() + .put(AmoebotParticle.class, ParticleLoaders::loadAmoebotParticle) + .put(SeparableAmoebotParticle.class, ParticleLoaders::loadSeparableAmoebotParticle) + .put(DirectedAmoebotParticle.class, ParticleLoaders::loadDirectedAmoebotParticle) + .put(ForagingAmoebotParticle.class, ParticleLoaders::loadForagingAmoebotParticle) + .put(ContinuousDirectedAmoebotParticle.class, ParticleLoaders::loadRandomContinuousDirectedAmoebotParticle) + .put(ContinuousParticle.class, ParticleLoaders::loadContinuousParticle) + .build(); + static void loadAmoebotParticle(ParticleGrid grid, Scanner input) { int x = input.nextInt(); int y = input.nextInt(); @@ -72,6 +84,18 @@ static void loadDirectedAmoebotParticle(ParticleGrid grid, Scanner input) { } } + static void loadRandomContinuousDirectedAmoebotParticle(ParticleGrid grid, Scanner input) { + int x = input.nextInt(); + int y = input.nextInt(); + + Particle p = new ContinuousDirectedAmoebotParticle(grid.getCompass(), Utils.randomDouble() * Math.PI * 2, false); + try { + grid.addParticle(p, Utils.getVector(x, y)); + } catch (Exception e) { + e.printStackTrace(); + } + } + static void loadForagingAmoebotParticle(ParticleGrid grid, Scanner input) { String type = input.next(); int x = input.nextInt(); @@ -88,4 +112,23 @@ static void loadForagingAmoebotParticle(ParticleGrid grid, Scanner input) { e.printStackTrace(); } } + + static void loadContinuousParticle(ParticleGrid grid, Scanner input) { + // TODO: This loader parses a legacy format. Fix it. + double x = input.nextDouble(); + double y = input.nextDouble(); + + // Apply the hex coord conversion + Vector pos = Utils.getVector(x, y); + Vector cartesian = AmoebotGrid.convertAxialToCartesian(pos); + + double rotation = input.nextDouble() * (Math.PI / 3); + + Particle p = new ContinuousParticle(0.5, rotation); + try { + grid.addParticle(p, cartesian); + } catch (Exception e) { + e.printStackTrace(); + } + } } diff --git a/src/main/java/com/cemgokmen/particles/io/SampleSystemMetadata.java b/src/main/java/com/cemgokmen/particles/io/SampleSystemMetadata.java index bc9b2e0..314156a 100644 --- a/src/main/java/com/cemgokmen/particles/io/SampleSystemMetadata.java +++ b/src/main/java/com/cemgokmen/particles/io/SampleSystemMetadata.java @@ -22,20 +22,26 @@ import com.cemgokmen.particles.models.ParticleGrid; import com.cemgokmen.particles.models.amoebot.AmoebotGrid; import com.cemgokmen.particles.models.amoebot.AmoebotParticle; -import com.cemgokmen.particles.models.amoebot.gridshapes.HexagonalAmoebotGrid; -import com.cemgokmen.particles.models.amoebot.gridshapes.QuadrilateralAmoebotGrid; -import com.cemgokmen.particles.models.amoebot.gridshapes.ToroidalAmoebotGrid; +import com.cemgokmen.particles.models.amoebot.gridshapes.*; +import com.cemgokmen.particles.models.amoebot.specializedparticles.ContinuousDirectedAmoebotParticle; import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; import com.cemgokmen.particles.models.amoebot.specializedparticles.ForagingAmoebotParticle; import com.cemgokmen.particles.models.amoebot.specializedparticles.SeparableAmoebotParticle; +import com.cemgokmen.particles.models.continuous.ContinuousParticle; +import com.cemgokmen.particles.models.continuous.ContinuousParticleGrid; public enum SampleSystemMetadata { AMOEBOT_1000_2CLASS("Amoebot/1000 2-class particles", HexagonalAmoebotGrid.class, SeparableAmoebotParticle.class, "sample_systems/separation/1000particles-2class-spread.txt"), AMOEBOT_100_2CLASS("Amoebot/100 2-class particles", HexagonalAmoebotGrid.class, SeparableAmoebotParticle.class, "sample_systems/separation/100particles-2class.txt"), + AMOEBOT_20_LINEAR("Amoebot/20 linear particles", CircularAmoebotGrid.class, DirectedAmoebotParticle.class, "sample_systems/alignment/20particles-linear.txt"), AMOEBOT_100_6DIRECTION("Amoebot/100 6-direction particles", ToroidalAmoebotGrid.class, DirectedAmoebotParticle.class, "sample_systems/alignment/100particles-randomdir.txt"), AMOEBOT_100_6DIRECTION_LARGE("Amoebot/100 6-direction particles w/ large grid", ToroidalAmoebotGrid.class, DirectedAmoebotParticle.class, "sample_systems/alignment/100particles-randomdir-largegrid.txt"), + AMOEBOT_100_CONTINUOUS("Amoebot/100 continuous-direction particles", ToroidalAmoebotGrid.class, ContinuousDirectedAmoebotParticle.class, "sample_systems/alignment/100particles-randomdir.txt"), + AMOEBOT_100_CONTINUOUS_LARGE("Amoebot/100 continuous-direction particles w/ large grid", ToroidalAmoebotGrid.class, ContinuousDirectedAmoebotParticle.class, "sample_systems/alignment/100particles-randomdir-largegrid.txt"), AMOEBOT_100_1FOOD("Amoebot/100 particles and 1 food", HexagonalAmoebotGrid.class, ForagingAmoebotParticle.class, "sample_systems/foraging/100particles-1food.txt"), - AMOEBOT_3PARTICLES("Amoebot/3 particles", HexagonalAmoebotGrid.class, AmoebotParticle.class, "sample_systems/compression/3particles.txt"); + AMOEBOT_3PARTICLES("Amoebot/3 particles", HexagonalAmoebotGrid.class, AmoebotParticle.class, "sample_systems/compression/3particles.txt"), + CONT_100("Continuous/100 particles", ContinuousParticleGrid.class, ContinuousParticle.class, "sample_systems/alignment/100particles-randomdir.txt"), + CONT_100_LARGE("Continuous/100 particles w/ large grid", ContinuousParticleGrid.class, ContinuousParticle.class, "sample_systems/alignment/100particles-randomdir-largegrid.txt"); public final String humanReadableName; public final Class gridClass; diff --git a/src/main/java/com/cemgokmen/particles/models/Particle.java b/src/main/java/com/cemgokmen/particles/models/Particle.java index 1088279..654aad7 100644 --- a/src/main/java/com/cemgokmen/particles/models/Particle.java +++ b/src/main/java/com/cemgokmen/particles/models/Particle.java @@ -19,15 +19,13 @@ package com.cemgokmen.particles.models; import com.cemgokmen.particles.algorithms.ParticleAlgorithm; -import com.cemgokmen.particles.models.amoebot.AmoebotGrid; import com.google.common.collect.ImmutableList; import org.la4j.Vector; import java.awt.*; +import java.util.function.Function; public abstract class Particle { - public final ImmutableList> COMPATIBLE_GRIDS = new ImmutableList.Builder>().add(ParticleGrid.class).build(); - protected ParticleGrid grid; protected ParticleAlgorithm algorithm; @@ -35,10 +33,16 @@ public void setGrid(ParticleGrid grid) { this.grid = grid; } + public ParticleGrid getGrid() { return this.grid; } + public ParticleAlgorithm getAlgorithm() { return this.algorithm; } + public boolean shouldDrawEdges() { + return true; + } + public void setAlgorithm(ParticleAlgorithm algorithm) { if (algorithm != null && !algorithm.isParticleAllowed(this)) { throw new RuntimeException("This particle is not allowed to run this algorithm."); @@ -52,5 +56,5 @@ public void activate() { } } - public abstract void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength); + public abstract void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength, Function gridToScreenCoords); } diff --git a/src/main/java/com/cemgokmen/particles/models/ParticleGrid.java b/src/main/java/com/cemgokmen/particles/models/ParticleGrid.java index c78f881..9ccd2ea 100644 --- a/src/main/java/com/cemgokmen/particles/models/ParticleGrid.java +++ b/src/main/java/com/cemgokmen/particles/models/ParticleGrid.java @@ -19,20 +19,35 @@ package com.cemgokmen.particles.models; import com.cemgokmen.particles.algorithms.ParticleAlgorithm; +import com.cemgokmen.particles.algorithms.RuleUtils; +import com.cemgokmen.particles.capabilities.ParticleCapability; +import com.cemgokmen.particles.graphics.GridGraphics; import com.cemgokmen.particles.storage.ParticleStorage; import com.cemgokmen.particles.util.RandomSelector; import com.cemgokmen.particles.util.Utils; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.*; import org.la4j.Vector; import javax.annotation.Nonnull; +import java.awt.*; +import java.awt.geom.Path2D; import java.util.*; +import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class ParticleGrid { private int activationsRun = 0; + private int movesMade = 0; + + public static class DataPoint { + public Vector position; + public double weight; + } + + private List history = Lists.newArrayList(); + private Particle chosenParticle = null; public static class Direction { private final Vector vector; @@ -54,7 +69,7 @@ public String toString() { public abstract static class Compass { abstract public ImmutableList getDirections(); - abstract public Direction shiftDirectionCounterclockwise(Direction d, int times); + abstract public Direction shiftDirectionCounterclockwise(Direction d, double times); abstract public double getAngleBetweenDirections(Direction a, Direction b); } @@ -63,11 +78,13 @@ public abstract static class Compass { abstract public Compass getCompass(); - abstract public boolean isPositionValid(Vector p); + abstract public boolean isPositionValid(Vector p, Particle forParticle); abstract public boolean isParticleValid(Particle p); - abstract public List getValidPositions(); + abstract public Stream getValidPositions(); + + abstract public Vector getRandomPosition(Particle particle); abstract public List getBoundaryVertices(); @@ -76,11 +93,19 @@ public boolean isParticleOnGrid(Particle p) { } public void addParticle(Particle p, Vector position) throws Exception { + if (!this.isPositionValid(position, p)) { + throw new Exception("Invalid add - position " + position + " out of bounds."); + } + if (this.isPositionOccupied(position)) { throw new Exception("Invalid add - there already is a particle at position " + position); } this.getStorage().addParticle(p, position); p.setGrid(this); + + if (this.chosenParticle == null) { + this.chosenParticle = p; + } } public void removeParticle(Particle p) throws Exception { @@ -92,12 +117,34 @@ public void removeParticle(Particle p) throws Exception { } public void moveParticle(Particle p, Vector v) throws Exception { + if (!this.isPositionValid(v, p)) { + throw new Exception("Invalid move - position " + v + " out of bounds."); + } + if (this.isPositionOccupied(v)) { throw new Exception("Cannot move to occupied position " + v); } this.removeParticle(p); this.addParticle(p, v); + this.movesMade++; + + // Get the largest component + Set largestComponent = RuleUtils.getLargestComponent(this); + Vector centroid = largestComponent.parallelStream().map(this::getParticlePosition) + .reduce(Vector.zero(2), Vector::add) + .divide(largestComponent.size()); + + // Add to the datapoints now + // Find the thing + double componentSize = largestComponent.size(); + double weight = componentSize / this.getParticleCount(); + + DataPoint dp = new DataPoint(); + dp.position = centroid; + dp.weight = weight; + + this.history.add(dp); } public Particle getParticleAtPosition(Vector position) { @@ -117,18 +164,12 @@ public Vector getPositionInDirection(Vector p, Direction d) { return p.add(d.getVector()); } - public Vector[] getAdjacentPositions(Vector p) { - List validDirections = this.getCompass().getDirections(); - Vector[] adjacentPositions = new Vector[validDirections.size()]; - for (int i = 0; i < validDirections.size(); i++) { - adjacentPositions[i] = this.getPositionInDirection(p, validDirections.get(i)); - } - - return adjacentPositions; + public Stream getAdjacentPositions(Vector p) { + return this.getCompass().getDirections().stream().map(d -> this.getPositionInDirection(p, d)); } public boolean arePositionsAdjacent(Vector p1, Vector p2) { - return Arrays.asList(this.getAdjacentPositions(p1)).contains(p2); + return this.getAdjacentPositions(p1).anyMatch(p -> p == p2); } public int getParticleCount() { @@ -165,15 +206,11 @@ public Particle getParticleNeighborInDirection(Particle p, Direction d, Predicat } public List getPositionNeighbors(Vector position, boolean includeNulls) { - List neighbors = new ArrayList<>(6); - for (Vector v : this.getAdjacentPositions(position)) { - Particle neighbor = this.getParticleAtPosition(v); - if (includeNulls || neighbor != null) { - neighbors.add(neighbor); - } - } + Stream stream = this.getAdjacentPositions(position).map(this::getParticleAtPosition); + + if (!includeNulls) stream = stream.filter(Objects::nonNull); - return neighbors; + return stream.collect(Collectors.toList()); } public List getPositionNeighbors(Vector position, @Nonnull Predicate filter, boolean includeNulls) { @@ -227,7 +264,19 @@ public void runActivations(int numActivations) { } } - public abstract List> getCompatibleAlgorithms(); + public List> getCompatibleAlgorithms() { + return ParticleAlgorithm.IMPLEMENTATIONS.stream() + .filter(impl -> { + try { + // Every algorithm that allows all particles on our grid is compatible. + ParticleAlgorithm instance = Utils.getZeroParameterPublicConstructor(impl).newInstance(); + return ParticleGrid.this.getAllParticles().allMatch(instance::isParticleAllowed); + } catch (Exception e) { + return false; + } + }) + .collect(Collectors.toList()); + } public Stream getRunningAlgorithms() { Set runningAlgorithms = new HashSet<>(); @@ -248,15 +297,44 @@ public int getActivationsRun() { return this.activationsRun; } + public int getMovesMade() { + return this.movesMade; + } + public Map getGridInformation() { LinkedHashMap map = new LinkedHashMap<>(); map.put("Particle count", this.getParticleCount() + ""); map.put("Activations run", this.activationsRun + ""); + map.put("Moves made", this.movesMade + ""); map.put("Center of mass", this.getCenterOfMass().toString()); this.getRunningAlgorithms().forEach(algorithm -> map.putAll(algorithm.getInformation(this))); return map; } + + public void drawBoundary(Graphics2D graphics) { + List extremities = this.getBoundaryVertices(); + Path2D.Double polygon = new Path2D.Double.Double(); + + Vector firstPosition = this.getUnitPixelCoordinates(extremities.get(0).multiply(GridGraphics.EDGE_LENGTH)); + polygon.moveTo(firstPosition.get(0), firstPosition.get(1)); + + for (Vector position : extremities) { + Vector screenPosition = this.getUnitPixelCoordinates(position).multiply(GridGraphics.EDGE_LENGTH); + polygon.lineTo(screenPosition.get(0), screenPosition.get(1)); + } + + polygon.closePath(); + + graphics.setColor(GridGraphics.BORDER_COLOR); + graphics.setStroke(GridGraphics.BORDER_STROKE); + graphics.draw(polygon); + } + + public List getAdditionalPlotPoints() { + // Return points and strengths between 0 and 1 to be plotted. + return ImmutableList.copyOf(this.history); + } } diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/AmoebotGrid.java b/src/main/java/com/cemgokmen/particles/models/amoebot/AmoebotGrid.java index 54496eb..f243611 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/AmoebotGrid.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/AmoebotGrid.java @@ -18,18 +18,16 @@ package com.cemgokmen.particles.models.amoebot; -import com.cemgokmen.particles.algorithms.*; import com.cemgokmen.particles.util.Utils; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import org.la4j.Matrix; import org.la4j.Vector; import org.la4j.matrix.dense.Basic2DMatrix; import java.util.HashMap; -import java.util.List; +import java.util.stream.Stream; public abstract class AmoebotGrid extends ParticleGrid { public static class AmoebotCompass extends Compass { @@ -40,7 +38,7 @@ public static class AmoebotCompass extends Compass { private static final Direction SW = new Direction(Utils.getVector(-1, 1)); private static final Direction S = new Direction(Utils.getVector(0, 1)); - private static final Direction[] directions = {SE, NE, N, NW, SW, S}; + private static final Direction[] directions = {N, NW, SW, S, SE, NE}; private final HashMap directionOrder; public AmoebotCompass() { @@ -56,9 +54,9 @@ public ImmutableList getDirections() { } @Override - public Direction shiftDirectionCounterclockwise(Direction d, int times) { + public Direction shiftDirectionCounterclockwise(Direction d, double times) { int index = this.directionOrder.get(d); - return directions[Math.floorMod(index + times, directions.length)]; + return directions[Math.floorMod(index + (int) times, directions.length)]; } public int getMinorArcLength(Direction a, Direction b) { @@ -92,16 +90,6 @@ public boolean isParticleValid(Particle p) { return p instanceof AmoebotParticle; } - @Override - public List> getCompatibleAlgorithms() { - return Lists.newArrayList( - CompressionAlgorithm.class, - SeparationAlgorithm.class, - AlignmentAlgorithm.class, - ForagingAlgorithm.class - ); - } - private static final Matrix axialToPixel = new Basic2DMatrix(new double[][]{ {3.0 / 2.0, 0.0}, {Math.sqrt(3) / 2.0, Math.sqrt(3)} @@ -109,6 +97,17 @@ public List> getCompatibleAlgorithms() { @Override public Vector getUnitPixelCoordinates(Vector in) { + return convertAxialToCartesian(in); + } + + public static Vector convertAxialToCartesian(Vector in) { return axialToPixel.multiply(in); } + + @Override + public Vector getRandomPosition(Particle particle) { + int cnt = (int) this.getValidPositions().count(); + long idx = Utils.random.nextInt(cnt - 1); + return this.getValidPositions().skip(idx).findFirst().get(); + } } \ No newline at end of file diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/AmoebotParticle.java b/src/main/java/com/cemgokmen/particles/models/amoebot/AmoebotParticle.java index c3d79f2..51589d4 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/AmoebotParticle.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/AmoebotParticle.java @@ -18,21 +18,22 @@ package com.cemgokmen.particles.models.amoebot; +import com.cemgokmen.particles.capabilities.MovementCapable; +import com.cemgokmen.particles.capabilities.NeighborDetectionCapable; +import com.cemgokmen.particles.capabilities.UniformRandomDirectionCapable; +import com.cemgokmen.particles.capabilities.SwapMovementCapable; import com.cemgokmen.particles.util.Utils; import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.models.ParticleGrid; -import com.google.common.collect.ImmutableList; import org.la4j.Vector; import java.awt.*; import java.awt.geom.Ellipse2D; import java.util.List; +import java.util.function.Function; import java.util.function.Predicate; -public class AmoebotParticle extends Particle { - // The capabilities of the model are implemented here. - public final ImmutableList> COMPATIBLE_GRIDS = new ImmutableList.Builder>().add(AmoebotGrid.class).build(); - +public class AmoebotParticle extends Particle implements UniformRandomDirectionCapable, MovementCapable, SwapMovementCapable, NeighborDetectionCapable { public List getNeighborParticles(boolean includeNulls, Predicate filter) { if (filter == null) { return this.grid.getParticleNeighbors(this, includeNulls); @@ -49,6 +50,13 @@ public List getAdjacentPositionNeighborParticles(ParticleGrid.Directio return this.grid.getPositionNeighbors(adjacentPosition, filter, includeNulls); } + @Override + public boolean isDirectionInBounds(ParticleGrid.Direction d) { + Vector curPos = this.grid.getParticlePosition(this); + Vector inDirection = this.grid.getPositionInDirection(curPos, d); + return this.grid.isPositionValid(inDirection, this); + } + public Particle getNeighborInDirection(ParticleGrid.Direction d, int counterclockwiseShift, Predicate filter) { if (counterclockwiseShift != 0) { d = this.grid.getCompass().shiftDirectionCounterclockwise(d, counterclockwiseShift); @@ -70,46 +78,62 @@ public Particle getAdjacentPositionNeighborInDirection(ParticleGrid.Direction d, return (filter == null || filter.test(neighbor)) ? neighbor : null; } - public ParticleGrid.Direction getRandomDirection() { + public ParticleGrid.Direction getUniformRandomDirection() { List directions = this.grid.getCompass().getDirections(); return directions.get(Utils.randomInt(directions.size())); } - public void move(ParticleGrid.Direction inDirection, boolean swapsAllowed, boolean nonSwapsAllowed) throws Exception { + public void move(ParticleGrid.Direction inDirection) { Vector current = this.grid.getParticlePosition(this); Vector target = this.grid.getPositionInDirection(current, inDirection); //System.out.printf("Moving particle of color %d from %s to %s\n", ((SeparableAmoebotParticle)this).getClassNumber(), current, target); Particle atTarget = this.grid.getParticleAtPosition(target); - if (atTarget == null) { - if (!nonSwapsAllowed) { - throw new InvalidMoveException("nonswap"); - } - - // Make the move - this.grid.moveParticle(this, target); - } else { - if (!swapsAllowed) { - throw new InvalidMoveException("swap"); - } - if (!this.getClass().equals(atTarget.getClass())) { - throw new InvalidMoveException("swap between different types"); + try { + if (atTarget == null) { + // Make the move + this.grid.moveParticle(this, target); + } else { + throw new InvalidMoveException("Position is occupied."); } + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + } - // Remove the occupying particle - this.grid.removeParticle(atTarget); + public void swapMove(ParticleGrid.Direction inDirection) { + Vector current = this.grid.getParticlePosition(this); + Vector target = this.grid.getPositionInDirection(current, inDirection); - // Move there - this.grid.moveParticle(this, target); + //System.out.printf("Moving particle of color %d from %s to %s\n", ((SeparableAmoebotParticle)this).getClassNumber(), current, target); - // Add occupying particle - this.grid.addParticle(atTarget, current); + Particle atTarget = this.grid.getParticleAtPosition(target); + try { + if (atTarget == null) { + // Make the move + this.grid.moveParticle(this, target); + } else { + if (!this.getClass().equals(atTarget.getClass())) { + throw new InvalidMoveException("swap between different types"); + } + + // Remove the occupying particle + this.grid.removeParticle(atTarget); + + // Move there + this.grid.moveParticle(this, target); + + // Add occupying particle + this.grid.addParticle(atTarget, current); + } + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); } } public boolean isDirectionWithinBounds(ParticleGrid.Direction d) { - return this.grid.isPositionValid(this.grid.getPositionInDirection(this.grid.getParticlePosition(this), d)); + return this.grid.isPositionValid(this.grid.getPositionInDirection(this.grid.getParticlePosition(this), d), this); } public static final int CIRCLE_RADIUS = 12; @@ -126,7 +150,7 @@ public Color getCircleFillColor() { } @Override - public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength) { + public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength, Function gridToScreenCoords) { graphics.setColor(CIRCLE_STROKE_COLOR); graphics.setPaint(this.getCircleFillColor()); graphics.setStroke(CIRCLE_STROKE); diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/InvalidMoveException.java b/src/main/java/com/cemgokmen/particles/models/amoebot/InvalidMoveException.java index 85b65e0..acd876f 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/InvalidMoveException.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/InvalidMoveException.java @@ -18,7 +18,7 @@ package com.cemgokmen.particles.models.amoebot; -public class InvalidMoveException extends Exception { +public class InvalidMoveException extends RuntimeException { public InvalidMoveException(String type) { super(type); } diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/CircularAmoebotGrid.java b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/CircularAmoebotGrid.java new file mode 100644 index 0000000..941fcfc --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/CircularAmoebotGrid.java @@ -0,0 +1,138 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.models.amoebot.gridshapes; + +import com.cemgokmen.particles.graphics.GridGraphics; +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.util.VectorWrapper; +import com.google.common.collect.Maps; +import org.la4j.Vector; + +import java.util.Map; + +public class CircularAmoebotGrid extends LinearAmoebotGrid { + private Map levels = Maps.newHashMap(); + private Vector wrapVector; + + public CircularAmoebotGrid(int halfLength) { + super(halfLength); + this.wrapVector = Vector.fromArray(new double[]{1, halfLength}); + } + + @Override + public Vector getPositionInDirection(Vector p, Direction d) { + Vector newPos = super.getPositionInDirection(p, d); + + return VectorWrapper.wrapVector(newPos, this.wrapVector); + } + + @Override + public void addParticle(Particle p, Vector position) throws Exception { + super.addParticle(p, position); + + this.levels.put(p, Vector.zero(2)); + } + + @Override + public void removeParticle(Particle p) throws Exception { + super.removeParticle(p); + + this.levels.remove(p); + } + + @Override + public void moveParticle(Particle p, Vector v) throws Exception { + Vector initialPosition = this.getParticlePosition(p); + Direction d = null; + + // Find the direction that takes us to the new position + for (Direction d2: this.getCompass().getDirections()) { + if (this.getPositionInDirection(initialPosition, d2).equals(v)) { + d = d2; + break; + } + } + + if (d == null) throw new RuntimeException("Jump move!"); + + // Find the non-wrapped position + Vector expectedPosition = initialPosition.add(d.getVector()); + + // Execute the move + super.moveParticle(p, v); + + // Now worry about the levels + if (!v.equals(initialPosition)) { + // A move has been made. + if (!expectedPosition.equals(v)) { + //System.out.println("A particle crossed a border"); + // There was a wraparound. Run level calculation. + Vector particleLevel = this.levels.get(p); + for (int i = 0; i < v.length(); i++) { + int actual = (int) v.get(i); + int expected = (int) expectedPosition.get(i); + + int level = (int) particleLevel.get(i); + + if (actual > expected) { + //System.out.printf("From dim %d low to high, down one level\n", i); + // This is a warp from lower edge to upper edge, so went down a level + level--; + } else if (actual < expected) { + //System.out.printf("From dim %d high to low, up one level\n", i); + // This is a warp from an upper edge to a lower edge, so went up a level + level++; + } + + particleLevel.set(i, level); + } + } + } + } + + public Vector getParticleLevel(Particle p) { + return this.levels.get(p); + } + + public Vector getLeveledParticlePosition(Particle p) { + // This is useful for CoM calculation + Vector position = this.getParticlePosition(p); + Vector level = this.getParticleLevel(p); + + return VectorWrapper.unwrapVector(position, this.wrapVector, level); + } + + @Override + public Vector getCenterOfMass() { + return this.getAllParticles() + .map(this::getLeveledParticlePosition) + .reduce(Vector.zero(2), Vector::add) + .divide(this.getParticleCount()); + } + + @Override + public Vector getUnitPixelCoordinates(Vector in) { + double theta = 2 * Math.PI / (2 * this.getHalfLength() + 1); + int chordCount = 2 * this.getHalfLength() + 1; + + double r = 1 / (2 * Math.sin(Math.PI / chordCount)); // Radius of the circumcircle + + return Vector.fromArray(new double[]{r * Math.cos(theta * in.get(1)), r * Math.sin(theta * in.get(1))}); + } +} diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/HexagonalAmoebotGrid.java b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/HexagonalAmoebotGrid.java index a9f27c8..8cf67c6 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/HexagonalAmoebotGrid.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/HexagonalAmoebotGrid.java @@ -18,16 +18,17 @@ package com.cemgokmen.particles.models.amoebot.gridshapes; +import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.storage.BiMapParticleStorage; import com.cemgokmen.particles.storage.ParticleStorage; -import com.cemgokmen.particles.storage.TableParticleStorage; import com.cemgokmen.particles.util.Utils; import com.cemgokmen.particles.models.amoebot.AmoebotGrid; import com.google.common.collect.Lists; import org.la4j.Vector; -import java.util.ArrayList; import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; public class HexagonalAmoebotGrid extends AmoebotGrid { private final int radius; @@ -48,7 +49,7 @@ protected ParticleStorage getStorage() { } @Override - public boolean isPositionValid(Vector p) { + public boolean isPositionValid(Vector p, Particle forParticle) { if (p.length() != 2) { return false; } @@ -58,18 +59,13 @@ public boolean isPositionValid(Vector p) { } @Override - public List getValidPositions() { - List vectors = new ArrayList<>(); - - for (int dx = -this.radius; dx <= this.radius; dx++) { - for (int dy = Math.max(-this.radius, -dx - this.radius); - dy <= Math.min(this.radius, -dx + this.radius); dy++) { + public Stream getValidPositions() { + return IntStream.rangeClosed(-this.radius, this.radius).boxed().flatMap(dx-> { + return IntStream.rangeClosed(-this.radius, this.radius).mapToObj(dy -> { int dz = -dx - dy; - vectors.add(Utils.getVector(dx, dz)); - } - } - - return vectors; + return Vector.fromArray(new double[]{dx, dz}); + }); + }); } @Override diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/LinearAmoebotGrid.java b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/LinearAmoebotGrid.java new file mode 100644 index 0000000..5f1c790 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/LinearAmoebotGrid.java @@ -0,0 +1,127 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.models.amoebot.gridshapes; + +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.amoebot.AmoebotGrid; +import com.cemgokmen.particles.storage.BiMapParticleStorage; +import com.cemgokmen.particles.storage.ParticleStorage; +import com.cemgokmen.particles.util.Utils; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.la4j.Vector; + +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class LinearAmoebotGrid extends AmoebotGrid { + public static class LinearAmoebotCompass extends AmoebotCompass { + private static final Direction N = new Direction(Utils.getVector(0, -1)); + private static final Direction S = new Direction(Utils.getVector(0, 1)); + + private static final Direction[] directions = {N, S}; + + @Override + public ImmutableList getDirections() { + return ImmutableList.copyOf(directions); + } + + @Override + public Direction shiftDirectionCounterclockwise(Direction d, double times) { + if (times % 2 == 0) { + return d == N ? N : S; + } else { + return d == N ? S : N; + } + } + + @Override + public int getMinorArcLength(Direction a, Direction b) { + return a == b ? 0 : 1; + } + + @Override + public double getAngleFromMinorArcLength(int minorArcLength) { + return minorArcLength == 0 ? 0 : Math.PI / 2; + } + + @Override + public double getAngleBetweenDirections(Direction a, Direction b) { + return this.getAngleFromMinorArcLength(this.getMinorArcLength(a, b)); + } + } + + private final LinearAmoebotGrid.LinearAmoebotCompass compass = new LinearAmoebotGrid.LinearAmoebotCompass(); + + @Override + public Compass getCompass() { + return this.compass; + } + + private final int halfLength; + private final ParticleStorage storage; + + public LinearAmoebotGrid(int halfLength) { + if (halfLength <= 0) { + throw new RuntimeException("Half-length should be a positive integer."); + } + + this.halfLength = halfLength; + this.storage = new BiMapParticleStorage(2 * this.halfLength); + } + + @Override + protected ParticleStorage getStorage() { + return this.storage; + } + + @Override + public boolean isPositionValid(Vector p, Particle forParticle) { + return p.length() == 2 && p.get(0) == 0 && Math.abs(p.get(1)) <= this.halfLength; + } + + @Override + public Stream getValidPositions() { + final int dx = 0; + + return IntStream.rangeClosed(-this.halfLength, this.halfLength).mapToObj(dy -> { + int dz = dx - dy; + return Vector.fromArray(new double[]{dx, dz}); + }); + } + + @Override + public List getBoundaryVertices() { + int coord = this.halfLength + 1; + + return Lists.newArrayList( + Utils.getVector(1, coord), + Utils.getVector(1, -coord), + Utils.getVector(-1, -coord), + Utils.getVector(-1, coord) + ); + } + + public int getHalfLength() { + return this.halfLength; + } + + +} diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/QuadrilateralAmoebotGrid.java b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/QuadrilateralAmoebotGrid.java index 3bd0cc2..8f6036e 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/QuadrilateralAmoebotGrid.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/QuadrilateralAmoebotGrid.java @@ -18,17 +18,17 @@ package com.cemgokmen.particles.models.amoebot.gridshapes; +import com.cemgokmen.particles.models.Particle; import com.cemgokmen.particles.storage.BiMapParticleStorage; import com.cemgokmen.particles.storage.ParticleStorage; -import com.cemgokmen.particles.storage.TableParticleStorage; import com.cemgokmen.particles.util.Utils; import com.cemgokmen.particles.models.amoebot.AmoebotGrid; import com.google.common.collect.Lists; import org.la4j.Vector; -import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; +import java.util.stream.Stream; public class QuadrilateralAmoebotGrid extends AmoebotGrid { private final int sideHalfLength; @@ -49,23 +49,19 @@ protected ParticleStorage getStorage() { } @Override - public boolean isPositionValid(Vector p) { + public boolean isPositionValid(Vector p, Particle forParticle) { return p.length() == 2 && Math.abs(p.get(0)) <= this.sideHalfLength && Math.abs(p.get(1)) <= this.sideHalfLength; } @Override - public List getValidPositions() { - List vectors = new ArrayList<>(); - + public Stream getValidPositions() { int max = this.sideHalfLength; - IntStream.rangeClosed(-max, max).forEach(x -> { - IntStream.rangeClosed(-max, max).forEach(y -> { - vectors.add(Utils.getVector(x, y)); + return IntStream.rangeClosed(-max, max).boxed().flatMap(x -> { + return IntStream.rangeClosed(-max, max).mapToObj(y -> { + return Utils.getVector(x, y); }); }); - - return vectors; } @Override diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/ToroidalAmoebotGrid.java b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/ToroidalAmoebotGrid.java index 5531fb5..eb0a3a6 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/ToroidalAmoebotGrid.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/gridshapes/ToroidalAmoebotGrid.java @@ -19,10 +19,10 @@ package com.cemgokmen.particles.models.amoebot.gridshapes; import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.util.VectorWrapper; import com.google.common.collect.Maps; import org.la4j.Vector; -import java.util.List; import java.util.Map; public class ToroidalAmoebotGrid extends QuadrilateralAmoebotGrid { @@ -35,54 +35,7 @@ public ToroidalAmoebotGrid(int sideHalfLength) { public Vector getPositionInDirection(Vector p, Direction d) { Vector newPos = super.getPositionInDirection(p, d); - return wrapVector(newPos, this.getSideHalfLength()); - } - - public static Vector wrapVector(Vector v, int wrapAroundDistance) { - Vector result = v.copy(); - - for (int i = 0; i < v.length(); i++) { - int x = (int) v.get(i); - - // Move it from the range -sHL <-> sHL to 0 <-> 2 * sHL, both inclusive - x += wrapAroundDistance; - - // Find equivalence in mod (2 * sHL + 1) - x = Math.floorMod(x, 2 * wrapAroundDistance + 1); - - // Move it back to the original range - x -= wrapAroundDistance; - - // Save it - result.set(i, x); - } - - return result; - } - - public static Vector unwrapVector(Vector v, int wrapAroundDistance, Vector levels) { - if (v.length() != levels.length()) throw new RuntimeException("Vector levels should match."); - - Vector result = v.copy(); - - for (int i = 0; i < v.length(); i++) { - int x = (int) v.get(i); - int level = (int) levels.get(i); - - // Move it from the range -sHL <-> sHL to 0 <-> 2 * sHL, both inclusive - x += wrapAroundDistance; - - // Add the level (2 * sHL + 1) - x += level * (2 * wrapAroundDistance + 1); - - // Move it back to the original range - x -= wrapAroundDistance; - - // Save it - result.set(i, x); - } - - return result; + return VectorWrapper.wrapVector(newPos, this.getSideHalfLength()); } @Override @@ -124,7 +77,7 @@ public void moveParticle(Particle p, Vector v) throws Exception { if (!v.equals(initialPosition)) { // A move has been made. if (!expectedPosition.equals(v)) { - System.out.println("A particle crossed a border"); + //System.out.println("A particle crossed a border"); // There was a wraparound. Run level calculation. Vector particleLevel = this.levels.get(p); for (int i = 0; i < v.length(); i++) { @@ -134,11 +87,11 @@ public void moveParticle(Particle p, Vector v) throws Exception { int level = (int) particleLevel.get(i); if (actual > expected) { - System.out.printf("From %s low to high, down one level\n", i == 0 ? "x" : "y"); + //System.out.printf("From dim %d low to high, down one level\n", i); // This is a warp from lower edge to upper edge, so went down a level level--; } else if (actual < expected) { - System.out.printf("From %s high to low, up one level\n", i == 0 ? "x" : "y"); + //System.out.printf("From dim %d high to low, up one level\n", i); // This is a warp from an upper edge to a lower edge, so went up a level level++; } @@ -158,7 +111,7 @@ public Vector getLeveledParticlePosition(Particle p) { Vector position = this.getParticlePosition(p); Vector level = this.getParticleLevel(p); - return unwrapVector(position, this.getSideHalfLength(), level); + return VectorWrapper.unwrapVector(position, this.getSideHalfLength(), level); } @Override diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/ContinuousDirectedAmoebotParticle.java b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/ContinuousDirectedAmoebotParticle.java new file mode 100644 index 0000000..2b05a05 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/ContinuousDirectedAmoebotParticle.java @@ -0,0 +1,92 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.models.amoebot.specializedparticles; + +import com.cemgokmen.particles.models.ParticleGrid; +import com.cemgokmen.particles.models.amoebot.AmoebotGrid; +import com.cemgokmen.particles.models.amoebot.AmoebotParticle; +import com.cemgokmen.particles.util.Utils; +import org.la4j.Vector; + +import java.awt.*; +import java.awt.geom.Line2D; +import java.util.Arrays; +import java.util.function.Function; + +public class ContinuousDirectedAmoebotParticle extends AmoebotParticle { + private double direction; // Radians from x axis, counterclockwise. + public final AmoebotGrid.Compass compass; + + public static ContinuousDirectedAmoebotParticle chosenOne; + + public ContinuousDirectedAmoebotParticle(AmoebotGrid.Compass compass, double direction, boolean greyscale) { + this.compass = compass; + this.direction = direction; + + if (chosenOne == null) chosenOne = this; + } + + public double getDirection() { + return this.direction; + } + + public void setDirection(double direction) { + this.direction = direction; + } + + public static final Color ARROW_COLOR = Color.RED; + + @Override + public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength, Function gridToScreenCoords) { + //super.drawParticle(graphics, screenPosition, edgeLength); + + // Calculate the arrow + // Note that we need our current position for non-linear transformations + // TODO: WE NEED TO CONVERT THIS INTO A ROTATION OF THE VECTORS FROM THE FUNCTION + Vector towardsArrowTip = Vector.fromArray(new double[]{Math.cos(this.direction + Math.PI / 2), -Math.sin(this.direction + Math.PI / 2)}); + towardsArrowTip = towardsArrowTip.multiply(CIRCLE_RADIUS / (towardsArrowTip.norm() * 1.1)); + + Vector towardsArrowLeft = Vector.fromArray(new double[]{towardsArrowTip.get(1), -towardsArrowTip.get(0)}); + Vector towardsArrowRight = towardsArrowLeft.multiply(-1); + Vector towardsArrowBase = towardsArrowTip.multiply(-1); + + Vector arrowLeft = screenPosition.add(towardsArrowLeft); + Vector arrowTip = screenPosition.add(towardsArrowTip); + Vector arrowRight = screenPosition.add(towardsArrowRight); + Vector arrowBase = screenPosition.add(towardsArrowBase); + + // Now make the polygon + Line2D.Double[] lines = new Line2D.Double[]{ + new Line2D.Double(arrowBase.get(0), arrowBase.get(1), arrowTip.get(0), arrowTip.get(1)), + new Line2D.Double(arrowLeft.get(0), arrowLeft.get(1), arrowTip.get(0), arrowTip.get(1)), + new Line2D.Double(arrowRight.get(0), arrowRight.get(1), arrowTip.get(0), arrowTip.get(1)), + }; + + graphics.setColor(this == chosenOne ? Color.BLUE : Color.RED); + Arrays.stream(lines).forEach(graphics::draw); + + //graphics.setColor(Color.BLACK); + //graphics.drawString(((ToroidalAmoebotGrid) this.grid).getParticleLevel(this).toString(), (int) screenPosition.get(0), (int) screenPosition.get(1)); + } + + @Override + public boolean shouldDrawEdges() { + return false; + } +} diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/DirectedAmoebotParticle.java b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/DirectedAmoebotParticle.java index 3566451..c7a62b2 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/DirectedAmoebotParticle.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/DirectedAmoebotParticle.java @@ -18,25 +18,31 @@ package com.cemgokmen.particles.models.amoebot.specializedparticles; +import com.cemgokmen.particles.capabilities.WrappedNormalRandomDirectionCapable; +import com.cemgokmen.particles.capabilities.SpinCapable; import com.cemgokmen.particles.models.ParticleGrid; import com.cemgokmen.particles.models.amoebot.AmoebotGrid; import com.cemgokmen.particles.models.amoebot.AmoebotParticle; -import com.cemgokmen.particles.models.amoebot.InvalidMoveException; -import com.cemgokmen.particles.models.amoebot.gridshapes.ToroidalAmoebotGrid; +import com.cemgokmen.particles.models.continuous.ContinuousParticleGrid; +import com.cemgokmen.particles.util.Utils; import org.la4j.Vector; import java.awt.*; import java.awt.geom.Line2D; -import java.awt.geom.Path2D; import java.util.Arrays; +import java.util.function.Function; -public class DirectedAmoebotParticle extends AmoebotParticle { +public class DirectedAmoebotParticle extends AmoebotParticle implements SpinCapable, WrappedNormalRandomDirectionCapable { private ParticleGrid.Direction direction; - public final AmoebotGrid.Compass compass; + private final AmoebotGrid.Compass compass; + + public static DirectedAmoebotParticle chosenOne; public DirectedAmoebotParticle(AmoebotGrid.Compass compass, ParticleGrid.Direction direction, boolean greyscale) { this.compass = compass; this.direction = direction; + + if (chosenOne == null) chosenOne = this; } public ParticleGrid.Direction getDirection() { @@ -47,15 +53,29 @@ public void setDirection(ParticleGrid.Direction direction) { this.direction = direction; } - public static final Color ARROW_COLOR = Color.RED; + @Override + public ParticleGrid.Compass getCompass() { + return compass; + } + + public static final Color ARROW_COLOR = Color.BLACK; + public static final int ARROW_THICKNESS = 4; + public static final BasicStroke ARROW_STROKE = new BasicStroke(ARROW_THICKNESS, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND); @Override - public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength) { + public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength, Function gridToScreenCoords) { //super.drawParticle(graphics, screenPosition, edgeLength); // Calculate the arrow - Vector towardsArrowTip = this.grid.getUnitPixelCoordinates(this.direction.getVector()); - towardsArrowTip = towardsArrowTip.multiply(CIRCLE_RADIUS / towardsArrowTip.norm()); + // Note that we need our current position for non-linear transformations + Vector towardsArrowTipOnGrid = this.direction.getVector(); + Vector position = this.grid.getParticlePosition(this); + + Vector arrowTipOnGrid = position.add(towardsArrowTipOnGrid); + Vector arrowTipOnScreen = gridToScreenCoords.apply(arrowTipOnGrid); + + Vector towardsArrowTip = arrowTipOnScreen.subtract(screenPosition); + towardsArrowTip = towardsArrowTip.multiply(CIRCLE_RADIUS / (towardsArrowTip.norm() * 1.1)); Vector towardsArrowLeft = Vector.fromArray(new double[]{towardsArrowTip.get(1), -towardsArrowTip.get(0)}); Vector towardsArrowRight = towardsArrowLeft.multiply(-1); @@ -73,10 +93,25 @@ public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLen new Line2D.Double(arrowRight.get(0), arrowRight.get(1), arrowTip.get(0), arrowTip.get(1)), }; - graphics.setColor(Color.RED); + //graphics.setColor(this == chosenOne ? Color.BLUE : Color.RED); + graphics.setColor(ARROW_COLOR); + graphics.setStroke(ARROW_STROKE); Arrays.stream(lines).forEach(graphics::draw); //graphics.setColor(Color.BLACK); //graphics.drawString(((ToroidalAmoebotGrid) this.grid).getParticleLevel(this).toString(), (int) screenPosition.get(0), (int) screenPosition.get(1)); } + + @Override + public ParticleGrid.Direction getWrappedNormalRandomDirection(ParticleGrid.Direction mean, double standardDeviation) { + // Default range is -Pi to Pi. Let's switch that to our range of [-3, 3]. + double sample = Utils.randomWrappedNorm(standardDeviation) * 3 / (Math.PI); + int steps = (int) Math.round(sample); + return this.compass.shiftDirectionCounterclockwise(mean, steps); + } + + @Override + public boolean shouldDrawEdges() { + return false; + } } diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/FoodAmoebotParticle.java b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/FoodAmoebotParticle.java index 6c18198..2070d07 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/FoodAmoebotParticle.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/FoodAmoebotParticle.java @@ -41,7 +41,7 @@ public void decrementLifetime(int maxLifetime) { } @Override - public void move(ParticleGrid.Direction inDirection, boolean swapsAllowed, boolean nonSwapsAllowed) throws InvalidMoveException { + public void move(ParticleGrid.Direction inDirection) { throw new InvalidMoveException("Cannot move food particle"); } diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/ForagingAmoebotParticle.java b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/ForagingAmoebotParticle.java index 2201b51..d750720 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/ForagingAmoebotParticle.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/ForagingAmoebotParticle.java @@ -22,6 +22,7 @@ import org.la4j.Vector; import java.awt.*; +import java.util.function.Function; public class ForagingAmoebotParticle extends AmoebotParticle { private int maximumFedActivations; @@ -118,8 +119,8 @@ public Color getCircleFillColor() { } @Override - public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength) { - super.drawParticle(graphics, screenPosition, edgeLength); + public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength, Function gridToScreenCoords) { + super.drawParticle(graphics, screenPosition, edgeLength, gridToScreenCoords); graphics.setColor(Color.BLACK); diff --git a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/SeparableAmoebotParticle.java b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/SeparableAmoebotParticle.java index a1d84e8..68156b5 100644 --- a/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/SeparableAmoebotParticle.java +++ b/src/main/java/com/cemgokmen/particles/models/amoebot/specializedparticles/SeparableAmoebotParticle.java @@ -22,6 +22,7 @@ import org.la4j.Vector; import java.awt.*; +import java.util.function.Function; public class SeparableAmoebotParticle extends AmoebotParticle { private final int classNumber; @@ -72,8 +73,8 @@ public Color getCircleFillColor() { } @Override - public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength) { - super.drawParticle(graphics, screenPosition, edgeLength); + public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength, Function gridToScreenCoords) { + super.drawParticle(graphics, screenPosition, edgeLength, gridToScreenCoords); graphics.setColor(Color.BLACK); int classNumber = this.getClassNumber(); diff --git a/src/main/java/com/cemgokmen/particles/models/continuous/ContinuousParticle.java b/src/main/java/com/cemgokmen/particles/models/continuous/ContinuousParticle.java new file mode 100644 index 0000000..3f646eb --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/models/continuous/ContinuousParticle.java @@ -0,0 +1,190 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.models.continuous; + +import com.cemgokmen.particles.capabilities.*; +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.ParticleGrid; +import com.cemgokmen.particles.models.amoebot.InvalidMoveException; +import com.cemgokmen.particles.util.Utils; +import org.la4j.Vector; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.util.Arrays; +import java.util.function.Function; +import java.util.function.Predicate; + +public class ContinuousParticle extends Particle implements MovementCapable, NeighborDetectionCapable, UniformRandomDirectionCapable, SpinCapable, WrappedNormalRandomDirectionCapable { + private double radius; + private ParticleGrid.Direction direction; + + private static final double NBR_GAP_BETWEEN_BOUNDARIES = 0.5; // <=1 radius between two circles is a nbrhood + + public double getRadius() { + return this.radius; + } + + @Override + public ParticleGrid.Direction getDirection() { + return this.direction; + } + + @Override + public void setDirection(ParticleGrid.Direction d) { + this.direction = d; + } + + @Override + public ParticleGrid.Compass getCompass() { + return this.grid.getCompass(); + } + + public ContinuousParticle(double radius, ContinuousParticleGrid.ContinuousDirection direction) { + this.radius = radius; + this.direction = direction; + } + + public ContinuousParticle(double radius, double degrees) { + this(radius, new ContinuousParticleGrid.ContinuousDirection(degrees)); + } + + public java.util.List getNeighborParticles(boolean includeNulls, Predicate filter) { + ContinuousParticleGrid g = (ContinuousParticleGrid) this.grid; + if (filter == null) { + return g.getParticleNeighbors(this, (2 + NBR_GAP_BETWEEN_BOUNDARIES) * this.getRadius()); + } + return g.getParticleNeighbors(this, (2 + NBR_GAP_BETWEEN_BOUNDARIES) * this.getRadius(), filter); + } + + public java.util.List getAdjacentPositionNeighborParticles(ParticleGrid.Direction d, boolean includeNulls, Predicate filter) { + ContinuousParticleGrid g = (ContinuousParticleGrid) this.grid; + Vector thisPosition = this.grid.getParticlePosition(this); + Vector adjacentPosition = this.grid.getPositionInDirection(thisPosition, d); + if (filter == null) { + return g.getPositionNeighbors(adjacentPosition, (2 + NBR_GAP_BETWEEN_BOUNDARIES) * this.getRadius()); + } + return g.getPositionNeighbors(adjacentPosition, (2 + NBR_GAP_BETWEEN_BOUNDARIES) * this.getRadius(), filter); + } + + @Override + public boolean isDirectionInBounds(ParticleGrid.Direction d) { + Vector curPos = this.grid.getParticlePosition(this); + Vector inDirection = this.grid.getPositionInDirection(curPos, d); + return this.grid.isPositionValid(inDirection, this); + } + + public ParticleGrid.Direction getUniformRandomDirection() { + return new ContinuousParticleGrid.ContinuousDirection(Utils.randomDouble() * Math.PI * 2); + } + + public void move(ParticleGrid.Direction inDirection) { + Vector current = this.grid.getParticlePosition(this); + Vector target = this.grid.getPositionInDirection(current, inDirection); + + //System.out.printf("Moving particle of color %d from %s to %s\n", ((SeparableAmoebotParticle)this).getClassNumber(), current, target); + + Particle atTarget = this.grid.getParticleAtPosition(target); + try { + if (atTarget == null) { + // Make the move + this.grid.moveParticle(this, target); + } else { + throw new InvalidMoveException("Position is occupied."); + } + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + } + + public boolean isDirectionWithinBounds(ParticleGrid.Direction d) { + return this.grid.isPositionValid(this.grid.getPositionInDirection(this.grid.getParticlePosition(this), d), this); + } + + public static final int CIRCLE_RADIUS = 12; + public static final Vector CIRCLE_TOP_LEFT_VECTOR = Utils.getVector(-CIRCLE_RADIUS, -CIRCLE_RADIUS); + + public static final int CIRCLE_STROKE_WIDTH = 1; + public static final BasicStroke CIRCLE_STROKE = new BasicStroke(CIRCLE_STROKE_WIDTH); + + public static final Color CIRCLE_FILL_COLOR = Color.WHITE; + public static final Color CIRCLE_STROKE_COLOR = Color.BLACK; + + public static final int ARROW_WIDTH = 3; + public static final BasicStroke ARROW_STROKE = new BasicStroke(ARROW_WIDTH); + + public static final Color ARROW_COLOR = Color.RED; + + public Color getCircleFillColor() { + return CIRCLE_FILL_COLOR; + } + + @Override + public ParticleGrid.Direction getWrappedNormalRandomDirection(ParticleGrid.Direction mean, double standardDeviation) { + // Suppose that the mean is a Continous Direction + ContinuousParticleGrid.ContinuousDirection cmean = (ContinuousParticleGrid.ContinuousDirection) mean; + + double sample = Utils.randomWrappedNorm(standardDeviation); + return new ContinuousParticleGrid.ContinuousDirection(cmean.getCCWAngleFromXAxis() + sample); + // TODO: Maybe the compass should do this ^ + } + + @Override + public void drawParticle(Graphics2D graphics, Vector screenPosition, int edgeLength, Function gridToScreenCoords) { + graphics.setPaint(this.getCircleFillColor()); + Vector topLeft = screenPosition.add(CIRCLE_TOP_LEFT_VECTOR.multiply(this.getRadius())); + + Ellipse2D.Double circle = new Ellipse2D.Double(topLeft.get(0), topLeft.get(1), + 2 * CIRCLE_RADIUS * this.getRadius(), 2 * CIRCLE_RADIUS * this.getRadius()); + //graphics.fill(circle); + + // Draw the arrow + Vector towardsArrowTip = this.direction.getVector().multiply(this.getRadius() * CIRCLE_RADIUS * 0.75); + Vector towardsArrowLeft = Vector.fromArray(new double[]{towardsArrowTip.get(1), -towardsArrowTip.get(0)}); + Vector towardsArrowRight = towardsArrowLeft.multiply(-1); + Vector towardsArrowBase = towardsArrowTip.multiply(-1); + + Vector arrowLeft = screenPosition.add(towardsArrowLeft); + Vector arrowTip = screenPosition.add(towardsArrowTip); + Vector arrowRight = screenPosition.add(towardsArrowRight); + Vector arrowBase = screenPosition.add(towardsArrowBase); + + // Now make the polygon + Line2D.Double[] lines = new Line2D.Double[]{ + new Line2D.Double(arrowBase.get(0), arrowBase.get(1), arrowTip.get(0), arrowTip.get(1)), + new Line2D.Double(arrowLeft.get(0), arrowLeft.get(1), arrowTip.get(0), arrowTip.get(1)), + new Line2D.Double(arrowRight.get(0), arrowRight.get(1), arrowTip.get(0), arrowTip.get(1)), + }; + + graphics.setColor(ARROW_COLOR); + graphics.setStroke(ARROW_STROKE); + graphics.setColor(Color.RED); + Arrays.stream(lines).forEach(graphics::draw); + + //graphics.setColor(CIRCLE_STROKE_COLOR); + //graphics.setStroke(CIRCLE_STROKE); + //graphics.draw(circle); + } + + @Override + public boolean shouldDrawEdges() { + return false; + } +} diff --git a/src/main/java/com/cemgokmen/particles/models/continuous/ContinuousParticleGrid.java b/src/main/java/com/cemgokmen/particles/models/continuous/ContinuousParticleGrid.java new file mode 100644 index 0000000..76d9c2c --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/models/continuous/ContinuousParticleGrid.java @@ -0,0 +1,256 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.models.continuous; + +import com.cemgokmen.particles.capabilities.NeighborDetectionCapable; +import com.cemgokmen.particles.graphics.GridGraphics; +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.ParticleGrid; +import com.cemgokmen.particles.models.continuous.boundary.ContinuousParticleGridBoundary; +import com.cemgokmen.particles.storage.BiMapParticleStorage; +import com.cemgokmen.particles.storage.ParticleStorage; +import com.cemgokmen.particles.util.Utils; +import com.google.common.collect.ImmutableList; +import org.la4j.Vector; + +import javax.annotation.Nonnull; +import java.awt.*; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ContinuousParticleGrid extends ParticleGrid { + public static class ContinuousDirection extends Direction { + private double ccwAngleFromXAxis; + + public ContinuousDirection(double ccwAngleFromXAxis) { + super(Vector.fromArray(new double[]{Math.cos(ccwAngleFromXAxis), - Math.sin(ccwAngleFromXAxis)})); + + // Store a simplified version of the angle, so that the angle is in the range [-pi, pi) + this.ccwAngleFromXAxis = (ccwAngleFromXAxis + Math.PI) % (2 * Math.PI); + if (this.ccwAngleFromXAxis < 0) this.ccwAngleFromXAxis += (2 * Math.PI); + this.ccwAngleFromXAxis -= Math.PI; + } + + @Override + public String toString() { + return String.format("Direction: %.2f degrees ccw from X axis", Math.toDegrees(this.ccwAngleFromXAxis)); + } + + public double getCCWAngleFromXAxis() { + return this.ccwAngleFromXAxis; + } + } + + public static class ContinuousCompass extends Compass { + @Override + public ImmutableList getDirections() { + throw new UnsupportedOperationException("This grid does not have discretized directions."); + } + + @Override + public Direction shiftDirectionCounterclockwise(Direction d, double times) { + // The times argument here will be interpreted as radians + double originalAngle = ((ContinuousDirection) d).ccwAngleFromXAxis; + return new ContinuousDirection(originalAngle + times); + } + + @Override + public double getAngleBetweenDirections(Direction a, Direction b) { + return Utils.getDifferenceBetweenAngles(((ContinuousDirection) a).getCCWAngleFromXAxis(), ((ContinuousDirection) b).getCCWAngleFromXAxis()); + } + } + + private final ContinuousCompass compass = new ContinuousCompass(); + + @Override + public Compass getCompass() { + return this.compass; + } // TODO: Delegate this! Compass should be assigned to particles + + private ContinuousParticleGridBoundary boundary; + private final ParticleStorage storage; + + public ContinuousParticleGrid(ContinuousParticleGridBoundary boundary) { + this.boundary = boundary; + + // Upper bound on r=1 particles to fit here + int upperBound = (int) (boundary.getArea() / Math.PI) + 1; + + this.storage = new BiMapParticleStorage(upperBound); + } + + @Override + protected ParticleStorage getStorage() { + return this.storage; + } + @Override + public boolean isPositionValid(Vector p, Particle forParticle) { + double radius = 0; + + if (forParticle != null) { + radius = ((ContinuousParticle) forParticle).getRadius(); + } + + return this.boundary.isVectorInBoundary(p, radius); + } + + @Override + public boolean isParticleValid(Particle p) { + return p instanceof ContinuousParticle; + } + + @Override + public Stream getValidPositions() { + // We do this so as not to confuse the graphics. + return Stream.empty(); + } + + @Override + public Vector getRandomPosition(Particle particle) { + // We do some rejection sampling here. + List zoomArea = this.boundary.getZoomAreaVertices(); + double minX = zoomArea.stream().mapToDouble(v -> v.get(0)).min().getAsDouble(); + double maxX = zoomArea.stream().mapToDouble(v -> v.get(0)).max().getAsDouble(); + + double minY = zoomArea.stream().mapToDouble(v -> v.get(1)).min().getAsDouble(); + double maxY = zoomArea.stream().mapToDouble(v -> v.get(1)).max().getAsDouble(); + + // Now renerate random coordinates in that range and try them + while (true) { + double x = Utils.randomDouble() * (maxX - minX) + minX; + double y = Utils.randomDouble() * (maxY - minY) + minY; + + Vector candidate = Utils.getVector(x, y); + if (this.isPositionValid(candidate, particle)) return candidate; + } + } + + @Override + public List getBoundaryVertices() { + return this.boundary.getZoomAreaVertices(); + } + + @Override + public Vector getUnitPixelCoordinates(Vector in) { + return in.copy(); + } + + @Override + public Particle getParticleAtPosition(Vector position) { + // Get any particles that are occupying this exact coordinate + return this.getAllParticles().filter(that -> { + Vector thatPosition = this.getParticlePosition(that); + return Utils.is2DVectorShorterThan(thatPosition.subtract(position), ((ContinuousParticle) that).getRadius()); + }).findAny().orElse(null); + } + + @Override + public boolean isPositionOccupied(Vector position) { + return this.getParticleAtPosition(position) != null; + } + + @Override + public Stream getAdjacentPositions(Vector p) { + throw new UnsupportedOperationException("There are infinite adjacent positions on a continuous particle grid."); + } + + @Override + public boolean arePositionsAdjacent(Vector p1, Vector p2) { + throw new UnsupportedOperationException("Not supported on a continuous grid. Try with distance."); + } + + @Override + public List getParticleNeighbors(Particle p, boolean includeNulls) { + // TODO: THIS COULD CAUSE INFINITE RECURSION + return ((NeighborDetectionCapable) p).getNeighborParticles(includeNulls, null); + } + + @Override + public List getParticleNeighbors(Particle p, @Nonnull Predicate filter, boolean includeNulls) { + // TODO: THIS COULD CAUSE INFINITE RECURSION + return ((NeighborDetectionCapable) p).getNeighborParticles(includeNulls, filter); + } + + @Override + public Particle getParticleNeighborInDirection(Particle p, Direction d) { + throw new UnsupportedOperationException("Not supported on a continuous grid."); + } + + @Override + public Particle getParticleNeighborInDirection(Particle p, Direction d, Predicate filter) { + throw new UnsupportedOperationException("Not supported on a continuous grid."); + } + + @Override + public List getPositionNeighbors(Vector position, boolean includeNulls) { + throw new UnsupportedOperationException("Not supported on a continuous grid. Try with distance."); + } + + @Override + public List getPositionNeighbors(Vector position, @Nonnull Predicate filter, boolean includeNulls) { + throw new UnsupportedOperationException("Not supported on a continuous grid. Try with distance."); + } + + @Override + public Particle getPositionNeighborInDirection(Vector p, Direction d) { + throw new UnsupportedOperationException("Not supported on a continuous grid."); + } + + @Override + public Particle getPositionNeighborInDirection(Vector p, Direction d, Predicate filter) { + throw new UnsupportedOperationException("Not supported on a continuous grid."); + } + + // NOW FOR THE THINGS WE DO SUPPORT + + public boolean arePositionsAdjacent(Vector p1, Vector p2, double distance) { + return Utils.is2DVectorShorterThan(p1.subtract(p2), distance); + } + + public List getParticleNeighbors(Particle p, double distance) { + return this.getParticleNeighbors(p, distance, x -> true); + } + + public List getParticleNeighbors(Particle p, double distance, @Nonnull Predicate filter) { + return this.getPositionNeighbors(this.getParticlePosition(p), distance, filter); + } + + public List getPositionNeighbors(Vector position, double distance) { + return this.getPositionNeighbors(position, distance, x -> true); + } + + public List getPositionNeighbors(Vector position, double distance, @Nonnull Predicate filter) { + // Note that the distance is between the centers + return this.getAllParticles().filter(that -> { + Vector thatPosition = this.getParticlePosition(that); + return Utils.is2DVectorShorterThan(thatPosition.subtract(position), distance); + }).filter(filter).collect(Collectors.toList()); + } + + @Override + public void drawBoundary(Graphics2D graphics) { + Shape shape = this.boundary.getShape(); + + graphics.setColor(GridGraphics.BORDER_COLOR); + graphics.setStroke(GridGraphics.BORDER_STROKE); + graphics.draw(shape); + } +} diff --git a/src/main/java/com/cemgokmen/particles/models/continuous/boundary/CircularBoundary.java b/src/main/java/com/cemgokmen/particles/models/continuous/boundary/CircularBoundary.java new file mode 100644 index 0000000..5784fda --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/models/continuous/boundary/CircularBoundary.java @@ -0,0 +1,65 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.models.continuous.boundary; + +import com.cemgokmen.particles.graphics.GridGraphics; +import com.cemgokmen.particles.util.Utils; +import com.google.common.collect.ImmutableList; +import org.la4j.Vector; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.util.List; + +public class CircularBoundary implements ContinuousParticleGridBoundary { + private double radius; + + public CircularBoundary(double r) { + this.radius = r; + } + + @Override + public boolean isVectorInBoundary(Vector v, double radius) { + return Utils.is2DVectorShorterThan(v, this.radius - radius); + } + + @Override + public double getArea() { + return Math.PI * this.radius * this.radius; + } + + @Override + public List getZoomAreaVertices() { + return ImmutableList.of( + Utils.getVector(-this.radius, -this.radius), + Utils.getVector(this.radius, -this.radius), + Utils.getVector(this.radius, this.radius), + Utils.getVector(-this.radius, this.radius) + ); + } + + @Override + public Shape getShape() { + // Calculate the position of a pixel at the boundary + double screenRadius = this.radius * GridGraphics.EDGE_LENGTH; + + return new Ellipse2D.Double(-screenRadius, -screenRadius,2 * screenRadius, 2 * screenRadius); + } +} diff --git a/src/main/java/com/cemgokmen/particles/models/continuous/boundary/ContinuousParticleGridBoundary.java b/src/main/java/com/cemgokmen/particles/models/continuous/boundary/ContinuousParticleGridBoundary.java new file mode 100644 index 0000000..e4bfbc4 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/models/continuous/boundary/ContinuousParticleGridBoundary.java @@ -0,0 +1,31 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.models.continuous.boundary; + +import org.la4j.Vector; + +import java.awt.*; +import java.util.List; + +public interface ContinuousParticleGridBoundary { + boolean isVectorInBoundary(Vector v, double radius); + double getArea(); + List getZoomAreaVertices(); + Shape getShape(); +} diff --git a/src/main/java/com/cemgokmen/particles/runners/AlignmentTrialRunner.java b/src/main/java/com/cemgokmen/particles/runners/AlignmentTrialRunner.java index 9d7d471..5eff121 100644 --- a/src/main/java/com/cemgokmen/particles/runners/AlignmentTrialRunner.java +++ b/src/main/java/com/cemgokmen/particles/runners/AlignmentTrialRunner.java @@ -48,10 +48,11 @@ public static void main(String[] args) throws Exception { protected final DoubleProperty translationBias = new SimpleDoubleProperty(); protected final DoubleProperty forwardBias = new SimpleDoubleProperty(); */ - Path basePath = Paths.get("/Users/cgokmen/research/results/newalignmenttests/"); + Path basePath = Paths.get("/Users/cgokmen/research/results/thesis-alignment/"); ImmutableMap> propertyValues = new ImmutableMap.Builder>() - .put("rotationBias", new ImmutableList.Builder().add(1.0, 5.0, 10.0, 20.0, 50.0).build()) + .put("rotationBias", new ImmutableList.Builder().add(20.0).build()) + //.put("rotationBias", new ImmutableList.Builder().add(1.0, 5.0, 10.0, 20.0, 50.0).build()) //.put("forwardBias", new ImmutableList.Builder().add(1.0, 1.1, 2.0, 5.0, 10.0).build()) .build(); @@ -77,7 +78,7 @@ public ParticleAlgorithm get() { final String propertyName = entry.getKey(); final Path propertyPath = basePath.resolve(propertyName); - final Table images = TrialUtils.runPropertyValueTrials(gridSupplier, algorithmSupplier, propertyName, entry.getValue(), stopArray, propertyPath, "png"); + final Table images = TrialUtils.runPropertyValueTrials(gridSupplier, algorithmSupplier, propertyName, entry.getValue(), stopArray, propertyPath, "pdf"); File htmlFile = propertyPath.resolve("index.html").toFile(); HTMLGenerator.saveHTML(htmlFile, "Alignment", propertyName, "Activations", images); diff --git a/src/main/java/com/cemgokmen/particles/runners/CompressedSeparationTrialRunner.java b/src/main/java/com/cemgokmen/particles/runners/CompressedSeparationTrialRunner.java index 3ab0db1..f7f1de2 100644 --- a/src/main/java/com/cemgokmen/particles/runners/CompressedSeparationTrialRunner.java +++ b/src/main/java/com/cemgokmen/particles/runners/CompressedSeparationTrialRunner.java @@ -18,7 +18,6 @@ package com.cemgokmen.particles.runners; -import com.cemgokmen.particles.algorithms.CompressionAlgorithm; import com.cemgokmen.particles.algorithms.SeparationAlgorithm; import com.cemgokmen.particles.graphics.GridGraphics; import com.cemgokmen.particles.io.GridIO; diff --git a/src/main/java/com/cemgokmen/particles/runners/GeneratedContinuousGridTrialRunner.java b/src/main/java/com/cemgokmen/particles/runners/GeneratedContinuousGridTrialRunner.java new file mode 100644 index 0000000..5f8e3d1 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/runners/GeneratedContinuousGridTrialRunner.java @@ -0,0 +1,70 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.runners; + +import com.cemgokmen.particles.algorithms.AlignmentAlgorithm; +import com.cemgokmen.particles.generators.RandomSystemGenerator; +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.ParticleGrid; +import com.cemgokmen.particles.models.amoebot.gridshapes.ToroidalAmoebotGrid; +import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; +import com.cemgokmen.particles.models.continuous.ContinuousParticle; +import com.cemgokmen.particles.models.continuous.ContinuousParticleGrid; +import com.cemgokmen.particles.models.continuous.boundary.CircularBoundary; +import com.cemgokmen.particles.models.continuous.boundary.ContinuousParticleGridBoundary; +import com.cemgokmen.particles.util.Utils; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class GeneratedContinuousGridTrialRunner { + private static final int PARTICLE_COUNT = 10000; + private static final int GRID_SIZE = 2 * (int) Math.sqrt(PARTICLE_COUNT); + private static final Path basePath = Paths.get("/Users/cgokmen/research/results/continuous-10-8--2/"); + + public static void main(String[] args) throws Exception { + ContinuousParticleGridBoundary boundary = new CircularBoundary(30); + ContinuousParticleGrid grid = new ContinuousParticleGrid(boundary); + + AlignmentAlgorithm algorithm = new AlignmentAlgorithm(4, 1, 0.5); + + Stream randomlyDirectedParticles = Utils.random.doubles().mapToObj(d -> new ContinuousParticle(0.5, d * Math.PI * 2)); + + ContinuousParticleGridBoundary smallerBoundary = new CircularBoundary(15); + RandomSystemGenerator.addParticles(grid, randomlyDirectedParticles, v -> smallerBoundary.isVectorInBoundary(v, 0.5), 100); + + grid.assignAllParticlesAlgorithm(algorithm); + + int iterations = 20000000; + int seconds = 20; + int fps = 24; + + int frames = seconds * fps; + int step = iterations / frames; + + int[] stops = IntStream.rangeClosed(0, frames).map(k -> step * k).toArray(); + + TrialUtils.runTrials(grid, stops, basePath, "pdf"); + } +} diff --git a/src/main/java/com/cemgokmen/particles/runners/GeneratedGridTrialRunner.java b/src/main/java/com/cemgokmen/particles/runners/GeneratedGridTrialRunner.java index c815f9a..50bc0de 100644 --- a/src/main/java/com/cemgokmen/particles/runners/GeneratedGridTrialRunner.java +++ b/src/main/java/com/cemgokmen/particles/runners/GeneratedGridTrialRunner.java @@ -29,6 +29,7 @@ import com.cemgokmen.particles.models.amoebot.AmoebotGrid; import com.cemgokmen.particles.models.amoebot.AmoebotParticle; import com.cemgokmen.particles.models.amoebot.gridshapes.QuadrilateralAmoebotGrid; +import com.cemgokmen.particles.models.amoebot.gridshapes.ToroidalAmoebotGrid; import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -46,26 +47,34 @@ public class GeneratedGridTrialRunner { private static final int PARTICLE_COUNT = 10000; private static final int GRID_SIZE = 2 * (int) Math.sqrt(PARTICLE_COUNT); - private static final Path basePath = Paths.get("/Users/cgokmen/research/results/generationtests/"); + private static final Path basePath = Paths.get("/Users/cgokmen/research/results/thesis-alignment-random-4/"); public static void main(String[] args) throws Exception { - AmoebotGrid grid = new QuadrilateralAmoebotGrid(GRID_SIZE); - ParticleGrid.Compass compass = grid.getCompass(); - List directions = compass.getDirections(); - AlignmentAlgorithm alg = new AlignmentAlgorithm(); + ToroidalAmoebotGrid grid = new ToroidalAmoebotGrid(15); + final ParticleGrid.Compass compass = grid.getCompass(); - List> suppliers = IntStream.range(0, 6).boxed() - .map(i -> - (Supplier) () -> { - Particle p = new DirectedAmoebotParticle(compass, directions.get(i), false); - p.setAlgorithm(alg); - return p; } - ).collect(Collectors.toList()); + AlignmentAlgorithm algorithm = new AlignmentAlgorithm(20, 1, 1.1); - RandomSystemGenerator.addUniformWeightedParticles(grid, suppliers, PARTICLE_COUNT); + List> directedParticleSuppliers = compass.getDirections().stream().map(d -> { + return (Supplier) () -> { + DirectedAmoebotParticle p = new DirectedAmoebotParticle(compass, d, false); + p.setAlgorithm(algorithm); + return p; + }; + }).collect(Collectors.toList()); + RandomSystemGenerator.addUniformWeightedParticles(grid, directedParticleSuppliers, 50); - grid.assignAllParticlesAlgorithm(new AlignmentAlgorithm()); + grid.assignAllParticlesAlgorithm(algorithm); - TrialUtils.runTrials(grid, new int[]{0}, basePath, "pdf"); + int iterations = 1000000; + int seconds = 20; + int fps = 1; //24; + + int frames = seconds * fps; + int step = iterations / frames; + + int[] stops = IntStream.rangeClosed(0, frames).map(k -> step * k).toArray(); + + TrialUtils.runTrials(grid, stops, basePath, "pdf"); } } diff --git a/src/main/java/com/cemgokmen/particles/runners/GenerationRunner.java b/src/main/java/com/cemgokmen/particles/runners/GenerationRunner.java new file mode 100644 index 0000000..c5f4e55 --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/runners/GenerationRunner.java @@ -0,0 +1,46 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.runners; + +import com.cemgokmen.particles.algorithms.AlignmentAlgorithm; +import com.cemgokmen.particles.generators.RandomSystemGenerator; +import com.cemgokmen.particles.models.Particle; +import com.cemgokmen.particles.models.ParticleGrid; +import com.cemgokmen.particles.models.amoebot.gridshapes.ToroidalAmoebotGrid; +import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class GenerationRunner { + public static void main(String[] args) { + ToroidalAmoebotGrid g = new ToroidalAmoebotGrid(25); + final ParticleGrid.Compass compass = g.getCompass(); + List> directedParticleSuppliers = compass.getDirections().stream().map(d -> { + return (Supplier) () -> { + DirectedAmoebotParticle p = new DirectedAmoebotParticle(compass, d, false); + p.setAlgorithm(new AlignmentAlgorithm()); + return p; + }; + }).collect(Collectors.toList()); + RandomSystemGenerator.addUniformWeightedParticles(g, directedParticleSuppliers, 100); + } +} diff --git a/src/main/java/com/cemgokmen/particles/runners/TrialUtils.java b/src/main/java/com/cemgokmen/particles/runners/TrialUtils.java index 0b1dd2a..f7007e7 100644 --- a/src/main/java/com/cemgokmen/particles/runners/TrialUtils.java +++ b/src/main/java/com/cemgokmen/particles/runners/TrialUtils.java @@ -49,7 +49,7 @@ public static Map runTrials(ParticleGrid grid, int[] stoppingPoint System.out.printf(" Next target: %d\n", target); grid.runActivations(toRun); - File image = targetPath.resolve(grid.getActivationsRun() + "." + imageExt).toFile(); + File image = targetPath.resolve(String.format("%09d.%s", grid.getActivationsRun(), imageExt)).toFile(); GridGraphics.saveGridImage(grid, image); images.put(grid.getActivationsRun(), image); System.out.printf(" Saved target: %d\n\n", target); diff --git a/src/main/java/com/cemgokmen/particles/ui/ParticlesViewController.java b/src/main/java/com/cemgokmen/particles/ui/ParticlesViewController.java index 55609bf..73eeac4 100644 --- a/src/main/java/com/cemgokmen/particles/ui/ParticlesViewController.java +++ b/src/main/java/com/cemgokmen/particles/ui/ParticlesViewController.java @@ -18,11 +18,15 @@ package com.cemgokmen.particles.ui; +import com.cemgokmen.particles.algorithms.AlignmentAlgorithm; import com.cemgokmen.particles.algorithms.ParticleAlgorithm; +import com.cemgokmen.particles.generators.RandomSystemGenerator; import com.cemgokmen.particles.graphics.GridGraphics; import com.cemgokmen.particles.graphics.MultipagePDFHandler; import com.cemgokmen.particles.io.GridIO; import com.cemgokmen.particles.io.SampleSystemMetadata; +import com.cemgokmen.particles.models.amoebot.gridshapes.ToroidalAmoebotGrid; +import com.cemgokmen.particles.models.amoebot.specializedparticles.DirectedAmoebotParticle; import com.cemgokmen.particles.util.PropertyUtils; import com.cemgokmen.particles.util.Utils; import com.cemgokmen.particles.models.Particle; @@ -51,8 +55,11 @@ import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; public class ParticlesViewController { private ParticleGrid grid; diff --git a/src/main/java/com/cemgokmen/particles/util/Utils.java b/src/main/java/com/cemgokmen/particles/util/Utils.java index 3ad4741..b9fe443 100644 --- a/src/main/java/com/cemgokmen/particles/util/Utils.java +++ b/src/main/java/com/cemgokmen/particles/util/Utils.java @@ -24,12 +24,16 @@ import java.util.Random; public class Utils { - public static final Random random = new Random(); + public static final Random random = new Random(1337); public static Vector getVector(int x, int y) { return Vector.fromArray(new double[]{x, y}); } + public static Vector getVector(double x, double y) { + return Vector.fromArray(new double[]{x, y}); + } + public static Constructor getZeroParameterPublicConstructor(Class clazz) { /*for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterCount() == 0) { @@ -52,4 +56,30 @@ public static double randomDouble() { public static int randomInt(int bound) { return random.nextInt(bound); } + + public static double randomWrappedNorm(double sigma) { + // Start in range [0, 2pi] + double rnorm = (random.nextGaussian() * sigma) + Math.PI; + double angle = rnorm % (2 * Math.PI); + if (angle < 0) angle += 2 * Math.PI; + + // Convert to our correct range and return + return angle - Math.PI; + } + + public static double getDifferenceBetweenAngles(double b1, double b2) { + double r = (b2 - b1) % (2 * Math.PI); + if (r < -Math.PI) + r += 2 * Math.PI; + if (r >= Math.PI) + r -= 2 * Math.PI; + return r; + } + + public static boolean is2DVectorShorterThan(Vector v, double length) { + double x = v.get(0); + double y = v.get(1); + + return x * x + y * y < length * length; + } } diff --git a/src/main/java/com/cemgokmen/particles/util/VectorWrapper.java b/src/main/java/com/cemgokmen/particles/util/VectorWrapper.java new file mode 100644 index 0000000..c51550f --- /dev/null +++ b/src/main/java/com/cemgokmen/particles/util/VectorWrapper.java @@ -0,0 +1,80 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.util; + +import org.la4j.Vector; + +public class VectorWrapper { + public static Vector wrapVector(Vector v, int wrapAroundDistance) { + return wrapVector(v, Vector.constant(v.length(), wrapAroundDistance)); + } + + public static Vector wrapVector(Vector v, Vector wrapAroundDistance) { + if (v.length() != wrapAroundDistance.length()) throw new RuntimeException("Vector dimensions should match."); + + Vector result = v.copy(); + + for (int i = 0; i < v.length(); i++) { + int x = (int) v.get(i); + + // Move it from the range -sHL <-> sHL to 0 <-> 2 * sHL, both inclusive + x += wrapAroundDistance.get(i); + + // Find equivalence in mod (2 * sHL + 1) + x = Math.floorMod(x, 2 * (int) wrapAroundDistance.get(i) + 1); + + // Move it back to the original range + x -= wrapAroundDistance.get(i); + + // Save it + result.set(i, x); + } + + return result; + } + + public static Vector unwrapVector(Vector v, int wrapAroundDistance, Vector levels) { + return wrapVector(v, Vector.constant(v.length(), wrapAroundDistance)); + } + + public static Vector unwrapVector(Vector v, Vector wrapAroundDistance, Vector levels) { + if (v.length() != wrapAroundDistance.length() || v.length() != levels.length()) throw new RuntimeException("Vector dimensions should match."); + + Vector result = v.copy(); + + for (int i = 0; i < v.length(); i++) { + int x = (int) v.get(i); + int level = (int) levels.get(i); + + // Move it from the range -sHL <-> sHL to 0 <-> 2 * sHL, both inclusive + x += wrapAroundDistance.get(i); + + // Add the level (2 * sHL + 1) + x += level * (2 * (int) wrapAroundDistance.get(i) + 1); + + // Move it back to the original range + x -= wrapAroundDistance.get(i); + + // Save it + result.set(i, x); + } + + return result; + } +} diff --git a/src/main/resources/sample_systems/alignment/20particles-linear.txt b/src/main/resources/sample_systems/alignment/20particles-linear.txt new file mode 100644 index 0000000..384a164 --- /dev/null +++ b/src/main/resources/sample_systems/alignment/20particles-linear.txt @@ -0,0 +1,22 @@ +25 +20 +0 12 0 +0 -7 1 +0 -11 0 +0 -19 1 +0 7 1 +0 5 1 +0 1 1 +0 18 0 +0 4 1 +0 19 1 +0 20 1 +0 11 1 +0 10 1 +0 -9 1 +0 9 1 +0 14 0 +0 -16 0 +0 6 0 +0 -14 1 +0 -5 1 diff --git a/src/main/resources/sample_systems/foraging/100particles-1food.txt b/src/main/resources/sample_systems/foraging/100particles-1food.txt index 9dcd1c2..e9e9514 100644 --- a/src/main/resources/sample_systems/foraging/100particles-1food.txt +++ b/src/main/resources/sample_systems/foraging/100particles-1food.txt @@ -1,7 +1,6 @@ 10 -100 +99 f 0 0 0 -p -1 1 0 p 1 0 1 p 1 -1 0 p 2 -2 1 diff --git a/src/test/java/com/cemgokmen/particles/models/amoebot/gridshapes/VectorWrapperTest.java b/src/test/java/com/cemgokmen/particles/models/amoebot/gridshapes/VectorWrapperTest.java index 14a6139..fb5861b 100644 --- a/src/test/java/com/cemgokmen/particles/models/amoebot/gridshapes/VectorWrapperTest.java +++ b/src/test/java/com/cemgokmen/particles/models/amoebot/gridshapes/VectorWrapperTest.java @@ -18,6 +18,7 @@ package com.cemgokmen.particles.models.amoebot.gridshapes; +import com.cemgokmen.particles.util.VectorWrapper; import org.junit.Test; import org.la4j.Vector; @@ -30,7 +31,7 @@ public void noWrapZeroVector() { int dist = 5; Vector v = Vector.constant(1, 0); - assertEquals(v, ToroidalAmoebotGrid.wrapVector(v, dist)); + assertEquals(v, VectorWrapper.wrapVector(v, dist)); } @Test @@ -38,7 +39,7 @@ public void noWrapPositiveVector() { int dist = 5; Vector v = Vector.constant(1, 2); - assertEquals(v, ToroidalAmoebotGrid.wrapVector(v, dist)); + assertEquals(v, VectorWrapper.wrapVector(v, dist)); } @Test @@ -46,7 +47,7 @@ public void noWrapNegativeVector() { int dist = 5; Vector v = Vector.constant(1, -2); - assertEquals(v, ToroidalAmoebotGrid.wrapVector(v, dist)); + assertEquals(v, VectorWrapper.wrapVector(v, dist)); } @Test @@ -55,7 +56,7 @@ public void wrapPositiveVector() { Vector v = Vector.constant(1, 6); Vector expected = Vector.constant(1, -5); - assertEquals(expected, ToroidalAmoebotGrid.wrapVector(v, dist)); + assertEquals(expected, VectorWrapper.wrapVector(v, dist)); } @Test @@ -64,7 +65,7 @@ public void wrapNegativeVector() { Vector v = Vector.constant(1, -12); Vector expected = Vector.constant(1, -1); - assertEquals(expected, ToroidalAmoebotGrid.wrapVector(v, dist)); + assertEquals(expected, VectorWrapper.wrapVector(v, dist)); } @Test @@ -73,6 +74,6 @@ public void wrapMoreNegativeVector() { Vector v = Vector.constant(1, -18); Vector expected = Vector.constant(1, 4); - assertEquals(expected, ToroidalAmoebotGrid.wrapVector(v, dist)); + assertEquals(expected, VectorWrapper.wrapVector(v, dist)); } } \ No newline at end of file diff --git a/src/test/java/com/cemgokmen/particles/util/UtilsTest.java b/src/test/java/com/cemgokmen/particles/util/UtilsTest.java new file mode 100644 index 0000000..1dabd2e --- /dev/null +++ b/src/test/java/com/cemgokmen/particles/util/UtilsTest.java @@ -0,0 +1,33 @@ +/* + * Particles, a self-organizing particle system simulator. + * Copyright (C) 2018 Cem Gokmen. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.cemgokmen.particles.util; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class UtilsTest { + + @Test + public void randomWrappedNorm() { + for (int i = 0; i < 1000; i++) { + System.out.println(Utils.randomWrappedNorm(1.3)); + } + } +} \ No newline at end of file