
# arclenCatParab.py
# Andrew Davison, Nov 2025, ad@coe.psu.ac.th
'''
  Draws a parabola and a catenary of equal length suspended
  between two points at (-2,0) and (2,0) which do not change.

  The plot includes a slider which changes the length of both
  curves to see how they change.
'''

import math
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from catSolver import getCatA


def genParabola(arcLen):
  # Parabola y = a(x^2 - H2^2) + yCoord
  a = solveParabolaA(arcLen)
  xs = [xLeft + i*dx for i in range(steps+1)]
  ys = [a*(x*x - H2*H2) + yCoord for x in xs]
  return xs, ys


def solveParabolaA(arcLen):
  ''' Choose an 'a' value by binary search 
      through the range lowA -- highA which gives
      a calculated length close to the actual length
  '''
  lowA = 0.0
  highA = 5.0
  for _ in range(40):
    midA = 0.5 * (lowA + highA)
    if paraArcLen(midA) > arcLen:
      highA = midA
    else:
      lowA = midA
  return 0.5 * (lowA + highA)


def paraArcLen(a):
  # Arc length = integral sqrt(1 + (2ax)^2) dx
  length = 0.0
  x = xLeft
  for _ in range(steps):
    # use summing to mimic integration
    slope = 2*a*x
    ds = math.sqrt(1 + slope*slope) * dx
    length += ds
    x += dx
  return length



def genCatenary(arcLen):
  # Catenary y = a cosh((x - h)/a) + k
  a = getCatA(H=H, L=arcLen)

  # vertical shift k so that y(left)=y(right)=yCoord=0
  k = yCoord - a * math.cosh(H2 / a)
  h = 0.5 * (xLeft + xRight)    # which = 0
  xs = [xLeft + i*dx for i in range(steps+1)]
  ys = [a*math.cosh((x - h)/a) + k for x in xs]
  return xs, ys


def updateSlider(val):
  arcLen = max(val, H*1.0001)   # clamp above minimum possible
  drawCurves(arcLen, ax)
  fig.canvas.draw_idle()


def drawCurves(arcLen, ax):
  ax.clear()
  ax.set_xlabel("x")
  ax.set_ylabel("y")
  ax.set_title("Parabola and Catenary of Equal Length")
  ax.grid(True)

  xsPar, ysPar = genParabola(arcLen)
  xsCat, ysCat = genCatenary(arcLen)
  
  ax.plot(xsPar, ysPar, label="Parabola")
  ax.plot(xsCat, ysCat, label="Catenary")
  ax.legend()
  ax.set_aspect("equal", adjustable="datalim")



# -----------------------
xLeft = -2.0   # location of hanging points
xRight = 2.0
yCoord = 0.0

H = xRight - xLeft   # width or span
H2 = H / 2.0  # half the span

steps = 800
dx = H / steps

fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)

initLen = 5.0  # initial length of chains
drawCurves(initLen, ax)

sliderAx = plt.axes([0.15, 0.1, 0.7, 0.05])
lengthSlider = Slider(sliderAx, "Length", 
                   4, 10.0, valinit=initLen)
lengthSlider.on_changed(updateSlider)

plt.show()

