Skip to content

Mine #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Mine #40

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions src/main/scala/org/learningconcurrency/exercises/ch5/ex3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package org.learningconcurrency.exercises.ch5

import java.awt.Color
import java.awt.image.BufferedImage
import java.io.File
import java.util.concurrent.Executors
import javax.imageio.ImageIO

import scala.annotation.tailrec
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.io.StdIn
import scala.util.{Success, Try}

/**
* Implement a program that renders the Mandelbrot set in parallel.
*/
object Ex3 extends App {

import org.learningconcurrency.ch5.warmedTimed

case class MandelbrotParams(imageWidth: Int,
imageHeight: Int,
xMin: Double,
xMax: Double,
yMin: Double,
yMax: Double,
maxIterations: Int) {
val xStep = if (xMin >= 0 && xMax >= 0) {
(xMin + xMax) / imageWidth
} else if (xMin < 0 && xMax >= 0) {
(xMax - xMin) / imageWidth
} else {
(-xMin + xMax) / imageWidth
}

val yStep = if (yMin >= 0 && yMax >= 0) {
(yMin + yMax) / imageHeight
} else if (xMin < 0 && yMax >= 0) {
(yMax - yMin) / imageHeight
} else {
(-yMin + yMax) / imageHeight
}
}

type MandelbrotSet = Seq[Seq[Int]]

trait MandelbrotSetBuilder {
def apply(params: MandelbrotParams): Future[MandelbrotSet]

protected final def calculateMandelbrotElement(point: Complex,
maxIterations: Int = 1000
): Int = {
@tailrec
def iterate(i: Int = 0, z: Complex = Complex(0, 0)): Int = {
if (i < maxIterations && z.abs < 4) {
val newPoint = z.sqr + point
iterate(i + 1, newPoint)
} else {
// todo Either[Unit, Int]
if (i == maxIterations) -1 else i
}
}
iterate()
}
}

case class Complex(x: Double, y: Double) {
def abs: Double = x * x + y * y

def sqr: Complex = Complex(x * x - y * y, 2 * x * y)

def +(c: Complex): Complex = Complex(x + c.x, y + c.y)
}

class SingleThreadBlockingGenerator extends MandelbrotSetBuilder {
override def apply(params: MandelbrotParams): Future[Seq[Seq[Int]]] = {
import params._

val result = for {
y0 <- 0 until imageHeight
} yield for {
x0 <- 0 until imageWidth
} yield {
val xToCheck = xMin + x0 * xStep
val yToCheck = yMin + y0 * yStep
val complexValueToCheck = Complex(xToCheck, yToCheck)
calculateMandelbrotElement(complexValueToCheck, maxIterations = params.maxIterations)
}
Future.successful(result)
}
}

class MultiThreadGenerator(segments: Int)(implicit ec: ExecutionContext) extends MandelbrotSetBuilder {
override def apply(params: MandelbrotParams): Future[Seq[Seq[Int]]] = {
import params._

val segmentHeight = params.imageHeight / segments

val fs = for {
segment <- 1 to segments
} yield {
Future {
(segment, for {
y0 <- ((segment - 1) * segmentHeight) to (segment * segmentHeight)
} yield {
for {
x0 <- 0 until imageWidth
} yield {
val xToCheck = xMin + x0 * xStep
val yToCheck = yMin + y0 * yStep
val complexValueToCheck = Complex(xToCheck, yToCheck)
calculateMandelbrotElement(complexValueToCheck, maxIterations = params.maxIterations)
}
})
}
}

Future.sequence(fs).map(_.sortBy(_._1).flatMap(_._2))
}
}

class MandelbrotSetImageSaver(format: String, filePath: String) {
def apply(params: MandelbrotParams, set: MandelbrotSet): Unit = {
val bi = new BufferedImage(params.imageWidth, params.imageHeight, BufferedImage.TYPE_INT_RGB)

val g = bi.createGraphics()

import params._

val histogram = set.flatten
.groupBy(identity)
.map(g => (g._1, g._2.size))
.filter(_._1 >= 0)
.map(g => (g._1 - 1, g._2))
.withDefaultValue(0)

val total = histogram.values.sum

for {
px <- 0 until imageWidth
py <- 0 until imageHeight
} {
val numIters = set(py)(px)

var colorVal = 0f
for (i <- 0 until numIters) {
colorVal += histogram(i) / total.toFloat
}
val rgb = Color.HSBtoRGB(0.1f + colorVal, 1.0f, colorVal * colorVal)

g.setPaint(new Color(rgb))
g.drawLine(px, py, px, py)
}

ImageIO.write(bi, format, new File(filePath))
}
}

val processors = Runtime.getRuntime.availableProcessors()

val pool = Executors.newFixedThreadPool(processors * 4)
implicit val ec = ExecutionContext.fromExecutor(pool)

val singleThreadGenerator = new SingleThreadBlockingGenerator
val multiThreadGenerator = new MultiThreadGenerator(segments = processors * 10)

val params = MandelbrotParams(900, 600, -2f, 1f, -1f, 1f, 1000)

val warmupTimes = 30

print("Warmup single thread")
val singleThreadGeneratorTime = warmedTimed(warmupTimes) {
print(".")
singleThreadGenerator(params)
}
println

println(s"Single thread generator time: $singleThreadGeneratorTime")

print("Warmup future multi thread")
val multiThreadGeneratorTime = warmedTimed(warmupTimes) {
print(".")
Await.result(multiThreadGenerator(params), Duration.Inf)
}
println

println(s"Multi thread generator time: $multiThreadGeneratorTime")

println("Save result to PNG file? [y/n]")
Try(StdIn.readChar()) match {
case Success('y') =>
val mandelbrotElements = Await.result(multiThreadGenerator(params), Duration.Inf)
val fileName = "mandelbrot.png"
new MandelbrotSetImageSaver("PNG", fileName).apply(params, mandelbrotElements)
println(s"See '$fileName' file with mandelbrot set in project folder")
case _ =>
}
pool.shutdown()
}
177 changes: 177 additions & 0 deletions src/main/scala/org/learningconcurrency/exercises/ch5/ex4.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package org.learningconcurrency.exercises.ch5

import java.awt.Color
import java.awt.image.BufferedImage
import java.io.File
import java.util.concurrent.Executors
import javax.imageio.ImageIO

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.io.StdIn
import scala.util.{Success, Try}

/**
* Implement a program that simulates a cellular automaton in parallel.
*/
object Ex4 extends App {

import org.learningconcurrency.ch5.warmedTimed

trait CellularAutomator {
type State
type Locality
type Field <: Seq[State]
type Position
type Rule = Locality => State

def rule: Rule

def localitySize: Int

def calculateNextState(currentState: Field): Field

def calculateNextStates(currentState: Field, n: Int): Iterator[Field]
}

object Rule30 extends OneDimentionCellularAutomator#Rule {
override def apply(locality: Byte): Boolean = locality match {
case 7 /*111*/ => false
case 6 /*110*/ => false
case 5 /*101*/ => false
case 4 /*100*/ => true
case 3 /*011*/ => true
case 2 /*010*/ => true
case 1 /*001*/ => true
case 0 /*000*/ => false
case v => throw new IllegalArgumentException(s"Value must be < 8 (with 3 cell counts), but $v found")
}
}

trait OneDimentionCellularAutomator extends CellularAutomator {
override type State = Boolean
override type Locality = Byte
override type Field = Seq[Boolean]
override type Position = Int

private val doubleLocality = localitySize * 2

protected def localityStates(pos: Position, currentState: Seq[Boolean]): Locality = {
currentState
.slice(pos - localitySize, pos + localitySize + 1)
.zipWithIndex
.foldLeft(0) {
case (r, (v, index)) => if (v) r | 1 << doubleLocality - index else r
}.toByte
}
}

class SingleThreadOneDimentionCellularAutomator(override val rule: Byte => Boolean,
override val localitySize: Int) extends OneDimentionCellularAutomator {
override def calculateNextState(currentState: Seq[Boolean]): Seq[Boolean] = {
for {
x <- currentState.indices
} yield rule(localityStates(x, currentState))
}

override def calculateNextStates(currentState: Seq[Boolean], n: Int): Iterator[Seq[Boolean]] = {
Iterator.iterate(currentState)(calculateNextState).take(n)
}
}

class FutureMultiThreadOneDimentionCellularAutomator(segments: Int,
override val rule: Byte => Boolean,
override val localitySize: Int)(implicit ec: ExecutionContext) extends OneDimentionCellularAutomator {
override def calculateNextState(currentState: Seq[Boolean]): Seq[Boolean] = {
// todo запускать рассчет соседей и после этого рассчет этой же точки в следующей генерации
val segmentSize = currentState.size / segments
val fs = for {
segment <- 1 to segments
} yield Future {
(segment, for {
x <- (segment - 1) * segmentSize until segment * segmentSize
} yield rule(localityStates(x, currentState)))
}
Await.result(Future.sequence(fs), Duration.Inf).sortBy(_._1).flatMap(_._2)
}

override def calculateNextStates(currentState: Seq[Boolean], n: Int): Iterator[Seq[Boolean]] = {
Iterator.iterate(currentState)(calculateNextState).take(n)
}
}

class ImageSaver(format: String, filePath: String) {
def apply(field: Iterator[Seq[Boolean]], size: Int, steps: Int) = {
val bi = new BufferedImage(size, steps, BufferedImage.TYPE_BYTE_BINARY)

val g = bi.createGraphics()
g.setPaint(Color.WHITE)

for {
(xs, y) <- field.zipWithIndex
(v, x) <- xs.zipWithIndex
if !v
} {
g.drawLine(x, y, x, y)
}

ImageIO.write(bi, format, new File(filePath))
}
}

val singleThreadAutomator = new SingleThreadOneDimentionCellularAutomator(Rule30, 1)

val processors = Runtime.getRuntime.availableProcessors()

val pool = Executors.newFixedThreadPool(processors * 4)
implicit val ec = ExecutionContext.fromExecutor(pool)
val futureMultiThreadAutomator = new FutureMultiThreadOneDimentionCellularAutomator(processors * 10, Rule30, 1)

val startState = {
val size = 50000
val a = Array.fill(size)(false)
a(size / 2) = true
a
}

val steps = 100
val warmUps = 10

print("Warmup single thread")
val singleThreadTime = warmedTimed(warmUps) {
print(".")
singleThreadAutomator.calculateNextStates(startState, steps).toList
}
println

println(s"Single thread calculation time: $singleThreadTime")

print("Warmup future multi thread")
val futureMultiThreadTime = warmedTimed(warmUps) {
print(".")
futureMultiThreadAutomator.calculateNextStates(startState, steps).toList
}
println

println(s"Future multi thread calculation time: $futureMultiThreadTime")

println("Print result to console? [y/n]")
Try(StdIn.readChar()) match {
case Success('y') =>
val result = futureMultiThreadAutomator.calculateNextStates(startState, steps)
result.foreach(field => println(field.map(v => if (v) '▉' else ' ').mkString))
case _ =>
}

println("Save result to PNG file? [y/n]")
Try(StdIn.readChar()) match {
case Success('y') =>
val result = futureMultiThreadAutomator.calculateNextStates(startState, steps)
val fileName = "cellular-automaton.png"
new ImageSaver("PNG", fileName).apply(result, startState.length, steps)
println(s"See '$fileName' file with cellular automaton evolution image in project folder")
case _ =>
}

pool.shutdown()
}
Loading