From 45ff6cc23a6d84ab696030d5d0fc46548cf2539f Mon Sep 17 00:00:00 2001 From: Carlos Quiroz Date: Thu, 30 Jan 2025 15:46:12 -0300 Subject: [PATCH] Restore LegacyITC test suite --- .../lucuma/itc/LegacyITCSimulation.scala | 618 ------------------ .../scala/lucuma/itc/legacy/package.scala | 10 +- .../itc/legacy/syntax/InstrumentSyntax.scala | 2 +- .../itc/search/syntax/GmosSouthFpu.scala | 2 +- .../lucuma/itc/tests/LegacyITCSuite.scala | 540 +++++++++++++++ 5 files changed, 545 insertions(+), 627 deletions(-) delete mode 100644 modules/benchmarks/src/test/scala/lucuma/itc/LegacyITCSimulation.scala create mode 100644 modules/tests/src/test/scala/lucuma/itc/tests/LegacyITCSuite.scala diff --git a/modules/benchmarks/src/test/scala/lucuma/itc/LegacyITCSimulation.scala b/modules/benchmarks/src/test/scala/lucuma/itc/LegacyITCSimulation.scala deleted file mode 100644 index 376a41fe..00000000 --- a/modules/benchmarks/src/test/scala/lucuma/itc/LegacyITCSimulation.scala +++ /dev/null @@ -1,618 +0,0 @@ -// 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.itc - -import cats.implicits.* -import coulomb.* -import coulomb.syntax.* -import coulomb.units.si.* -import eu.timepit.refined.types.numeric.PosInt -import io.circe.syntax.* -import io.gatling.core.Predef.* -import io.gatling.http.Predef.* -import io.gatling.http.funspec.GatlingHttpFunSpec -import lucuma.core.enums.* -import lucuma.core.math.Angle -import lucuma.core.math.BrightnessUnits.* -import lucuma.core.math.BrightnessValue -import lucuma.core.math.Redshift -import lucuma.core.math.Wavelength -import lucuma.core.math.dimensional.* -import lucuma.core.math.dimensional.syntax.* -import lucuma.core.math.units.* -import lucuma.core.model.SourceProfile -import lucuma.core.model.SpectralDefinition -import lucuma.core.model.UnnormalizedSED -import lucuma.core.model.sequence.gmos.GmosCcdMode -import lucuma.core.util.Enumerated -import lucuma.itc.legacy.* -import lucuma.itc.legacy.given -import lucuma.itc.search.GmosNorthFpuParam -import lucuma.itc.search.GmosSouthFpuParam -import lucuma.itc.search.ItcObservationDetails -import lucuma.itc.search.ObservingMode -import lucuma.itc.search.TargetData -import lucuma.itc.search.syntax.* -import lucuma.refined.* - -import scala.collection.immutable.SortedMap - -/** - * This is a unit test mostly to ensure all possible combination of params can be parsed by the - * legacy ITC (Note that the ITC may still return an error but we want to ensure it can parse the - * values - */ -class LegacyITCSimulation extends GatlingHttpFunSpec { - val headers_10 = Map("Content-Type" -> """application/json""") - val baseUrl = "https://gemini-new-itc.herokuapp.com" - - val sourceDefinition = ItcSourceDefinition( - TargetData( - SourceProfile.Point( - SpectralDefinition.BandNormalized( - UnnormalizedSED.StellarLibrary(StellarLibrarySpectrum.A0V).some, - SortedMap( - Band.R -> BrightnessValue - .unsafeFrom(5) - .withUnit[VegaMagnitude] - .toMeasureTagged - ) - ) - ), - Redshift(0.03) - ), - Band.R.asLeft - ) - - val lsAnalysisMethod = ItcObservationDetails.AnalysisMethod.Aperture.Auto(5) - val ifuAnalysisMethod = - ItcObservationDetails.AnalysisMethod.Ifu.Single(skyFibres = 250, offset = 5.0) - - val obs = ItcObservationDetails( - calculationMethod = ItcObservationDetails.CalculationMethod.SignalToNoise.Spectroscopy( - exposureCount = 1, - coadds = None, - exposureDuration = 1, - sourceFraction = 1.0, - ditherOffset = Angle.Angle0 - ), - analysisMethod = lsAnalysisMethod - ) - - val telescope = ItcTelescopeDetails( - wfs = ItcWavefrontSensor.OIWFS - ) - val instrument = ItcInstrumentDetails.fromObservingMode( - ObservingMode.SpectroscopyMode.GmosNorth( - Wavelength.decimalNanometers.getOption(600).get, - GmosNorthGrating.B1200_G5301, - GmosNorthFpuParam(GmosNorthFpu.LongSlit_5_00), - none, - GmosCcdMode(GmosXBinning.Two, - GmosYBinning.Two, - GmosAmpCount.Twelve, - GmosAmpGain.High, - GmosAmpReadMode.Fast - ).some, - GmosRoi.FullFrame.some - ) - ) - - val conditions = ItcObservingConditions(ImageQuality.PointEight, - CloudExtinction.OnePointFive, - WaterVapor.Median, - SkyBackground.Bright, - 2 - ) - - def bodyCond(c: ItcObservingConditions) = - ItcParameters( - sourceDefinition, - obs, - c, - telescope, - instrument - ) - - Enumerated[ImageQuality].all.map { iq => - spec { - http("sanity_cond_iq") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodyCond(conditions.copy(iq = iq)).asJson.noSpaces)) - } - } - - Enumerated[CloudExtinction].all.map { ce => - spec { - http("sanity_cond_ce") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodyCond(conditions.copy(cc = ce)).asJson.noSpaces)) - } - } - - Enumerated[WaterVapor].all.map { wv => - spec { - http("sanity_cond_wv") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodyCond(conditions.copy(wv = wv)).asJson.noSpaces)) - } - } - - Enumerated[SkyBackground].all.map { sb => - spec { - http("sanity_cond_sb") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("ItcSpectroscopyResult").exists) - .check(substring("decode").notExists) - .body(StringBody(bodyCond(conditions.copy(sb = sb)).asJson.noSpaces)) - } - } - - val gnConf = ObservingMode.SpectroscopyMode.GmosNorth( - Wavelength.decimalNanometers.getOption(600).get, - GmosNorthGrating.B1200_G5301, - GmosNorthFpuParam(GmosNorthFpu.LongSlit_1_00), - none, - GmosCcdMode(GmosXBinning.Two, - GmosYBinning.Two, - GmosAmpCount.Twelve, - GmosAmpGain.High, - GmosAmpReadMode.Fast - ).some, - GmosRoi.FullFrame.some - ) - - val gsConf = ObservingMode.SpectroscopyMode.GmosSouth( - Wavelength.decimalNanometers.getOption(600).get, - GmosSouthGrating.B1200_G5321, - GmosSouthFpuParam(GmosSouthFpu.LongSlit_1_00), - none, - GmosCcdMode(GmosXBinning.Two, - GmosYBinning.Two, - GmosAmpCount.Twelve, - GmosAmpGain.High, - GmosAmpReadMode.Fast - ).some, - GmosRoi.FullFrame.some - ) - - def bodyConf( - c: ObservingMode.SpectroscopyMode, - analysis: ItcObservationDetails.AnalysisMethod = lsAnalysisMethod - ) = - ItcParameters( - sourceDefinition, - obs.copy(analysisMethod = analysis), - ItcObservingConditions(ImageQuality.PointEight, - CloudExtinction.OnePointFive, - WaterVapor.Median, - SkyBackground.Dark, - 2 - ), - telescope, - ItcInstrumentDetails.fromObservingMode(c) - ) - - Enumerated[GmosNorthGrating].all.map { d => - spec { - http("sanity_gn_disperser") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodyConf(gnConf.copy(disperser = d)).asJson.noSpaces)) - } - } - - Enumerated[GmosNorthFpu].all.map { f => - spec { - http("sanity_gn_fpu") - .post("/json") - .headers(headers_10) - .check(status.in(200, 400)) - .check(substring("decode").notExists) - .body(StringBody(bodyConf(gnConf.copy(fpu = GmosNorthFpuParam(f))).asJson.noSpaces)) - } - } - - Enumerated[GmosNorthFilter].all.map { f => - spec { - http("sanity_gn_filter") - .post("/json") - .headers(headers_10) - .check(status.in(200, 400)) - .check(substring("decode").notExists) - // .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodyConf(gnConf.copy(filter = f.some)).asJson.noSpaces)) - } - } - - Enumerated[GmosSouthGrating].all.map { d => - spec { - http("sanity_gs_disperser") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodyConf(gsConf.copy(disperser = d)).asJson.noSpaces)) - } - } - - Enumerated[GmosSouthFpu].all - .filter(f => f =!= GmosSouthFpu.Bhros) - .map { f => - val conf = - if (f.isGSIfu) - bodyConf(gsConf.copy(fpu = GmosSouthFpuParam(f)), ifuAnalysisMethod) - else - bodyConf(gsConf.copy(fpu = GmosSouthFpuParam(f))) - spec { - http("sanity_gs_fpu") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(conf.asJson.noSpaces)) - } - } - - Enumerated[GmosSouthFilter].all.map { f => - spec { - http("sanity_gn_filter") - .post("/json") - .headers(headers_10) - .check(status.in(200, 400)) - .check(substring("decode").notExists) - // .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodyConf(gsConf.copy(filter = f.some)).asJson.noSpaces)) - } - } - - def bodySED(c: UnnormalizedSED) = - ItcParameters( - sourceDefinition.copy( - target = sourceDefinition.target.copy( - sourceProfile = SourceProfile.unnormalizedSED - .modifyOption(_ => c.some)(sourceDefinition.sourceProfile) - .getOrElse(sourceDefinition.sourceProfile) - ) - ), - obs, - conditions, - telescope, - instrument - ) - - Enumerated[StellarLibrarySpectrum].all.map { f => - spec { - http("stellar_library") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodySED(UnnormalizedSED.StellarLibrary(f)).asJson.noSpaces)) - } - } - - Enumerated[CoolStarTemperature].all.map { f => - spec { - http("cool_star") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodySED(UnnormalizedSED.CoolStarModel(f)).asJson.noSpaces)) - } - } - - Enumerated[GalaxySpectrum].all.map { f => - spec { - http("galaxy") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodySED(UnnormalizedSED.Galaxy(f)).asJson.noSpaces)) - } - } - - Enumerated[PlanetSpectrum].all.map { f => - spec { - http("planet") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodySED(UnnormalizedSED.Planet(f)).asJson.noSpaces)) - } - } - - Enumerated[QuasarSpectrum].all.map { f => - spec { - http("quasar") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodySED(UnnormalizedSED.Quasar(f)).asJson.noSpaces)) - } - } - - Enumerated[HIIRegionSpectrum].all.map { f => - spec { - http("hiiregion") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodySED(UnnormalizedSED.HIIRegion(f)).asJson.noSpaces)) - } - } - - Enumerated[PlanetaryNebulaSpectrum].all.map { f => - spec { - http("quasar") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body(StringBody(bodySED(UnnormalizedSED.PlanetaryNebula(f)).asJson.noSpaces)) - } - } - - def bodyIntMagUnits(c: BrightnessMeasure[Integrated]) = - ItcParameters( - sourceDefinition.copy( - target = sourceDefinition.target.copy( - sourceProfile = SourceProfile - .integratedBrightnessIn(Band.R) - .replace(c)(sourceDefinition.sourceProfile) - ) - ), - obs, - conditions, - telescope, - instrument - ) - - Brightness.Integrated.all.map { f => - spec { - http("integrated units") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body( - StringBody( - bodyIntMagUnits( - f.withValueTagged(BrightnessValue.unsafeFrom(5)) - ).asJson.noSpaces - ) - ) - } - } - - def bodySurfaceMagUnits(c: BrightnessMeasure[Surface]) = - ItcParameters( - sourceDefinition.copy( - target = sourceDefinition.target.copy( - sourceProfile = SourceProfile.Uniform( - SpectralDefinition.BandNormalized( - UnnormalizedSED.StellarLibrary(StellarLibrarySpectrum.A0V).some, - SortedMap(Band.R -> c) - ) - ) - ) - ), - obs, - conditions, - telescope, - instrument - ) - - Brightness.Surface.all.map { f => - spec { - http("surface units") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body( - StringBody( - bodySurfaceMagUnits( - f.withValueTagged(BrightnessValue.unsafeFrom(5)) - ).asJson.noSpaces - ) - ) - } - } - - def bodyIntGaussianMagUnits(c: BrightnessMeasure[Integrated]) = - ItcParameters( - sourceDefinition.copy( - target = sourceDefinition.target.copy( - sourceProfile = SourceProfile.Gaussian( - Angle.fromDoubleArcseconds(10), - SpectralDefinition.BandNormalized( - UnnormalizedSED.StellarLibrary(StellarLibrarySpectrum.A0V).some, - SortedMap(Band.R -> c) - ) - ) - ) - ), - obs, - conditions, - telescope, - instrument - ) - - Brightness.Integrated.all.map { f => - spec { - http("gaussian integrated units") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body( - StringBody( - bodyIntGaussianMagUnits( - f.withValueTagged(BrightnessValue.unsafeFrom(5)) - ).asJson.noSpaces - ) - ) - } - } - - def bodyPowerLaw(c: Int) = - ItcParameters( - sourceDefinition.copy( - target = sourceDefinition.target.copy( - sourceProfile = SourceProfile.Gaussian( - Angle.fromDoubleArcseconds(10), - SpectralDefinition.BandNormalized( - UnnormalizedSED.PowerLaw(c).some, - SortedMap( - Band.R -> - BrightnessValue - .unsafeFrom(5) - .withUnit[VegaMagnitude] - .toMeasureTagged - ) - ) - ) - ) - ), - obs, - conditions, - telescope, - instrument - ) - - List(-10, 0, 10, 100).map { f => - spec { - http("power law") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body( - StringBody( - bodyPowerLaw(f).asJson.noSpaces - ) - ) - } - } - - def bodyBlackBody(c: PosInt) = - ItcParameters( - sourceDefinition.copy( - target = sourceDefinition.target.copy( - sourceProfile = SourceProfile.Gaussian( - Angle.fromDoubleArcseconds(10), - SpectralDefinition.BandNormalized( - UnnormalizedSED.BlackBody(c.withUnit[Kelvin]).some, - SortedMap( - Band.R -> - BrightnessValue - .unsafeFrom(5) - .withUnit[VegaMagnitude] - .toMeasureTagged - ) - ) - ) - ) - ), - obs, - conditions, - telescope, - instrument - ) - - List[PosInt](10.refined, 100.refined).map { f => - spec { - http("black body") - .post("/json") - .headers(headers_10) - .check(status.in(200)) - .check(substring("decode").notExists) - .check(substring("ItcSpectroscopyResult").exists) - .body( - StringBody( - bodyBlackBody(f).asJson.noSpaces - ) - ) - } - } - - // def bodyEmissionLine(c: PosBigDecimal) = - // ItcParameters( - // sourceDefinition.copy(profile = - // SourceProfile.Point( - // SpectralDefinition.EmissionLines( - // SortedMap( - // Wavelength.decimalNanometers.getOption(600).get -> EmissionLine( - // c.withUnit[KilometersPerSecond], - // c - // .withUnit[WattsPerMeter2] - // .toMeasureTagged - // ) - // ), - // BigDecimal(5) - // .withRefinedUnit[Positive, WattsPerMeter2Micrometer] - // .toMeasureTagged - // ) - // ) - // ), - // obs, - // conditions, - // telescope, - // instrument - // ) - // - // List[PosBigDecimal](BigDecimal(0.1), BigDecimal(10), BigDecimal(100)).map { f => - // println(bodyEmissionLine(f).asJson) - // spec { - // http("emission line") - // .post("/json") - // .headers(headers_10) - // .check(status.in(200)) - // .check(substring("decode").notExists) - // .check(substring("ItcSpectroscopyResult").exists) - // .body( - // StringBody( - // bodyEmissionLine(f).asJson.noSpaces - // ) - // ) - // } - // } - -} diff --git a/modules/service/src/main/scala/lucuma/itc/legacy/package.scala b/modules/service/src/main/scala/lucuma/itc/legacy/package.scala index 9e8e81af..c0511404 100644 --- a/modules/service/src/main/scala/lucuma/itc/legacy/package.scala +++ b/modules/service/src/main/scala/lucuma/itc/legacy/package.scala @@ -38,10 +38,6 @@ case class ItcParameters( case class ItcInstrumentDetails(mode: ObservingMode) -object ItcInstrumentDetails: - def fromObservingMode(mode: ObservingMode): ItcInstrumentDetails = - apply(mode) - private def buildSourceDefinition( target: TargetData, atWavelength: Wavelength @@ -77,7 +73,7 @@ def spectroscopyGraphParams( telescope = ItcTelescopeDetails( wfs = ItcWavefrontSensor.OIWFS ), - instrument = ItcInstrumentDetails.fromObservingMode(observingMode) + instrument = ItcInstrumentDetails(observingMode) ) (parameters, bandOrLine) @@ -108,7 +104,7 @@ def spectroscopyExposureTimeParams( telescope = ItcTelescopeDetails( wfs = ItcWavefrontSensor.OIWFS ), - instrument = ItcInstrumentDetails.fromObservingMode(observingMode) + instrument = ItcInstrumentDetails(observingMode) ) (parameters, bandOrLine) @@ -137,6 +133,6 @@ def imagingParams( telescope = ItcTelescopeDetails( wfs = ItcWavefrontSensor.OIWFS ), - instrument = ItcInstrumentDetails.fromObservingMode(observingMode) + instrument = ItcInstrumentDetails(observingMode) ) (parameters, bandOrLine) diff --git a/modules/service/src/main/scala/lucuma/itc/legacy/syntax/InstrumentSyntax.scala b/modules/service/src/main/scala/lucuma/itc/legacy/syntax/InstrumentSyntax.scala index 15f7aa84..e54a7892 100644 --- a/modules/service/src/main/scala/lucuma/itc/legacy/syntax/InstrumentSyntax.scala +++ b/modules/service/src/main/scala/lucuma/itc/legacy/syntax/InstrumentSyntax.scala @@ -173,7 +173,7 @@ trait GmosSouthFpuSyntax: case LongSlit_1_50 => "LONGSLIT_5" case LongSlit_2_00 => "LONGSLIT_6" case LongSlit_5_00 => "LONGSLIT_7" - case Bhros => "BHROs" + case Bhros => "BHROS" } object gmossouthfpu extends GmosSouthFpuSyntax diff --git a/modules/service/src/main/scala/lucuma/itc/search/syntax/GmosSouthFpu.scala b/modules/service/src/main/scala/lucuma/itc/search/syntax/GmosSouthFpu.scala index 165639c1..952b1acb 100644 --- a/modules/service/src/main/scala/lucuma/itc/search/syntax/GmosSouthFpu.scala +++ b/modules/service/src/main/scala/lucuma/itc/search/syntax/GmosSouthFpu.scala @@ -26,4 +26,4 @@ extension (self: GmosSouthFpu) case LongSlit_1_50 => false case LongSlit_2_00 => false case LongSlit_5_00 => false - case Bhros => sys.error("obsolete") + case Bhros => false diff --git a/modules/tests/src/test/scala/lucuma/itc/tests/LegacyITCSuite.scala b/modules/tests/src/test/scala/lucuma/itc/tests/LegacyITCSuite.scala new file mode 100644 index 00000000..23e44528 --- /dev/null +++ b/modules/tests/src/test/scala/lucuma/itc/tests/LegacyITCSuite.scala @@ -0,0 +1,540 @@ +// 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.itc + +import cats.implicits.* +import coulomb.* +import coulomb.syntax.* +import coulomb.units.si.* +import eu.timepit.refined.types.numeric.PosInt +import io.circe.syntax.* +import lucuma.core.enums.* +import lucuma.core.math.Angle +import lucuma.core.math.BrightnessUnits.* +import lucuma.core.math.BrightnessValue +import lucuma.core.math.Redshift +import lucuma.core.math.Wavelength +import lucuma.core.math.dimensional.* +import lucuma.core.math.dimensional.Units +import lucuma.core.math.dimensional.syntax.* +import lucuma.core.math.units.* +import lucuma.core.model.SourceProfile +import lucuma.core.model.SpectralDefinition +import lucuma.core.model.UnnormalizedSED +import lucuma.core.model.sequence.gmos.GmosCcdMode +import lucuma.core.util.* +import lucuma.itc.legacy.* +import lucuma.itc.legacy.given +import lucuma.itc.search.GmosNorthFpuParam +import lucuma.itc.search.GmosSouthFpuParam +import lucuma.itc.search.ItcObservationDetails +import lucuma.itc.search.ObservingMode +import lucuma.itc.search.TargetData +import lucuma.itc.service.Main.ReverseClassLoader +import munit.FunSuite + +import java.io.File +import java.io.FileFilter +import scala.collection.immutable.SortedMap + +/** + * This is a unit test mostly to ensure all possible combination of params can be parsed by the + * legacy ITC (Note that the ITC may still return an error but we want to ensure it can parse the + * values + */ +class LegacyITCSuite extends FunSuite { + + val sourceDefinition = ItcSourceDefinition( + TargetData( + SourceProfile.Point( + SpectralDefinition.BandNormalized( + UnnormalizedSED.StellarLibrary(StellarLibrarySpectrum.A0V).some, + SortedMap( + Band.R -> BrightnessValue + .unsafeFrom(9) + .withUnit[VegaMagnitude] + .toMeasureTagged + ) + ) + ), + Redshift(0.03) + ), + Band.R.asLeft + ) + + val lsAnalysisMethod = ItcObservationDetails.AnalysisMethod.Aperture.Auto(5) + val ifuAnalysisMethod = + ItcObservationDetails.AnalysisMethod.Ifu.Single(skyFibres = 250, offset = 5.0) + + val obs = ItcObservationDetails( + calculationMethod = ItcObservationDetails.CalculationMethod.SignalToNoise.SpectroscopyWithSNAt( + sigma = 1, + wavelength = Wavelength.decimalNanometers.getOption(600).get, + coadds = None, + sourceFraction = 1.0, + ditherOffset = Angle.Angle0 + ), + analysisMethod = lsAnalysisMethod + ) + + val telescope = ItcTelescopeDetails( + wfs = ItcWavefrontSensor.OIWFS + ) + val instrument = ItcInstrumentDetails( + ObservingMode.SpectroscopyMode.GmosNorth( + Wavelength.decimalNanometers.getOption(600).get, + GmosNorthGrating.B1200_G5301, + GmosNorthFpuParam(GmosNorthFpu.LongSlit_5_00), + none, + GmosCcdMode(GmosXBinning.One, + GmosYBinning.One, + GmosAmpCount.Twelve, + GmosAmpGain.High, + GmosAmpReadMode.Fast + ).some, + GmosRoi.FullFrame.some + ) + ) + + val conditions = ItcObservingConditions(ImageQuality.PointEight, + CloudExtinction.OnePointFive, + WaterVapor.Median, + SkyBackground.Bright, + 1 + ) + + def bodyCond(c: ItcObservingConditions) = + ItcParameters( + sourceDefinition, + obs, + c, + telescope, + instrument + ) + + def allowedErrors(err: List[String]) = + err.exists(_.contains("Invalid S/N")) || err.exists(_.contains("do not overlap")) || + err.exists(_.contains("Unsupported configuration")) || + err.exists(_.contains("Unsupported calculation method")) || + err.exists(_.contains("target is too bright")) + + val localItc = { + val jarFiles = + new File("modules/service/ocslib").listFiles(new FileFilter() { + override def accept(file: File): Boolean = + file.getName().endsWith(".jar"); + }) + LocalItc( + new ReverseClassLoader(jarFiles.map(_.toURI.toURL), ClassLoader.getSystemClassLoader()) + ) + } + + test("image quality") { + Enumerated[ImageQuality].all.foreach { iq => + assert( + localItc + .calculateIntegrationTime(bodyCond(conditions.copy(iq = iq)).asJson.noSpaces) + .isRight + ) + } + } + + test("cloud extinction") { + Enumerated[CloudExtinction].all.foreach { ce => + assert( + localItc + .calculateIntegrationTime(bodyCond(conditions.copy(cc = ce)).asJson.noSpaces) + .isRight + ) + } + } + + test("water vapor") { + Enumerated[WaterVapor].all.foreach { wv => + assert( + localItc + .calculateIntegrationTime(bodyCond(conditions.copy(wv = wv)).asJson.noSpaces) + .isRight + ) + } + } + + test("sky background") { + Enumerated[SkyBackground].all.foreach { sb => + assert( + localItc + .calculateIntegrationTime(bodyCond(conditions.copy(sb = sb)).asJson.noSpaces) + .isRight + ) + } + } + + val gnConf = ObservingMode.SpectroscopyMode.GmosNorth( + Wavelength.decimalNanometers.getOption(600).get, + GmosNorthGrating.B1200_G5301, + GmosNorthFpuParam(GmosNorthFpu.LongSlit_1_00), + none, + GmosCcdMode(GmosXBinning.One, + GmosYBinning.One, + GmosAmpCount.Twelve, + GmosAmpGain.High, + GmosAmpReadMode.Fast + ).some, + GmosRoi.FullFrame.some + ) + + def bodyConf( + c: ObservingMode.SpectroscopyMode, + analysis: ItcObservationDetails.AnalysisMethod = lsAnalysisMethod + ) = + ItcParameters( + sourceDefinition, + obs.copy(analysisMethod = analysis), + ItcObservingConditions(ImageQuality.PointEight, + CloudExtinction.OnePointFive, + WaterVapor.Median, + SkyBackground.Dark, + 2 + ), + telescope, + ItcInstrumentDetails(c) + ) + + test("gmos north grating") { + Enumerated[GmosNorthGrating].all.map { d => + assert( + localItc + .calculateIntegrationTime(bodyConf(gnConf.copy(disperser = d)).asJson.noSpaces) + .isRight + ) + } + } + + test("gmos north filter") { + Enumerated[GmosNorthFilter].all.map { f => + val result = localItc + .calculateIntegrationTime(bodyConf(gnConf.copy(filter = f.some)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + test("gmos north fpu") { + Enumerated[GmosNorthFpu].all.map { f => + val result = localItc + .calculateIntegrationTime( + bodyConf(gnConf.copy(fpu = GmosNorthFpuParam(f)), + analysis = if (f.isIFU) ifuAnalysisMethod else lsAnalysisMethod + ).asJson.noSpaces + ) + assert(result.fold(allowedErrors, _ => true)) + } + } + + val gsConf = ObservingMode.SpectroscopyMode.GmosSouth( + Wavelength.decimalNanometers.getOption(600).get, + GmosSouthGrating.B1200_G5321, + GmosSouthFpuParam(GmosSouthFpu.LongSlit_1_00), + none, + GmosCcdMode(GmosXBinning.One, + GmosYBinning.One, + GmosAmpCount.Twelve, + GmosAmpGain.High, + GmosAmpReadMode.Fast + ).some, + GmosRoi.FullFrame.some + ) + + test("gmos south grating") { + Enumerated[GmosSouthGrating].all.map { d => + assert( + localItc + .calculateIntegrationTime(bodyConf(gsConf.copy(disperser = d)).asJson.noSpaces) + .isRight + ) + } + } + + test("gmos south filter") { + Enumerated[GmosSouthFilter].all.map { f => + val result = localItc + .calculateIntegrationTime(bodyConf(gsConf.copy(filter = f.some)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + test("gmos south fpu") { + Enumerated[GmosSouthFpu].all.foreach { f => + val result = localItc + .calculateIntegrationTime( + bodyConf(gsConf.copy(fpu = GmosSouthFpuParam(f)), + analysis = if (f.isIFU) ifuAnalysisMethod else lsAnalysisMethod + ).asJson.noSpaces + ) + assert(result.fold(allowedErrors, _ => true)) + } + } + + def bodySED(c: UnnormalizedSED) = + ItcParameters( + sourceDefinition.copy( + target = sourceDefinition.target.copy( + sourceProfile = SourceProfile.unnormalizedSED + .modifyOption(_ => c.some)(sourceDefinition.sourceProfile) + .getOrElse(sourceDefinition.sourceProfile) + ) + ), + obs, + conditions, + telescope, + instrument + ) + + test("stellar library spectrum") { + Enumerated[StellarLibrarySpectrum].all.map { f => + val result = localItc + .calculateIntegrationTime(bodySED(UnnormalizedSED.StellarLibrary(f)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + test("cool star") { + Enumerated[CoolStarTemperature].all.map { f => + val result = localItc + .calculateIntegrationTime(bodySED(UnnormalizedSED.CoolStarModel(f)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + test("galaxy spectrum") { + Enumerated[GalaxySpectrum].all.map { f => + val result = localItc + .calculateIntegrationTime(bodySED(UnnormalizedSED.Galaxy(f)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + test("planet spectrum") { + Enumerated[PlanetSpectrum].all.map { f => + val result = localItc + .calculateIntegrationTime(bodySED(UnnormalizedSED.Planet(f)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + test("quasar spectrum") { + Enumerated[QuasarSpectrum].all.map { f => + val result = localItc + .calculateIntegrationTime(bodySED(UnnormalizedSED.Quasar(f)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + test("hii region spectrum") { + Enumerated[HIIRegionSpectrum].all.map { f => + val result = localItc + .calculateIntegrationTime(bodySED(UnnormalizedSED.HIIRegion(f)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + test("planetary nebula spectrum") { + Enumerated[PlanetaryNebulaSpectrum].all.map { f => + val result = localItc + .calculateIntegrationTime(bodySED(UnnormalizedSED.PlanetaryNebula(f)).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + def bodyIntMagUnits(c: BrightnessMeasure[Integrated]) = + ItcParameters( + sourceDefinition.copy( + target = sourceDefinition.target.copy( + sourceProfile = SourceProfile + .integratedBrightnessIn(Band.R) + .replace(c)(sourceDefinition.sourceProfile) + ) + ), + obs, + conditions, + telescope, + instrument + ) + + test("brightness integrated units") { + Brightness.Integrated.all.map { f => + val result = localItc + .calculateIntegrationTime( + bodyIntMagUnits(f.withValueTagged(BrightnessValue.unsafeFrom(5))).asJson.noSpaces + ) + assert(result.fold(allowedErrors, _ => true)) + } + } + + def bodySurfaceMagUnits(c: BrightnessMeasure[Surface]) = + ItcParameters( + sourceDefinition.copy( + target = sourceDefinition.target.copy( + sourceProfile = SourceProfile.Uniform( + SpectralDefinition.BandNormalized( + UnnormalizedSED.StellarLibrary(StellarLibrarySpectrum.A0V).some, + SortedMap(Band.R -> c) + ) + ) + ) + ), + obs, + conditions, + telescope, + instrument + ) + + test("surface units") { + Brightness.Surface.all.map { f => + val result = localItc + .calculateIntegrationTime( + bodySurfaceMagUnits(f.withValueTagged(BrightnessValue.unsafeFrom(5))).asJson.noSpaces + ) + assert(result.fold(allowedErrors, _ => true)) + } + } + + def bodyIntGaussianMagUnits(c: BrightnessMeasure[Integrated]) = + ItcParameters( + sourceDefinition.copy( + target = sourceDefinition.target.copy( + sourceProfile = SourceProfile.Gaussian( + Angle.fromDoubleArcseconds(10), + SpectralDefinition.BandNormalized( + UnnormalizedSED.StellarLibrary(StellarLibrarySpectrum.A0V).some, + SortedMap(Band.R -> c) + ) + ) + ) + ), + obs, + conditions, + telescope, + instrument + ) + + test("gaussian units") { + Brightness.Integrated.all.map { f => + val result = localItc + .calculateIntegrationTime( + bodyIntGaussianMagUnits(f.withValueTagged(BrightnessValue.unsafeFrom(5))).asJson.noSpaces + ) + assert(result.fold(allowedErrors, _ => true)) + } + } + + def bodyPowerLaw(c: Int) = + ItcParameters( + sourceDefinition.copy( + target = sourceDefinition.target.copy( + sourceProfile = SourceProfile.Gaussian( + Angle.fromDoubleArcseconds(10), + SpectralDefinition.BandNormalized( + UnnormalizedSED.PowerLaw(c).some, + SortedMap( + Band.R -> + BrightnessValue + .unsafeFrom(5) + .withUnit[VegaMagnitude] + .toMeasureTagged + ) + ) + ) + ) + ), + obs, + conditions, + telescope, + instrument + ) + + test("power law") { + List(-10, 0, 10, 100).map { f => + val result = localItc + .calculateIntegrationTime(bodyPowerLaw(f).asJson.noSpaces) + assert(result.fold(allowedErrors, _ => true)) + } + } + + def bodyBlackBody(c: PosInt) = + ItcParameters( + sourceDefinition.copy( + target = sourceDefinition.target.copy( + sourceProfile = SourceProfile.Gaussian( + Angle.fromDoubleArcseconds(10), + SpectralDefinition.BandNormalized( + UnnormalizedSED.BlackBody(c.withUnit[Kelvin]).some, + SortedMap( + Band.R -> + BrightnessValue + .unsafeFrom(5) + .withUnit[VegaMagnitude] + .toMeasureTagged + ) + ) + ) + ) + ), + obs, + conditions, + telescope, + instrument + ) + + // test("black body") { + // List[PosInt](10.refined, 100.refined).map { f => + // val result = localItc + // .calculateIntegrationTime(bodyBlackBody(f).asJson.noSpaces) + // println(result) + // assert(result.fold(allowedErrors, _ => true)) + // } + // } + + // def bodyEmissionLine(c: PosBigDecimal) = + // ItcParameters( + // sourceDefinition.copy(profile = + // SourceProfile.Point( + // SpectralDefinition.EmissionLines( + // SortedMap( + // Wavelength.decimalNanometers.getOption(600).get -> EmissionLine( + // c.withUnit[KilometersPerSecond], + // c + // .withUnit[WattsPerMeter2] + // .toMeasureTagged + // ) + // ), + // BigDecimal(5) + // .withRefinedUnit[Positive, WattsPerMeter2Micrometer] + // .toMeasureTagged + // ) + // ) + // ), + // obs, + // conditions, + // telescope, + // instrument + // ) + // + // List[PosBigDecimal](BigDecimal(0.1), BigDecimal(10), BigDecimal(100)).map { f => + // println(bodyEmissionLine(f).asJson) + // spec { + // http("emission line") + // .post("/json") + // .headers(headers_10) + // .check(status.in(200)) + // .check(substring("decode").notExists) + // .check(substring("ItcSpectroscopyResult").exists) + // .body( + // StringBody( + // bodyEmissionLine(f).asJson.noSpaces + // ) + // ) + // } + // } + +}