All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lucuma.core.math.Parallax.scala Maven / Gradle / Ivy

There is a newer version: 0.108.0
Show newest version
// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package lucuma.core.math

import cats.*
import coulomb.*
import coulomb.ops.algebra.cats.all.given
import coulomb.policy.spire.standard.given
import coulomb.syntax.*
import eu.timepit.refined.api.*
import eu.timepit.refined.auto.*
import eu.timepit.refined.cats.*
import eu.timepit.refined.numeric.*
import eu.timepit.refined.refineV
import lucuma.core.math.units.{*, given}
import lucuma.core.optics.*
import spire.math.Rational
import spire.std.long.*

import scala.math.BigDecimal.RoundingMode

/**
 * Parallax stored as microarcseconds
 * Normally parallax is expressed in milliarcseconds but simbad reports them
 * with a higher precision thus using microarcseconds gives us enough space.
 * Parallax values need to be in the interval [0°, 180°].
 * 180° is a theoretical limit - actual astronomical values will be very small.
 */
sealed abstract case class Parallax protected (
  μas: Quantity[Parallax.LongParallaxμas, MicroArcSecond]
) {
  val mas: Quantity[Rational, MilliArcSecond] = μas.toValue[Rational].toUnit[MilliArcSecond]

  override def toString: String =
    s"Parallax(${μas.show})"
}

object Parallax extends ParallaxOptics {
  type Parallaxμas     = Interval.Closed[0, Angle.Angle180µas]
  type LongParallaxμas = Long Refined Parallaxμas

  /**
   * The `No parallax`
   * @group Constructors
   */
  val Zero: Parallax = applyUnsafe(0L)

  // Internal unbounded constructor
  private def applyUnsafe(μas: Long): Parallax =
    apply(refineV[Parallaxμas](μas).toOption.get.withUnit[MicroArcSecond])

  def apply(μas: Quantity[LongParallaxμas, MicroArcSecond]): Parallax =
    new Parallax(μas) {}

  /** @group Typeclass Instances */
  given Order[Parallax] =
    Order.by(_.μas)

  /**
   * Construct a new Parallax of the given magnitude in integral microarcseconds. Exact.
   * Finds the normalized signed angle for the microarcseconds, then takes
   * the absolute value.
   * @group Constructors
   */
  def fromMicroarcseconds(μas: Long): Parallax =
    applyUnsafe(Angle.signedMicroarcseconds.normalize(μas).abs)
}

sealed trait ParallaxOptics {

  /**
   * This `Parallax` in microarcseconds. modulo 180°.
   */
  lazy val microarcseconds: SplitMono[Parallax, Long] =
    SplitMono(_.μas.value, Parallax.fromMicroarcseconds)

  lazy val μas: SplitMono[Parallax, Long] = microarcseconds

  /**
   * This `Parallax` as in milliarcseconds.
   */
  lazy val milliarcseconds: SplitMono[Parallax, BigDecimal] =
    microarcseconds
      .imapB(
        _.setScale(3, RoundingMode.HALF_UP).underlying.movePointRight(3).longValue,
        n => new java.math.BigDecimal(n).movePointLeft(3)
      )

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy