Skip to content

Commit ad38e07

Browse files
committed
Add module for material_system and classes for Solid Solution
1 parent 6bb44df commit ad38e07

File tree

2 files changed

+333
-1
lines changed

2 files changed

+333
-1
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
from .schema import *
1+
from .schema import *
2+
from .material_systems import *
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
"""
2+
Module containing NOMAD classes for material systems used in UV/Vis/NIR Transmission.
3+
"""
4+
5+
import numpy as np
6+
from ase.data import chemical_symbols
7+
8+
from nomad.datamodel.metainfo.basesections import (
9+
CompositeSystem,
10+
PubChemPureSubstanceSection,
11+
PureSubstance,
12+
SystemComponent,
13+
EntryData,
14+
)
15+
from nomad.metainfo import (
16+
Quantity,
17+
SubSection,
18+
MEnum,
19+
Section,
20+
)
21+
from nomad.datamodel.data import (
22+
ArchiveSection,
23+
)
24+
from nomad.datamodel.metainfo.annotations import (
25+
ELNAnnotation,
26+
SectionProperties,
27+
Filter,
28+
)
29+
30+
31+
class CrystalProperties(ArchiveSection):
32+
pass
33+
34+
35+
class CrystallinePureSubstance(PureSubstance, EntryData):
36+
m_def = Section(
37+
description='A pure substance component having a crystalline structure.',
38+
a_eln=ELNAnnotation(
39+
properties=SectionProperties(
40+
order=[
41+
'name',
42+
'substance_name',
43+
'molecular_formula',
44+
]
45+
)
46+
),
47+
)
48+
molecular_formula = Quantity(
49+
type=str,
50+
description='Molecular formula of the component.',
51+
a_eln={'component': 'StringEditQuantity'},
52+
)
53+
crystal_properties = SubSection(
54+
section_def=CrystalProperties,
55+
description='Properties of the crystalline structure.',
56+
)
57+
58+
def normalize(self, archive, logger: 'BoundLogger') -> None:
59+
# make PubChem API calls when required
60+
if self.pure_substance is None or self.pure_substance.iupac_name is None:
61+
pure_substance = PubChemPureSubstanceSection()
62+
if self.molecular_formula is not None:
63+
pure_substance.molecular_formula = self.molecular_formula
64+
pure_substance.normalize(archive, logger)
65+
if pure_substance.iupac_name is not None:
66+
# material was found in PubChem database
67+
self.pure_substance = pure_substance
68+
self.molecular_formula = pure_substance.molecular_formula
69+
super().normalize(archive, logger)
70+
71+
72+
class HostComponent(SystemComponent):
73+
m_def = Section(
74+
description='Host component of the solid solution composed of crystalline '
75+
'substance.',
76+
a_eln=ELNAnnotation(
77+
properties=SectionProperties(
78+
order=[
79+
'name',
80+
'system',
81+
'molecular_formula',
82+
]
83+
)
84+
),
85+
)
86+
system = Quantity(
87+
type=CrystallinePureSubstance,
88+
a_eln=dict(component='ReferenceEditQuantity'),
89+
)
90+
molecular_formula = Quantity(
91+
type=str,
92+
description='The molecular formula of the host component, e.g., LiYF4.',
93+
a_eln=dict(component='StringEditQuantity'),
94+
)
95+
96+
def normalize(self, archive, logger: 'BoundLogger') -> None:
97+
super().normalize(archive, logger)
98+
try:
99+
self.molecular_formula = self.system.pure_substance.molecular_formula
100+
except AttributeError:
101+
pass
102+
103+
104+
class DopantComponent(SystemComponent):
105+
m_def = Section(
106+
description='Dopant component of the solid solution composed of a crystalline '
107+
'substance.',
108+
a_eln=ELNAnnotation(
109+
properties=SectionProperties(
110+
order=[
111+
'name',
112+
'system',
113+
'molecular_formula',
114+
]
115+
)
116+
),
117+
)
118+
system = Quantity(
119+
type=CrystallinePureSubstance,
120+
a_eln=dict(component='ReferenceEditQuantity'),
121+
)
122+
molecular_formula = Quantity(
123+
type=str,
124+
description='The molecular formula of the dopant component, e.g., Pr.',
125+
a_eln=dict(component='StringEditQuantity'),
126+
)
127+
128+
def normalize(self, archive, logger: 'BoundLogger') -> None:
129+
super().normalize(archive, logger)
130+
try:
131+
self.molecular_formula = self.system.pure_substance.molecular_formula
132+
except AttributeError:
133+
pass
134+
135+
136+
class ElementalDopantComponent(DopantComponent):
137+
m_def = Section(
138+
description=(
139+
'An elemental dopant added in small quantities which substitutes another '
140+
'element in the solvent.'
141+
),
142+
a_eln=ELNAnnotation(
143+
properties=SectionProperties(
144+
order=[
145+
'name',
146+
'system',
147+
'molecular_formula',
148+
'substitution_element',
149+
'nominal_concentration',
150+
'measured_concentration',
151+
],
152+
)
153+
),
154+
)
155+
molecular_formula = Quantity(
156+
type=MEnum(chemical_symbols[1:]),
157+
description='The symbol of the dopant element, e.g., Pr.',
158+
a_eln=dict(component='AutocompleteEditQuantity'),
159+
)
160+
substitution_element = Quantity(
161+
type=MEnum(chemical_symbols[1:]),
162+
description=('The element in the host crystal that is being replaced.'),
163+
a_eln=dict(component='AutocompleteEditQuantity'),
164+
)
165+
nominal_concentration = Quantity(
166+
type=np.float64,
167+
description=(
168+
'Atomic percentage of solute element with respect to the '
169+
'substitution element determined during preparation. '
170+
'For example, 1 at.% Pr-doped LiYF4 is equivalent to Li(Pr0.01Y0.99)F4'
171+
),
172+
a_eln={
173+
'component': 'NumberEditQuantity',
174+
'minValue': 0,
175+
'maxValue': 100,
176+
},
177+
unit='dimensionless',
178+
)
179+
measured_concentration = Quantity(
180+
type=np.float64,
181+
description=(
182+
'Atomic percentage of solute element with respect to the '
183+
'substitution element measured in the prepared sample using techniques like '
184+
'Transmission Spectrophotometry.'
185+
),
186+
a_eln={
187+
'component': 'NumberEditQuantity',
188+
'minValue': 0,
189+
'maxValue': 100,
190+
},
191+
unit='dimensionless',
192+
)
193+
194+
195+
class SolidSolution(CompositeSystem):
196+
m_def = Section(
197+
links=['https://doi.org/10.1351/goldbook.M03940'],
198+
description=(
199+
'A crystal containing other constituents (solutes) which fit into and '
200+
'are distributed in the lattice of the host crystal (solvent).'
201+
),
202+
a_eln=ELNAnnotation(
203+
properties=SectionProperties(
204+
order=[
205+
'name',
206+
'molecular_formula',
207+
'host',
208+
'dopants',
209+
'elemental_composition',
210+
'crystal_properties',
211+
],
212+
visible=Filter(
213+
exclude=[
214+
'datetime',
215+
'lab_id',
216+
'description',
217+
'components',
218+
],
219+
),
220+
),
221+
),
222+
)
223+
molecular_formula = Quantity(
224+
type=str,
225+
description='Molecular formula of the solid solution. Dopant concentrations is '
226+
'expressed relatively as fraction of substitution element. For example, '
227+
'1 at.% Pr-doped LiYF4 is expressed as Li(Pr0.01Y0.99)F4',
228+
a_eln={'component': 'StringEditQuantity'},
229+
)
230+
host = SubSection(
231+
section_def=HostComponent,
232+
)
233+
dopants = SubSection(
234+
section_def=ElementalDopantComponent,
235+
repeats=True,
236+
)
237+
crystal_properties = SubSection(
238+
section_def=CrystalProperties,
239+
description='Properties of the crystalline structure.',
240+
)
241+
242+
def adjust_composition_for_dopant_substitution(self, archive, logger):
243+
"""
244+
Adjust the atomic fraction of each element based on each dopant substitution.
245+
Dopants are solutes which are added in small quantities to the solvent.
246+
Dopant concentration is given as a percentage of the substitution element. It can
247+
be the measured concentration (first preference) or the nominal concentration.
248+
"""
249+
250+
if not self.host:
251+
return
252+
if not self.dopants:
253+
return
254+
for dopant in self.dopants:
255+
if dopant.measured_concentration is not None:
256+
dopant_concentration = dopant.measured_concentration
257+
elif dopant.nominal_concentration is not None:
258+
dopant_concentration = dopant.nominal_concentration
259+
else:
260+
continue
261+
262+
# find the substitution element and the dopant in the elemental composition
263+
# and adjust their atomic fractions
264+
for element_substituted in self.elemental_composition:
265+
if element_substituted.element == dopant.substitution_element:
266+
for element_substituting in self.elemental_composition:
267+
if element_substituting.element == dopant.molecular_formula:
268+
element_substituting.atomic_fraction = (
269+
dopant_concentration
270+
/ 100
271+
* element_substituted.atomic_fraction
272+
)
273+
element_substituted.atomic_fraction -= (
274+
element_substituting.atomic_fraction
275+
)
276+
break
277+
break
278+
279+
@staticmethod
280+
def derive_molecular_formula(host, dopants):
281+
"""
282+
Derive the molecular formula of the solid solution based on dopant substitutions.
283+
"""
284+
if host.molecular_formula:
285+
molecular_formula = host.molecular_formula
286+
else:
287+
return ''
288+
for dopant in dopants:
289+
if dopant.molecular_formula is None or dopant.substitution_element is None:
290+
continue
291+
if dopant.measured_concentration is not None:
292+
dopant_concentration = dopant.measured_concentration
293+
elif dopant.nominal_concentration is not None:
294+
dopant_concentration = dopant.nominal_concentration
295+
else:
296+
continue
297+
fractional_symbol = (
298+
'('
299+
+ dopant.molecular_formula
300+
+ str('{:.2f}'.format(dopant_concentration.magnitude / 100))
301+
+ ' '
302+
+ dopant.substitution_element
303+
+ str('{:.2f}'.format(1 - dopant_concentration.magnitude / 100))
304+
+ ')'
305+
)
306+
molecular_formula = molecular_formula.replace(
307+
dopant.substitution_element, fractional_symbol
308+
)
309+
310+
return molecular_formula
311+
312+
def normalize(self, archive, logger: 'BoundLogger') -> None:
313+
self.components = []
314+
self.elemental_composition = []
315+
if self.host:
316+
self.components.append(self.host)
317+
if self.dopants:
318+
for dopant in self.dopants:
319+
self.components.append(dopant)
320+
super().normalize(archive, logger)
321+
322+
if self.host and self.dopants:
323+
self.adjust_composition_for_dopant_substitution(archive, logger)
324+
# reset the mass fractions and recalculate them
325+
for element in self.elemental_composition:
326+
element.mass_fraction = None
327+
super().normalize(archive, logger)
328+
329+
self.molecular_formula = self.derive_molecular_formula(
330+
host=self.host, dopants=self.dopants
331+
)

0 commit comments

Comments
 (0)