Catenária entre dois pontos (corrente pendurada)

8

Esse é um dos vários desafios que a Calvin's Hobbies deixou para a comunidade .

A curva que uma corda ou corrente pendurada idealizada faz é uma catenária .

Uma corrente formando uma catenária
Imagem de Bin im Garten, via Wikimedia Commons. Usado sob a licença CC-By-SA 3.0 .

Escrever um programa que irá chamar uma catenária, como uma imagem, em um quadrante do plano dado dois pontos (x 1 , y 1 ) , (x 2 , Y 2 ) , e o "comprimento de corda" L . L será maior que a distância entre os dois pontos.

Você também deve desenhar eixos nos lados esquerdo e inferior da imagem (400x400 px min) para a escala. Desenhe apenas o quadrante de xey no intervalo de 0 a 100. (Você pode assumir que os pontos estão no intervalo.)

Pontos ou algo semelhante devem ser desenhados nos pontos de extremidade (x 1 , y 1 ) , (x 2 , y 2 ) para distingui-los. A curva deve ser desenhada apenas no espaço entre esses pontos.

absinto
fonte
Quão preciso precisamos ser? A imagem precisa ser anti-alias? Quão larga deve / pode ser a linha?
Sparr
Também assumimos que a curva em si (não apenas os pontos) está dentro do alcance, certo? Ou desenharíamos dois arcos cortados pelo eixo se ficar abaixo disso?
Geobits 11/09/14
@Sparr A imagem não precisa ser anti-alias. A linha deve ter pelo menos 1px de espessura. A catenária deve ser tão precisa quanto a aritmética de ponto flutuante do seu idioma.
absinthe
5
Eu ia fazer isso até perceber que a matemática pode ser um pouco mais complexa do que o meu pré-cálculo atual. Talvez no próximo ano.
Stretch Maniac
1
@BetaDecay Não sei o que é isso. Vamos dizer que é 0.
absinthe

Respostas:

5

Python + NumPy + Matplotlib, 1131

Apenas para começar, aqui está uma tentativa que não utiliza nenhum conhecimento de cálculo ou física além do fato de que a catenária minimiza a energia de uma cadeia. Ei, meu algoritmo pode não ser eficiente, mas pelo menos também não é implementado com eficiência!

import math
import random
import numpy as np
import matplotlib.pyplot as plt
length, x0, y0, x1, y1 = input(), input(), input(), input(), input()
chain = np.array([[x0] + [length / 1000.]*1000, [y0] + [0.] * 1000])
def rotate(angle, x, y):
 return x * math.cos(angle) + y * math.sin(angle), -x * math.sin(angle) + y  * math.cos(angle)
def eval(chain, x1, y1):
 mysum = chain.cumsum(1)
 springpotential = 1000 * ((mysum[0][-1] - x1) ** 2 + (mysum[1][-1] - y1)  ** 2)
 potential = mysum.cumsum(1)[1][-1]
 return springpotential + potential
def jiggle(chain, x1, y1):
 for _ in xrange(100000):
  pre = eval(chain, x1, y1)
  angle = random.random() * 2 * math.pi
  index = random.randint(1,1000)
  chain[0][index], chain[1][index] = rotate(angle, chain[0][index], chain[1][index])
  if( pre < eval(chain, x1, y1)):
   chain[0][index], chain[1][index] = rotate(-angle, chain[0][index], chain[1][index])
jiggle(chain, x1, y1)
sum = chain.cumsum(1)
x1 = 2 * x1 - sum[0][-1]
y1 = 2 * y1 - sum[1][-1]
jiggle(chain, x1, y1)
sum = chain.cumsum(1)
plt.plot(sum[0][1:], sum[1][1:])
plt.show()
QuadmasterXLII
fonte
3

BBC Basic, 300 caracteres ASCII, tamanho de arquivo tokenised 260

  INPUTr,s,u,v,l:r*=8s*=8u*=8v*=8l*=8z=0REPEATz+=1E-3UNTILFNs(z)/z>=SQR(l^2-(v-s)^2)/(u-r)a=(u-r)/2/z
  p=(r+u-a*LN((l+v-s)/(l-v+s)))/2q=(v+s-l*FNc(z)/FNs(z))/2MOVE800,0DRAW0,0DRAW0,800CIRCLEu,v,8CIRCLEr,s,8FORx=r TOu
    DRAW x,a*FNc((x-p)/a)+q
  NEXT
  DEFFNs(t)=(EXP(t)-EXP(-t))/2
  DEFFNc(t)=(EXP(t)+EXP(-t))/2

Emulador em http://www.bbcbasic.co.uk/bbcwin/bbcwin.html

Obviamente, isso já foi resolvido antes, então a primeira coisa que fiz foi ver o que os outros fizeram.

A equação de uma catenária centrada na origem é simples y=a*cosh(x/a). Torna-se um pouco mais complicado se não estiver centrado na origem.

Várias fontes dizem que, se o comprimento e os pontos de extremidade forem conhecidos, o valor adeverá ser determinado numericamente. Há um parâmetro não especificado hno artigo da wikipedia. Encontrei outro site e basicamente segui o método aqui: http://www.math.niu.edu/~rusin/known-math/99_incoming/catenary

Como o BBC Basic não possui sinhe foi coshincorporado, defini duas funções no final do programa para calculá-las usandoEXP

as coordenadas do ponto da esquerda devem ser fornecidas antes do ponto da direita, OP confirmou que isso está OK. O comprimento é dado por último. Os valores podem ser separados por vírgulas ou novas linhas.

Código ungolfed

  INPUT r,s,u,v,l

  REM convert input in range 0-100 to graphic coordinates in range 0-800 
  r*=8 s*=8 u*=8 v*=8 l*=8

  REM solve for z numerically
  z=0
  REPEAT
    z+=1E-3
  UNTIL FNs(z)/z>=SQR(l^2-(v-s)^2)/(u-r)

  REM calculate the curve parameters
  a=(u-r)/2/z
  p=(r+u-a*LN((l+v-s)/(l-v+s)))/2
  q=(v+s-l*FNc(z)/FNs(z))/2

  REM draw axes, 800 graphics units long = 400 pixels long (2 graphics units per pixel)
  MOVE 800,0
  DRAW 0,0
  DRAW 0,800

  REM draw markers at end and beginning of curve (beginning last, so that cursor is in right place for next step)
  CIRCLE u,v,8
  CIRCLE r,s,8

  REM draw curve from beginning to end
  FORx=r TOu
    DRAW x,a*FNc((x-p)/a)+q
  NEXT

  REM definitions of sinh and cosh
  DEF FNs(t)=(EXP(t)-EXP(-t))/2
  DEF FNc(t)=(EXP(t)+EXP(-t))/2

insira a descrição da imagem aqui

Level River St
fonte
1

Python 2.7 + matplotlib, 424

Correr como

python thisscript.py [length] [x0] [y0] [x1] [y1]

Se eu puder assumir que x0 é sempre menor que x1, a contagem de caracteres reduz para 398

from numpy import *
from pylab import *
from scipy.optimize import *
import sys
c=cosh
l,p,q,u,w=map(float,sys.argv[1:])
if p>u:
 p,q,u,w=u,w,p,q
h=u-p
v=w-q
a=brentq(lambda a:(2.*h/a*sinh(0.5*a))**2-l**2-v**2,1e-20,600)
b=brentq(lambda b:c(a*(1.-b))-c(a*b)-a*v/h,-600/a,600/a)
r=linspace(p,u,100)
plot([p,u],[q,w],'ro')
plot(r,h/a*c(((r-p)/h-b)*a)-h/a*c(a*b)+q,'k-')
gca().set_xlim((0,100))
gca().set_ylim((0,100))
show()

O número mágico 600 que você vê aparecendo em alguns lugares se deve ao fato de cosh (x) e sinh (x) começarem a transbordar em torno de x = 710 (portanto, 600 para manter alguma margem)

Basicamente, resolvo o problema no quadro em que a catenária passa por (0,0) e (x1-x0, (y1-y0) / (x1-x0)) e, em seguida, remapeei para o quadro original. Isso melhora a estabilidade numérica muito .

camelthemammel
fonte