<resource schema="chianti" resdir=".">
	<meta name="creationDate">2024-10-29T12:45:54Z</meta>

	<meta name="title">CHIANTI Lines in LineTAP</meta>
	<meta name="description">
		CHIANTI consists of a critically evaluated set of up-to-date atomic data
		and derived spectral lines.	At http://www.chiantidatabase.org/, many
		tools for this data set are available.	The present resource only
		takes the line data and makes it available in standard LineTAP.

		This is the state as of CHIANTI 10.1.
	</meta>
	<meta name="subject">atomic-spectroscopy</meta>
	<meta name="subject">line-positions</meta>

	<meta name="version">10.1</meta>

	<meta name="creator">Dere, K.; Mason, H.; Monsignori-Fossi, B.; Landi, E.;
		Young, P.; Landini, M.; Pike, D.; Bromage, G.; Bromage, B.; Del Zanna G.;
		Phillips, K.J.H.</meta>

	<meta name="source">2023ApJS..268...52D</meta>
	<meta name="contentLevel">Research</meta>
	<meta name="type">Catalog</meta>

	<meta name="coverage.waveband">Infrared</meta>
	<meta name="coverage.waveband">Optical</meta>
	<meta name="coverage.waveband">UV</meta>
	<meta name="coverage.waveband">EUV</meta>
	<meta name="coverage.waveband">X-Ray</meta>

	<meta name="rights">Please use the sentence “CHIANTI is a collaborative project
	involving George Mason University, the University of Michigan (USA),
	University of Cambridge (UK) and NASA Goddard Space Flight Center
	(USA).” in your acknowlegement and reference 2019ApJS..241...22D</meta>

	<table id="lines" onDisk="True" adql="True"
		mixin="//linetap#table-0">
		<publish/>
		<index columns="element"/>
		<index columns="ion_charge"/>
		<index columns="vacuum_wavelength"/>
		<index columns="inchi"/>
		<index columns="inchikey"/>
	</table>

	<coverage>
		<updater spectralTable="lines"/>
	</coverage>

	<data id="import">
		<sources pattern="data/*.wgfa" recurse="True"/>

		<columnGrammar endMarker=" *-1\s*$">
			<sourceFields>
				<setup imports="gavo.api,gavo.utils.pyfits"/>
				<code>
				# the scups files (lines/line strength) we are parsing are
				# accompanied by .elvlc files which contain the metadata of
				# the levels.  We parse these manually into a temporary levels
				# tables that the rowmaker can use to pull its metadata from.
				levelsDD = data.dd.rd.getById("get_levels")
				levelsTable = api.makeData(
						levelsDD,
						forceSource=sourceToken.replace(".wgfa", ".elvlc")
					).getPrimaryTable()
				rec= {"levels": levelsTable}

				with pyfits.open(sourceToken.replace(".wgfa", ".scups.fits.gz")
						) as hdus:
					colNames = [c.name for c in hdus[1].data.columns]
					global_meta = dict(zip(colNames, hdus[1].data[0]))
				rec.update(global_meta)

				return rec
			</code></sourceFields>
			<colDefs>
				LVL1: 1-5
				LVL2: 7-10
				wavelength: 12-25
				gf: 27-40
				einstein_a: 42-55
				trans_info: 57-
			</colDefs>
		</columnGrammar>

		<make table="lines">
			<rowmaker idmaps="*">
				<!-- CHIANTI gives their transistion in Rydbergs.  Uh. -->
				<var name="Rydberg">2.1798723611035e-18</var>
				<var name="element">@ION_ROMAN.split()[0]</var>
				<var name="ion_charge">@ION_N-1</var>
				<var name="LVL1">int(@LVL1)</var>
				<var name="LVL2">int(@LVL2)</var>
				<var name="wavelength">abs(float(@wavelength))</var>

				<!-- we cannot have the "autoionizing" things here, as a
				wavelength of 0 corresponds to infinite energy -->
				<ignoreOn><keyIs key="wavelength" value="0.0000"/></ignoreOn>


				<apply procDef="//linetap#InChIForAtom" name="getInChI">
					<bind name="element">@element</bind>
					<bind name="charge">@ion_charge</bind>
				</apply>

				<apply name="getEnergies">
					<setup><code>
						def selectEnergy(row):
							if row["observed_energy"] is None:
								return "theory", row["theoretical_energy"]
							else:
								return "observation", row["observed_energy"]
					</code></setup>
					<code>
						method1, lowerWN = selectEnergy(@levels.getRow(@LVL1))
						method2, upperWN = selectEnergy(@levels.getRow(@LVL2))
						# if one method is theory, the whole thing is theory
						# (we have to pick one for LineTAP)
						if "theory" in [method1, method2]:
							@method = "theory"
						else:
							@method = "observation"
						
						if lowerWN==upperWN:
							# sometimes two levels have exactly the same energy
							# in the observation column.  We *could* fall back
							# to theory (where they then are different, naturally),
							# but all that's so exotic that we simply skip the line
							raise IgnoreThisRow(
								"Degenerate {element}{ion_charge} {LVL1} {LVL2}".
								format(**vars))

						if lowerWN>upperWN:
							lowerWN, upperWN = upperWN, lowerWN

						@upper_energy = PLANCK_H*upperWN*100*LIGHT_C
						@lower_energy = PLANCK_H*lowerWN*100*LIGHT_C
					</code>
				</apply>

				<apply name="check_consistency">
					<doc>Let's make sure that the wavelength is actually
						consistent with the energy levels.</doc>
					<setup>
						<code>
							BREAKAGE_LOG = open("bad-transitions.txt", "w")
						</code>
					</setup>
					<code><![CDATA[
						if @wavelength==0:
							raise IgnoreThisRow("Not a transition")
						return
						from_energy = LIGHT_C*PLANCK_H/(@upper_energy-@lower_energy)
						if not 0.999<from_energy/(@wavelength*1e-10)<1.001:
							if "corrected" not in "".join(@COMMENTS):
								BREAKAGE_LOG.write("Transition energy severely off: {element}"
									"{ion_charge} ({LVL1}, {LVL2}) {wavelength}\n".
									format(**vars))
					]]></code>
				</apply>

				<apply procDef="//linetap#populate-0" name="populate">
					<bind name="einstein_a">@einstein_a</bind>
					<bind name="element">@element</bind>
					<bind name="inchi">@inchi</bind>
					<bind name="ion_charge">@ion_charge</bind>
					<bind name="line_reference">"\metaString{source}"</bind>
					<bind name="lower_energy">@lower_energy</bind>
					<!-- does chianti really not have isotopes? -->
					<bind name="mass_number">None</bind>
					<bind name="method">@method</bind>
					<bind name="title">"{} {:.3f} nm".format(
						@ION_ROMAN,
						vars["wavelength"]/10)</bind>
					<bind name="upper_energy">@upper_energy</bind>
					<bind name="vacuum_wavelength">@wavelength</bind>
				</apply>
			</rowmaker>
		</make>
	</data>

	<!-- temporary levels table and its parsing.  This is a helper for
		lines's import -->
	
	<table id="levels" primary="level_index">
		<meta name="description">A set of energy levels for internal use</meta>
		<column name="level_index" type="integer" required="True"
			description="Levels can be arranged in any order, although following the
				observed or theoretical energy ordering is recommended. Level 1 is
				recommended to be the ground level, although this is not essential."/>
		<column name="configuration" type="text"
			description="The recommended configuration format is, e.g.,
				“3s2.3p2(2P).3d”. That is, orbitals are separated by a “.” and parent
				terms are placed in brackets. However, separating orbitals by white
				space is also acceptable, e.g., “3s2 3p2(2P) 3d”."/>
		<column name="level_label_string" type="text"
			description="This can be used to attach a label to a level. An example is
				for Fe II for which the common multiplets are identified by a single
				letter."/>
		<column name="two_s" type="integer"
			description="2S+1">
			<values nullLiteral="-1"/>
		</column>
		<column name="l" type="text"
			description="Takes values of S, P, D, F, G, etc.">
			<values nullLiteral="-1"/>
		</column>
		<column name="j"
			description="J value"/>
		<column name="observed_energy" type="double precision"
			unit="cm**-1"
			description="This is NULL if not given"/>
		<column name="theoretical_energy" type="double precision"
			unit="cm**-1"
			description="Any level that does not have an observed energy must have an
				energy in this column. For levels that do have an observed energy, a
				value in this column is optional."/>
	</table>

	<data id="get_levels" auto="False">
		<!-- a helper for the import data -->
		<sources item="fed by import"/>
		<columnGrammar commentIntroducer="%" endMarker=" *-1\s*$">
			<colDefs>
				level_index: 1-7
				configuration: 8-38
				level_label_string: 39-42
				two_s: 43-47
				l: 48-53
				j: 54-59
				observed_energy: 60-72
				theoretical_energy: 74-87
			</colDefs>
		</columnGrammar>

		<make table="levels">
			<rowmaker idmaps="*">
				<map key="observed_energy" nullExpr="-1."/>
				<map key="theoretical_energy" nullExpr="-1."/>
			</rowmaker>
		</make>
	</data>

	<regSuite title="chianti regression">
		<regTest title="chianti LineTAP serves some data">
			<url parSet="TAP" QUERY="SELECT * FROM chianti.lines
				WHERE inchi='InChI=1S/He/q+1'
				AND vacuum_wavelength BETWEEN 1640.5 AND 1641"
				>/tap/sync</url>
			<code>
				row = self.getFirstVOTableRow()
				self.assertEqual(row["title"], 'He II 164.053 nm')
				self.assertAlmostEqual(row['upper_energy'], 7.749939155470103E-18)
				self.assertEqual(row["inchi"], 'InChI=1S/He/q+1')
				self.assertEqual(row["ion_charge"], 1)
				self.assertEqual(row['method'], 'observation')
			</code>
		</regTest>

	</regSuite>
</resource>
