Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into update/munit-1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
hugo-vrijswijk committed Feb 7, 2025
2 parents 23a4731 + babae12 commit 263342d
Show file tree
Hide file tree
Showing 17 changed files with 345 additions and 168 deletions.
26 changes: 13 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,24 @@ concurrency:

jobs:
build:
name: Build and Test
name: Test
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-22.04]
scala: [3]
java: [temurin@17]
project: [rootJS, rootJVM]
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- name: Install sbt
uses: sbt/setup-sbt@v1

- name: Checkout current branch (full)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup sbt
uses: sbt/setup-sbt@v1

- name: Setup Java (temurin@17)
id: setup-java-temurin-17
if: matrix.java == 'temurin@17'
Expand All @@ -60,11 +60,11 @@ jobs:
run: sbt githubWorkflowCheck

- name: Check headers and formatting
if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest'
if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck lucumaScalafmtCheck lucumaScalafixCheck

- name: Check scalafix lints
if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest'
if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' 'scalafixAll --check'

- name: scalaJSLink
Expand All @@ -75,11 +75,11 @@ jobs:
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test

- name: Check binary compatibility
if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest'
if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues

- name: Generate API documentation
if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest'
if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-22.04'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc

- name: Aggregate coverage reports
Expand All @@ -94,18 +94,18 @@ jobs:
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/topic/scala3')
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-22.04]
java: [temurin@17]
runs-on: ${{ matrix.os }}
steps:
- name: Install sbt
uses: sbt/setup-sbt@v1

- name: Checkout current branch (full)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup sbt
uses: sbt/setup-sbt@v1

- name: Setup Java (temurin@17)
id: setup-java-temurin-17
if: matrix.java == 'temurin@17'
Expand Down
4 changes: 2 additions & 2 deletions .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pull_request_rules:
- body~=labels:.*early-semver-patch
- body~=labels:.*early-semver-minor
- 'title=flake.lock: Update'
- status-success=Build and Test (ubuntu-latest, 3, temurin@17, rootJS)
- status-success=Build and Test (ubuntu-latest, 3, temurin@17, rootJVM)
- status-success=Test (ubuntu-22.04, 3, temurin@17, rootJS)
- status-success=Test (ubuntu-22.04, 3, temurin@17, rootJVM)
actions:
merge: {}
- name: Label benchmarks PRs
Expand Down
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.8.5"
version = "3.8.6"
include ".scalafmt-common.conf"
project.includePaths = [] # disables formatting
runner.dialect = scala3
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ThisBuild / tlBaseVersion := "0.114"
ThisBuild / tlBaseVersion := "0.115"
ThisBuild / tlCiReleaseBranches := Seq("master")
ThisBuild / githubWorkflowEnv += "MUNIT_FLAKY_OK" -> "true"

Expand Down
12 changes: 6 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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.conditions

import coulomb.Quantity
import coulomb.units.accepted.*
import eu.timepit.refined.*
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean.Not
import eu.timepit.refined.numeric.Less
import lucuma.core.enums.CloudExtinction
import lucuma.core.enums.Site
import lucuma.core.enums.SkyBackground
import lucuma.core.enums.WaterVapor
import lucuma.core.math.Declination
import lucuma.core.math.Wavelength
import lucuma.core.math.erf
import lucuma.core.model.AirMassPredicate
import lucuma.core.model.AirMassValue
import lucuma.core.model.IntCentiPercent
import lucuma.refined.*

import scala.math.Pi
import scala.math.abs
import scala.math.pow
import scala.math.sin

// Calculations of likelihood of occurrence of observing conditions
// Taken from:
// https://github.com/andrewwstephens/pyexplore/blob/3edd50f6c41509752cda6ad493ccaadd5eb5ad82/test/percentile.py
//

/**
* Return the minimum airmass at a certain declination at a site.
*/
def minimumAirmass(dec: Declination, site: Site): AirMassValue =
val latitude = site.place.latitude
// Maximum elevation in degrees
val elevation = 90.0 - abs(dec.toAngle.toSignedDoubleDegrees - latitude.toAngle.toSignedDoubleDegrees)
refineV[AirMassPredicate](BigDecimal(1.0 / sin((elevation + 244.0 / (165.0 + 47.0 * pow(elevation, 1.1))) * Pi / 180.0))).getOrElse(sys.error("Not possible"))

/**
* Return the percentile of the sky brightness.
* https://www.gemini.edu/observing/telescopes-and-sites/sites#SkyBackground
*/
def percentileSkyBackground(bg: SkyBackground): IntCentiPercent =
bg match
case SkyBackground.Darkest => IntCentiPercent(2000.refined)
case SkyBackground.Dark => IntCentiPercent(5000.refined)
case SkyBackground.Gray => IntCentiPercent(8000.refined)
case SkyBackground.Bright => IntCentiPercent(10000.refined)

/**
* Return the percentile of a measured extinction.
* extinction in magnitudes
*/
def percentileCloudCoverage(extinction: CloudExtinction): IntCentiPercent =
extinction match
case CloudExtinction.PointOne => IntCentiPercent(5000.refined)
case CloudExtinction.PointThree => IntCentiPercent(7000.refined)
case CloudExtinction.PointFive => IntCentiPercent(7500.refined)
case CloudExtinction.OnePointZero => IntCentiPercent(8000.refined)
case CloudExtinction.OnePointFive => IntCentiPercent(9000.refined)
case CloudExtinction.TwoPointZero => IntCentiPercent(9500.refined)
case CloudExtinction.ThreePointZero => IntCentiPercent(10000.refined)

/**
* Return the percentile of the water vapor.
* https://www.gemini.edu/observing/telescopes-and-sites/sites#SkyTransparencyWater
*/
def percentileWaterVapor(wv: WaterVapor): IntCentiPercent =
wv match
case WaterVapor.VeryDry => IntCentiPercent(2000.refined)
case WaterVapor.Dry => IntCentiPercent(5000.refined)
case WaterVapor.Median => IntCentiPercent(8000.refined)
case WaterVapor.Wet => IntCentiPercent(10000.refined)

/**
* Calculate the percentile of on-source image quality.
* fwhm in arcsec (on-source) wavelength in microns
*/
def percentileImageQuality(fwhm: Quantity[BigDecimal, ArcSecond], wavelength: Wavelength, airmass: AirMassValue): IntCentiPercent =
val zenithFwhm = fwhm.value.toDouble / pow(airmass.value.toDouble, 0.6)

// model fit to QAP from 2004-2024: (the extra +0.5 is to force 100% in the worst IQ)
val c = Array(50.10221383, 0.87712202, 0.78467697, 16.10928544, 0.13778389, -15.8255612, 49.37405633 + 0.5)
// The equation should give a number between 0 and 100 but rounding can give numbers below 0 or above 100. It is clamped to that range.
val result = c(0) * erf(c(1) * pow(wavelength.toMicrometers.value.value.toDouble, c(2)) + c(3) * pow(zenithFwhm, c(4)) + c(5)) + c(6)
IntCentiPercent.fromBigDecimal.getOption(result).getOrElse {
if (result < 0) IntCentiPercent.Min
else IntCentiPercent.Max
}

def conditionsLikelihood(bg: SkyBackground, extinction: CloudExtinction, wv: WaterVapor, fwhm: Quantity[BigDecimal, ArcSecond], wavelength: Wavelength, dec: Declination, site: Site): IntCentiPercent =
(percentileSkyBackground(bg) *
percentileCloudCoverage(extinction) *
percentileImageQuality(fwhm, wavelength, minimumAirmass(dec, site)) *
percentileWaterVapor(wv)).round

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ sealed abstract class CloudExtinction(val tag: String, val toDeciBrightness: Int
def label: String = f"< $toBrightness%.1f mag"
}

object CloudExtinction {
object CloudExtinction:
case object PointOne extends CloudExtinction("point_one", 1)
case object PointThree extends CloudExtinction("point_three", 3)
case object PointFive extends CloudExtinction("point_five", 5)
Expand All @@ -20,7 +20,7 @@ object CloudExtinction {
case object TwoPointZero extends CloudExtinction("two_point_zero", 20)
case object ThreePointZero extends CloudExtinction("three_point_zero", 30)

implicit val CloudExtinctionEnumerated: Enumerated[CloudExtinction] =
given Enumerated[CloudExtinction] =
Enumerated.from(
PointOne,
PointThree,
Expand All @@ -31,6 +31,5 @@ object CloudExtinction {
ThreePointZero
).withTag(_.tag)

implicit val CloudExtinctionDisplay: Display[CloudExtinction] =
given Display[CloudExtinction] =
Display.byShortName(_.label)
}
12 changes: 6 additions & 6 deletions modules/core/shared/src/main/scala/lucuma/core/enums/F2Fpu.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ enum F2Fpu(

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("LongSlit1", "Long Slit 1px", "1-Pixel Long Slit", 1, F2Decker.LongSlit, false)
case LongSlit2 extends F2Fpu("LongSlit2", "Long Slit 2px", "2-Pixel Long Slit", 2, F2Decker.LongSlit, false)
case LongSlit3 extends F2Fpu("LongSlit3", "Long Slit 3px", "3-Pixel Long Slit", 3, F2Decker.LongSlit, false)
case LongSlit4 extends F2Fpu("LongSlit4", "Long Slit 4px", "4-Pixel Long Slit", 4, F2Decker.LongSlit, false)
case LongSlit6 extends F2Fpu("LongSlit6", "Long Slit 6px", "6-Pixel Long Slit", 6, F2Decker.LongSlit, false)
case LongSlit8 extends F2Fpu("LongSlit8", "Long Slit 8px", "8-Pixel Long Slit", 8, F2Decker.LongSlit, 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)
26 changes: 26 additions & 0 deletions modules/core/shared/src/main/scala/lucuma/core/math/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,29 @@ type LineFluxValue = LineFluxValue.Type
type FluxDensityContinuumValueRefinement = Interval.Closed[0, 1]
object FluxDensityContinuumValue extends RefinedNewType[BigDecimal, FluxDensityContinuumValueRefinement]
type FluxDensityContinuumValue = FluxDensityContinuumValue.Type

// Copied from:
// https://github.com/ghewgill/picomath/blob/master/scala/src/Erf.scala
object Erf:
import scala.math.{abs, exp}

// constants
private val a1: Double = 0.254829592
private val a2: Double = -0.284496736
private val a3: Double = 1.421413741
private val a4: Double = -1.453152027
private val a5: Double = 1.061405429
private val p: Double = 0.3275911

// Calculates the error function using Horner’s method.
def erf(x: Double): Double =
// Save the sign of x
val sign = if (x < 0) -1 else 1
val absx = abs(x)

// A&S formula 7.1.26, rational approximation of error function
val t = 1.0/(1.0 + p*absx);
val y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*exp(-x*x);
sign*y

export Erf.erf
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ def averageParallacticAngle(

// the number of samples we need to have a sampling rate >= than expected
val cnt: Int = math.ceil(defined.duration.toDouble / rate).toInt

// the precise rate in milliseconds that corresponds to the expected rate
val preciseRate: Double = defined.duration.toDouble / cnt
val preciseRate: Double =
if cnt != 0
then defined.duration.toDouble / cnt
else 0.0 // arbitrary, just can't be NaN

/** Calculates a vector with times that cover the given interval. */
val times: Vector[Long] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import monocle.Lens
import monocle.Optional

/** Constraints for an observation. */
final case class ConstraintSet(
case class ConstraintSet(
imageQuality: ImageQuality,
cloudExtinction: CloudExtinction,
skyBackground: SkyBackground,
Expand Down
Loading

0 comments on commit 263342d

Please sign in to comment.