Math::...hCurve(3pm)

πŸ“¦
version 129 Β· 3 versions
, in debian bookworm, debian trixie, ubuntu jammy, ubuntu noble, ubuntu resolute, ubuntu stonking

πŸ’– Math::PlanePath::KochCurve πŸ’–

okay bestie, welcome to the horizontal Koch curve β€” it's a Math::PlanePath thing, it lives in the User Contributed Perl Documentation corner of your 3pm pages, and honestly? she's iconic. let's go πŸ¦‹


✨ NAME ✨

Math::PlanePath::KochCurve β€” horizontal Koch curve. no cap, that's literally what it is.


🌸 SYNOPSIS 🌸

use Math::PlanePath::KochCurve;
my $path = Math::PlanePath::KochCurve->new;
my ($x, $y) = $path->n_to_xy (123);

πŸ’œ DESCRIPTION πŸ’œ

so this is the integer version of the self-similar Koch curve, and the original paper is:

Helge von Koch, "Une MΓ©thode GΓ©omΓ©trique Γ‰lΓ©mentaire pour l'Γ‰tude de Certaines Questions de la ThΓ©orie des Courbes Planes", Acta Arithmetica, volume 30, 1906, pages 145-174. <http://archive.org/details/actamathematica11lefgoog>

the way it works: it cruises along the X axis and makes lil triangular excursions upward. it's giving mountain range but make it math ✨

                              8                                   3
                            /  \
                     6---- 7     9----10                18-...    2
                      \              /                    \
            2           5          11          14          17     1
          /  \        /              \        /  \        /
    0----1     3---- 4                12----13    15----16    <- Y=0

    ^
   X=0   2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19

the shape that replicates is the initial N=0 to N=4:

           *
          / \
     *---*   *---*

and then it gets rotated and repeated 3 times in the same pattern, giving you sections N=4 to N=8, N=8 to N=12, and N=12 to N=16. then that whole N=0 to N=16 thing gets itself replicated three times at the angles of the base pattern. and then again. and again. infinitely. she never stops, periodt πŸ’…

the X,Y coordinates sit on a square grid using every second point, per "Triangular Lattice" in Math::PlanePath. the result? flattened triangular segments with diagonals at a 45 degree angle. slay.

🌈 Level Ranges 🌈

every replication adds 3 copies of the existing points, so it's 4 times bigger. so if you count N=0 to N=4 as level 1, then a given replication level runs from

Nstart = 0
Nlevel = 4^level   (inclusive)

each replication is 3 times the width. the initial N=0 to N=4 figure is 6 wide, and in general a level runs from

Xstart = 0
Xlevel = 2*3^level   at N=Nlevel

the highest Y is 3 times greater at each level too, same energy. the peak sits at the midpoint of each level:

Npeak = (4^level)/2
Ypeak = 3^level
Xpeak = 3^level

and lowkey notice how the N=6 point backtracks horizontally to the same X as the start of its section N=4 to N=8? that happens in the further replications too, and it's the maximum extent of the backtracking. queen of going backwards a lil bit πŸŽ€

the Nlevel gets multiplied by 4 to get the end of the next higher level. the same 4*N applies to all points N=0 to N=Nlevel to get the same shape but a factor of 3 bigger X,Y coordinates. and the in-between points 4*N+1, 4*N+2 and 4*N+3 are the new finer structure in the higher level. cute!

🧚 Fractal 🧚

Koch conceived the curve as having a fixed length and infinitely fine structure, which makes it continuous everywhere but differentiable nowhere 😭. but the code here can absolutely be pressed into use for that sort of construction for a given level of granularity, by scaling

X/3^level
Y/3^level

which makes it a fixed 2 wide by 1 high. or, if you want unit-side equilateral triangles, apply further factors 1/2 and sqrt(3)/2, as noted in "Triangular Lattice" in Math::PlanePath:

(X/2) / 3^level
(Y*sqrt(3)/2) / 3^level

⭐ Area ⭐

the area under the curve to a given level can be calculated from its self-similar nature. the curve at level+1 is 3 times wider and higher and adds a triangle of unit area onto each line segment. so reckoning the line segment N=0 to N=1 as level=0 (which means area[0]=0),

area[level] = 9*area[level-1] + 4^(level-1)
            = 4^(level-1) + 9*4^(level-2) + ... + 9^(level-1)*4^0

              9^level - 4^level
            = -----------------
                      5

            = 0, 1, 13, 133, 1261, 11605, 105469, ...  (A016153)

the sides are 6 different angles. the triangles added on the sides are always the same shape, either pointing up or down. base width=2 and height=1 gives area=1.

   *            *-----*   ^
  / \            \   /    | height=1
 /   \            \ /     |
*-----*            *      v     triangle area = 2*1/2 = 1

<-----> width=2

if the Y coordinates get stretched to make equilateral triangles, then the number of triangles isn't changed, so the area increases by a factor of the area of the equilateral triangle, sqrt(3)/4. math girlies stay winning πŸ”₯


🫢 FUNCTIONS 🫢

see "FUNCTIONS" in Math::PlanePath for behaviour common to all path classes.

  • $path = Math::PlanePath::KochCurve->new () β€” create and return a new path object. brand new baby path, go off πŸ‘‘
  • ($x,$y) = $path->n_to_xy ($n) β€” return the X,Y coordinates of point number $n on the path. points begin at 0, and if $n < 0 then the return is an empty list.

fractional positions give an X,Y position along a straight line between the integer positions. so like, in-between vibes.

  • ($n_lo, $n_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2) β€” the returned range is exact, meaning $n_lo and $n_hi are the smallest and biggest in the rectangle. no rounding, no guessing, fr fr.
  • $n = $path->n_start() β€” return 0, the first N in the path.

πŸ’« Level Methods πŸ’«

  • ($n_lo, $n_hi) = $path->level_to_n_range($level) β€” return (0, 4**$level). that's it, that's the range.

✨ FORMULAS ✨

okay bestie, this is the math part and it's honestly kinda iconic, stay with me πŸ’…

N to Turn πŸŒ€

the curve is so predictable in the best way β€” it literally always turns either +60 degrees or -120 degrees, it never ever goes straight ahead. periodt. so if you look at N in base 4, the lowest non-zero digit is what gives you the turn. the first turn is at N=1 so there's always a non-zero digit in N, no cap.

   low digit
    base 4         turn
   ---------   ------------
      1         +60 degrees (left)
      2        -120 degrees (right)
      3         +60 degrees (left)

for example N=8 is 20 base 4, so lowest nonzero "2" means turn -120 degrees for the next segment. slay.

if the least significant digit is non-zero then it determines the turn, making the base N=0 to N=4 shape. if the least significant is zero then the next level up is in control, eg. N=0,4,8,12,16, making a turn according to the base shape again at that higher level. the first and last segments of the base shape are "straight" so there's no extra adjustment to apply in those higher digits. ok that's the vibe.

this base 4 digit rule is equivalent to counting low 0-bits. a low base-4 digit 1 or 3 is an even number of low 0-bits and a low digit 2 is an odd number of low 0-bits. literally the same thing twice, it's giving duality ✨

    count low 0-bits         turn
    ----------------     ------------
         even             +60 degrees (left)
         odd             -120 degrees (right)

for example N=8 in binary "1000" has 3 low 0-bits and 3 is odd so turn -120 degrees (right). math said what it said πŸ’€

see "Turn" in Math::PlanePath::GrayCode for a similar turn sequence arising from binary Gray code. πŸ‘‘


N to Next Turn πŸ”„

the turn at N+1, ie the next turn, can be found from the base-4 digits by considering how the digits of N change when 1 is added, and the low-digit turn calculation is applied on those changed digits. ok so basically we're predicting the future here bestie, main character energy.

adding 1 means low digit 0, 1 or 2 will become non-zero. any low 3s wrap around to become low 0s. so the turn at N+1 can be found from the digits of N by seeking the lowest non-3

   lowest non-3       turn
    digit of N       at N+1
   ------------   ------------
        0          +60 degrees (left)
        1         -120 degrees (right)
        2          +60 degrees (left)

N to Direction 🧭

the total turn at a given N can be found by counting digits 1 and 2 in base 4. it's giving arithmetic, it's giving angles 🌸

   direction = ((count of 1-digits in base 4)
                - (count of 2-digits in base 4)) * 60 degrees

for example N=11 is "23" in base 4, so 60*(0-1) = -60 degrees. yes bestie, negative angles are real.

in this formula the count of 1s and 2s can go past 360 degrees, representing a spiralling around which occurs at progressively higher replication levels. the direction can be taken mod 360 degrees, or the count mod 6, for a direction 0 to 5 if desired. so it just keeps spiraling, we love a curve that never stops πŸ’«


N to abs(dX),abs(dY) πŸ“

the direction expressed as abs(dX) and abs(dY) can be calculated simply from N modulo 3. abs(dX) is a repeating pattern 2,1,1 and abs(dY) repeating 0,1,1. so satisfying, the pattern ate fr fr.

   N mod 3     abs(dX),abs(dY)
   -------     ---------------
      0             2,0            horizontal, East or West
      1             1,1            slope North-East or South-West
      2             1,1            slope North-West or South-East

this works because the direction calculation above corresponds to N mod 3. each N digit in base 4 becomes

   N digit
   base 4    direction add
   -------   -------------
      0            0
      1            1
      2           -1
      3            0

notice that direction == Ndigit mod 3. then because 4==1 mod 3 the power-of-4 for each digit reduces down to 1,

   N = 4^k * digit_k + ... 4^0 * digit_0
   N mod 3 = 1 * digit_k + ... 1 * digit_0
           = digit_k + ... digit_0
   same as
   direction = digit_k + ... + digit_0    taken mod 3

the way that 4 just equals 1 mod 3?? the math is mathing πŸ’…


Rectangle to N Range -- Level πŸ“

an easy over-estimate of the N values in a rectangle can be had from the Xlevel formula above. if Xlevel>rectangleX then Nlevel is past the rectangle extent. okay so we're being lazy here but in a smart way, lowkey efficient ✨

   X = 2*3^level

so

   floorlevel = floor log_base_3(X/2)
   Nhi = 4^(floorlevel+1) - 1

for example a rectangle extending to X=13 has floorlevel = floor(log3(13/2))=1 and so Nhi=4^(1+1)-1=15. see? numbers doing numbers things πŸŽ€

the rounding-down of the log3 ensures a point such as X=18 which is the first in the next Nlevel will give that next level. so floorlevel=log3(18/2)=2 (exactly) and Nhi=4^(2+1)-1=63. no cap.

the worst case for this over-estimate is when rectangleX==Xlevel, ie. the rectangle is just into the next level. in that case Nhi is nearly a factor 4 bigger than it needs to be. so it's not perfect, but it's giving rough sketch vibes πŸ™ˆ


Rectangle to N Range -- Exact 🎯

the exact Nlo and Nhi in a rectangle can be found by searching along the curve. for Nlo search forward from the origin N=0. for Nhi search backward from the Nlevel over-estimate described above. now THIS is the precise version, we're being thorough bestie πŸ’–

at a given digit position in the prospective N the sub-part of the curve comprising the lower digits has a certain triangular extent. if it's outside the target rectangle then step to the next digit value, and to the next of the digit above when past digit=3 (or below digit=0 when searching backwards). it's giving methodical.

there's six possible orientations for the curve sub-part. in the following pictures "o" is the start and the surrounding lines show the triangular extent. there's just four curve parts shown in each, but these triangles bound a sub-curve of any level.

   rot=0   -+-               +-----------------+
         --   --              - .-+-*   *-+-o -
       --   *   --             --    \ /    --
     --    / \    --             --   *   --
    - o-+-*   *-+-. -              --   --
   +-----------------+       rot=3   -+-

   rot=1
   +---------+               rot=4    /+
   |      . /                        / |
   |     / /                        / o|
   |*-+-* /                        / / |
   | \   /                        / *  |
   |  * /                        /   \ |
   | / /                        / *-+-*|
   |o /                        / /     |
   | /                        / .      |
   +/                        +---------+

   +\  rot=2                 +---------+
   | \                        \ o      |
   |. \                        \ \     |
   | \ \                        \ *-+-*|
   |  * \                        \   / |
   | /   \                        \ *  |
   |*-+-* \                        \ \ |
   |     \ \                        \ .|
   |      o \                rot=5   \ |
   +---------+                        \+

the "." is the start of the next sub-curve. it belongs to the next digit value and so can be excluded. for rot=0 and rot=3 this means simply shortening the X range permitted. for rot=1 and rot=4 similarly the Y range. for rot=2 and rot=5 it would require a separate test. okay so the triangles are doing their thing, very geometry, very cute πŸ“βœ¨

tight sub-part extent checking reduces the sub-parts which are examined, but it works perfectly well with a looser check, such as a square box for the sub-curve extents. doing that might be easier if the target region is not a rectangle but instead some trickier shape. so like, you do you boo, both work πŸ’…


🌈 OEIS 🌈

the Koch curve is in Sloane's Online Encyclopedia of Integer Sequences in various forms, and honestly the catalogue is iconic

   <http://oeis.org/A035263> (etc)

   A335358   (X-Y)/2 diagonal coordinate
   A335359   Y coordinate

   A035263   turn 1=left,0=right, by morphism
   A096268   turn 0=left,1=right, period doubling sequence
   A056832   turn 1=left,2=right, by replicate and flip last
   A309873   turn 1=left,-1=right
   A029883   turn +/-1=left,0=right, Thue-Morse first differences
   A089045   turn +/-1=left,0=right, by +/- something

   A177702   abs(dX) from N=1 onwards, being 1,1,2 repeating
   A011655   abs(dY), being 0,1,1 repeating

   A003159   N positions of left turns, ending even number 0 bits
   A036554   N positions of right turns, ending odd number 0 bits
   A332206   N on X axis
   A001196   N segments on X axis (N and N+1 on X axis)

   A065359   segment direction, *60 degrees
   A229216   segment direction, 1,2,3,-1,-2,-3
   A050292   num left turns 1 to N
   A123087   num right turns 1 to N
   A020988   num left turns 1 to 4^k-1, being 2*(4^k-1)/3
   A002450   num right turns 1 to 4^k-1, being (4^k-1)/3
   A016153   area under the curve, (9^k-4^k)/5

for reference, A217586 is not quite the same as A096268 right turn. A217586 differs by a 0<->1 flip at N=2^k due to different initial a(1)=1. so like, sneaky little difference, don't get them confused bestie 🫢


πŸ‘€ SEE ALSO πŸ‘€

Math::PlanePath, Math::PlanePath::PeanoCurve, Math::PlanePath::HilbertCurve, Math::PlanePath::KochPeaks, Math::PlanePath::KochSnowflakes, Math::PlanePath::CCurve

Math::Fractal::Curve

go read those too, they're all in the same universe πŸ¦‹


🏠 HOME PAGE 🏠

<http://user42.tuxfamily.org/math-planepath/index.html>


πŸ“œ LICENSE πŸ“œ

Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde

Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.

Math-PlanePath 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 Math-PlanePath. If not, see <http://www.gnu.org/licenses/>.

fr fr bestie, the legal stuff matters, it's not just vibes πŸ’–

okay that's a wrap on the whole thing, we did it πŸ’…βœ¨ this page came from perl v5.32.0 and it was generated way back on 2021-01-23, for the Math::...hCurve(3pm) page. go forth and curve, queen πŸ‘‘πŸ¦‹