I have been trying to use a computer to plot some hypotrochoids, but I've run into some issues. For those that are unfamiliar, the parametric equations of a hypotrochoid are:

$$x(\theta) = (R - r)\cos\theta + d\cdot \cos(\frac{R-r}r\theta)$$ and $$y(\theta) = (R - r)\sin\theta - d\cdot \sin(\frac{R-r}r\theta)$$

The definition on Wikipedia of a hypotrochoid can further explain:

A hypotrochoid is a roulette traced by a point attached to a circle of radius r rolling around the inside of a fixed circle of radius R, where the point is a distance d from the center of the interior circle.

So a hypotrochoid with values $r=d=1$ and $R = 3$, should look something like this:

enter image description here

But that is certainly not what I am ending up with using my computing method. My hypotrochoid (with the same values) looks something like this:

enter image description here

Since the x and y values are determined by a function of x and y at angle $\theta$ I assumed I could simply loop through the values of $\theta$ from 0 to $2\pi$ and calculate the x and y values separately at certain intervals, then plot the coordinate in polar form (where $r^2 = x^2 + y^2$), but I suppose I thought wrong. What methods SHOULD be used to calculate a hypotrochoid if my methods are wrong?


Here is the code, it's written in python:

class _BaseCurve(event.EventAware):

    # This is a basic curve class from which all other curves inherit from (as
    # you will see below with the Hypotrochoid class). Basically what happens is
    # each new curve class must implement a function (relation) to calculate the
    # radius of the equation at each angle interval, then plots the equation in
    # other code elsewhere.

    def __init__(self, radius, init_angle, end_angle, speed, acceleration, *args, **kwargs):

        # Initialize geometric data...
        self.radius = radius

        # Initialize curve start and end angles...
        self.init_angle = init_angle
        self.end_angle = end_angle

        # Initialize time-based curve attributes...
        self.speed = speed
        self.accel = acceleration
        self.current_pos = 0

        # Initialize defaults...
        self.max_speed = inf
        self.min_speed = neginf

        self.args = args
        self.kwargs = kwargs

    def set_max_speed(self, speed):
        """Set the maximum speed the path can achieve."""
        if speed < self.min_speed:
            errmsg = "Max speed cannot be less than min speed."
            raise ValueError(errmsg)
        self.max_speed = speed

    def set_min_speed(self, speed):
        """Set the minimum speed the path can achieve."""
        if speed > self.max_speed:
            errmsg = "Min speed cannot be greater than max speed."
            raise ValueError(errmsg)
        self.max_speed = speed

    def set_acceleration(self, acceleration):
        """Set a new acceleration for the path."""
        self.accel = acceleration

    def move(self):
        """Progress the path forward one step.

        The amount progressed each time (curve).move is called
        depends on the path's speed parameter and the distance
        (i.e. angle_difference) it has to travel. The calculation
        is as follows:

        angle = angle_difference * current_position + init_angle

        Where current_position is the position incremented by the
        set speed in (curve).move().
        self.current_pos += self.speed
        if self.accel != 1:
            new_speed = self.speed * self.accel
            self.speed = max(min(new_speed, self.max_speed), self.min_speed)

    def angle(self):
        """Return the angle of the curve at the current position."""
        return self.angle_difference * self.current_pos + self.init_angle

    def relation(self):
        """Return the relationship of the current angle to the radius.

        This is a blank function left to be filled in by subclasses of
        _BasicCurve. The return value for this function must be a function
        (or lambda expression), of which that function's return value should
        be the radius of the curve at the current position. The parameters of
        the return equation should be as follows:

        (Assuming `r` is the function representing the relation):

        radius = r(current_angle, *args, **kwargs)

        Where *args and **kwargs are the additional *args and **kwargs specified
        upon initializing the curve.
        return NotImplemented

    def position(self):
        """Calculate the position on the curve at the current angle.

        The return value of this function is the coordinate in polar
        form. To view the coordinate in cartesian form, use the
        `to_cartesian` function. # Ignore the `to_cartesian` function in this code snippet, it simply converts polar to cartesian coordinates.

        NOTE: This function requires self.relation to be implemented.
        r = self.relation()
        theta = self.current_angle

        args = self.args
        kwargs = self.kwargs

        radius = self.radius*r(theta, *args, **kwargs)
        return radius, theta

    def angle_difference(self):
        """The difference between the start and end angles specified."""
        return (self.end_angle - self.init_angle)

    def current_angle(self):
        """The current angle (specified by self.current_pos)."""
        return self.angle_difference * self.current_pos + self.init_angle

Curve = _BaseCurve

class Hypotrochoid(Curve):
    def relation(self):
        def _relation(theta, r, R, d):
            x = (R - r)*math.cos(theta) + d*math.cos((R - r)/r * theta)
            y = (R - r)*math.sin(theta) - d*math.sin((R - r)/r * theta)
            return (x**2 + y**2)**(1/2)
        return _relation


So I found there was a slight error in my parametric equations after all (which Lucian pointed out), which was in the calculation for $y$. I was adding the two sines together when I really needed to be subtracting them.

Now I'm getting this shape, which is a step in the right direction:

enter image description here


Okay, so I found out that if I make the relation for the Hypotrochoid class return an x, y coordinate instead of a radius to be used in a polar equation, the curve actually works! I have no idea why the calculation would fail to properly plot the hypotrochoid normally however. Here is the edited code, take a look and see if you find any major differences that I can't point out:

class Hypotrochoid(Curve):
    def relation(self):
        def _relation(theta, b, a, h):
            x = h*math.cos((theta*(a - b))/b) + (a - b)*math.cos(theta)
            y = (a - b)*math.sin(theta) - h*math.sin((theta*(a - b))/b)
            return x, y
        return _relation

    def position(self):
        r = self.relation()
        theta = self.current_angle

        args = self.args
        kwargs = self.kwargs

        x, y = r(theta, *args, **kwargs)
        return self.radius*x, self.radius*y

I didn't think such an adjustment would work since all I was doing before is calculating the radius, multiplying it by the constant (self.radius), and then converting it back to cartesian coordinates with the formula $x = r\cos\theta$ and $y = r\sin\theta$.

Also, here is the code I use to convert from polar to cartesian:

def to_cartesian(r, theta):
    """Return the cartesian equivalent of a polar coordinate."""
    x = r*math.cos(theta)
    y = r*math.sin(theta)
    return x, y

This has worked for the past 20 curves I've tested, why wouldn't it work for this one?

  • $\begingroup$ If you are computing $x$ and $y$, why not plot them directly instead of converting to polar form? Can you paste your code into the question? Maybe it is buggy? $\endgroup$
    – MPW
    Apr 6, 2014 at 11:51
  • $\begingroup$ Added the code. And looking back at it, I don't entirely understand why I calculate it in polar form only to convert it back to cartesian form later on. $\endgroup$ Apr 6, 2014 at 12:13
  • $\begingroup$ I don't use Python, but I have read a good bit of it. Why are you setting radius = self.radius * r(theta) ? $\endgroup$
    – MPW
    Apr 6, 2014 at 12:26
  • $\begingroup$ @MPW To make it bigger. If I set self.radius = 100, then I can simply multiply the result of calculating $r$ at $\theta$ by self.radius to get a bigger picture. The problem is that I'm plotting it with pygame (it's the only thing I know to do graphical stuff with in python) and if I didn't make the image bigger it would be unrecognizable on the screen. $\endgroup$ Apr 6, 2014 at 12:38
  • $\begingroup$ Ok, so self.radius must be constant. I thought that might be the problem, but I guess not. $\endgroup$
    – MPW
    Apr 6, 2014 at 12:40

1 Answer 1


The title and your entire post use the word hypotrochoid, but both the two equations and the first graphic obviously belong to a hypocycloid known as deltoid, while the latter graphic is clearly that of an epicycloid called cardioid. As you can clearly see, their equations are quite similar, differing only by a small sign $(+,-)$ here and there, which probably explains the cause of all the little bugs and errors from your Python code. :-) Hope this helps.

  • $\begingroup$ A hypocycloid is just a special case of hypotrochoid where $d = r$. The equation for a hypotrochoid is essentially the same as that of a hypocycloid. And indeed, there was an error in the two equations (thanks for pointing that out :) ), but now I'm getting a clover-esque shape. $\endgroup$ Apr 6, 2014 at 12:56
  • $\begingroup$ @user3002473: So basically your code graphs EPI-cycloids-trochoids instead of HYPO-cycloids-trochoids, which means that you sum the two radii instead of subtracting them (or vice versa). Just replace $R-r$ with $R+r$, or vice versa, and see what happens then. $\endgroup$
    – Lucian
    Apr 6, 2014 at 13:22
  • $\begingroup$ Strangely enough changing the $R - r$'s with $R + r$'s didn't even make it plot a proper epitrochoid; the edges (or the indifferentiable parts of the curve) are still smoother than should be when $d = r$, when they should be making sharp cusps. $\endgroup$ Apr 6, 2014 at 13:30
  • $\begingroup$ @user3002473: I've just finished plotting various such hypotrochoids with GeoGebra, using the exact same Wikipedia formulas as you, and everything's as it should. I'm afraid I don't understand why your Python does not yield the same result. $\endgroup$
    – Lucian
    Apr 6, 2014 at 14:03
  • $\begingroup$ This is remarkable. I can't find ANYTHING that could lead to a problem of this sorts anywhere else in the code. I tried rewriting the equations in a few different ways and all the results yielded the same thing, the picture I posted above in black and green. I am completely stumped. $\endgroup$ Apr 6, 2014 at 14:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .