diff --git a/model/src/main/scala/fr/iscpif/spacematters/model/container/Container.scala b/model/src/main/scala/fr/iscpif/spacematters/model/container/Container.scala
new file mode 100644
index 0000000..f2334cb
--- /dev/null
+++ b/model/src/main/scala/fr/iscpif/spacematters/model/container/Container.scala
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 Romain Reuillon
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 fr.iscpif.spacematters.model.container
+
+import fr.iscpif.spacematters.model.Cell
+
+import scala.util.Random
+
+trait Container {
+
+ def size : Int
+
+ def container(implicit rng: Random): Seq[Seq[Cell]]
+
+
+
+ /**
+ * Stringify just to check validity of generators ; has no sense in general context as a new instance will be randomly generated at each
+ * call of container.
+ *
+ * @return
+ */
+ override def toString: String = {
+ var res = ""
+ container(new Random).foreach(
+ (row : Seq[Cell]) => {
+ row.foreach(
+ (c : Cell) => {res = res +c.capacity+" | "}
+ )
+ res = res + "\n"
+ }
+ )
+ res
+ }
+
+
+}
diff --git a/model/src/main/scala/fr/iscpif/spacematters/model/container/ExpMixtureContainer.scala b/model/src/main/scala/fr/iscpif/spacematters/model/container/ExpMixtureContainer.scala
new file mode 100644
index 0000000..0bb2358
--- /dev/null
+++ b/model/src/main/scala/fr/iscpif/spacematters/model/container/ExpMixtureContainer.scala
@@ -0,0 +1,38 @@
+package fr.iscpif.spacematters.model.container
+
+import fr.iscpif.spacematters.model.Cell
+
+import scala.util.Random
+
+trait ExpMixtureContainer <: Container {
+
+ /** maximal capacity C_m */
+ def maxCapacity : Int
+
+ /** Size of exponential kernels, of the form C_m*exp(-||x-x_0||/r_0) */
+ def kernelRadius : Double
+
+ /** Number of exponential kernels */
+ def centersNumber : Int
+
+ def container(implicit rng: Random) = {
+ val arrayVals = Array.fill[Cell](size, size) {
+ new Cell(0, 0, 0)
+ }
+
+ // generate random center positions
+ val centers = Array.fill[Int](centersNumber, 2) {
+ rng.nextInt(size)
+ }
+
+ for (i <- 0 to size - 1; j <- 0 to size - 1) {
+ for (c <- 0 to centersNumber - 1) {
+ arrayVals(i)(j).capacity = arrayVals(i)(j).capacity + maxCapacity * math.exp(-math.sqrt(math.pow((i - centers(c)(0)), 2) + math.pow((j - centers(c)(1)), 2)) / kernelRadius)
+ }
+ }
+
+ Seq.tabulate(size,size){(i:Int,j:Int)=>Cell(arrayVals(i)(j).capacity,0,0) }
+
+ }
+
+ }
diff --git a/model/src/main/scala/fr/iscpif/spacematters/model/container/PrefAttDiffusionContainer.scala b/model/src/main/scala/fr/iscpif/spacematters/model/container/PrefAttDiffusionContainer.scala
new file mode 100644
index 0000000..b53cf0c
--- /dev/null
+++ b/model/src/main/scala/fr/iscpif/spacematters/model/container/PrefAttDiffusionContainer.scala
@@ -0,0 +1,109 @@
+package fr.iscpif.spacematters.model.container
+
+import fr.iscpif.spacematters.model.Cell
+
+import scala.util.Random
+
+trait PrefAttDiffusionContainer <: Container {
+
+ /** sum of all capacities */
+ def totalCapacity : Double
+
+ /** diffusion parameters */
+ def diffusion : Double
+ def diffusionSteps : Int
+
+ /** Growth rate */
+ def growthRate : Int
+
+ /** Preferential attachment parameter */
+ def alphaAtt : Double
+
+
+
+
+ /**
+ *
+ * @param rng
+ * @return
+ */
+ def container(implicit rng: Random) = {
+ var arrayVals = Array.fill[Cell](size, size){new Cell(0,0,0)}
+ var population :Double = 0
+
+ while(population < totalCapacity) {
+
+ // add new population following pref att rule
+ if(population==0){
+ //choose random patch
+ for(_<- 1 to growthRate){val i = rng.nextInt(size);val j = rng.nextInt(size) ; arrayVals(i)(j).capacity = arrayVals(i)(j).capacity + 1 }
+ } else{
+ val oldPop = arrayVals.clone()
+ val ptot = oldPop.flatten.map((c:Cell)=>math.pow(c.capacity / population , alphaAtt)).sum
+
+ /*
+ val drawings = Array.fill[Double](growthRate){rng.nextDouble()}
+ Sorting.quickSort(drawings)
+ var s = 0.0; var i =0;var j =0;var k=0;
+ oldPop.flatten.foreach(
+ (c:Cell)=>{
+ s=s+(math.pow(c.capacity/population,alphaAtt)/ptot)
+ while(sCell(arrayVals(i)(j).capacity,0,0) }
+
+
+ }
+
+
+
+
+
+
+ /**
+ * Diffuse to neighbors proportion alpha of capacities
+ * @param a
+ */
+ def diffuse(a : Array[Array[Cell]],alpha : Double): Array[Array[Cell]] = {
+ val newVals = a.clone()
+ for (i <- 0 to size - 1; j <- 0 to size - 1) {
+ if(i>=1){newVals(i-1)(j).capacity = newVals(i-1)(j).capacity + (alpha / 8)*a(i)(j).capacity ; newVals(i)(j).capacity = newVals(i)(j).capacity - (alpha / 8)*a(i)(j).capacity }
+ if(i=1){newVals(i)(j-1).capacity = newVals(i)(j-1).capacity + (alpha / 8)*a(i)(j).capacity ; newVals(i)(j).capacity = newVals(i)(j).capacity - (alpha / 8)*a(i)(j).capacity }
+ if(j=1&&j>=1){newVals(i-1)(j-1).capacity = newVals(i-1)(j-1).capacity + (alpha / 8)*a(i)(j).capacity ; newVals(i)(j).capacity = newVals(i)(j).capacity - (alpha / 8)*a(i)(j).capacity }
+ if(i>=1&&j=1){newVals(i+1)(j-1).capacity = newVals(i+1)(j-1).capacity + (alpha / 8)*a(i)(j).capacity ; newVals(i)(j).capacity = newVals(i)(j).capacity - (alpha / 8)*a(i)(j).capacity }
+ if(i= capacity
def isEmpty = population <= 0
def population = red + green
diff --git a/scala/model/src/main/scala/fr/iscpif/spacematters/model/Schelling.scala b/scala/model/src/main/scala/fr/iscpif/spacematters/model/Schelling.scala
index 4f5320a..d821370 100644
--- a/scala/model/src/main/scala/fr/iscpif/spacematters/model/Schelling.scala
+++ b/scala/model/src/main/scala/fr/iscpif/spacematters/model/Schelling.scala
@@ -18,8 +18,6 @@ package fr.iscpif.spacematters.model
import fr.iscpif.spacematters.model.initial._
import fr.iscpif.spacematters.model.move._
-import fr.iscpif.spacematters.model.initial.InitialState
-import fr.iscpif.spacematters.model.move.Moves
import scala.util.Random
diff --git a/scala/model/src/main/scala/fr/iscpif/spacematters/model/Simulation.scala b/scala/model/src/main/scala/fr/iscpif/spacematters/model/Simulation.scala
index b7a41cc..e822aad 100644
--- a/scala/model/src/main/scala/fr/iscpif/spacematters/model/Simulation.scala
+++ b/scala/model/src/main/scala/fr/iscpif/spacematters/model/Simulation.scala
@@ -6,7 +6,8 @@ import initial._
import move._
import metric._
import stop._
-import Moran._
+import container._
+import metric.Moran
import scalax.io.Resource
@@ -16,12 +17,21 @@ object Simulation extends App {
implicit val rng = new Random
- val simulation = new Schelling with RandomState with RandomMoves with SpeilmanStop {
+ val simulation = new Schelling with RandomState with RandomContainer with RandomMoves with SpeilmanStop {
override def size: Int = 50
override def greenRatio: Double = 0.5
override def redRatio: Double = 0.35
override def maxCapacity: Int = 50
override def similarWanted: Double = 0.4
+
+ /*
+ override def totalCapacity :Double = 50000
+ override def diffusion : Double = 0.02
+ override def diffusionSteps : Int = 2
+ override def growthRate : Int = 100
+ override def alphaAtt : Double = 1.1
+*/
+
}
simulation.run
@@ -40,7 +50,7 @@ object Simulation extends App {
(state, step) <- simulation.states.take(100).zipWithIndex
} {
def unsatisfied = simulation.unsatisfieds(state).map(_.number).sum
- println(s"Step $step: # of unsatisfied: $unsatisfied, Dissimilarity D: ${"%.3f".format(dissimilarity(state, Green, Red))}, Moran I Red: ${"%.3f".format(colorRatioMoran(state, Red))}, Entropy H: ${"%.3f".format(segregationEntropy(state, Green, Red))}, Exposure Reds to Greens :${"%.3f".format(exposureOfColor1ToColor2(state, Red, Green))}, Isolation Reds :${"%.3f".format(isolation(state, Red, Green))}, Concentration Greens : ${"%.3f".format(delta(state, Green, Red))}")
+ println(s"Step $step: # of unsatisfied: $unsatisfied, Dissimilarity D: ${"%.3f".format(dissimilarity(state, Green, Red))}, Moran I Red: ${"%.3f".format(Moran.colorRatioMoran(state, Red))}, Entropy H: ${"%.3f".format(segregationEntropy(state, Green, Red))}, Exposure Reds to Greens :${"%.3f".format(exposureOfColor1ToColor2(state, Red, Green))}, Isolation Reds :${"%.3f".format(isolation(state, Red, Green))}, Concentration Greens : ${"%.3f".format(delta(state, Green, Red))}")
for { (position @ (i, j), c) <- state.cells } {
def agents = Color.all.map(_.cellColor.get(c)).mkString(",")
@@ -56,7 +66,7 @@ object Simulation extends App {
val similarWanted = simulation.similarWanted
output2.append(
- s"""$step, $unsatisfied,${dissimilarity(state, Green, Red)}, ${colorRatioMoran(state, Red)}, ${segregationEntropy(state, Green, Red)}, ${exposureOfColor1ToColor2(state, Red, Green)},${exposureOfColor1ToColor2(state, Green, Red)}, ${isolation(state, Red, Green)}, ${isolation(state, Green, Red)},${delta(state, Red, Green)},${delta(state, Green, Red)}, $size, $greenRatio,$redRatio, $maxCapacity, $similarWanted\n""".stripMargin)
+ s"""$step, $unsatisfied,${dissimilarity(state, Green, Red)}, ${Moran.colorRatioMoran(state, Red)}, ${segregationEntropy(state, Green, Red)}, ${exposureOfColor1ToColor2(state, Red, Green)},${exposureOfColor1ToColor2(state, Green, Red)}, ${isolation(state, Red, Green)}, ${isolation(state, Green, Red)},${delta(state, Red, Green)},${delta(state, Green, Red)}, $size, $greenRatio,$redRatio, $maxCapacity, $similarWanted\n""".stripMargin)
}
diff --git a/scala/model/src/main/scala/fr/iscpif/spacematters/model/initial/RandomState.scala b/scala/model/src/main/scala/fr/iscpif/spacematters/model/initial/RandomState.scala
index 08d2722..8e70426 100644
--- a/scala/model/src/main/scala/fr/iscpif/spacematters/model/initial/RandomState.scala
+++ b/scala/model/src/main/scala/fr/iscpif/spacematters/model/initial/RandomState.scala
@@ -17,18 +17,17 @@
package fr.iscpif.spacematters.model.initial
import fr.iscpif.spacematters.model._
+import fr.iscpif.spacematters.model.container._
import scala.util.Random
-trait RandomState <: InitialState { self: Schelling =>
+trait RandomState <: InitialState with Container { self: Schelling =>
def maxCapacity: Int
def initialState(implicit rng: Random) = {
- val cells = Seq.fill(size, size) {
- val capacity = rng.nextInt(maxCapacity)
- Cell(capacity = capacity, green = 0, red = 0)
- }
+
+ val cells = container(rng)
val totalCapacity = cells.flatten.map(_.capacity).sum
val greens = (totalCapacity * greenRatio).toInt
diff --git a/scala/model/src/main/scala/fr/iscpif/spacematters/model/metric/Convolution.scala b/scala/model/src/main/scala/fr/iscpif/spacematters/model/metric/Convolution.scala
new file mode 100644
index 0000000..db6f6d3
--- /dev/null
+++ b/scala/model/src/main/scala/fr/iscpif/spacematters/model/metric/Convolution.scala
@@ -0,0 +1,59 @@
+package fr.iscpif.spacematters.model.metric
+
+import org.apache.commons.math3.complex.Complex
+import org.apache.commons.math3.transform.{ TransformType, DftNormalization, FastFourierTransformer }
+import org.apache.commons.math3.util.MathArrays
+
+import scala.math._
+
+object Convolution {
+
+ /**
+ * Generic convol for double Arrays (in O(nlog(n)), using FFT)
+ *
+ * @param x
+ * @param k centered kernel
+ * @return y = x*k with y_i = \sum_{j=1}{|K|}{x_{i-j-|K|/2}*k_j}
+ */
+ def convolution(x: Array[Double], k: Array[Double]): Array[Double] = {
+ val xl = pow(2.0, ceil(log(x.length) / log(2.0)) + 1)
+ val xp = x.padTo(x.length + (xl.toInt - x.length) / 2, 0.0).reverse.padTo(xl.toInt, 0.0).reverse
+ val kp = k.padTo(k.length + (xl.toInt - k.length) / 2, 0.0).reverse.padTo(xl.toInt, 0.0).reverse
+ val tr = new FastFourierTransformer(DftNormalization.STANDARD)
+ val ftx = tr.transform(xp, TransformType.FORWARD)
+ val ftk = tr.transform(kp, TransformType.FORWARD)
+ val real = MathArrays.ebeSubtract(MathArrays.ebeMultiply(ftx.map { z => z.getReal }, ftk.map { z => z.getReal }), MathArrays.ebeMultiply(ftx.map { z => z.getImaginary }, ftk.map { z => z.getImaginary }))
+ val im = MathArrays.ebeAdd(MathArrays.ebeMultiply(ftx.map { z => z.getReal }, ftk.map { z => z.getImaginary }), MathArrays.ebeMultiply(ftx.map { z => z.getImaginary }, ftk.map { z => z.getReal }))
+ val trinv = tr.transform(Array.tabulate(real.length) { i => new Complex(real(i), im(i)) }, TransformType.INVERSE).map { z => z.getReal }
+ trinv.splitAt(trinv.length - x.length / 2)._2 ++ trinv.splitAt(x.length - x.length / 2)._1
+ }
+
+ /**
+ * Square convol (for tests)
+ *
+ * @param x
+ * @param k
+ * @return
+ */
+ def directConvol(x: Array[Double], k: Array[Double]): Array[Double] = {
+ val kl = k.length
+ val xpadded = x.padTo(x.length + kl, 0.0).reverse.padTo(x.length + 2 * kl, 0.0).reverse
+ Array.tabulate(x.length + k.length) { i => MathArrays.ebeMultiply(k.reverse, xpadded.splitAt(i + 1)._2.splitAt(k.length)._1).sum }
+ }
+
+ /**
+ * 2D convolution
+ * Using bijection [|1,N|]2 ~ [|1,N|] by flattening, after having good paddling
+ *
+ * @param x
+ * @param k
+ */
+ def convolution2D(x: Array[Array[Double]], k: Array[Array[Double]]): Array[Array[Double]] = {
+ val xpad = x.map { row => row.padTo(2 * (row.length / 2) + 1 + k(0).length, 0.0).reverse.padTo(2 * (row.length / 2) + 1 + 2 * k(0).length, 0.0).reverse }.padTo(2 * (x.length / 2) + 1 + k.length, Array.fill(2 * (x(0).length / 2) + 1 + 2 * k(0).length) { 0.0 }).reverse.padTo(2 * (x.length / 2) + 1 + 2 * k.length, Array.fill(2 * (x(0).length / 2) + 1 + 2 * k(0).length) { 0.0 })
+ val xpos = Array.fill(x.length, x(0).length) { 1.0 }.map { row => row.padTo(2 * (row.length / 2) + 1 + k(0).length, 0.0).reverse.padTo(2 * (row.length / 2) + 1 + 2 * k(0).length, 0.0).reverse }.padTo(2 * (x.length / 2) + 1 + k.length, Array.fill(2 * (x(0).length / 2) + 1 + 2 * k(0).length) { 0.0 }).reverse.padTo(2 * (x.length / 2) + 1 + 2 * k.length, Array.fill(2 * (x(0).length / 2) + 1 + 2 * k(0).length) { 0.0 }).flatten
+ val kpad = k.map { row => row.padTo(row.length + (xpad(0).length - row.length) / 2, 0.0).reverse.padTo(xpad(0).length, 0.0).reverse }.padTo(k.length + (xpad.length - k.length) / 2, Array.fill(xpad(0).length) { 0.0 }).reverse.padTo(xpad.length, Array.fill(xpad(0).length) { 0.0 })
+ val flatconv = convolution(xpad.flatten, kpad.flatten)
+ flatconv.zipWithIndex.filter { case (_, j) => xpos(j) > 0 }.map { case (d, _) => d }.sliding(x(0).length, x.length).toArray.reverse
+ }
+
+}
diff --git a/scala/model/src/main/scala/fr/iscpif/spacematters/model/metric/package.scala b/scala/model/src/main/scala/fr/iscpif/spacematters/model/metric/package.scala
index 9ad1ff5..a03da37 100644
--- a/scala/model/src/main/scala/fr/iscpif/spacematters/model/metric/package.scala
+++ b/scala/model/src/main/scala/fr/iscpif/spacematters/model/metric/package.scala
@@ -190,4 +190,50 @@ package object metric {
if i != oi || j != oj
} yield state(i + oi)(j + oj)
+ /**
+ * Moran index using fast convolution.
+ *
+ * @param matrix
+ * @return
+ */
+ def moran_convol(matrix: Matrix[T], quantity: Quantity[T]): Double = {
+ val conf = matrix.map { row => row.map(quantity).toArray }.toArray
+ val n = conf.length
+ val flatConf = conf.flatten
+ val popMean = flatConf.sum / flatConf.length
+ val centeredConf = conf.map { r => r.map { d => d - popMean } }
+ val variance = MathArrays.ebeMultiply(centeredConf.flatten, centeredConf.flatten).sum
+ val weights = spatialWeights(2 * n - 1)
+ val totWeight = Convolution.convolution2D(Array.fill(n, n) { 1.0 }, weights).flatten.sum
+ flatConf.length / (totWeight * variance) * MathArrays.ebeMultiply(centeredConf.flatten, Convolution.convolution2D(centeredConf, weights).flatten).sum
+ }
+
+ def spatialWeights(n: Int): Array[Array[Double]] = {
+ Array.tabulate(n, n) { (i, j) => if (i == n / 2 && j == n / 2) 0.0 else 1 / Math.sqrt((i - n / 2) * (i - n / 2) + (j - n / 2) * (j - n / 2)) }
+ }
+
+ /**
+ * Mean distance using fast convolution.
+ *
+ * @param matrix
+ * @return
+ */
+ def distance_convol(matrix: Matrix[T], quantity: Quantity[T]): Double = {
+ val conf = matrix.map { row => row.map(quantity).toArray }.toArray
+ val totPop = conf.flatten.sum
+ val dmat = distanceMatrix(2 * conf.length - 1)
+ val conv = Convolution.convolution2D(conf, dmat)
+ math.sqrt(math.Pi) / (conf.length * totPop * totPop) * MathArrays.ebeMultiply(conv.flatten, conf.flatten).sum
+ }
+
+ /**
+ * Distance kernel
+ *
+ * @param n
+ * @return
+ */
+ def distanceMatrix(n: Int): Array[Array[Double]] = {
+ Array.tabulate(n, n) { (i, j) => Math.sqrt((i - n / 2) * (i - n / 2) + (j - n / 2) * (j - n / 2)) }
+ }
+
}
diff --git a/scala/model/src/main/scala/fr/iscpif/spacematters/model/stop/SpeilmanStop.scala b/scala/model/src/main/scala/fr/iscpif/spacematters/model/stop/SpeilmanStop.scala
index 414a917..45c2c1b 100644
--- a/scala/model/src/main/scala/fr/iscpif/spacematters/model/stop/SpeilmanStop.scala
+++ b/scala/model/src/main/scala/fr/iscpif/spacematters/model/stop/SpeilmanStop.scala
@@ -17,7 +17,6 @@
package fr.iscpif.spacematters.model.stop
import fr.iscpif.spacematters.model._
-
import scala.util.{ Random, Try }
trait SpeilmanStop <: Schelling {