<resource schema="mlcolour" resdir=".">
	<meta name="creationDate">2025-06-27T11:27:07Z</meta>

	<meta name="title">LCO light curves of gravitationally lensed quasars</meta>
	<meta name="description">
		This resource publishes the R and V band light curves of the individual
		images of lensed quasars, in particular HE0435-1223, HE1104-1805,
		HE2149-2745, and Q2237+0305.  The light curves show intrinsic quasar
		variations and additionally are affected by chromatic microlensing.  Access
		this data from our ivoa.obscore table.  The collection name is ``Chroma
		ML``.
	</meta>
	<meta name="subject">quasars</meta>
	<meta name="subject">light-curves</meta>
	<meta name="subject">gravitational-lensing</meta>
	<meta name="subject">strong-gravitational-lensing</meta>
	<meta name="subject">gravitational-microlensing</meta>
	<meta name="subject">quasar-microlensing</meta>

	<meta name="creator">Sorgenfrei, Christian; Schmidt, Robert W.;
		Wambsganss, Joachim</meta>
	<meta name="instrument">1-meter Sinistro telescopes</meta>
	<meta name="facility">Las Cumbres Observatory (LCO)</meta>

	<meta name="source">2024A&amp;A...683A.119S</meta>
	<meta name="contentLevel">Research</meta>
	<meta name="type">Archive</meta>

	<meta name="coverage.waveband">Optical</meta>

	<meta name="ssap.dataSource">pointed</meta>
	<meta name="ssap.creationType">archival</meta>
	<meta name="productType">spectrum</meta>
	<meta name="ssap.testQuery">MAXREC=1</meta>

	<FEED source="//procs#license-cc-by" what="The LCO quasar lightcurves"/>

	<table id="raw_data" onDisk="True" adql="hidden"
			namePath="//ssap#instance">
		<LOOP listItems="ssa_dstitle ssa_targname ssa_length ssa_timeExt
			ssa_bandpass ssa_specstart ssa_specend">
			<events>
				<column original="\item"/>
			</events>
		</LOOP>
		<column original="//obscore#ObsCore.t_min"/>
		<column original="//obscore#ObsCore.t_max"/>
		<column name="component_id" type="text"
			ucd="meta.code.multip"
			tablehead="Comp"
			description="Identifier for the component (i.e., lens image)"/>

		<mixin>//products#table</mixin>
		<mixin>//ssap#plainlocation</mixin>
		<mixin>//ssap#simpleCoverage</mixin>
	</table>

	<data id="import">
		<recreateAfter>make_view</recreateAfter>
		<property key="previewDir">previews</property>
		<sources recurse="True"
			pattern="data/*-ts.txt"/>

		<embeddedGrammar>
			<iterator>
				<setup imports="gavo.protocols.simbadinterface">
					<par name="object_meta">{
						'he0435': {
							'lens_name': 'HE 0435-1223',
							'ra': 069.56208,
							'dec': -12.28733,
							},
						'he1104': {
							'lens_name': 'HE 1104-1805',
							},
						'he2149': {
							'lens_name': 'HE2149-2745',
							},
						'q2237': {
							'lens_name': 'Q2237+0305',
							},
						}
					</par>
				</setup>
				<code>
					row = {}
					mat = re.match(".*/(.*)(.)-ts.txt$", self.sourceToken)
					obj_name = mat.group(1)
					row["ssa_bandpass"] = "Bessell "+mat.group(2)
					if row["ssa_bandpass"]=='Bessell R':
						row["ssa_specstart"], row["ssa_specend"] = 500e-9, 850e-9
					elif row["ssa_bandpass"]=='Bessell V':
						row["ssa_specstart"], row["ssa_specend"] = 485e-9, 670e-9

					row.update(object_meta[obj_name])
					with open(self.sourceToken) as f:
						lines = f.readlines()
					
					if "ra" not in row:
						row["ra"], row["dec"] = simbadinterface.getSimbadPositions(
							row["lens_name"])

					# col 0 is mjd; and then we have 4..8 columns with photometry
					# for the components which we return as separate time series
					row["t_min"] = float(lines[0].split()[0])
					row["t_max"] = float(lines[-1].split()[0])
					row["ssa_length"] = len(lines)

					base_st = self.sourceToken[:-4]
					# we munge the accrefs to lose the .txt and add the component
					# id
					n_comp = (len(lines[0].split())-1)//2
					for comp_id in [chr(c) for c in range(65, 65+n_comp)]:
						self.sourceToken = base_st+"-"+comp_id
						row["component_id"] = comp_id
						row["ssa_targname"] = row["lens_name"]+f" Comp. {comp_id}"
						yield row
				</code>
			</iterator>
			<rowfilter procDef="//products#define">
				<bind key="table">"\schema.raw_data"</bind>
				<bind key="path">\fullDLURL{sdl}</bind>
				<bind key="fsize">100000</bind>
				<bind key="datalink">"\rdId#sdl"</bind>
				<bind key="mime">"application/x-votable+xml"</bind>
				<bind key="preview">\standardPreviewPath</bind>
				<bind key="preview_mime">"image/png"</bind>
			</rowfilter>
		</embeddedGrammar>

		<make table="raw_data">
			<rowmaker idmaps="*" id="basic-import">
				<apply procDef="//ssap#fill-plainlocation">
					<bind key="ra">@ra</bind>
					<bind key="dec">@dec</bind>
					<bind key="aperture">0.1/3600</bind>
				</apply>

				<map key="ssa_dstitle">"{} light curve for {}".format(
					@ssa_bandpass, @ssa_targname)</map>
				<map key="ssa_timeExt">(@t_max-@t_min)*86400</map>
			</rowmaker>
		</make>
	</data>


	<table id="data" onDisk="True" adql="hidden">
		<!-- the SSA table (on which the service is based -->

		<meta name="_associatedDatalinkService">
			<meta name="serviceId">sdl</meta>
			<meta name="idColumn">ssa_pubDID</meta>
		</meta>

		<mixin
			sourcetable="raw_data"
			copiedcolumns="*"
			ssa_aperture="0.1/3600."
			ssa_dstype="'timeseries'"
			ssa_fluxunit="'mag'"
			ssa_spectralunit="''"
			ssa_collection="'Chroma ML'"
			ssa_fluxcalib="'ABSOLUTE'"
			ssa_fluxucd="'phot.mag'"
			ssa_speccalib="'ABSOLUTE'"
			ssa_spectralucd="''"
			ssa_targclass="'lensed-q'"
		>//ssap#view</mixin>

<!--		<mixin
			calibLevel="2"
			coverage="ssa_region"
			sResolution="ssa_spaceRes"
			oUCD="ssa_fluxucd"
			emUCD="ssa_spectralucd"
			>//obscore#publishSSAPMIXC</mixin>-->
	</table>

	<data id="make_view" auto="False">
		<make table="data"/>
	</data>

	<coverage>
		<updater sourceTable="data"/>
	</coverage>

	<table id="instance" onDisk="False">
		<meta name="description">The \metaString{source} light curve
			for {ssa_targname} in the {ssa_bandpass} filter.</meta>
		<mixin
			effectiveWavelength="@ssa_specmid"
			filterIdentifier="@ssa_bandpass"
			magnitudeSystem="Vega"
			phot_description="Gaia-calibrated magnitude"
			phot_ucd="phot.mag"
			phot_unit="mag"
			refposition="TOPOCENTER"
			refframe="ICRS"
			time0="2400000.5"
			timescale="UTC"
			>//timeseries#phot-0</mixin>
		<param original="data.ssa_bandpass"/>
		<param original="data.ssa_specstart"/>
		<param original="data.ssa_specmid"/>
		<param original="data.ssa_specend"/>
		<param original="data.ssa_location"/>
		<column name="err_phot" type="double precision"
			unit="mag" ucd="stat.error;phot.flux"
			description="Error in the magnitude"/>
	</table>

	<data id="build-ts" auto="False">
		<!-- The source token is the datalink descriptor from the sdl
			datalink service; we need, in particular, the component_id
			in order to let us pick the right phot/error column pair. -->
		<embeddedGrammar>
			<iterator>
				<code>
					phot_col = 1+(ord(self.sourceToken.ssa_row["component_id"])-65)*2
					err_col = phot_col+1
					with open(self.sourceToken.src_name) as f:
						for ln in f:
							parts = ln.split()
							yield {
								'obs_time': float(parts[0]),
								'phot': float(parts[phot_col]),
								'err_phot': float(parts[phot_col+1]),}
				</code>
			</iterator>
			<pargetter>
				<code>
					return self.sourceToken.ssa_row
				</code>
			</pargetter>
		</embeddedGrammar>

		<make table="instance">
			<rowmaker idmaps="*" id="make-ts"/>
			<parmaker id="make-ts-par" idmaps="ssa_bandpass, ssa_specstart,
				ssa_specend, ssa_location, ssa_specmid">
			</parmaker>
		</make>
	</data>

	<!-- the datalink service spitting out SDM spectra (and other formats
		on request) -->
	<service id="sdl" allowed="dlget,dlmeta">
		<meta name="title">\schema Datalink Service</meta>
		<datalinkCore>
			<descriptorGenerator>
				<code>
					# The pubdid is the inputsDir-relative path with
					# a component Id added and the .txt removed.  To read
					# the data, undo this.
					descriptor = ProductDescriptor.fromAccref(
						pubDID, pubDID.split("?")[-1])
					mat = re.search(r"\?(.*)-([ABCD])$", pubDID)
					descriptor.src_name = base.getConfig("inputsDir"
						) / (mat.group(1)+".txt")
					descriptor.component_id = mat.group(2)

					with base.getTableConn() as conn:
						descriptor.ssa_row = next(conn.queryToDicts(
							"SELECT * FROM \schema.data"
							" WHERE ssa_pubdid=%(pubDID)s",
							{"pubDID": pubDID}))
					return descriptor
				</code>
			</descriptorGenerator>
			<dataFunction>
				<setup imports="gavo.rsc"/>
				<code>
					descriptor.data = rsc.makeData(
						rd.getById("build-ts"),
						forceSource=descriptor)

					tab = descriptor.data.getPrimaryTable()
					tab.setMeta("description",
						base.getMetaText(tab, "description").format( **descriptor.ssa_row))

					mag_ucd = "phot.mag;em.opt.V"
					if descriptor.ssa_row["ssa_bandpass"]=="Bessell R":
						mag_ucd = "phot.mag;em.opt.R"
					tab.tableDef.getColumnByName("phot").ucd = mag_ucd
					tab.tableDef.getColumnByName("err_phot").ucd = "stat.error;"+mag_ucd
				</code>
			</dataFunction>

			<dataFormatter>
				<setup imports="gavo.formats.votablewrite"/>
				<code>
					return ("application/x-votable+xml;version=1.5",
						votablewrite.getAsVOTable(descriptor.data, version=(1,5)))
				</code>
			</dataFormatter>
		</datalinkCore>
	</service>

	<service id="web" defaultRenderer="fixed">
		<meta name="shortName">\schema Web</meta>

		<fixedQueryCore
			query="select accref, ssa_targname, ssa_bandpass, t_min, t_max
				from \schema.raw_data">
			<outputTable namePath="raw_data"
				autoCols="accref, ssa_targname, ssa_bandpass, t_min, t_max"/>
		</fixedQueryCore>
	</service>

	<regSuite title="mlcolour regression">

		<regTest title="mlcolour web shows data">
			<url>web/form</url>
			<code>
				self.assertHasStrings(
					"HE 0435-1223 Comp. B",
					"2015-09-13T09:24:20Z", # t_min 0435R
					'/getproduct/mlcolour/data/he0435R-ts-A')
			</code>
		</regTest>

		<regTest title="mlcolour dataset generation works">
			<url httpHonorRedirects="True"
				>/getproduct/mlcolour/data/he0435R-ts-D</url>
			<code><![CDATA[
				rows = self.getVOTableRows()
				self.assertAlmostEqual(rows[0]["obs_time"], 57278.3919)
				self.assertAlmostEqual(rows[-1]["phot"], 19.1306)
				self.assertAlmostEqual(rows[-1]["err_phot"], 0.0118)

				self.assertXpath("//v:TIMESYS", {
					"refposition": "TOPOCENTER",
					"timescale": "UTC"})
				self.assertXpath("//v:FIELD[@name='phot']", {
					"ucd": "phot.mag;em.opt.R"})
				self.assertXpath(
					"//v:PARAM[@ID=//v:PARAMref[@utype='photDM:PhotometryFilter.identifier']/@ref]", {
						"value": "Bessell R",})
				self.assertXpath("//v:TABLE/v:DESCRIPTION", {
					None: "The 2024A&A...683A.119S light curve for"
						" HE 0435-1223 Comp. D in the\\nBessell R filter."})
			]]></code>
		</regTest>

	</regSuite>
</resource>
