So I just wrote a little snippet to generate the Mandelbrot fractal and imagine my surprise when it came out all ugly and skewed (as you can see at the bottom). I'd appreciate a point in the direction of why this would even happen. It's a learning experience and I'm not looking for anyone to do it for me, but I'm kinda at a dead end debugging it. The offending generation code is:
module Mandelbrot where
import Complex
import Image
main = writeFile "mb.ppm" $ imageMB 1000
mandelbrotPixel x y = mb (x:+y) (0:+0) 0
mb c x iter | magnitude x > 2 = iter
| iter >= 255 = 255
| otherwise = mb c (c+q^2) (iter+1)
where q = x -- Mandelbrot
-- q = (abs.realPart $ x) :+ (abs.imagPart $ x) --Burning Ship
argandPlane x0 x1 y0 y1 width height = [ (x,y) |
y <- [y1, y1 - dy .. y0], --traverse from
x <- [x0, x0 + dx .. x1] ] --top-left to bottom-right
where dx = (x1 - x0) / width
dy = (y1 - y0) / height
drawPicture :: (a -> b -> c) -> (c -> Colour) -> [(a, b)] -> Image
drawPicture function colourFunction = map (colourFunction . uncurry function)
imageMB s = createPPM s s
$ drawPicture mandelbrotPixel (replicate 3)
$ argandPlane (-1.8) (-1.7) (0.02) 0.055 s' s'
where s' = fromIntegral s
And the image code (which I'm fairly confident in) is:
module Image where
type Colour = [Int]
type Image = [Colour]
createPPM :: Int -> Int -> Image -> String
createPPM w h i = concat ["P3 ", show w, " ", show h, " 255\n",
unlines.map (unwords.map show) $ i]
Well, the image is skewed because the dimensions are wrong, but that's obvious. You're specifying the image size and then spitting out a list of pixels, but with an incorrect number of pixels per line somewhere.
More specifically, note that the image wraps around almost exactly once: In other words, skew per line * height of the image = width of the image. Since the image is square, that means you're generating an extra pixel per line--a good old off-by-one error.
The obvious place for this to happen is when you're generating the coordinates to iterate on. Let's try a small set and see what it gives us:
> length $ argandPlane (-2.5) (-2) 1.5 2 10 10
121
> 10 ^ 2
100
> 11 ^ 2
121
And so. I suspect the error is because you're calculating the increment as real distance divided by pixel size, which generates the correct number of intervals, but an extra point. Consider the interval from 0.0 to 1.0. Using your calculation with a width of 4, we get:
> let x0 = 0.0
> let x1 = 1.0
> let width = 4.0
> let dx = (x1 - x0) / width
> dx
0.25
> let xs = [x0, x0 + dx .. x1]
> xs
[0.0, 0.25, 0.5, 0.75, 1.0]
> length xs
5
So, to get the correct number of points, just reduce the size by 1 when generating the the coordinates.
It's a learning experience and I'm not looking for anyone to do it for me, but I'm kinda at a dead end debugging it
I know camccann already solved your problem, but he kind of "gave you the fish" while "teaching you how to fish" could be more useful.
So I'll share what I believe could be a useful way of reaching the solution.
So your mandelbrot image is skewed. Some likely possible causes:
You have a bug in your mandelbrot formula
You have a bug in presenting/saving your picture
You could do an experiment to learn further if any of the above explanations are relevant or not. Such an experiment could be for example drawing trivial images of, say, horizontal and vertical lines.
After doing that experiement you will see that your vertical lines are not so vertical. Going back to the likely possible causes, it's clear that you have a bug in presenting/saving your image, and that explains everything. You may still have a bug in your mandelbrot formula but you probably don't, and that's not relevant to the issue at hand now.
Now you should ponder what kind of image saving bug will cause vertical lines to be diagonal. If no idea pops up you can make your simple example smaller and smaller until the PPM result becomes small enough that you could examine it by hand. Then you'll surely catch the bug.
Related
I have a 3D geometry problem and I am not certain of the best approach to solve it. I have a model with two boxes, one above the others. They have the same dimension, L (length) * p (depth) * e (thickness), and are separated by a height of h. They are perfectly superposed, with no offset between them.
For each point of my bottom box, I want to get the zenith of all lines that can cross the top box and arrive to this point. It doesn't matter if the line crosses the top box by the top or the side.
The zenith is the angle of "looking up". In our case, a zenith of 0 represents the point directly above the point P, and an angle of 90 is directly looking in front. A zenith of 180 would be looking below the point, but for our use, it's useless. The zeniths we look for are between 0 and 90°.
For a more intuitive visualization, let's say that I have a hole in the ceiling, and that I want to map the zenith of all light that crosses this hole and reaches the floor.
This is what it looks like:
For any point P of the bottom box, I want an array containing the zeniths of all "rays" that cross the top box before arriving on P. The red lines are the "edges", the last zeniths I would get for each corner.
I am working on a way to code it in MATLAB and I was wondering if there was a better algorithm that I am not seeing. My approach, in pseudocode, would be this:
bottomBox = [1:L, 1:p, 1:e];
topBox = [1:L, 1:p, 1+h:e+h];
results = zeros(L:p) * NaN; % Array of results, one per "case" on the bottom box
zeniths = zeros(L:p) * NaN; % Array of zeniths for each result case
for i = 1:L
for j = 1:p % Browsing the bottom box case by case
for k = 1:L
for l = 1:p
for m = 1:e % For each bottom box case, browsing the top box case by case
p1 = topBox(k,l,m); % p1 is each case on the top box
p2 = bottomBox(i,j,1); % p2 is the current bottom box case, z doesn't mattter
p3 = topBox(i,j,m); % p3 is the projection of p2 on the top box (zenith = 0)
v1 = p1 - p2;
v2 = p3 - p2;
zeniths(k,l) = rad2deg(atan2(norm(cross(p1, p2)), dot(p1, p2)));
end
end
end
results(i,j) = zeniths;
end
end
I tried to implement this and I couldn't get it to work. More specifically, the angle calculation doesn't seem to work, I have an error stating:
Error using cross;
A and B must be of length 3 in the dimension in which the cross product is taken.
I am looking for advice on how to build the algorithm.
Please tell me if the question is better suited for another StackExchange community, such as Math.
I'll get you started showing you one way to do it for 1 point and I'll let you build the final loop to do the calc for all your points.
As expressed in the comment, for the purpose of these calculations, you do not need to consider the thickness of your plates, you can model them simply with two parallel planes separated by a distance H.
I don't know the size of your plates nor the grid size you want so I'll keep it simple for this example:
H = 5 ; % distance between the planes
[X,Y] = meshgrid(-3:3,-2:2) ;
GridSize = size(X) ;
Zb = zeros(GridSize) ;
Zt = zeros(GridSize) + H ;
This gives you 4 matrices, defining 2 planes. The bottom plane is composed of [X,Y,Zb] and the top plane is formed by [X,Y,Zt].
If you want to visualise them, you can run the following code (optional):
%% Display planes
figure ;
ht = surf(X,Y,Zt, 'FaceColor',[.8 .8 .8],'DisplayName','Top plate') ;
hold on
hb = surf(X,Y,Zb, 'FaceColor',[.6 .6 .6],'DisplayName','Bottom plate') ;
xlabel('X') ; ylabel('Y') ; zlabel('Z') ;
axis equal ; legend show
Now for the rest of the example, I selected a point P, at coordinate [-2,1,0]. This choice is completely arbitrary, just for the example. In your final algorythm you will still have to loop over several points Pi (although remember that your problem is symetric so if your domain is too large you can reduce your computations by using the symetries of your model).
%% This will have to be embedded into a loop over the points Pi
% Assuming points P=(-2,1,0)
p = [-2;1;0] ;
zn = [0;0;1] ; % unitary vector, oriented Oz
dx = X - p(1) ; % `x` distance between all points of the plane and P
dy = Y - p(2) ; % `y` distance between all points of the plane and P
dz = zeros(size(X)) + H ; % `z` distance (all the same)
V = [dx(:) dy(:) dz(:)].' ; % to obtain list of vector V = [dx;dy;dz] ;
nv = size(V,2) ; % number of points/angle to calculate
zenith = zeros(nv,1) ; % preallocate result matrix (always good!)
for k=1:nv
% [u] is the vector going from `P` to the current point considered on the top plane
u = V(:,k) ;
% determine the angle between [u] and [zn]
zenith(k) = atan2( norm(cross(u,zn)) , dot(u,zn) ) ;
end
% Reshape "zenith" from vector to matrix so it matches the base grid system
zenith = reshape( zenith , GridSize ) ;
You now have, for this point P, a matrix of angle with every other point of the top plane:
>> rad2deg(zenith)
ans =
32.31 30.96 32.31 35.80 40.32 45.00 49.39
24.09 21.80 24.09 29.50 35.80 41.81 47.12
15.79 11.31 15.79 24.09 32.31 39.51 45.56
11.31 0 11.31 21.80 30.96 38.66 45.00
15.79 11.31 15.79 24.09 32.31 39.51 45.56
Once again, completely optionally, if you want to visualise the vectors which were used for the calculations:
for k=1:nv
hp(k) = plot3([p(1) X(k)],[p(2) Y(k)],[0 H],'Marker','o','MarkerFaceColor','k') ;
end
will yield:
Now for your final result, remember you have a 2D matrix for each point P of your bottom plane, so your final result will either be a collection of 2D matrices or a large 3D matrix.
Zenith angle is just
atan2(h, sqrt(dx^2+dy^2))
where dx, dy are coordinate differences along L and p axes (i-k and j-l in your loops)
Perhaps h+m (m as your variable for m = 1:e) instead of h if you need points inside top box
I am trying to figure out how to sample for two random variables uniformly in the region where the sum of the two is greater than zero. I thought a solution might be to sample for X~U(-1,1) and then sample for Y~U(-x,1) where x would be the current sample for X.
But this resulted in a distribution that looks like this.
This doesn't look uniformly distributed as the density of points at the top left is higher and keeps reducing as we move to the right. Can someone point out where the flaw in my reasoning is and how to possibly fix this?
Thank you
You just need to make sure that adjust the density of x points away from the "top-left" corner appropriately. I'd also suggest generating in [0,1] and then transforming into [-1,1] afterwards.
For example:
import numpy as np
# generate points, sqrt takes care of moving points away from zero
n = 50000
x = np.sqrt(np.random.uniform(size=n))
y = np.random.uniform(1-x)
# transform to -1,1
x = x * 2 - 1
y = y * 2 - 1
plotting these gives:
which looks reasonable to me. Note I've colored the [-1,1] square to show where it should fit.
Could you please elaborate a bit on how you arrived at the answer?
Well, the main problem consists in getting a fair way to sample the non-uniform distribution of coordinate X.
From elementary geometry, the area of the part of the upper triangle with x < x0 is: (1/2) * (x0 + 1)2. As the total area of this upper triangle is equal to 2, it follows that the cumulative probability P of (X < x0) within the upper triangle is: P = (1/4) * (x0 + 1)2.
So, inverting the last formula, we have: x0 = 2*sqrt(P) - 1
Now, from the Inverse Transform Sampling theorem, we know that we can generate a fair sampling of X by reinterpreting P as a random variable U0 uniformly distributed between 0 and 1.
In Python, this gives us:
u0 = random.uniform(0.0, 1.0)
x = (2*math.sqrt(u0)) - 1.0
or equivalently:
u0 = random.random()
x = (2 * math.sqrt(u0)) - 1.0
Note that this is essentially the same maths as in the excellent answer by #SamMason. That thing comes from a general statistical principle. It can just as well be used to prove that a fair sampling of the latitude on a 3D sphere is given by arcsin(2*u - 1).
So now we have x, but we still need y. The underlying 2D density is an uniform one, so for a given x, all possible values of y are equidistributed.
The interval of possible values for y is [-x, 1]. So if U1 is yet another independent random variable uniformly distributed between 0 and 1, y can be drawn from the equation:
y = (1+x) * u1 - x
which in Python is rendered by:
u1 = random.random()
y = (1+x)*u1 - x
Overall, the Python code can be written like this:
import math
import random
import matplotlib.pyplot as plt
def mySampler():
u0 = random.random()
u1 = random.random()
x = 2*math.sqrt(u0) - 1.0
y = (1+x)*u1 - x
return (x,y)
#--- Main program:
points = (mySampler() for _ in range(10000)) # an iterator object
xx, yy = zip(*points)
plt.scatter(xx, yy, s=0.2)
plt.show()
Graphically, the result looks good enough:
Side note: a cheaper, ad hoc solution:
There is always the possibility of sampling uniformly in the whole square, and rejecting the points whose x+y sum happens to be negative. But this is a bit wasteful. We can have a more elegant solution by noting that the “bad” region has the same shape and area as the “good” region.
So if we get a “bad” point, instead of just rejecting it, we can replace it by its symmetic point with respect to the x+y=0 dividing line. This can be done using the following Python code:
def mySampler2():
x0 = random.uniform(-1.0, 1.0)
y0 = random.uniform(-1.0, 1.0)
s = x0+y0
if (s >= 0):
return (x0, y0) # good point
else:
return (x0-s, y0-s) # symmetric of bad point
This works fine too. And this is probably the cheapest possible solution regarding CPU time, as we reject nothing, and we don't need to compute a square root.
Following Generate random locations within a triangular domain
Code, to sample uniformly in any triangle, Python 3.9.4, Win 10 x64
import math
import random
import matplotlib.pyplot as plt
def trisample(A, B, C):
"""
Given three vertices A, B, C,
sample point uniformly in the triangle
"""
r1 = random.random()
r2 = random.random()
s1 = math.sqrt(r1)
x = A[0] * (1.0 - s1) + B[0] * (1.0 - r2) * s1 + C[0] * r2 * s1
y = A[1] * (1.0 - s1) + B[1] * (1.0 - r2) * s1 + C[1] * r2 * s1
return (x, y)
random.seed(312345)
A = (1, 0)
B = (1, 1)
C = (0, 1)
points = [trisample(A, B, C) for _ in range(10000)]
xx, yy = zip(*points)
plt.scatter(xx, yy, s=0.2)
plt.show()
I know there are a lot of questions alrady answered about this. However, mine varies slightly. Whenever we implement the smooth coloring algorithim as I understand it.
mu = 1 + n + math.log2(math.log2(z)) / math.log2(2)
where n is the escape iteration and 2 is the power z is to, and if im not mistaken z is the modulus of the complex number at that escape iteration. We then use this renormalized escape value in our linear interpolation between colors to produce a smoothly banded mandelbrot set. I've seen answers to other questions about this where we run this value through a HSB to RGB conversion, however I still fail to understand how this would provide a smooth gradient of colors and how to implement this in python.
However, whenever I attempted to implement this it produces floating point RGB values, but there isn't an image format that I know of, besides a .tiff file, that would support this, and if we round off to integers we still have unsmooth banding. So how is this supposed to produce a smoothly banded image if we cannot directly use the RGB values it produces? Example code of what I tried below, since I don't undertand fully how to implement this I made an attempt at a solution that somewhat produces smooth banding. This produces a somewhat smoothly banded image between two colors blue for the full set and a progressively whiter color the further we zoom in on the set to the point where at a certain depth everything just appears blurred. Since I'm using tkinter to do this I had to convert the RGB values to hex to be able to draw them to the canvas.
I;m computing the set recursively, and in my other function (not posted below) i am setting the window width and height then iterating over these for the pixels of the tkinter window and computing this recursion in the inner loop.
def linear_interp(self, color_1, color_2, i):
r = (color_1[0] * (1 - i)) + (color_2[0] * i)
g = (color_1[1] * (1 - i)) + (color_2[1] * i)
b = (color_1[2] * (1 - i)) + (color_2[2] * i)
rgb_list = [r, g, b]
for value in rgb_list:
if value > MAX_COLOR:
rgb_list[rgb_list.index(value)] = MAX_COLOR
if value < 0:
rgb_list[rgb_list.index(value)] = abs(value)
return (int(rgb_list[0]), int(rgb_list[1]),
int(rgb_list[2]))
def rgb_to_hex(self, color):
return "#%02x%02x%02x" % color
def mandel(self, x, y, z, iteration):
bmin = 100
bmax = 255
power_z = 2
mod_z = math.sqrt((z.real * z.real) + (z.imag * z.imag))
#If its not in the set or we have reached the maximum depth
if abs(z) >= float(power_z) or iteration == DEPTH:
z = z
if iteration > 255:
factor = (iteration / DEPTH) * 255
else:
factor = iteration
logs = math.log2(math.log2(abs(z) + 1 ) / math.log2(power_z))
r = g = math.floor(factor + 5 - logs)
b = bmin + (bmax - bmin) * r / 255
rgb = (abs(r), abs(g), abs(round(b)))
self.canvas.create_line(x, y, x + 1, y + 1,
fill = self.rgb_to_hex(rgb))
else:
z = (z * z) + self.c
self.mandel(x, y, z, iteration + 1)
return z
The difference between colors #000000, #010000, ..., #FE0000, #FF0000 is so small that you obtain a smooth gradient from black to red. Hence, simply round your values: Suppose your smoothened color values of your smoothness function range from 0 to (excl) 1, then you simply use
(int) (value * 256)
Now I am doing this in VB6 but I don't think it matters what I do it in, does it? I believe it has to do with math.
Here is the problem, have a look at this picture
As you can see in this image, there is a black line and a grey circle. I want the circle to move from the bottom left to the bottom right, but I also want it to stay along the path of the line so it reaches our second picture like this:
Now how can I accomplish this? Again, using VB6.
There are various ways of accomplishing this I think, but here's the first that comes to my mind. It makes some assumptions... like that your line goes in a positive direction and it starts at 0,0. If either of these things aren't true then you've got more code to write to adjust for that.
=================================================
Psuedocode:
'To track current coordinates of the center of the circle
dim x as float, y as float
x = 0: y = 0
'Coordinates for the line
dim x1 as float, y1 as float, x2 as float, y2 as float
x1=0: y1=0: x2=50: y2=75
'How much we're going to move the circle at a time
dim xStep as float, yStep as float, stepSize as float
stepSize = 100
xStep = x2 / stepSize
yStep = y2 / stepSize
Do
'Draw circle here with x, y for coordinates
x = x + xStep
y = y + yStep
Loop Until xStep > x2
Ok, I don't know VBA6 but, since you said:
I don't think it matters what I do it in
I will give a generic solution that involves you having the center of the circles coordinates, and the lines endpoints.
This line can be treated as a vector:
(line.x2-line.x1, line.y2-line.y1)
You don't need to write this in your program or anything just saying it is a vector.
What you do need to is get the magnitude of the vector and assign it to a variable:
unitSize = sqrt((line.x2-line.x1)^2 + (line.y2-line.y1)^2)
Now make it into unit vector components and get the separate components:
unitX = (line.x2-line.x1)/unitSize
unitY = (line.y2-line.y1)/unitSize
Now how ever you update the circle:
do {
circle.x = circle.x + unitX * incrementSize //incrementSize scales how big the movement is assign it to whatever you seem fit.
circle.y = circle.y + unitY * incrementSize
until (circle.x >= line.x2) //Or <= line.x2 depends which way you are going.
Hopefully this helps.
I am trying to build a function grapher,
The user enters xmin, xmax, ymin, ymax, function.
I got the x, y for all points.
Now i want to translate this initial referential to a Canvas starting at 0,0 up to
250,250.
Is there a short way or should i just check
if x < 0
new x = (x - xmin) * (250 / (xmax - xmin)) ?
etc ..
Also this basic approach does not optimise sampling.
For example if my function f(x) = 5 i dont need to sample the xrange in 500 points,
i only need two points. I could do some heuristic checks.
But for a function like sin(2/x) i need more sampling around x (-1,1) how would you aproach such a thing ?
Thanks
Instead of iterating over x in the original coordinates, iterate over the canvas and then transform back to the original coordinates:
for (int xcanvas = 0; xcanvas <= 250; i++) {
double x = ((xmax - xmin) * xcanvas / 250.0) + xmin;
double y = f(x);
int ycanvas = 250 * (y - ymin) / (ymax - ymin) + .5;
// Plot (xcanvas, ycanvas)
}
This gives you exactly one function evaluation for each column of the canvas.
You can estimate the derivative (if you have one).
You can use bidirectional (dichotomic) approach: estimate the difference and split the segment if necessary.
I think I would start by reasoning about this in terms of transformations from canvas to maths contexts.
(canvas_x, canvas_y) -> (maths_x, maths_y)
(maths_x, maths_y) -> (canvas_x, canvas_y)
maths_x -> maths_y
You iterate over the points that a displayable, looping over canvas_x.
This would translate to some simple functions:
maths_x = maths_x_from_canvas_x(canvas_x, min_maths_x, max_maths_x)
maths_y = maths_y_from_maths_x(maths_x) # this is the function to be plotted.
canvas_y = canvas_y_from_maths_y(maths_y, min_maths_y, max_maths_y)
if (canvas_y not out of bounds) plot(canvas_x, canvas_y)
Once you get here, it's relatively simple to write these simple functions into code.
Optimize from here.
I think that for this approach, you won't need to know too much about sample frequencies, because you sample at a rate appropriate for the display. It wouldn't be optimal - your example of y = 5 is a good example, but you'd be guaranteed not to sample more than you can display.