Repository: gemini-hlsw/lucuma-core
Pull request #1089: Add units to F2 enums
Author: login: cquiroz
Labels: name: core
Created: 2025-04-23T21:08:49Z | Merged: 2025-04-23T21:51:12Z

I'm also removing obsolete items


Diff:
diff --git a/modules/core/shared/src/main/scala/lucuma/core/enums/F2Fpu.scala b/modules/core/shared/src/main/scala/lucuma/core/enums/F2Fpu.scala
index 743dcb703..a6fb45f89 100644
--- a/modules/core/shared/src/main/scala/lucuma/core/enums/F2Fpu.scala
+++ b/modules/core/shared/src/main/scala/lucuma/core/enums/F2Fpu.scala
@@ -3,7 +3,9 @@
 
 package lucuma.core.enums
 
-import cats.syntax.eq.*
+import coulomb.*
+import lucuma.core.math.syntax.units.*
+import lucuma.core.math.units.Pixels
 import lucuma.core.util.Enumerated
 
 /**
@@ -14,16 +16,15 @@ enum F2Fpu(
   val tag: String,
   val shortName: String,
   val longName: String,
-  val slitWidth: Int, // pixels
-  val decker: F2Decker,
-  val obsolete: Boolean
+  val slitWidth: Quantity[Int, Pixels],
+  val decker: F2Decker
 ) derives Enumerated:
 
-  case Pinhole       extends F2Fpu("Pinhole",       "Pinhole",         "2-Pixel Pinhole Grid", 0, F2Decker.Imaging, false)
-  case SubPixPinhole extends F2Fpu("SubPixPinhole", "Sub-Pix Pinhole", "Sub-Pixel Pinhole Gr", 0, F2Decker.Imaging, false)
-  case LongSlit1     extends F2Fpu("LongSlit_1",     "Long Slit 1px",   "1-Pixel Long Slit",    1, F2Decker.LongSlit, false)
-  case LongSlit2     extends F2Fpu("LongSlit_2",     "Long Slit 2px",   "2-Pixel Long Slit",    2, F2Decker.LongSlit, false)
-  case LongSlit3     extends F2Fpu("LongSlit_3",     "Long Slit 3px",   "3-Pixel Long Slit",    3, F2Decker.LongSlit, false)
-  case LongSlit4     extends F2Fpu("LongSlit_4",     "Long Slit 4px",   "4-Pixel Long Slit",    4, F2Decker.LongSlit, false)
-  case LongSlit6     extends F2Fpu("LongSlit_6",     "Long Slit 6px",   "6-Pixel Long Slit",    6, F2Decker.LongSlit, false)
-  case LongSlit8     extends F2Fpu("LongSlit_8",     "Long Slit 8px",   "8-Pixel Long Slit",    8, F2Decker.LongSlit, false)
+  case Pinhole       extends F2Fpu("Pinhole",       "Pinhole",         "2-Pixel Pinhole Grid", 0.pixels, F2Decker.Imaging)
+  case SubPixPinhole extends F2Fpu("SubPixPinhole", "Sub-Pix Pinhole", "Sub-Pixel Pinhole Gr", 0.pixels, F2Decker.Imaging)
+  case LongSlit1     extends F2Fpu("LongSlit_1",    "Long Slit 1px",   "1-Pixel Long Slit",    1.pixels, F2Decker.LongSlit)
+  case LongSlit2     extends F2Fpu("LongSlit_2",    "Long Slit 2px",   "2-Pixel Long Slit",    2.pixels, F2Decker.LongSlit)
+  case LongSlit3     extends F2Fpu("LongSlit_3",    "Long Slit 3px",   "3-Pixel Long Slit",    3.pixels, F2Decker.LongSlit)
+  case LongSlit4     extends F2Fpu("LongSlit_4",    "Long Slit 4px",   "4-Pixel Long Slit",    4.pixels, F2Decker.LongSlit)
+  case LongSlit6     extends F2Fpu("LongSlit_6",    "Long Slit 6px",   "6-Pixel Long Slit",    6.pixels, F2Decker.LongSlit)
+  case LongSlit8     extends F2Fpu("LongSlit_8",    "Long Slit 8px",   "8-Pixel Long Slit",    8.pixels, F2Decker.LongSlit)
diff --git a/modules/core/shared/src/main/scala/lucuma/core/enums/F2LyotWheel.scala b/modules/core/shared/src/main/scala/lucuma/core/enums/F2LyotWheel.scala
index 8d4acf0b2..f7604a708 100644
--- a/modules/core/shared/src/main/scala/lucuma/core/enums/F2LyotWheel.scala
+++ b/modules/core/shared/src/main/scala/lucuma/core/enums/F2LyotWheel.scala
@@ -4,7 +4,10 @@
 package lucuma
 package core
 package enums
-import cats.syntax.eq.*
+
+import lucuma.core.math.syntax.units.*
+import lucuma.core.math.units.PixelScale
+import lucuma.core.math.units.PlateScale
 import lucuma.core.util.Enumerated
 
 /**
@@ -21,16 +24,12 @@ enum F2LyotWheel(
   val tag: String,
   val shortName: String,
   val longName: String,
-  val plateScale: Double, // arcsec/mm
-  val pixelScale: Double, // arcsec/pixel
-  val obsolete: Boolean
+  val plateScale: PlateScale, // arcsec/mm
+  val pixelScale: PixelScale  // arcsec/pixel
 ) derives Enumerated:
 
-  case F16       extends F2LyotWheel("F16",       "f/16",            "f/16 (Open)",               1.61,  0.18, false)
-  case F32High   extends F2LyotWheel("F32High",   "f/32 High",       "f/32 MCAO high background", 0.805, 0.09, true)
-  case F32Low    extends F2LyotWheel("F32Low",    "f/32 Low",        "f/32 MCAO low background",  0.805, 0.09, true)
-  case F33Gems   extends F2LyotWheel("F33Gems",   "f/33 GeMS",       "f/33 (GeMS)",               0.784, 0.09, true)
-  case GemsUnder extends F2LyotWheel("GemsUnder", "GeMS Under",      "f/33 (GeMS under-sized)",   0.784, 0.09, false)
-  case GemsOver  extends F2LyotWheel("GemsOver",  "GeMS Over",       "f/33 (GeMS over-sized)",    0.784, 0.09, false)
-  case HartmannA extends F2LyotWheel("HartmannA", "Hartmann A (H1)", "Hartmann A (H1)",           0.0,   0.0,  false)
-  case HartmannB extends F2LyotWheel("HartmannB", "Hartmann B (H2)", "Hartmann B (H2)",           0.0,   0.0,  false)
+  case F16       extends F2LyotWheel("F16",       "f/16",            "f/16 (Open)",             1.61.plateScale,  0.18.pixelScale)
+  case GemsUnder extends F2LyotWheel("GemsUnder", "GeMS Under",      "f/33 (GeMS under-sized)", 0.784.plateScale, 0.09.pixelScale)
+  case GemsOver  extends F2LyotWheel("GemsOver",  "GeMS Over",       "f/33 (GeMS over-sized)",  0.784.plateScale, 0.09.pixelScale)
+  case HartmannA extends F2LyotWheel("HartmannA", "Hartmann A (H1)", "Hartmann A (H1)",         0.0.plateScale,   0.0.pixelScale)
+  case HartmannB extends F2LyotWheel("HartmannB", "Hartmann B (H2)", "Hartmann B (H2)",         0.0.plateScale,   0.0.pixelScale)
diff --git a/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2OiwfsProbeArm.scala b/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2OiwfsProbeArm.scala
index 95f4e8558..764b56b72 100644
--- a/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2OiwfsProbeArm.scala
+++ b/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2OiwfsProbeArm.scala
@@ -6,7 +6,6 @@ package lucuma.core.geom.f2
 import cats.syntax.all.*
 import coulomb.*
 import coulomb.policy.spire.standard.given
-import coulomb.syntax.*
 import coulomb.units.accepted.*
 import coulomb.units.si.prefixes.*
 import lucuma.core.enums.F2LyotWheel
@@ -67,7 +66,7 @@ trait F2OiwfsProbeArm:
     lyot:      F2LyotWheel,
     port:      PortDisposition
   ): ShapeExpression =
-    val plateScale = BigDecimal(lyot.plateScale).withUnit[ArcSecondPerMillimeter]
+    val plateScale = lyot.plateScale
     shape(plateScale) ⟲ armAngle(posAngle, guideStar, offsetPos, port, plateScale) ↗ guideStar
 
   /**
diff --git a/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2PatrolField.scala b/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2PatrolField.scala
index 5f9362cc4..af9437c66 100644
--- a/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2PatrolField.scala
+++ b/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2PatrolField.scala
@@ -86,7 +86,7 @@ trait F2PatrolField:
     lyotWheel: F2LyotWheel,
     port:      PortDisposition
   ): ShapeExpression =
-    val plateScale = BigDecimal(lyotWheel.plateScale).withUnit[ArcSecondPerMillimeter]
+    val plateScale = lyotWheel.plateScale
     val pf = patrolField(plateScale)
     val s  = if (port === PortDisposition.Bottom) pf.flipP else pf
     s ↗ offsetPos ⟲ posAngle
diff --git a/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2ScienceAreaGeometry.scala b/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2ScienceAreaGeometry.scala
index 988f5731c..c8f952e53 100644
--- a/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2ScienceAreaGeometry.scala
+++ b/modules/core/shared/src/main/scala/lucuma/core/geom/f2/F2ScienceAreaGeometry.scala
@@ -3,6 +3,7 @@
 
 package lucuma.core.geom.f2
 
+import algebra.instances.all.given
 import coulomb.*
 import coulomb.policy.spire.standard.given
 import coulomb.syntax.*
@@ -17,7 +18,6 @@ import lucuma.core.math.Offset
 import lucuma.core.math.units.*
 import lucuma.core.model.sequence.f2.F2FpuMask
 import spire.math.*
-import spire.std.bigDecimal.*
 
 /**
   * F2 science area geometry.
@@ -30,15 +30,15 @@ trait F2ScienceAreaGeometry:
 
   def scienceAreaDimensions(lyotWheel: F2LyotWheel, fpu: F2FpuMask): (Angle, Angle) =
     lyotWheel match
-      case F2LyotWheel.F16 | F2LyotWheel.F32High | F2LyotWheel.F32Low =>
+      case F2LyotWheel.F16 =>
         val pixelScale = lyotWheel.pixelScale
-        val plateScale = BigDecimal(lyotWheel.plateScale).withUnit[ArcSecondPerMillimeter]
+        val plateScale = lyotWheel.plateScale
         fpu match
           case F2FpuMask.Imaging | F2FpuMask.Builtin(F2Fpu.Pinhole) | F2FpuMask.Builtin(F2Fpu.SubPixPinhole) =>
             val size = ImagingFOVSize ⨱ plateScale
             (size.toAngle, size.toAngle)
           case F2FpuMask.Builtin(fpu) =>
-            (Angle.fromBigDecimalArcseconds(fpu.slitWidth * pixelScale),
+            (Angle.fromBigDecimalArcseconds((fpu.slitWidth * pixelScale).value),
               (LongSlitFOVHeight ⨱ plateScale).toAngle)
           case F2FpuMask.Custom(_, _) =>
             ((MOSFOVWidth ⨱ plateScale).toAngle,
@@ -52,7 +52,7 @@ trait F2ScienceAreaGeometry:
     lyotWheel: F2LyotWheel,
     fpu:       F2FpuMask
   ): ShapeExpression =
-    val plateScale = BigDecimal(lyotWheel.plateScale).withUnit[ArcSecondPerMillimeter]
+    val plateScale = lyotWheel.plateScale
     val scienceAreaWidth = scienceAreaDimensions(lyotWheel, fpu)._1
     val shape =
       fpu.fold(
diff --git a/modules/core/shared/src/main/scala/lucuma/core/geom/syntax/package.scala b/modules/core/shared/src/main/scala/lucuma/core/geom/syntax/package.scala
index c74a3583b..0fe781e49 100644
--- a/modules/core/shared/src/main/scala/lucuma/core/geom/syntax/package.scala
+++ b/modules/core/shared/src/main/scala/lucuma/core/geom/syntax/package.scala
@@ -15,7 +15,7 @@ import lucuma.core.math.Offset
 import lucuma.core.math.units.*
 import spire.std.bigDecimal.*
 
-// Syntax used in the JTS / JVM implementation only.
+// Syntax used in the JTS implementation only.
 object all extends shapeexpression:
   extension[U](q: Quantity[BigDecimal, U])(using UnitConversion[BigDecimal, U, Millimeter])
     def toTelescopePlane: Quantity[BigDecimal, ArcSecond] =
diff --git a/modules/core/shared/src/main/scala/lucuma/core/math/syntax/units.scala b/modules/core/shared/src/main/scala/lucuma/core/math/syntax/units.scala
new file mode 100644
index 000000000..622e151cd
--- /dev/null
+++ b/modules/core/shared/src/main/scala/lucuma/core/math/syntax/units.scala
@@ -0,0 +1,23 @@
+// Copyright (c) 2016-2025 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.syntax
+
+import coulomb.Quantity
+import coulomb.syntax.*
+import lucuma.core.math.units.*
+
+trait ToUnitsOps {
+  extension(v: BigDecimal)
+    def plateScale: PlateScale =
+      v.withUnit[ArcSecondPerMillimeter]
+
+    def pixelScale: PixelScale =
+      v.withUnit[ArcSecondPerPixel]
+
+  extension(v: Int)
+    def pixels: Quantity[Int, Pixels] =
+      v.withUnit[Pixels]
+}
+
+object units extends ToUnitsOps
diff --git a/modules/core/shared/src/main/scala/lucuma/core/syntax/package.scala b/modules/core/shared/src/main/scala/lucuma/core/syntax/package.scala
index dbec84f58..31a739257 100644
--- a/modules/core/shared/src/main/scala/lucuma/core/syntax/package.scala
+++ b/modules/core/shared/src/main/scala/lucuma/core/syntax/package.scala
@@ -4,6 +4,7 @@
 package lucuma.core.syntax
 
 import lucuma.core.math.syntax.ToIntOps
+import lucuma.core.math.syntax.ToUnitsOps
 import lucuma.core.optics.syntax.ToPrismOps
 
 /**
@@ -21,5 +22,6 @@ object all
     with ToTreeMapOps
     with ToTreeSetCompanionOps
     with ToEnumeratedOps
+    with ToUnitsOps
     with timespan
-    with validation
\ No newline at end of file
+    with validation


Reviews:
user: login: rpiaggio
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T21:19:31Z
user: login: rpiaggio
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T21:21:27Z
user: login: cquiroz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T21:25:22Z
user: login: rpiaggio
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T21:37:13Z
user: login: cquiroz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T21:51:09Z

Review comments:
diff_hunk: @@ -0,0 +1,23 @@
+// Copyright (c) 2016-2025 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.syntax
path: modules/core/shared/src/main/scala/lucuma/core/math/syntax/units.scala
user: login: rpiaggio
body: I think we are concentranting all syntax extensions in `lucuma.core.syntax`.
created_at: 2025-04-23T21:19:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,23 @@
+// Copyright (c) 2016-2025 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.syntax
path: modules/core/shared/src/main/scala/lucuma/core/math/syntax/units.scala
user: login: cquiroz
body: We have e.g.
`import lucuma.core.math.syntax.int.*`
created_at: 2025-04-23T21:25:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,23 @@
+// Copyright (c) 2016-2025 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.syntax
path: modules/core/shared/src/main/scala/lucuma/core/math/syntax/units.scala
user: login: rpiaggio
body: Yeah, we still have that. Maybe we should bring that over to `lucuma.core.syntax`.
created_at: 2025-04-23T21:37:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,23 @@
+// Copyright (c) 2016-2025 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.syntax
path: modules/core/shared/src/main/scala/lucuma/core/math/syntax/units.scala
user: login: cquiroz
body: an exercise for a newcomer
created_at: 2025-04-23T21:51:09Z
author_association: COLLABORATOR

PR comments:
user: login: bundlemon[bot]
created_at: 2025-04-23T21:18:23Z
body: <!-- bundlemon-lucuma-catalog_sjs1_3 -->
## BundleMon (lucuma-catalog_sjs1_3)

  
  
  
  
  
  
  
<details>
<summary>Unchanged files (1)</summary>

Status | Path | Size | Limits
:------------: | ------------ | :------------: | :------------:
:white_check_mark: | main.js<br/><sub>main.js<br/></sub> | 53.92KB | -
</details>



No change in files bundle size

Final result: :white_check_mark:

[View report in BundleMon website ➡️](https://app.bundlemon.dev/projects/67df38973c8d3967123a6535/reports/680958d4f8666787a82d0bb5)

---
<p align="center"><a href="https://app.bundlemon.dev/projects/67df38973c8d3967123a6535/reports?branch=f2-units&resolution=all&subProject=lucuma-catalog_sjs1_3" target="_blank" rel="noreferrer noopener">Current branch size history</a> | <a href="https://app.bundlemon.dev/projects/67df38973c8d3967123a6535/reports?branch=main&resolution=days&subProject=lucuma-catalog_sjs1_3" target="_blank" rel="noreferrer noopener">Target branch size history</a></p>
author_association: NONE
user: login: bundlemon[bot]
created_at: 2025-04-23T21:18:23Z
body: <!-- bundlemon-lucuma-ags_sjs1_3 -->
## BundleMon (lucuma-ags_sjs1_3)

  
  
  
  
  
  
  
<details>
<summary>Unchanged files (1)</summary>

Status | Path | Size | Limits
:------------: | ------------ | :------------: | :------------:
:white_check_mark: | main.js<br/><sub>main.js<br/></sub> | 53.92KB | -
</details>



No change in files bundle size

Final result: :white_check_mark:

[View report in BundleMon website ➡️](https://app.bundlemon.dev/projects/67df38973c8d3967123a6535/reports/680958d4f8666787a82d0bb4)

---
<p align="center"><a href="https://app.bundlemon.dev/projects/67df38973c8d3967123a6535/reports?branch=f2-units&resolution=all&subProject=lucuma-ags_sjs1_3" target="_blank" rel="noreferrer noopener">Current branch size history</a> | <a href="https://app.bundlemon.dev/projects/67df38973c8d3967123a6535/reports?branch=main&resolution=days&subProject=lucuma-ags_sjs1_3" target="_blank" rel="noreferrer noopener">Target branch size history</a></p>
author_association: NONE