forked from TheAlgorithms/C-Sharp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Mandelbrot set visualization (TheAlgorithms#209)
Co-authored-by: Andrii Siriak <[email protected]>
- Loading branch information
1 parent
6613c85
commit e5859fa
Showing
6 changed files
with
287 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,62 @@ | ||
using System; | ||
using Algorithms.Numeric; | ||
using NUnit.Framework; | ||
using System.Collections.Generic; | ||
using FluentAssertions; | ||
|
||
namespace Algorithms.Tests.Numeric | ||
{ | ||
public static class EulerMethodTest | ||
{ | ||
[Test] | ||
public static void TestLinearEquation() | ||
{ | ||
Func<double, double, double> exampleEquation = (x, y) => x; | ||
List<double[]> points = Algorithms.Numeric.EulerMethod.EulerFull(0, 4, 0.001, 0, exampleEquation); | ||
double yEnd = points[points.Count - 1][1]; | ||
yEnd.Should().BeApproximately(8, 0.01); | ||
} | ||
|
||
[Test] | ||
public static void TestExampleWikipedia() | ||
{ | ||
// example from https://en.wikipedia.org/wiki/Euler_method | ||
Func<double, double, double> exampleEquation = (x, y) => y; | ||
List<double[]> points = Algorithms.Numeric.EulerMethod.EulerFull(0, 4, 0.0125, 1, exampleEquation); | ||
double yEnd = points[points.Count - 1][1]; | ||
yEnd.Should().BeApproximately(53.26, 0.01); | ||
} | ||
|
||
[Test] | ||
public static void TestExampleGeeksForGeeks() | ||
{ | ||
// example from https://www.geeksforgeeks.org/euler-method-solving-differential-equation/ | ||
// Euler method: y_n+1 = y_n + stepSize * f(x_n, y_n) | ||
// differential equation: f(x, y) = x + y + x * y | ||
// initial conditions: x_0 = 0; y_0 = 1; stepSize = 0.025 | ||
// solution: | ||
// y_1 = 1 + 0.025 * (0 + 1 + 0 * 1) = 1.025 | ||
// y_2 = 1.025 + 0.025 * (0.025 + 1.025 + 0.025 * 1.025) = 1.051890625 | ||
Func<double, double, double> exampleEquation = (x, y) => x + y + x * y; | ||
List<double[]> points = Algorithms.Numeric.EulerMethod.EulerFull(0, 0.05, 0.025, 1, exampleEquation); | ||
double y_1 = points[1][1]; | ||
double y_2 = points[2][1]; | ||
Assert.AreEqual(y_1, 1.025); | ||
Assert.AreEqual(y_2, 1.051890625); | ||
} | ||
|
||
[Test] | ||
public static void StepsizeIsZeroOrNegative_ThrowsArgumentOutOfRangeException() | ||
{ | ||
Func<double, double, double> exampleEquation = (x, y) => x; | ||
Assert.Throws<ArgumentOutOfRangeException>(() => Algorithms.Numeric.EulerMethod.EulerFull(0, 4, 0, 0, exampleEquation)); | ||
} | ||
|
||
[Test] | ||
public static void StartIsLargerThanEnd_ThrowsArgumentOutOfRangeException() | ||
{ | ||
Func<double, double, double> exampleEquation = (x, y) => x; | ||
Assert.Throws<ArgumentOutOfRangeException>(() => Algorithms.Numeric.EulerMethod.EulerFull(0, -4, 0.1, 0, exampleEquation)); | ||
} | ||
} | ||
using System; | ||
using Algorithms.Numeric; | ||
using NUnit.Framework; | ||
using System.Collections.Generic; | ||
using FluentAssertions; | ||
|
||
namespace Algorithms.Tests.Numeric | ||
{ | ||
public static class EulerMethodTest | ||
{ | ||
[Test] | ||
public static void TestLinearEquation() | ||
{ | ||
Func<double, double, double> exampleEquation = (x, y) => x; | ||
List<double[]> points = Algorithms.Numeric.EulerMethod.EulerFull(0, 4, 0.001, 0, exampleEquation); | ||
double yEnd = points[points.Count - 1][1]; | ||
yEnd.Should().BeApproximately(8, 0.01); | ||
} | ||
|
||
[Test] | ||
public static void TestExampleWikipedia() | ||
{ | ||
// example from https://en.wikipedia.org/wiki/Euler_method | ||
Func<double, double, double> exampleEquation = (x, y) => y; | ||
List<double[]> points = Algorithms.Numeric.EulerMethod.EulerFull(0, 4, 0.0125, 1, exampleEquation); | ||
double yEnd = points[points.Count - 1][1]; | ||
yEnd.Should().BeApproximately(53.26, 0.01); | ||
} | ||
|
||
[Test] | ||
public static void TestExampleGeeksForGeeks() | ||
{ | ||
// example from https://www.geeksforgeeks.org/euler-method-solving-differential-equation/ | ||
// Euler method: y_n+1 = y_n + stepSize * f(x_n, y_n) | ||
// differential equation: f(x, y) = x + y + x * y | ||
// initial conditions: x_0 = 0; y_0 = 1; stepSize = 0.025 | ||
// solution: | ||
// y_1 = 1 + 0.025 * (0 + 1 + 0 * 1) = 1.025 | ||
// y_2 = 1.025 + 0.025 * (0.025 + 1.025 + 0.025 * 1.025) = 1.051890625 | ||
Func<double, double, double> exampleEquation = (x, y) => x + y + x * y; | ||
List<double[]> points = Algorithms.Numeric.EulerMethod.EulerFull(0, 0.05, 0.025, 1, exampleEquation); | ||
double y_1 = points[1][1]; | ||
double y_2 = points[2][1]; | ||
Assert.AreEqual(y_1, 1.025); | ||
Assert.AreEqual(y_2, 1.051890625); | ||
} | ||
|
||
[Test] | ||
public static void StepsizeIsZeroOrNegative_ThrowsArgumentOutOfRangeException() | ||
{ | ||
Func<double, double, double> exampleEquation = (x, y) => x; | ||
Assert.Throws<ArgumentOutOfRangeException>(() => Algorithms.Numeric.EulerMethod.EulerFull(0, 4, 0, 0, exampleEquation)); | ||
} | ||
|
||
[Test] | ||
public static void StartIsLargerThanEnd_ThrowsArgumentOutOfRangeException() | ||
{ | ||
Func<double, double, double> exampleEquation = (x, y) => x; | ||
Assert.Throws<ArgumentOutOfRangeException>(() => Algorithms.Numeric.EulerMethod.EulerFull(0, -4, 0.1, 0, exampleEquation)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using System; | ||
using System.Drawing; | ||
using NUnit.Framework; | ||
|
||
namespace Algorithms.Tests.Other | ||
{ | ||
public static class MandelbrotTest | ||
{ | ||
[Test] | ||
public static void BitmapWidthIsZeroOrNegative_ThrowsArgumentOutOfRangeException() | ||
{ | ||
Assert.Throws<ArgumentOutOfRangeException>(() => Algorithms.Other.Mandelbrot.GetBitmap(bitmapWidth:-200)); | ||
} | ||
|
||
[Test] | ||
public static void BitmapHeightIsZeroOrNegative_ThrowsArgumentOutOfRangeException() | ||
{ | ||
Assert.Throws<ArgumentOutOfRangeException>(() => Algorithms.Other.Mandelbrot.GetBitmap(bitmapHeight:0)); | ||
} | ||
|
||
[Test] | ||
public static void MaxStepIsZeroOrNegative_ThrowsArgumentOutOfRangeException() | ||
{ | ||
Assert.Throws<ArgumentOutOfRangeException>(() => Algorithms.Other.Mandelbrot.GetBitmap(maxStep:-1)); | ||
} | ||
|
||
[Test] | ||
public static void TestBlackAndWhite() | ||
{ | ||
Bitmap bitmap = Algorithms.Other.Mandelbrot.GetBitmap(useDistanceColorCoding:false); | ||
// Pixel outside the Mandelbrot set should be white. | ||
Assert.AreEqual(bitmap.GetPixel(0,0), Color.FromArgb(255, 255, 255, 255)); | ||
|
||
// Pixel inside the Mandelbrot set should be black. | ||
Assert.AreEqual(bitmap.GetPixel(400,300), Color.FromArgb(255, 0, 0, 0)); | ||
} | ||
|
||
[Test] | ||
public static void TestColorCoded() | ||
{ | ||
Bitmap bitmap = Algorithms.Other.Mandelbrot.GetBitmap(useDistanceColorCoding:true); | ||
// Pixel distant to the Mandelbrot set should be red. | ||
Assert.AreEqual(bitmap.GetPixel(0,0), Color.FromArgb(255, 255, 0, 0)); | ||
|
||
// Pixel inside the Mandelbrot set should be black. | ||
Assert.AreEqual(bitmap.GetPixel(400,300), Color.FromArgb(255, 0, 0, 0)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
using System; | ||
using System.Drawing; | ||
|
||
namespace Algorithms.Other | ||
{ | ||
/// <summary> | ||
/// The Mandelbrot set is the set of complex numbers "c" for which the series | ||
/// "z_(n+1) = z_n * z_n + c" does not diverge, i.e. remains bounded. Thus, a | ||
/// complex number "c" is a member of the Mandelbrot set if, when starting with | ||
/// "z_0 = 0" and applying the iteration repeatedly, the absolute value of | ||
/// "z_n" remains bounded for all "n > 0". Complex numbers can be written as | ||
/// "a + b*i": "a" is the real component, usually drawn on the x-axis, and "b*i" | ||
/// is the imaginary component, usually drawn on the y-axis. Most visualizations | ||
/// of the Mandelbrot set use a color-coding to indicate after how many steps in | ||
/// the series the numbers outside the set cross the divergence threshold. | ||
/// Images of the Mandelbrot set exhibit an elaborate and infinitely | ||
/// complicated boundary that reveals progressively ever-finer recursive detail | ||
/// at increasing magnifications, making the boundary of the Mandelbrot set a | ||
/// fractal curve. | ||
/// (description adapted from https://en.wikipedia.org/wiki/Mandelbrot_set) | ||
/// (see also https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set). | ||
/// </summary> | ||
public static class Mandelbrot | ||
{ | ||
/// <summary> | ||
/// Method to generate the bitmap of the Mandelbrot set. Two types of coordinates | ||
/// are used: bitmap-coordinates that refer to the pixels and figure-coordinates | ||
/// that refer to the complex numbers inside and outside the Mandelbrot set. The | ||
/// figure-coordinates in the arguments of this method determine which section | ||
/// of the Mandelbrot set is viewed. The main area of the Mandelbrot set is | ||
/// roughly between "-1.5 < x < 0.5" and "-1 < y < 1" in the figure-coordinates. | ||
/// To save the bitmap the command 'GetBitmap().Save("Mandelbrot.png")' can be used. | ||
/// </summary> | ||
/// <param name="bitmapWidth">The width of the rendered bitmap.</param> | ||
/// <param name="bitmapHeight">The height of the rendered bitmap.</param> | ||
/// <param name="figureCenterX">The x-coordinate of the center of the figure.</param> | ||
/// <param name="figureCenterY">The y-coordinate of the center of the figure.</param> | ||
/// <param name="figureWidth">The width of the figure.</param> | ||
/// <param name="maxStep">Maximum number of steps to check for divergent behavior.</param> | ||
/// <param name="useDistanceColorCoding">Render in color or black and white.</param> | ||
/// <returns>The bitmap of the rendered Mandelbrot set.</returns> | ||
public static Bitmap GetBitmap( | ||
int bitmapWidth = 800, | ||
int bitmapHeight = 600, | ||
double figureCenterX = -0.6, | ||
double figureCenterY = 0, | ||
double figureWidth = 3.2, | ||
int maxStep = 50, | ||
bool useDistanceColorCoding = true) | ||
{ | ||
if (bitmapWidth <= 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(bitmapWidth), $"{nameof(bitmapWidth)} should be greater than zero"); | ||
} | ||
|
||
if (bitmapHeight <= 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(bitmapHeight), $"{nameof(bitmapHeight)} should be greater than zero"); | ||
} | ||
|
||
if (maxStep <= 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(maxStep), $"{nameof(maxStep)} should be greater than zero"); | ||
} | ||
|
||
var bitmap = new Bitmap(bitmapWidth, bitmapHeight); | ||
var figureHeight = figureWidth / bitmapWidth * bitmapHeight; | ||
|
||
// loop through the bitmap-coordinates | ||
for (var bitmapX = 0; bitmapX < bitmapWidth; bitmapX++) | ||
{ | ||
for (var bitmapY = 0; bitmapY < bitmapHeight; bitmapY++) | ||
{ | ||
// determine the figure-coordinates based on the bitmap-coordinates | ||
var figureX = figureCenterX + ((double)bitmapX / bitmapWidth - 0.5) * figureWidth; | ||
var figureY = figureCenterY + ((double)bitmapY / bitmapHeight - 0.5) * figureHeight; | ||
|
||
var distance = GetDistance(figureX, figureY, maxStep); | ||
|
||
// color the corresponding pixel based on the selected coloring-function | ||
bitmap.SetPixel( | ||
bitmapX, | ||
bitmapY, | ||
useDistanceColorCoding ? ColorCodedColorMap(distance) : BlackAndWhiteColorMap(distance)); | ||
} | ||
} | ||
|
||
return bitmap; | ||
} | ||
|
||
/// <summary> | ||
/// Black and white color-coding that ignores the relative distance. The Mandelbrot | ||
/// set is black, everything else is white. | ||
/// </summary> | ||
/// <param name="distance">Distance until divergence threshold.</param> | ||
/// <returns>What?.</returns> | ||
private static Color BlackAndWhiteColorMap(double distance) | ||
{ | ||
return distance >= 1 | ||
? Color.FromArgb(255, 0, 0, 0) | ||
: Color.FromArgb(255, 255, 255, 255); | ||
} | ||
|
||
/// <summary> | ||
/// Color-coding taking the relative distance into account. The Mandelbrot set | ||
/// is black. | ||
/// </summary> | ||
/// <param name="distance">Distance until divergence threshold.</param> | ||
/// <returns>What?.</returns> | ||
private static Color ColorCodedColorMap(double distance) | ||
{ | ||
if (distance >= 1) | ||
{ | ||
return Color.FromArgb(255, 0, 0, 0); | ||
} | ||
|
||
// simplified transformation of HSV to RGB | ||
// distance determines hue | ||
var hue = 360 * distance; | ||
double saturation = 1; | ||
double val = 255; | ||
var hi = (int)Math.Floor(hue / 60) % 6; | ||
var f = hue / 60 - Math.Floor(hue / 60); | ||
|
||
var v = (int)val; | ||
var p = 0; | ||
var q = (int)(val * (1 - f * saturation)); | ||
var t = (int)(val * (1 - (1 - f) * saturation)); | ||
|
||
switch (hi) | ||
{ | ||
case 0: return Color.FromArgb(255, v, t, p); | ||
case 1: return Color.FromArgb(255, q, v, p); | ||
case 2: return Color.FromArgb(255, p, v, t); | ||
case 3: return Color.FromArgb(255, p, q, v); | ||
case 4: return Color.FromArgb(255, t, p, v); | ||
default: return Color.FromArgb(255, v, p, q); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Return the relative distance (ratio of steps taken to maxStep) after which the complex number | ||
/// constituted by this x-y-pair diverges. Members of the Mandelbrot set do not | ||
/// diverge so their distance is 1. | ||
/// </summary> | ||
/// <param name="figureX">The x-coordinate within the figure.</param> | ||
/// <param name="figureY">The y-coordinate within the figure.</param> | ||
/// <param name="maxStep">Maximum number of steps to check for divergent behavior.</param> | ||
/// <returns>The relative distance as the ratio of steps taken to maxStep.</returns> | ||
private static double GetDistance(double figureX, double figureY, int maxStep) | ||
{ | ||
var a = figureX; | ||
var b = figureY; | ||
var currentStep = 0; | ||
for (var step = 0; step < maxStep; step++) | ||
{ | ||
currentStep = step; | ||
var aNew = a * a - b * b + figureX; | ||
b = 2 * a * b + figureY; | ||
a = aNew; | ||
|
||
// divergence happens for all complex number with an absolute value | ||
// greater than 4 (= divergence threshold) | ||
if (a * a + b * b > 4) | ||
{ | ||
break; | ||
} | ||
} | ||
|
||
return (double)currentStep / (maxStep - 1); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters