Eu tenho uma função (não invertível) ak([u,v,w])
Isso pega um ponto na superfície do octaedro unitário (p: tal que |u|+|v|+|w| = 1) e retorna um ponto na superfície da esfera unitária. A função não é perfeita, mas a intenção é manter a distância entre os pontos autálicos.
Eu estava pensando em usar o SciPy minimize para fornecer um inverso numérico, mas não consigo entender.
entrada: pt esférico [x,y,z],
saída pts octaédricos [u,v,w] tais que ak([u,v,w])=[x,y,z]
Minha função ak() é definida assim:
def ak(p):
# Convert point on octahedron onto the sphere.
# Credit to Anders Kaseorg: https://math.stackexchange.com/questions/5016695/
# input: oct_pt is a Euclidean point on the surface of a unit octagon.
# output: UVW on a unit sphere.
a = 3.227806237143884260376580641604959964752197265625 # 𝛂 - vis. Kaseorg.
p1 = (np.pi * p) / 2.0
tp1 = np.tan(p1)
xu, xv, xw = tp1[0], tp1[1], tp1[2]
u2, v2, w2 = xu ** 2, xv ** 2, xw ** 2
y0p = xu * (v2 + w2 + a * w2 * v2) ** 0.25
y1p = xv * (u2 + w2 + a * u2 * w2) ** 0.25
y2p = xw * (u2 + v2 + a * u2 * v2) ** 0.25
pv = np.array([y0p, y1p, y2p])
return pv / np.linalg.norm(pv, keepdims=True)
Esta função é baseada em uma postagem que fiz no Math StackExchange .
Alguma dica?
Uma ideia que descobri e que ajudou muito foi converter isso de um problema de minimização em um problema de descoberta de raízes .
Os localizadores de raízes não suportam restrições, então você precisa alterar a função objetivo para forçar x a ser normalizado L1 antes de converter em coordenadas cartesianas.
Depois de fazer isso, você pode encontrar o ponto onde
ak(op / norm) - tx = 0
.(Também alterei o palpite inicial do centro da face do octaedro para
np.arcsin(tsp) / (np.pi / 2)
. Descobri que isso reduziu o tempo que levou para convergir.)Descobri que isso reduziu o erro da sua solução atual em um fator de cerca de 10^7, enquanto usava menos chamadas para ak() do que sua solução anterior.
Código de teste completo
Nota: O parâmetro 𝛂 tem muito mais dígitos do que o necessário. Quando o Python faz cálculos com esse número, ele ignora todos os dígitos que não se encaixam em um float de precisão dupla, o que significa que o número é convertido para 3,2278062371438843 internamente.
Outras abordagens tentadas
Eu também tentei criar um Jacobiano analítico para essa função usando SymPy, mas tive muita dificuldade para fazer a diferenciação automática funcionar. Se você conseguisse encontrar isso, conseguiria diferenciar a função de forma mais rápida e precisa.
Também tentei uma solução baseada em
scipy.interpolate.RbfInterpolator
. Ela obteve precisão e velocidade semelhantes ao método minimizar.@Reinderian apontou que eu poderia ter usado code-review, mas quando escrevi a pergunta, não tinha nada para revisar. Estou cauteloso com cross-posting, então vou deixar esta pergunta aqui.
A resposta de @nick-odell foi particularmente útil - e adotei seus pensamentos de todo o coração.
A resposta a seguir é uma atualização da resposta original (que deixei abaixo, especialmente porque foi comentada por Nick)
Isso segue principalmente o código de Nick, mas adicionou o jacobian que ele sugeriu, então pensei que ele (ou uma pessoa interessada nessa área) gostaria de ver onde cheguei. @Nick, estou preocupado que você tenha revelado a localização de muitos submarinos - embora um definitivamente pareça estar preso em um pequeno lago em Nunavut, Canadá.
Os erros de ida e volta do MDC (longitude/latitude) estão bem abaixo de 1 µm, o que é muito mais impressionante do que eu esperava.
A sugestão de Nick para initial_guess parece perder muita viabilidade com N=1e+5 (mesma semente) - e eu substituí por ,
np.sign(tsp) * 1./3.
que teve menos chamadas e parece mais estável comjac
.Uma configuração de tolerância de 1e-12 está retornando um erro máximo de 0,75 µm para N=1e+5, com três cálculos exigindo 32 chamadas para convergência.
Modificar a tolerância para 1e-15 é bom para 4 nm e requer até 41 chamadas para convergência.
Coloquei as rotinas em uma classe (estática) para arrumação e também porque estava com dificuldades para fazer a
jac
função ser chamada corretamente. Além disso, a análise sympy jacobian não é rápida, então é bom gerá-la apenas uma vez.O Charles Karney's
GeographicLib
foi importado para obter métricas de distância confiáveis - este é o sistema geodésico subjacente usado por (quase) todas as bibliotecas GIS. Ele é usado apenas para medir a precisão.Créditos e elogios ao Nick e também ao Anders.
Estou geralmente interessado em projeções autálicas (ou quase autálicas) entre o octaedro unitário e a esfera unitária - especialmente em uma grade triangular, especialmente aquelas que são fáceis de ver, e onde não há muito desvio do triângulo equilátero (nem preciso dizer que estou ciente de que os cantos do vértice da projeção da esfera estão em 90º, enquanto no octaedro estão em 60º). Estou igualmente interessado em melhorias (velocidade, precisão, eficiência) no código.
Resposta anterior
E a saída...