diff --git a/dependencies/parsers/electronic b/dependencies/parsers/electronic index 5c077f48adcda8e6ec2f5cfb2220adc04100c3e6..62a093b8d34752675e4203b53629a201caf57391 160000 --- a/dependencies/parsers/electronic +++ b/dependencies/parsers/electronic @@ -1 +1 @@ -Subproject commit 5c077f48adcda8e6ec2f5cfb2220adc04100c3e6 +Subproject commit 62a093b8d34752675e4203b53629a201caf57391 diff --git a/gui/src/components/entry/properties/ElectronicPropertiesCard.js b/gui/src/components/entry/properties/ElectronicPropertiesCard.js index ca81caf2cb0d98bbd76061ab1984c2597203bc6f..5a3b94587064ee05f81e7d6eb5170ae4cf73f384 100644 --- a/gui/src/components/entry/properties/ElectronicPropertiesCard.js +++ b/gui/src/components/entry/properties/ElectronicPropertiesCard.js @@ -15,20 +15,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react' +import React, { useMemo } from 'react' import PropTypes from 'prop-types' import { PropertyCard } from './PropertyCard' -import { getLocation, resolveInternalRef } from '../../../utils' +import { getLocation, resolveInternalRef, parseNomadUrl } from '../../../utils' import ElectronicProperties from '../../visualization/ElectronicProperties' import { refPath } from '../../archive/metainfo' +import { useEntryStoreObj } from '../../DataStore' +// import { useEntryPageContext } from '../EntryPageContext' +// import { useApi } from '../../api' const ElectronicPropertiesCard = React.memo(({index, properties, archive}) => { const urlPrefix = `${getLocation()}/data` + // const {installationUrl} = useEntryPageContext() + // const api = useApi() // Find out which properties are present const hasDos = properties.has('dos_electronic') const hasBs = properties.has('band_structure_electronic') + // const loadArchive = useMemo((url) => { + // const parsedUrl = parseNomadUrl(url) + // const entryStoreObj = useEntryStoreObj(parsedUrl.installationUrl, parsedUrl.entryId, false, '*') + // return entryStoreObj.archive + // }) + console.log(useEntryStoreObj, useMemo, parseNomadUrl) + console.log(typeof archive) + // Do not show the card if none of the properties are available if (!hasDos && !hasBs) { return null @@ -36,17 +49,38 @@ const ElectronicPropertiesCard = React.memo(({index, properties, archive}) => { // Resolve DOS data let dos = hasDos ? undefined : false + // const dos = [] const dosData = archive?.results?.properties?.electronic?.dos_electronic if (dosData) { - dos = {} - dos.energies = resolveInternalRef(dosData.energies, archive) - dos.densities = resolveInternalRef(dosData.total, archive).map(dos => dos.value) - if (dosData.band_gap) { - dos.energy_highest_occupied = Math.max(...dosData.band_gap.map(x => x.energy_highest_occupied)) - } - dos.m_path = `${urlPrefix}/${refPath(dosData.energies.split('/').slice(0, -1).join('/'))}` + dos = [] + const dosArchive = archive + dosData.forEach(source => { + const d = {} + if (!source.energies.startsWith('/')) { + console.log(source) + // dosArchive = loadArchive(source.energies) + } else { + d.energies = resolveInternalRef(source.energies, dosArchive) + d.densities = resolveInternalRef(source.total, dosArchive).map(dos => dos.value) + } + // if (source.energies.startswith('../entries')) { + // // resolve first external archive + // const uploadId = archive?.metadata?.upload_id + // const entryId = archive?.metadata?.entry_id + // const baseUrl = `${installationUrl}/uploads/${uploadId}/archive/${entryId}` + // const path = source.energies + // const url = resolveNomadUrl(source.energies, baseUrl) + // api.post(`/entries/${url.entryId}/archive/query`, {required: '*'}) + // } + d.name = source.name + if (source.band_gap) { + d.energy_highest_occupied = Math.max(...source.band_gap.map(x => x.energy_highest_occupied)) + } + d.m_path = `${urlPrefix}/${refPath(source.energies.split('/').slice(0, -1).join('/'))}` + if (d.energies && d.densities) dos.push(d) + }) + dos = dos.length === 0 ? false : dos } - // Resolve data for band structure, brillouin zone and band gaps. let bs = hasBs ? undefined : false let band_gap = hasBs ? undefined : false diff --git a/gui/src/components/visualization/DOS.js b/gui/src/components/visualization/DOS.js index 4a1e5235b1d356aaa341ea4b6f56376e4900807a..4ae3775a55dac2d321306d33715fd63332e7acc5 100644 --- a/gui/src/components/visualization/DOS.js +++ b/gui/src/components/visualization/DOS.js @@ -77,72 +77,80 @@ const DOS = React.memo(({ return } - // Determine the energy reference. - let energyHighestOccupied + // Create the final data that will be plotted. + const plotData = [] let normalized - if (type === 'vibrational') { - energyHighestOccupied = 0 - normalized = true - } else { - if (data.energy_highest_occupied === undefined) { + const mins = [] + const maxes = [] + data.forEach((d, n) => { + console.log(d) + // Determine the energy reference. + let energyHighestOccupied + if (type === 'vibrational') { energyHighestOccupied = 0 - normalized = false - } else { - energyHighestOccupied = new Quantity(data.energy_highest_occupied, energyUnit).toSystem(units).value() normalized = true + } else { + if (d.energy_highest_occupied === undefined) { + energyHighestOccupied = 0 + normalized = false + } else { + energyHighestOccupied = new Quantity(d.energy_highest_occupied, energyUnit).toSystem(units).value() + normalized = true + } } - } - // Convert units and determine range - const mins = [] - const maxes = [] - const nChannels = data.densities.length - let energies = new Quantity(data.energies, energyUnit).toSystem(units).value() - const values1 = new Quantity(data.densities[0], valueUnit).toSystem(units).value() - let values2 - mins.push(Math.min(...values1)) - maxes.push(Math.max(...values1)) - if (nChannels === 2) { - values2 = new Quantity(data.densities[1], valueUnit).toSystem(units).value() - mins.push(Math.min(...values2)) - maxes.push(Math.max(...values2)) - } - const range = [Math.min(...mins), Math.max(...maxes)] - if (energyHighestOccupied !== 0) { - energies = add(energies, -energyHighestOccupied) - } + // Convert units and determine range + const nChannels = d.densities.length + let energies = new Quantity(d.energies, energyUnit).toSystem(units).value() + const values1 = new Quantity(d.densities[0], valueUnit).toSystem(units).value() + let values2 + mins.push(Math.min(...values1)) + maxes.push(Math.max(...values1)) + if (nChannels === 2) { + values2 = new Quantity(d.densities[1], valueUnit).toSystem(units).value() + mins.push(Math.min(...values2)) + maxes.push(Math.max(...values2)) + } + if (energyHighestOccupied !== 0) { + energies = add(energies, -energyHighestOccupied) + } - // Create the final data that will be plotted. - const plotData = [] - if (nChannels === 2) { + const dash = (d.name !== undefined && n === 1) ? 'dot' : undefined + if (nChannels === 2) { + plotData.push( + { + x: values2, + y: energies, + type: 'scatter', + mode: 'lines', + showlegend: false, + line: { + color: theme.palette.secondary.main, + width: 2, + dash: dash + } + } + ) + } plotData.push( { - x: values2, + x: values1, y: energies, + name: d.name, type: 'scatter', mode: 'lines', - showlegend: false, + showlegend: d.name !== undefined, line: { - color: theme.palette.secondary.main, - width: 2 + color: theme.palette.primary.main, + width: 2, + dash: dash } } ) - } - plotData.push( - { - x: values1, - y: energies, - type: 'scatter', - mode: 'lines', - showlegend: false, - line: { - color: theme.palette.primary.main, - width: 2 - } - } - ) + }) + const range = [Math.min(...mins), Math.max(...maxes)] + console.log(range) // Normalization line if (type !== 'vibrational' && normalizedToHOE) { plotData.push({ @@ -197,12 +205,13 @@ const DOS = React.memo(({ DOS.propTypes = { data: PropTypes.oneOfType([ PropTypes.bool, // Set to False to show NoData component - PropTypes.shape({ + PropTypes.arrayOf(PropTypes.shape({ energies: PropTypes.array.isRequired, // DOS energies array densities: PropTypes.array.isRequired, // DOS values array energy_highest_occupied: PropTypes.number, // Highest occupied energy. - m_path: PropTypes.string // Path of the section containing the data in the Archive - }) + m_path: PropTypes.string, // Path of the section containing the data in the Archive + name: PropTypes.string + })) ]), layout: PropTypes.object, className: PropTypes.string, diff --git a/nomad/datamodel/metainfo/workflow.py b/nomad/datamodel/metainfo/workflow.py index 907e4b87e7fa46773fd68c4c041e573b06e35c7e..e91c4726668c2fddeab40051338fd5e6a90b3704 100644 --- a/nomad/datamodel/metainfo/workflow.py +++ b/nomad/datamodel/metainfo/workflow.py @@ -4,7 +4,7 @@ from nptyping import NDArray from nomad.metainfo import ( # pylint: disable=unused-import MSection, MEnum, Quantity, Section, SubSection, SectionProxy, Reference, derived) -from nomad.datamodel.metainfo.simulation.calculation import Calculation +from nomad.datamodel.metainfo.simulation.calculation import Calculation, Dos from nomad.datamodel.metainfo.simulation.run import Run from nomad.datamodel.metainfo.simulation.system import System, Atoms, AtomsGroup from .common import FastAccess @@ -1988,6 +1988,40 @@ class MeanSquaredDisplacement(CorrelationFunction): mean_squared_displacement_values = SubSection(sub_section=MeanSquaredDisplacementValues.m_def, repeats=True) +class GW(MSection): + ''' + Section containing results of a GW workflow + ''' + + m_def = Section(validate=False) + + # TODO external referencing is a bit tricky for the gui + dos_dft = Quantity( + type=Reference(Dos), + description=''' + DFT density of states + ''') + + dos_gw = Quantity( + type=Reference(Dos), + description=''' + GW density of states + ''') + + # dos_dft = Quantity( + # type=Dos, + # shape=['*'] + # ) + # dos_gw = Quantity( + # type=Dos, + # shape=['*'] + # ) + + # dos_dft = SubSection(sub_section=Dos.m_def, repeats=False) + + # dos_gw = SubSection(sub_section=Dos.m_def, repeats=False) + + class SinglePoint(MSection): ''' Section containing results of a single point workflow. @@ -2120,6 +2154,7 @@ class Workflow(MSection): type = Quantity( type=MEnum([ + "gw", "single_point", "geometry_optimization", "phonon", @@ -2208,8 +2243,11 @@ class Workflow(MSection): single_point = SubSection( sub_section=SinglePoint.m_def, - # TODO determine if there is a need for this to be a repeating section - # such as in the context of fhi-vibes single_point + repeats=False, + categories=[FastAccess]) + + gw = SubSection( + sub_section=GW.m_def, repeats=False, categories=[FastAccess]) diff --git a/nomad/datamodel/results.py b/nomad/datamodel/results.py index a166e4a45434de38d1978d78086f500f51e54460..f12faacf8f1d223b07383f41060f8c653aa0176c 100644 --- a/nomad/datamodel/results.py +++ b/nomad/datamodel/results.py @@ -1519,6 +1519,12 @@ class DOSElectronic(DOS): Contains the total electronic density of states. ''', ) + name = Quantity( + type=str, + description=''' + Label to identify the DOS data, e.g. the method employed. + ''') + spin_polarized = Quantity( type=bool, description=''' @@ -1824,7 +1830,7 @@ class ElectronicProperties(MSection): ''', ) band_structure_electronic = SubSection(sub_section=BandStructureElectronic.m_def, repeats=False) - dos_electronic = SubSection(sub_section=DOSElectronic.m_def, repeats=False) + dos_electronic = SubSection(sub_section=DOSElectronic.m_def, repeats=True) class QuantityDynamic(MSection): diff --git a/nomad/normalizing/results.py b/nomad/normalizing/results.py index 765919918959cd1f830fb785e8aab51d35ae362b..f2d6a25a8e6cc993fecf0d9808b705f1674b600d 100644 --- a/nomad/normalizing/results.py +++ b/nomad/normalizing/results.py @@ -278,7 +278,7 @@ class ResultsNormalizer(Normalizer): return None - def dos_electronic(self) -> Union[DOSElectronic, None]: + def dos_electronic(self) -> List[Union[DOSElectronic, None]]: """Returns a reference to the section containing an electronic dos. In the case of multiple valid DOSes, only the latest one is reported. @@ -286,25 +286,40 @@ class ResultsNormalizer(Normalizer): - There is a non-empty array of dos_values_normalized. - There is a non-empty array of dos_energies. """ - path = ["run", "calculation", "dos_electronic"] - for dos in self.traverse_reversed(path): - energies = dos.energies - values = np.array([d.value.magnitude for d in dos.total]) - if valid_array(energies) and valid_array(values): - dos_new = DOSElectronic() - dos_new.energies = dos - dos_new.total = dos.total - n_channels = values.shape[0] - dos_new.spin_polarized = n_channels > 1 - dos_new.energy_fermi = dos.energy_fermi - for info in dos.band_gap: - info_new = dos_new.m_create(BandGap) - info_new.index = info.index - info_new.energy_highest_occupied = info.energy_highest_occupied - info_new.energy_lowest_unoccupied = info.energy_lowest_unoccupied - return dos_new - return None + def resolve_dos(path): + for dos in self.traverse_reversed(path): + energies = dos.energies + values = np.array([d.value.magnitude for d in dos.total]) + if valid_array(energies) and valid_array(values): + dos_new = DOSElectronic() + dos_new.energies = dos + dos_new.total = dos.total + n_channels = values.shape[0] + dos_new.spin_polarized = n_channels > 1 + dos_new.energy_fermi = dos.energy_fermi + for info in dos.band_gap: + info_new = dos_new.m_create(BandGap) + info_new.index = info.index + info_new.energy_highest_occupied = info.energy_highest_occupied + info_new.energy_lowest_unoccupied = info.energy_lowest_unoccupied + return dos_new + + try: + workflow_type = self.entry_archive.workflow[-1].type + except Exception: + workflow_type = None + + if workflow_type == 'gw': + dos_dft = resolve_dos(["workflow", "gw", "dos_dft"]) + dos_dft.name = 'DFT' + dos_gw = resolve_dos(["workflow", "gw", "dos_gw"]) + dos_gw.name = 'GW' + return [dos_dft, dos_gw] if dos_dft and dos_gw else [] + elif workflow_type == 'single_point': + return [resolve_dos(["run", "calculation", "dos_electronic"])] + else: + return [] def band_structure_phonon(self) -> Union[BandStructurePhonon, None]: """Returns a new section containing a phonon band structure. In diff --git a/nomad/normalizing/workflow.py b/nomad/normalizing/workflow.py index e5a1a5adbdc105c0794ac2ca2dfb41322296f83a..770420e458685690818b5aadfe60d89e37331d46 100644 --- a/nomad/normalizing/workflow.py +++ b/nomad/normalizing/workflow.py @@ -507,7 +507,9 @@ class WorkflowNormalizer(Normalizer): if not self.entry_archive.run: return + created = False if not self.entry_archive.workflow: + created = True self.entry_archive.m_create(Workflow) for n, sec_workflow in enumerate(self.entry_archive.workflow): @@ -547,5 +549,5 @@ class WorkflowNormalizer(Normalizer): ThermodynamicsNormalizer(self.entry_archive, n).normalize() # remove the section workflow again, if the parser/normalizer could not produce a result - if sec_workflow.calculation_result_ref is None: + if sec_workflow.calculation_result_ref is None and created: self.entry_archive.m_remove_sub_section(EntryArchive.workflow, n) diff --git a/nomad/parsing/parser.py b/nomad/parsing/parser.py index e488a22d94a807d179430c7c5f02c1f86fdf3aff..26cdd0984ab499bd59ead0fe71b0210578eb7fba 100644 --- a/nomad/parsing/parser.py +++ b/nomad/parsing/parser.py @@ -347,6 +347,9 @@ class MatchingParserInterface(MatchingParser): return self._mainfile_parser def parse(self, mainfile: str, archive: EntryArchive, logger=None, child_archives=None): + # TODO include child_archives in parse + if child_archives: + self.mainfile_parser._child_archives = child_archives self.mainfile_parser.parse(mainfile, archive, logger) def import_parser_class(self): @@ -361,6 +364,22 @@ class MatchingParserInterface(MatchingParser): return parser + def is_mainfile( + self, filename: str, mime: str, buffer: bytes, decoded_buffer: str, + compression: str = None) -> Union[bool, Iterable[str]]: + is_mainfile = super().is_mainfile( + filename=filename, mime=mime, buffer=buffer, + decoded_buffer=decoded_buffer, compression=compression) + if is_mainfile: + try: + self.creates_children = True + # try to resolve mainfile keys from parser + mainfile_keys = self.mainfile_parser.get_mainfile_keys(filename) + return mainfile_keys + except Exception: + return is_mainfile + return is_mainfile + class ArchiveParser(MatchingParser): def __init__(self):