<resource schema="toss">
	<meta name="title">TOSS -- Tübingen Oscillator Strengths Service</meta>
	<meta name="creationDate">2015-08-10T11:30:00</meta>
	<meta name="schema-rank">100</meta>
	<meta name="description">
		This service provides oscillator strengths and transition probabilities.
		Mainly based on experimental energy levels, these were calculated with the
		pseudo-relativistic Hartree-Fock method including core-polarization
		corrections.
	</meta>
	<meta name="copyright">
		When publishing research making use of this tool, please acknowledge:
		"The TOSS service (http://dc.g-vo.org/TOSS) used for this paper
		was constructed as part of the activities of the German Astrophysical
		Virtual Observatory."
	</meta>
	<meta name="creator">Rauch, T.; Quinet, P.; Hoyer, D.; Werner, K.;
		Demleitner, M.; Kruk, J. W.</meta>

	<meta name="subject">atomic-physics</meta>
	<meta name="subject">spectral-line-identification</meta>

	<meta name="source">2016A&amp;A...587A..39R</meta>
	<meta name="doi">10.21938/3i01isnuCODnh1zjbCvuwA</meta>

	<meta name="_news" date="2016-01-15" author="MD">
		Added lines for Krypton.
	
	</meta>
	<meta name="_news" date="2016-09-26" author="MD">
		Added lines for Zirconium and Xenon.
	</meta>

	<meta name="_news" date="2017-01-17" author="MD">
		Added lines for Selen, Tellur, and Iodine.
	</meta>

	<meta name="_news" date="2019-09-11" author="MD">
		Added lines for Copper.
	</meta>

	<meta>
		date: 2023-10-27
		date.role: ExportRequested
	</meta>

	<table id="data" onDisk="True" adql="True" mixin="//slap#basic">
		<meta name="description">A table of transitions, their species, and
			their properties.</meta>
		<index columns="chemical_element"/>
		<index columns="ion_stage"/>
		<index columns="wavelength"/>
		<index columns="c_wavelength"/>

		<script type="afterMeta" lang="python" name="add biblinks">
			from gavo.protocols import biblinks

			biblinks.clearLinks(table.connection, table.tableDef.rd)

			src_id = base.getMetaText(
				table.tableDef.rd.getById("line_tap"), "identifier")
			pubs_mentioned = [r[0] for r in
				table.connection.query("SELECT DISTINCT pub FROM toss.data")]
			biblinks.defineLinks(table.connection,
				table.tableDef.rd,
				[(pub, "IsSupplementedBy", src_id)
					for pub in pubs_mentioned])
		</script>
	
		<column original="chemical_element">
			<values>
				<option title="Z = 29: Cu">Cu</option>
				<option title="Z = 30: Zn">Zn</option>
				<option title="Z = 31: Ga">Ga</option>
				<option title="Z = 32: Ge">Ge</option>
				<option title="Z = 34: Se">Se</option>
				<option title="Z = 36: Kr">Kr</option>
				<option title="Z = 38: Sr">Sr</option>
				<option title="Z = 40: Zr">Zr</option>
				<option title="Z = 42: Mo">Mo</option>
				<option title="Z = 43: Tc">Tc</option>
				<option title="Z = 52: Te">Te</option>
				<option title="Z = 53: I">I</option>
				<option title="Z = 54: Xe">Xe</option>
				<option title="Z = 56: Ba">Ba</option>
			</values>
		</column>
		<column name="ion_stage" type="text"
			ucd="phys.atmol.ionStage"
			tablehead="Stage"
			description="Ionisation stage"
			verbLevel="1"/>

		<column original="wavelength"/>
		<column name="c_wavelength" type="double precision"
			unit="Angstrom" ucd="em.wl;phys.atmol.transition"
			tablehead="Lambda_c"
			description="Conventional wavelength of the transition.
				This is the air wavelength between 2000 and 20000 Angstrom,
				the vacuum wavelength otherwise."
			verbLevel="1">
		</column>

		<LOOP listItems="init final">
			<events>
				<column name="par_\item" type="char" required="True"
					ucd="phys.atmol.parity"
					tablehead="par_\item"
					description="Parity of the \item level of the transition"
					verbLevel="1">
					<values>
						<option>e</option>
						<option>o</option>
					</values>
				</column>
				<column name="j_\item"
					ucd="phys.atmol.qn"
					tablehead="j_\item"
					description="Angular momentum quantum number of the \item level of
						the transition."
					verbLevel="1"/>
			</events>
		</LOOP>
		<column name="log_gf"
			ucd="phys.atmol.wOscStrength"
			tablehead="log(gf)"
			description="Calculated HRF log(gf) (g -- statistical weight
				of the lower level; f -- oscillator strength)"
			verbLevel="1">
			<values min="-9.87" max="1.57"/>
		</column>
		<column name="ga"
			unit="1e9s**-1" ucd="phys.atmol.transProb"
			tablehead="gA"
			description="Transition probability times the statistical weight
				of the upper level"
			verbLevel="1"
			displayHint="sf=4"/>
		<column name="cf"
			ucd=""
			tablehead="CF"
			description="Cancellation factor as defined by 1981tass.book.....C"
			verbLevel="1"/>
	</table>

	<table id="legacy_data" onDisk="True" namePath="data">
		<meta name="description">A view on top of toss.data that
		has values in conventional units and with air wavelengths
		(rather than vacuum) between 2000 and 20000 Ångstrøm.</meta>
		<column original="c_wavelength"/>
		<column original="chemical_element"/>
		<column original="ion_stage"/>
		<column original="log_gf"/>
		<column original="ga"/>
		<column original="initial_level_energy" unit="cm**-1"
			displayHint="sf=0"/>
		<column original="par_init"/>
		<column original="j_init"/>
		<column original="final_level_energy" unit="cm**-1"
			displayHint="sf=0"/>
		<column original="par_final"/>
		<column original="j_final"/>
		<column original="pub"/>
		<viewStatement>
			CREATE VIEW \curtable as (SELECT
				c_wavelength,
				chemical_element,
				ion_stage,
				log_gf,
				ga,
				initial_level_energy*5.034116651114543e+22 as initial_level_energy,
				par_init,
				j_init,
				final_level_energy*5.034116651114543e+22 as final_level_energy,
				par_final,
				j_final,
				pub
				FROM \schema.data)
		</viewStatement>
	</table>

	<data id="import">
		<recreateAfter>make_view</recreateAfter>
		<sources pattern="data/*.dat"/>
		<columnGrammar id="toss_grammar">
			<sourceFields>
				<setup>
					<par key="PUBLICATION_MAP"><![CDATA[{
						"AA_2012_20014": "2012A&A...546A..55R",
						"AA_2014_24199": "2015A&A...574A..29W",
						"AA_2014_23491": "2014A&A...564A..41R",
						"AA_2014_23878": "2014A&A...566A..10R",
						"AA_2014_25326": "2015A&A...577A...6R",
						"AA_2015": "2015A&A...587A..39R",
						"AA_2016_28131": "2016A&A...590A.128R",
						"AA_2016_29794": "2017A&A...599A.142R",
						"AA_2017_30383": "2017A&A...606A.105R",
						"AA_2019_36620": "2020A&A...637A...4R"
					}]]></par>
				</setup>
				<code>
					# add the publication
					key = re.sub("_table.*", "", os.path.basename(sourceToken))
					return {
						"pub": PUBLICATION_MAP[key],
					}
				</code>
			</sourceFields>
			<colDefs>
				chemical_element: 1-2
				ion_stage: 3-10
				wavelength:12-22
				c_wavelength:24-34
				initial_nubar: 36-45
				par_init: 47
				j_init: 49-52
				final_nubar: 54-63
				par_final: 65
				j_final: 67-70
				log_gf: 72-78
				ga: 80-89
				cf: 91-98
			</colDefs>
		</columnGrammar>

		<make table="data">
			<rowmaker idmaps="*">
				<var key="j_init"/>
				<var key="j_final"/>
				<var key="chemical_element"
					>@chemical_element.lower().capitalize()</var>

				<apply procDef="//slap#fillBasic">
					<setup>
						<par name="PER_CENTIMETER"
							>base.computeConversionFactor("cm**-1", "J")</par>
					</setup>
					<bind key="initial_level_energy"
						>float(@initial_nubar)*PER_CENTIMETER</bind>
					<bind key="final_level_energy"
						>float(@final_nubar)*PER_CENTIMETER</bind>
					<bind key="initial_name"
						>"%s %s %s/2 %s"%(@chemical_element, @ion_stage, int(float(@j_init))*2,
							@par_init)</bind>
					<bind key="final_name"
						>"%s %s %s/2 %s"%(@chemical_element, @ion_stage, int(float(@j_final))*2,
							@par_final)</bind>
					<bind key="wavelength">float(@wavelength)*1e-10</bind>
					<bind key="linename">"%s %s %s A"%(
						@chemical_element, @ion_stage, @wavelength)</bind>
				</apply>

				<map key="ga">float(@ga)/1e9</map>
			</rowmaker>

		</make>
	</data>

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

	<table id="line_tap" onDisk="True" adql="True">
		<publish/>
		<meta name="description">
			This table contains line metadata computed a pseudo-relativistic
			Hartree-Fock method including core-polarization corrections.  Its
			schema follows the first Working Draft of LineTAP.
		</meta>
		<index columns="title"/>
		<index columns="element"/>
		<index columns="ion_charge"/>
		<index columns="vacuum_wavelength"/>
		<mixin>//linetap#table-0</mixin>
	</table>

	<data id="make_lt">
		<!-- since this is in some sense more comprehensive than the ssldm-
		based data, this won't really work as a view over it, and we re-import
		everything here.  Consider turning data into a view over this once
		LineTAP has stabilised. -->
		<sources pattern="data/*.dat"/>
		<columnGrammar original="toss_grammar"/>
		<make table="line_tap">
			<rowmaker idmaps="*">
				<var key="charge">@ion_stage</var>
				<var key="chemical_element"
					>@chemical_element.lower().capitalize()</var>

				<apply procDef="//procs#dictMap">
					<bind name="mapping">{"I": 0, "II": -1, "III": -2, "IV": -3,
							"V": -4, "VI": -5, "VII": -6}</bind>
					<bind name="key">"charge"</bind>
				</apply>

				<apply name="get_ground_state_energy">
					<setup imports="json">
						<code>
							# the file is produced by res/res/get_ground_states.py
							with open(rd.getAbsPath(
									"res/ground_states.json")) as f:
								GROUND_STATE_MAP = json.load(f)
						</code>
					</setup>
					<code>
						try:
							@E0 = GROUND_STATE_MAP[@chemical_element][@ion_stage]
						except KeyError:
							raise base.ReportableError("Can't find ground state"
								 " energy for {} at ionisation {}.  See"
								 " res/get_ground_states.py for how to fix this.".format(
								 @chemical_element, @ion_stage))
					</code>
				</apply>

				<!-- TODO: replace with //linetap#InChIForAtom -->
				<apply name="compute_inchis">
					<setup imports="gavo.protocols.linetap">
						<code>
							# map (atom, ionsation) to (inchi, inchikey)
							KNOWN_INCHIS = {}
						</code>
					</setup>
					<code>
						key = (@chemical_element, @charge)
						if key not in KNOWN_INCHIS:
							il = linetap.getILib()
							iin = il.getInput([
								il.getAtom(@chemical_element, charge=@charge)])
							inchi = il.getInChI(iin)
							inchiKey = il.getInChIKey(inchi)
							KNOWN_INCHIS[key] = (inchi, inchiKey)
						
						@inchi, @inchikey = KNOWN_INCHIS[key]
					</code>
				</apply>

				<apply name="compute_energies">
					<setup>
						<par name="PER_CENTIMETER"
							>base.computeConversionFactor("cm**-1", "J")</par>
					</setup>

					<code>
						energies = [
							float(@initial_nubar)*PER_CENTIMETER,
							float(@final_nubar)*PER_CENTIMETER]
						@lower_energy = max(energies)
						@upper_energy = min(energies)
					</code>
				</apply>

				<apply procDef="//linetap#populate-0">
					<bind name="inchi">@inchi</bind>
					<bind name="inchikey">@inchikey</bind>
					<bind name="ion_charge">@charge</bind>
					<bind name="line_reference">@pub</bind>
					<bind name="lower_energy">@E0-@lower_energy</bind>
					<bind name="method">"theory"</bind>
<!--					<bind name="oscillator_strength">@log_gf</bind>-->
					<bind name="title">"%s %s %s A"%(
						@chemical_element, @ion_stage, @wavelength)</bind>
					<bind name="upper_energy">@E0-@upper_energy</bind>
					<bind name="vacuum_wavelength">float(@wavelength)</bind>
					<bind name="einstein_a">float(@ga)/1e9</bind>
					<bind name="element">@chemical_element</bind>

<!--	To be worked out (I've asked Thomas:
					<bind name="line_strength"/>
					<bind name="weighted_oscillator_strength">float(@log_gf)</bind> -->
				</apply>
			</rowmaker>
		</make>
	</data>

	<service id="legacy" allowed="form">
		<meta name="shortName">TOSS legacy</meta>
		<meta name="title"
			>Tübingen Oscillator Strengths Service Form Interface</meta>
		<meta name="_intro" format="rst">TOSS data is also available in a form
			compliant to the IVOA spectral line data model in a
			\RSTservicelink{toss/q/q/info}{SLAP service} and within our TAP
			service (\RSTservicelink{toss/q/examples}{usage examples}).
		</meta>
			
		<dbCore queriedTable="legacy_data">
			<condDesc buildFrom="c_wavelength"/>
			<condDesc>
				<inputKey original="chemical_element"
					tablehead="Atomic Number or Element"
					showItems="9"/>
			</condDesc>
			<condDesc buildFrom="ion_stage"/>
			<condDesc buildFrom="log_gf"/>
		</dbCore>
	</service>

	<service id="q" allowed="form,slap.xml,static">
		<property key="staticData">static</property>
		<meta name="_intro" format="rst">A legacy interface (conventional
			units, air wavelength around the optical range) to
			TOSS data is also available at
			\RSTservicelink{toss/q/legacy/form}.  See also
			\RSTservicelink{toss/q/examples}{usage examples} for how to do
			TAP queries on this data.
		</meta>
		<meta name="shortName">TOSS slap</meta>
		<meta name="slap.dataSource">theoretical</meta>
		<meta name="slap.testQuery">wavelength=7e-7/7.1e-7</meta>

		<publish render="slap.xml" sets="ivo_managed"/>
		<publish render="form" sets="ivo_managed,local" service="legacy"/>

		<dbCore queriedTable="data">
			<condDesc buildFrom="wavelength"/>
			<condDesc buildFrom="chemical_element"/>
			<condDesc buildFrom="ion_stage"/>
			<condDesc buildFrom="log_gf"/>
		</dbCore>
		<meta name="_example" title="Matching a line list"><![CDATA[
			TOSS is accessible for remote database queries through TAP as
			:taptable:`toss.data`.  You can therefore match local line lists.
			This example assumes you're using the TOPCAT TAP client (but any other
			should do as well).
			
			Load the fictional line list from
			http://dc.g-vo.org/toss/q/s/static/sample.vot.  Then open VO/TAP, search
			for TOSS and select the GAVO DC server.

			To simply match lines (with a bit of a wiggle), you could use the
			following query::

				SELECT t.* FROM toss.data AS t
					JOIN TAP_UPLOAD.t1 AS u
					ON (abs(t.wavelength-in_unit(u.lambda, 'm'))<1e-10)
	
			Note that ``toss.data`` uses the IVOA spectral line data model
			and therefore has vacuum wavelengths throughout the
			electromagnetic spectrum.

			You may have to change the ``t1`` after ``TAP_UPLOAD`` and adapt the 1 the
			number the sample table has in TOPCAT's table list.  If you use
			tables of your own, you will have to change lambda to whatever name
			the column has in your table.

			What's happening here is that ``toss.data`` is "joined" (in effect,
			building a cartesian product) with the table you uploaded.  There's a
			join condition (after the ON) which says that the two wavelengths
			must be within an Angstrom of each other.
			
			Tables in the GAVO data center in general are in plain metric units to
			save headaches when combining them.  The uploaded table has lambda in
			Angstrom (check the table metadata), so we use the ``in_unit`` function
			to turn it into meters.

			Also note the table aliases: To save typing, we're assinging t and u
			to the database and uploaded tables, respectively, in the AS clauses.
			You can even use that in the FROM clause: t.* just takes all the columns
			from the database table.

			There's quite a lot of rows coming back there.  To only retrieve the
			10 lines with the highest oscillator stength, add two simple
			ingredients:

			.. tapquery::

				SELECT TOP 10 t.wavelength, linename, log_gf, ga
				FROM toss.data AS t
					JOIN TAP_UPLOAD.t1 AS u
					ON (abs(t.wavelength-in_unit(u.lambda, 'm'))<1e-10)
				ORDER BY log_gf DESC

			In addition to our last query, we have restricted the result to 10
			matches and told the engine to sort by log_gf (DESCending).

			Also, we now specify what columns we want in the result.  In such
			a join, it's enough to just give the name if the column is only
			present in one table.  Otherwise, it must be "qualified", i.e.,
			furnished with a table name (as wavelength is in the example, though
			it wouldn't have been necessary here).
		]]></meta>

		<meta name="_example" title="Aggregate functions on TOSS"><![CDATA[
			To follow this example, you should prepare your TAP client as in
			the `previous example`_; again, we're talking about
			:taptable:`toss.data`.

			The `sample table`_ used there has another column, strength.  Let's say
			that's something like a filter width and we want to know how many lines
			are within than many Angstroms of the line center::

				SELECT u.lambda, COUNT(*) as n_lines
				FROM toss.data AS t
					JOIN TAP_UPLOAD.t1 AS u
					ON (abs(t.wavelength-in_unit(u.lambda, 'm'))<strength*1e-10)
				GROUP BY u.lambda

			GROUP BY takes a bit of wrapping one's mind around -- essentially, it groups the
			result by distinct values of its argument.  Clearly, the select
			clause can then only refer to what's constant within such a group.
			This can be what the table is grouped by, or it can be an "aggregate
			function", a function working on the elements of the group.  COUNT
			just counts them, but there's also things like AVG and SUM:

			.. tapquery::

				SELECT u.lambda, sqrt(COUNT(*)) as weight,
					log(SUM(exp(log_gf))) as gfprod, AVG(cf) as cfavg
				FROM toss.data AS t
					JOIN TAP_UPLOAD.t1 AS u
					ON (abs(t.wavelength-in_unit(u.lambda, 'm'))<strength*1e-10)
				GROUP BY u.lambda

			For this example's sake, we sum the oscillator strengths (rather than
			doing something sensible with them) -- this shows that the arguments
			of the aggregate functions can be arithmetic expressions.

			.. _previous example: #Matchingalinelist
			.. _sample table: http://dc.g-vo.org/toss/q/s/static/sample.vot
		]]></meta>
	</service>

	<regSuite title="TOSS">
		<regTest title="TOSS slap gives reasonable  results">
			<url wavelength="702.110e-10/702.120e-10"
				chemical_element="Mo">q/slap.xml</url>
			<code>
				rows = self.getVOTableRows()
				self.assertEqual(len(rows), 1)
				self.assertAlmostEqual(rows[0]['initial_level_energy'],
					2.05219718095184e-19)
				self.assertEqual(rows[0]["linename"], "Mo IV 702.115 A")
				self.assertAlmostEqual(rows[0]["ga"], 0.0365)
			</code>
		</regTest>

		<regTest title="TOSS conventional data">
			<url parSet="form" chemical_element="Mo"
				c_wavelength="702.110 .. 702.120" _FORMAT="CSV"
				>legacy/form</url>
			<code>
				self.assertHasStrings(
					"c_wavelength,chemical_element,ion_stage,log_gf,ga,initial_level_energy,par_init,j_init,final_level_energy,par_final,j_final,pub\\r\\n",
					"702.115,Mo,IV,-2.57",
					",10331.000171505884,e,1.5,152757.00253593305,o,1.5,2015A&amp;A...587A..39R\\r\\n")
			</code>
		</regTest>

		<regTest title="TOSS LineTAP">
			<url parSet="TAP" QUERY="SELECT * FROM toss.line_tap WHERE
				vacuum_wavelength BETWEEN 153 AND 154
				AND title='Ge V 153.196 A'">/tap/sync</url>
			<code>
				row = self.getFirstVOTableRow()
				self.assertAlmostEqual(row["upper_energy"],
					1.3639274950980258e-17)
				self.assertEqual(row["line_reference"],
					"2012A&amp;A...546A..55R")
				self.assertEqual(row["ion_charge"], -4)
				self.assertEqual(row["element"], "Ge")
			</code>
		</regTest>
	</regSuite>

	<regSuite title="DaCHS SLAP">
		<regTest title="SLAP response has stupid xmlns decl">
			<url MAXREC="1">q/slap.xml</url>
			<code>
				self.assertHasStrings(
					'xmlns:ssldm="http://www.ivoa.net/xml/SimpleSpectrumLineDM/SimpleSpectrumLineDM-v1.0.xsd"',
					"query limit was reached")
				self.assertXpath("//v:INFO[@name='QUERY_STATUS']",
					{"value": "OK"})
			</code>
		</regTest>
	</regSuite>
</resource>
