Commit 0bde4980 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Merge branch 'v0.10.3' into 'master'

Merge for release

See merge request !322
parents c90507e5 a9517569
Pipeline #101663 passed with stages
in 28 minutes and 42 seconds
......@@ -138,9 +138,6 @@
path = dependencies/parsers/onetep
url = https://github.com/nomad-coe/nomad-parser-onetep.git
branch = master
[submodule "dependencies/optimade-python-tools"]
path = dependencies/optimade-python-tools
url = https://github.com/markus1978/optimade-python-tools.git
[submodule "dependencies/parsers/namd"]
path = dependencies/parsers/namd
url = https://github.com/nomad-coe/nomad-parser-namd.git
......
============================= test session starts ==============================
platform darwin -- Python 3.7.7, pytest-3.10.0, py-1.10.0, pluggy-0.13.1 -- /Users/markus/Documents/Projects/nomad-fairdi/.pyenv/bin/python
cachedir: .pytest_cache
rootdir: /Users/markus/Documents/Projects/nomad-fairdi/tests, inifile: pytest.ini
plugins: celery-4.4.7, cov-2.7.1, timeout-1.4.2
collecting ...
========================= no tests ran in 0.01 seconds =========================
......@@ -46,6 +46,12 @@ contributing, and API reference.
Omitted versions are plain bugfix releases with only minor changes and fixes.
### v0.10.3
- fixes in the VASP parser
- new turbemole parser
- property placeholders while loading entry page
- improved UI navigation with breadcrumbs
### v0.10.2
- fixes small parser and normalizer issues
- fixes broken embargo lifting
......
Subproject commit b5731ab61f5ef0d019426523b8b21ad4c82596a2
Subproject commit 4a6ba4d186f76406cd9afba3da23b11bebb76767
Subproject commit 96bfa6e2949c27a98e5480e31c57b603f05ab10f
Subproject commit e51352236af14d8f1a4345f8570329f00bec1b5e
Subproject commit 2e3447c0d0b054fc916bbb8824073099c3085ca4
Subproject commit a58cde2ea11970b947fd0e5da3e61da5c7ec828e
Subproject commit cbdce6c1287f186924e91c7d6ba2ff14e23f81ae
......@@ -16,12 +16,14 @@ You work in a Python virtual environment.
#### pyenv
The nomad code currently targets python 3.7. If you host machine has an older version installed,
you can use [pyenv](https://github.com/pyenv/pyenv) to use python 3.7 in parallel to your
system's python.
system's python. Never the less, we have good experience with 3.8 and 3.9 users as well
and everything might work with newer versions as well.
#### virtualenv
We strongly recommend to use *virtualenv* to create a virtual environment. It will allow you
to keep nomad and its dependencies separate from your system's python installation.
Make sure to base the virtual environment on Python 3.
To install *virtualenv*, create an environment and activate the environment use:
```
pip install virtualenv
......@@ -29,6 +31,9 @@ virtualenv -p `which python3` .pyenv
source .pyenv/bin/activate
```
If you use *pyenv* (or similar solutions) make sure that the `-p` arguments evaluates
to the `python` binary with the desired version.
#### conda
If you are a conda user, there is an equivalent, but you have to install pip and the
right python version while creating the environment.
......
{
"name": "nomad-fair-gui",
"version": "0.10.2",
"version": "0.10.3",
"commit": "e98694e",
"private": true,
"workspaces": [
......
......@@ -9,7 +9,7 @@ window.nomadEnv = {
'matomoUrl': 'https://nomad-lab.eu/fairdi/stat',
'matomoSiteId': '2',
'version': {
'label': '0.10.2',
'label': '0.10.3',
'isBeta': false,
'isTest': true,
'usesBetaData': true,
......
......@@ -77,7 +77,10 @@ function UserdataPage() {
initialRequest={{order_by: 'upload_time', uploads_grouped: true}}
initialResultTab="uploads"
availableResultTabs={['uploads', 'datasets', 'entries', ...(encyclopediaEnabled ? ['materials'] : [])]}
resultListProps={{selectedColumnsKey: 'userEntries', selectedColumns: ['formula', 'upload_time', 'mainfile', 'published', 'co_authors', 'references', 'datasets']}}
resultListProps={{
selectedColumnsKey: 'userEntries',
entryPagePathPrefix: '/userdata',
selectedColumns: ['formula', 'upload_time', 'mainfile', 'published', 'co_authors', 'references', 'datasets']}}
/>
}
......
......@@ -16,7 +16,7 @@
* limitations under the License.
*/
import React, { useMemo } from 'react'
import { Typography, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails, makeStyles, Link, ExpansionPanelActions, Button, Grid, TextField } from '@material-ui/core'
import { Typography, Accordion, AccordionSummary, AccordionDetails, makeStyles, Link, AccordionActions, Button, Grid, TextField } from '@material-ui/core'
import tutorials from '../../toolkitMetadata'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import Markdown from '../Markdown'
......@@ -128,17 +128,17 @@ export default function AIToolkitPage() {
<div>
{section.tutorials.map(tutorial => {
const key = tutorial.key
return <ExpansionPanel
return <Accordion
key={key}
disabled={!filter(tutorial)}
expanded={expanded === key}
onChange={() => setExpanded(expanded === key ? null : key)}
className={classes.tutorial}
>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography className={classes.tutorialTitle}>{tutorial.title}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.tutorialDetails}>
</AccordionSummary>
<AccordionDetails className={classes.tutorialDetails}>
<Typography>
{tutorial.authors
.map(name => {
......@@ -191,16 +191,16 @@ export default function AIToolkitPage() {
)).reduce((prev, curr) => [prev, ', ', curr])
}
</Typography>
</ExpansionPanelDetails>
<ExpansionPanelActions>
</AccordionDetails>
<AccordionActions>
<Button color="primary" href={tutorial.link} target="tutorial">
open with login
</Button>
<Button color="primary" href={tutorial.link_public} target="tutorial">
open as guest
</Button>
</ExpansionPanelActions>
</ExpansionPanel>
</AccordionActions>
</Accordion>
})}
</div>
</div>
......
......@@ -28,11 +28,10 @@ import { Matrix, Number } from './visualizations'
import Structure from '../visualization/Structure'
import BrillouinZone from '../visualization/BrillouinZone'
import BandStructure from '../visualization/BandStructure'
import { ErrorHandler } from '../ErrorHandler'
import DOS from '../visualization/DOS'
import Markdown from '../Markdown'
import { UnitSelector } from './UnitSelector'
import { convertSI } from '../../utils'
import { convertSI, getHighestOccupiedEnergy } from '../../utils'
import { conversionMap } from '../../units'
import { electronicRange } from '../../config'
......@@ -223,17 +222,18 @@ function archiveSearchOptions(data) {
}
class ArchiveAdaptor extends Adaptor {
constructor(obj, def, context) {
constructor(obj, def, parent, context) {
super(obj)
this.def = def
this.parent = parent
this.context = context
}
adaptorFactory(obj, def, context) {
adaptorFactory(obj, def, parent, context) {
if (def.m_def === 'Section') {
return new SectionAdaptor(obj, def, context || this.context)
return new SectionAdaptor(obj, def, parent, context || this.context)
} else if (def.m_def === 'Quantity') {
return new QuantityAdaptor(obj, def, context || this.context)
return new QuantityAdaptor(obj, def, parent, context || this.context)
}
}
......@@ -256,9 +256,9 @@ class SectionAdaptor extends ArchiveAdaptor {
} else if (property.m_def === 'SubSection') {
const sectionDef = resolveRef(property.sub_section)
if (property.repeats) {
return this.adaptorFactory(value[parseInt(index || 0)], sectionDef)
return this.adaptorFactory(value[parseInt(index || 0)], sectionDef, this.e)
} else {
return this.adaptorFactory(value, sectionDef)
return this.adaptorFactory(value, sectionDef, this.e)
}
} else if (property.m_def === 'Quantity') {
if (property.type.type_kind === 'reference' && property.shape.length === 0) {
......@@ -269,15 +269,15 @@ class SectionAdaptor extends ArchiveAdaptor {
if (resolvedDef.name === 'User' && !resolved.user_id) {
resolved.user_id = value
}
return this.adaptorFactory(resolved, resolveRef(property.type.type_data))
return this.adaptorFactory(resolved, resolveRef(property.type.type_data), this.e)
}
return this.adaptorFactory(value, property)
return this.adaptorFactory(value, property, this.e)
} else {
throw new Error('Unknown metainfo meta definition')
}
}
render() {
return <Section section={this.e} def={this.def} />
return <Section section={this.e} def={this.def} parent={this.parent} />
}
}
......@@ -371,7 +371,7 @@ QuantityValue.propTypes = ({
* An optional overview for a section displayed directly underneath the section
* title.
*/
function Overview({section, def}) {
function Overview({section, def, parent}) {
// States
const [mode, setMode] = useState('bs')
const units = useRecoilValue(unitsState)
......@@ -385,8 +385,7 @@ function Overview({section, def}) {
margin: 'auto'
},
structure: {
width: '20rem',
height: '20rem',
width: '28rem',
margin: 'auto'
},
dos: {
......@@ -432,7 +431,7 @@ function Overview({section, def}) {
visualizedSystem.nAtoms = nAtoms
return <Structure
aspectRatio={1}
aspectRatio={4 / 3}
className={style.structure}
system={system}
></Structure>
......@@ -456,6 +455,7 @@ function Overview({section, def}) {
</Structure>
// Band structure plot for section_k_band
} else if (def.name === 'KBand') {
section.energy_highest_occupied = getHighestOccupiedEnergy(section, parent)
return section.band_structure_kind !== 'vibrational'
? <>
{mode === 'bs'
......@@ -497,26 +497,33 @@ function Overview({section, def}) {
data={section}
aspectRatio={1}
unitsState={unitsState}
type='vibrational'
></BandStructure>
</Box>
// DOS plot for section_dos
} else if (def.name === 'Dos') {
const isVibrational = section.dos_kind === 'vibrational'
const layout = isVibrational
? undefined
: {yaxis: {autorange: false, range: convertSI(electronicRange, 'electron_volt', units, false)}}
return <DOS
className={style.dos}
layout={section.dos_kind === 'vibrational' ? undefined : {yaxis: {autorange: false, range: convertSI(electronicRange, 'electron_volt', units, false)}}}
layout={layout}
data={section}
aspectRatio={1 / 2}
unitsState={unitsState}
type={isVibrational ? 'vibrational' : null}
></DOS>
}
return null
}
Overview.propTypes = ({
def: PropTypes.object,
section: PropTypes.object
section: PropTypes.object,
parent: PropTypes.object
})
function Section({section, def}) {
function Section({section, def, parent}) {
const config = useRecoilValue(configState)
if (!section) {
......@@ -527,9 +534,7 @@ function Section({section, def}) {
const filter = config.showCodeSpecific ? def => true : def => !def.name.startsWith('x_')
return <Content>
<Title def={def} data={section} kindLabel="section" />
<ErrorHandler message="The section overview could not be rendered, due to an unexpected error.">
<Overview def={def} section={section}></Overview>
</ErrorHandler>
<Overview def={def} section={section} parent={parent}></Overview>
<Compartment title="sub sections">
{def.sub_sections
.filter(subSectionDef => section[subSectionDef.name] || config.showAllDefined)
......@@ -579,7 +584,8 @@ function Section({section, def}) {
}
Section.propTypes = ({
section: PropTypes.object.isRequired,
def: PropTypes.object.isRequired
def: PropTypes.object.isRequired,
parent: PropTypes.object.isRequired
})
function Quantity({value, def}) {
......
......@@ -15,12 +15,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useContext, useState, useEffect } from 'react'
import React, { useContext, useState, useMemo, useEffect } from 'react'
import PropTypes from 'prop-types'
import { Box, Card, CardContent, Grid, Typography, Link, makeStyles, Divider } from '@material-ui/core'
import { apiContext } from '../api'
import ElectronicStructureOverview from '../visualization/ElectronicStructureOverview'
import VibrationalOverview from '../visualization/VibrationalOverview'
import ElectronicProperties from '../visualization/ElectronicProperties'
import VibrationalProperties from '../visualization/VibrationalProperties'
import GeometryOptimization from '../visualization/GeometryOptimization'
import { ApiDialog } from '../ApiDialogButton'
import Structure from '../visualization/Structure'
import NoData from '../visualization/NoData'
......@@ -31,12 +32,11 @@ import { Link as RouterLink } from 'react-router-dom'
import { DOI } from '../search/DatasetList'
import { domains } from '../domains'
import { errorContext } from '../errors'
import { authorList, convertSI, mergeObjects } from '../../utils'
import { authorList, convertSI, mergeObjects, getHighestOccupiedEnergy } from '../../utils'
import { resolveRef, refPath } from '../archive/metainfo'
import _ from 'lodash'
import {appBase, encyclopediaEnabled, normalizeDisplayValue} from '../../config'
import GeoOptOverview from '../visualization/GeoOptOverview'
const useHeaderStyles = makeStyles(theme => ({
root: {
......@@ -127,9 +127,17 @@ const useStyles = makeStyles(theme => ({
cardHeader: {
paddingBottom: 0
},
sidebar: {
leftSidebar: {
maxWidth: '32%',
flexBasis: '32%',
flexGrow: 0,
paddingRight: theme.spacing(3)
},
rightSidebar: {
maxWidth: '67.99%',
flexBasis: '67.99%',
flexGrow: 0
},
divider: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1)
......@@ -146,17 +154,32 @@ const useStyles = makeStyles(theme => ({
* Shows an informative overview about the selected entry.
*/
export default function DFTEntryOverview({data}) {
const availableProps = useMemo(() => {
let properties
if (data?.dft?.searchable_quantities) {
properties = new Set(data.dft.searchable_quantities)
} else {
properties = new Set()
}
if (data?.dft?.workflow?.workflow_type === 'geometry_optimization') {
properties.add('geometry_optimization')
}
return properties
}, [data])
const {api} = useContext(apiContext)
const {raiseError} = useContext(errorContext)
const [electronicStructure, setElectronicStructure] = useState(null)
const [vibrationalData, setVibrationalData] = useState(null)
const [geoOpt, setGeoOpt] = useState(null)
const [dosElectronic, setDosElectronic] = useState(availableProps.has('electronic_dos') ? null : false)
const [bsElectronic, setBsElectronic] = useState(availableProps.has('electronic_band_structure') ? null : false)
const [bsPhonon, setBsPhonon] = useState(availableProps.has('phonon_band_structure') ? null : false)
const [dosPhonon, setDosPhonon] = useState(availableProps.has('phonon_dos') ? null : false)
const [heatCapacity, setHeatCapacity] = useState(availableProps.has('thermodynamical_property_heat_capacity_C_v') ? null : false)
const [freeEnergy, setFreeEnergy] = useState(availableProps.has('vibrational_free_energy_at_constant_volume') ? null : false)
const [dataGeoOpt, setDataGeoOpt] = useState(availableProps.has('geometry_optimization') ? null : false)
const [structures, setStructures] = useState(null)
const [method, setMethod] = useState(null)
const [loading, setLoading] = useState(true)
const [showAPIDialog, setShowAPIDialog] = useState(false)
const materialType = data?.encyclopedia?.material?.material_type
const styles = useStyles()
// When loaded for the first time, start downloading the archive. Once
......@@ -199,11 +222,12 @@ export default function DFTEntryOverview({data}) {
}
if (!e_bs && scc.section_k_band) {
const first_band = scc.section_k_band[scc.section_k_band.length - 1]
first_band.energy_highest_occupied = getHighestOccupiedEnergy(first_band, scc)
if (first_band.band_structure_kind !== 'vibrational') {
e_bs = {
'section_system': scc.single_configuration_calculation_to_system_ref,
'section_method': scc.single_configuration_calculation_to_system_ref,
'section_k_band': scc.section_k_band[scc.section_k_band.length - 1],
'section_k_band': first_band,
'path': `${url}/section_run/section_single_configuration_calculation:${i}/section_k_band:${scc.section_k_band.length - 1}`
}
}
......@@ -219,10 +243,11 @@ export default function DFTEntryOverview({data}) {
}
}
if (e_dos || e_bs) {
setElectronicStructure({
'dos': e_dos, 'bs': e_bs
})
if (e_dos) {
setDosElectronic(e_dos)
}
if (e_bs) {
setBsElectronic(e_bs)
}
// See if there are workflow results
......@@ -235,7 +260,6 @@ export default function DFTEntryOverview({data}) {
if (wfType === 'geometry_optimization') {
let failed = false
let energies = []
const trajectory = []
try {
const calculations = section_wf.calculations_ref
let initialEnergy = null
......@@ -257,17 +281,6 @@ export default function DFTEntryOverview({data}) {
initialEnergy = e
}
energies.push(e - initialEnergy)
let sys = calc.single_configuration_calculation_to_system_ref
sys = resolveRef(sys, archive)
if (sys === undefined) {
throw Error('invalid system reference')
}
trajectory.push({
species: sys.atom_species,
cell: sys.lattice_vectors ? convertSI(sys.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
positions: convertSI(sys.atom_positions, 'meter', {length: 'angstrom'}, false),
pbc: sys.configuration_periodic_dimensions
})
}
} catch (err) {
failed = true
......@@ -277,10 +290,10 @@ export default function DFTEntryOverview({data}) {
const e_criteria_wf = section_wf?.section_geometry_optimization?.input_energy_difference_tolerance
const sampling_method = section_run?.section_sampling_method
const e_criteria_fs = sampling_method && sampling_method[0]?.geometry_optimization_energy_change
const e_criteria = e_criteria_wf || e_criteria_fs
setGeoOpt({energies: energies, structures: trajectory, energy_change_criteria: e_criteria})
const e_criteria = convertSI(e_criteria_wf || e_criteria_fs, 'joule', {energy: 'electron_volt'}, false)
setDataGeoOpt({energies: energies, energy_change_criteria: e_criteria})
} else {
setGeoOpt({})
setDataGeoOpt({})
}
} else if (wfType === 'phonon') {
// Find phonon dos and dispersion
......@@ -306,7 +319,6 @@ export default function DFTEntryOverview({data}) {
// Find thermal properties
let free_energy = null
let heat_capacity = null
let temperature = null
const sequences = section_run.section_frame_sequence
const sequence = sequences && sequences[sequences.length - 1]
if (sequence) {
......@@ -314,25 +326,20 @@ export default function DFTEntryOverview({data}) {
if (properties) {
heat_capacity = {
thermodynamical_property_heat_capacity_C_v: properties.thermodynamical_property_heat_capacity_C_v,
path: `${url}/section_run/section_frame_sequence:${sequences.length - 1}/section_thermodynamical_properties/thermodynamical_property_heat_capacity_C_v`
path: `${url}/section_run/section_frame_sequence:${sequences.length - 1}/section_thermodynamical_properties/thermodynamical_property_heat_capacity_C_v`,
temperature: properties.thermodynamical_property_temperature
}
free_energy = {
vibrational_free_energy_at_constant_volume: properties.vibrational_free_energy_at_constant_volume,
path: `${url}/section_run/section_frame_sequence:${sequences.length - 1}/section_thermodynamical_properties/vibrational_free_energy_at_constant_volume`
path: `${url}/section_run/section_frame_sequence:${sequences.length - 1}/section_thermodynamical_properties/vibrational_free_energy_at_constant_volume`,
temperature: properties.thermodynamical_property_temperature
}
temperature = properties.thermodynamical_property_temperature
}
}
if (v_dos || v_bs || free_energy || heat_capacity) {
setVibrationalData({
dos: v_dos,
bs: v_bs,
free_energy: free_energy,
heat_capacity: heat_capacity,
temperature: temperature
})
}
v_dos && setDosPhonon(v_dos)
v_bs && setBsPhonon(v_bs)
free_energy && setFreeEnergy(free_energy)
heat_capacity && setHeatCapacity(heat_capacity)
}
}
......@@ -408,7 +415,7 @@ export default function DFTEntryOverview({data}) {
raiseError(error)
}
}).finally(() => setLoading(false))
}, [data, api, raiseError, setElectronicStructure, setStructures])
}, [data, api, raiseError])
const quantityProps = {data: data, loading: !data}
const domain = data.domain && domains[data.domain]
......@@ -418,7 +425,7 @@ export default function DFTEntryOverview({data}) {
<Grid container spacing={0} className={styles.root}>
{/* Left column */}
<Grid item xs={4} className={styles.sidebar}>
<Grid item xs={4} className={styles.leftSidebar}>
<SidebarCard title='Method'>
<Quantity flex>
<Quantity quantity="dft.code_name" label='code name' noWrap {...quantityProps}/>
......@@ -510,7 +517,7 @@ export default function DFTEntryOverview({data}) {
</Grid>
{/* Right column */}
<Grid item xs={8}>
<Grid item xs={8} className={styles.rightSidebar}>
<PropertyCard title="Material">
<Grid container spacing={1}>
<Grid item xs={5}>
......@@ -553,23 +560,33 @@ export default function DFTEntryOverview({data}) {
</Grid>
</Grid>
</PropertyCard>
{electronicStructure &&
{(dosElectronic !== false ||
bsElectronic !== false) &&
<PropertyCard title="Electronic properties">
<ElectronicStructureOverview
data={electronicStructure}>
</ElectronicStructureOverview>
<ElectronicProperties
bs={bsElectronic}
dos={dosElectronic}
>
</ElectronicProperties>
</PropertyCard>
}
{geoOpt && structures &&
{dataGeoOpt !== false &&
<PropertyCard title="Geometry optimization">
<GeoOptOverview data={geoOpt}></GeoOptOverview>
<GeometryOptimization data={dataGeoOpt}></GeometryOptimization>
</PropertyCard>
}
{vibrationalData &&
{(dosPhonon !== false ||
bsPhonon !== false ||
heatCapacity !== false ||
freeEnergy !== false) &&
<PropertyCard title="Vibrational properties">
<VibrationalOverview
data={vibrationalData}>
</VibrationalOverview>
<VibrationalProperties
bs={bsPhonon}
dos={dosPhonon}
heatCapacity={heatCapacity}
freeEnergy={freeEnergy}
>
</VibrationalProperties>
</PropertyCard>
}
</Grid>
......
......@@ -17,7 +17,7 @@
*/
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, Fab, Typography, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails } from '@material-ui/core'
import { withStyles, Fab, Typography, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core'
import { compose } from 'recompose'
import { withApi } from '../api'
import Download from './Download'
......@@ -55,20 +55,20 @@ class LogEntryUnstyled extends React.Component {