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

lucuma.ags.AgsAnalysis.scala Maven / Gradle / Ivy

There is a newer version: 0.48.3
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.ags

import cats.Eq
import cats.Order
import cats.data.NonEmptyList
import cats.derived.*
import cats.syntax.all.*
import eu.timepit.refined.cats.*
import lucuma.catalog.BandsList
import lucuma.core.enums.Band
import lucuma.core.enums.GuideProbe
import lucuma.core.enums.GuideSpeed
import lucuma.core.geom.Area
import lucuma.core.math.Angle
import lucuma.core.math.BrightnessValue

sealed trait AgsAnalysis derives Eq {
  def quality: AgsGuideQuality = AgsGuideQuality.Unusable
  def isUsable: Boolean        = quality =!= AgsGuideQuality.Unusable
  def target: GuideStarCandidate
  def message(withProbe: Boolean): String
}

object AgsAnalysis {

  case class ProperMotionNotAvailable(target: GuideStarCandidate) extends AgsAnalysis derives Eq {
    override def message(withProbe: Boolean): String =
      "Cannot calculate proper motion."
  }

  case class VignettesScience(target: GuideStarCandidate, position: AgsPosition) extends AgsAnalysis
      derives Eq {
    override def message(withProbe: Boolean): String =
      "The target overlaps with the science target"
  }

  case class NoGuideStarForProbe(
    guideProbe: GuideProbe,
    target:     GuideStarCandidate
  ) extends AgsAnalysis
      derives Eq {
    override def message(withProbe: Boolean): String = {
      val p = if (withProbe) s"$guideProbe " else ""
      s"No ${p}guide star selected."
    }
  }

  case class MagnitudeTooFaint(
    guideProbe:     GuideProbe,
    target:         GuideStarCandidate,
    showGuideSpeed: Boolean
  ) extends AgsAnalysis
      derives Eq {
    override def message(withProbe: Boolean): String = {
      val p  = if (withProbe) s"use $guideProbe" else "guide"
      val gs = if (showGuideSpeed) ", even using the slowest guide speed" else ""
      s"Cannot $p with the star in these conditions$gs."
    }
  }

  case class MagnitudeTooBright(
    guideProbe: GuideProbe,
    target:     GuideStarCandidate
  ) extends AgsAnalysis
      derives Eq {
    override def message(withProbe: Boolean): String = {
      val p = if (withProbe) s"$guideProbe g" else "G"
      s"${p}uide star is too bright to guide."
    }
  }

  case class NotReachableAtPosition(
    position:   AgsPosition,
    guideProbe: GuideProbe,
    guideSpeed: Option[GuideSpeed],
    target:     GuideStarCandidate
  ) extends AgsAnalysis
      derives Eq {
    override def message(withProbe: Boolean): String = {
      val p = if (withProbe) s"with ${guideProbe} " else ""
      s"The star is not reachable ${p}at $position."
    }
  }

  case class NoMagnitudeForBand(
    guideProbe: GuideProbe,
    target:     GuideStarCandidate
  ) extends AgsAnalysis
      derives Eq {
    private val probeBands: List[Band]               = BandsList.GaiaBandsList.bands
    override def message(withProbe: Boolean): String = {
      val p = if (withProbe) s"${guideProbe} g" else "G"
      if (probeBands.length == 1) {
        s"${p}uide star ${probeBands.head}-band magnitude is missing. Cannot determine guiding performance."
      } else {
        s"${p}uide star ${probeBands.map(_.shortName).mkString(", ")}-band magnitudes are missing. Cannot determine guiding performance."
      }
    }
  }

  case class Usable(
    guideProbe:           GuideProbe,
    target:               GuideStarCandidate,
    guideSpeed:           GuideSpeed,
    override val quality: AgsGuideQuality,
    vignetting:           NonEmptyList[(Angle, Area)]
  ) extends AgsAnalysis
      derives Eq {
    override def message(withProbe: Boolean): String = {
      val qualityMessage = quality match {
        case AgsGuideQuality.DeliversRequestedIq => ""
        case _                                   => s"${quality.message} "
      }
      val p              = if (withProbe) s"${guideProbe} " else ""
      val gs             = s"Guide Speed: ${guideSpeed.toString()}"
      s"$qualityMessage$p$gs. vignetting: ${vignetting.head._2.toMicroarcsecondsSquared} µas^2"
      s"$p $quality $gs. vignetting: ${vignetting.head._2.toMicroarcsecondsSquared} µas^2"
    }

    def sortedVignetting: Usable = copy(vignetting = vignetting.sortBy(_._2))
  }

  object Usable {
    val rankingOrder: Order[Usable] =
      Order.by(u =>
        (u.guideSpeed,
         u.quality,
         u.vignetting.minimumBy(_._2)._2.toMicroarcsecondsSquared,
         u.target.gBrightness,
         u.target.id
        )
      )

  }

  val rankingOrder: Order[AgsAnalysis] =
    Order.from {
      case (a: Usable, b: Usable) => Usable.rankingOrder.compare(a, b)
      case (_: Usable, _)         => Int.MinValue
      case (_, _: Usable)         => Int.MaxValue
      case _                      => Int.MinValue
    }

  val rankingOrdering: Ordering[AgsAnalysis] = rankingOrder.toOrdering

  extension (analysis: AgsAnalysis)
    def posAngle: Option[Angle] = analysis match
      case AgsAnalysis.Usable(_, _, _, _, v) => Some(v.head._1)
      case _                                 => None

  extension (analysis: Option[AgsAnalysis])
    def posAngle: Option[Angle] = analysis.flatMap(_.posAngle)

  extension (results: List[AgsAnalysis])
    /**
     * This method will sort the analysis for quality and internally for positions that give the
     * lowest vignetting.
     *
     * Non usable positions wii be discarded.
     */
    def sortUsablePositions: List[AgsAnalysis] = {
      val usablePerTarget: List[AgsAnalysis] =
        results
          .groupBy(_.target.id)
          .flatMap { (_, analyses) =>
            val usable = analyses.collect { case u: Usable => u }
            // If there is at least a single usable result, we can sort by vignetting
            // and discard the non usable results
            if (usable.nonEmpty) {
              List(
                usable
                  .reduce((a, b) => a.copy(vignetting = a.vignetting.concatNel(b.vignetting)))
                  .sortedVignetting
              )
            } else Nil // Ignore if there are no usable results
          }
          .toList
      usablePerTarget.sorted(rankingOrdering)
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy