Commit 50ab2622 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Merge branch 'v0.9.0' into 'master'

V0.9.0

See merge request !188
parents 239a3bce 4031b61e
Pipeline #83349 passed with stages
in 10 seconds
...@@ -121,9 +121,13 @@ The NOMAD GUI requires static artifacts that are generated from the NOMAD Python ...@@ -121,9 +121,13 @@ The NOMAD GUI requires static artifacts that are generated from the NOMAD Python
``` ```
nomad dev metainfo > gui/src/metainfo.json nomad dev metainfo > gui/src/metainfo.json
nomad dev searchQuantities > gui/src/searchQuantities.json nomad dev searchQuantities > gui/src/searchQuantities.json
nomad dev units > gui/src/units.js
./gitinfo.sh ./gitinfo.sh
``` ```
In additional, you have to do some more steps to prepare your working copy to run all
the tests. See below.
## Build and run the infrastructure with docker ## Build and run the infrastructure with docker
### Docker and nomad ### Docker and nomad
...@@ -218,6 +222,33 @@ yarn start ...@@ -218,6 +222,33 @@ yarn start
``` ```
## Run the tests ## Run the tests
### additional settings and artifacts
To run the tests some additional settings and files are necessary that are not part
of the code base.
First you need to create a `nomad.yaml` with the admin password for the user management
system:
```
keycloak:
password: <the-password>
```
Secondly, you need to provide the `springer.msg` Springer materials database. It can
be copied from `/nomad/fairdi/db/data/springer.msg` on our servers and should
be placed at `nomad/normalizing/data/springer.msg`.
Thirdly, you have to provide static files to serve the docs and NOMAD distribution:
```
cd docs
make html
cd ..
python setup.py compile
python setup.py sdist
cp dist/nomad-lab-*.tar.gz dist/nomad-lab.tar.gz
```
### run the necessary infrastructure
You need to have the infrastructure partially running: elastic, rabbitmq. You need to have the infrastructure partially running: elastic, rabbitmq.
The rest should be mocked or provided by the tests. Make sure that you do no run any The rest should be mocked or provided by the tests. Make sure that you do no run any
worker, as they will fight for tasks in the queue. worker, as they will fight for tasks in the queue.
...@@ -266,7 +297,7 @@ line size ruler, etc. ...@@ -266,7 +297,7 @@ line size ruler, etc.
"git.enableSmartCommit": true, "git.enableSmartCommit": true,
"eslint.autoFixOnSave": true, "eslint.autoFixOnSave": true,
"python.linting.pylintArgs": [ "python.linting.pylintArgs": [
"--load-plugins=pylint_mongoengine", "--load-plugins=pylint_mongoengine,nomad/metainfo/pylint_plugin",
], ],
"python.linting.pep8Path": "pycodestyle", "python.linting.pep8Path": "pycodestyle",
"python.linting.pep8Enabled": true, "python.linting.pep8Enabled": true,
......
...@@ -2,33 +2,40 @@ ...@@ -2,33 +2,40 @@
A simple example that uses the NOMAD client library to access the archive. A simple example that uses the NOMAD client library to access the archive.
''' '''
from nomad import config
from nomad.client import ArchiveQuery from nomad.client import ArchiveQuery
from nomad.metainfo import units from nomad.metainfo import units
query = ArchiveQuery( query = ArchiveQuery(
# url='http://nomad-lab.eu/prod/rae/beta/api',
query={ query={
'dft.compound_type': 'binary', '$and': [
'dft.crystal_system': 'cubic', {'dft.code_name': 'VASP'},
'dft.code_name': 'FHI-aims', {'$gte': {'n_atoms': 3}},
'atoms': ['O'] {'$lte': {'dft.workflow.section_relaxation.final_energy_difference': 1e-24}}
]
}, },
required={ required={
'section_run[0]': { 'section_workflow': {
'section_single_configuration_calculation[-2]': { 'section_relaxation': {
'energy_total': '*' 'final_calculation_ref': {
}, 'energy_total': '*',
'section_system[-2]': '*' 'single_configuration_calculation_to_system_ref': {
'chemical_composition_reduced': '*'
}
}
}
} }
}, },
per_page=10, parallel=10,
max=1000) per_page=1000,
max=10000)
print(query) for i, result in enumerate(query):
if i < 10:
for result in query[0:10]: calc = result.section_workflow.section_relaxation.final_calculation_ref
run = result.section_run[0] energy = calc.energy_total
energy = run.section_single_configuration_calculation[0].energy_total formula = calc.single_configuration_calculation_to_system_ref.chemical_composition_reduced
formula = run.section_system[0].chemical_composition_reduced
print('%s: energy %s' % (formula, energy.to(units.hartree))) print('%s: energy %s' % (formula, energy.to(units.hartree)))
print(query)
This diff is collapsed.
...@@ -2,12 +2,12 @@ import sys ...@@ -2,12 +2,12 @@ import sys
from nomad.cli.parse import parse, normalize_all from nomad.cli.parse import parse, normalize_all
# match and run the parser # match and run the parser
backend = parse(sys.argv[1]) archive = parse(sys.argv[1])
# run all normalizers # run all normalizers
normalize_all(backend) normalize_all(archive)
# get the 'main section' section_run as a metainfo object # get the 'main section' section_run as a metainfo object
section_run = backend.resource.contents[0].section_run[0] section_run = archive.section_run[0]
# get the same data as JSON serializable Python dict # get the same data as JSON serializable Python dict
python_dict = section_run.m_to_dict() python_dict = section_run.m_to_dict()
'''
A simple example used in the NOMAD webinar API tutorial
'''
from nomad.client import ArchiveQuery
query = ArchiveQuery(
url='http://labdev-nomad.esc.rzg.mpg.de/fairdi/nomad/reprocess/api',
query={
'dft.code_name': 'VASP',
'atoms': ['Ti', 'O']
},
required={
'section_run': {
'section_single_configuration_calculation[-1]': {
'energy_total': '*',
'section_dos': '*'
}
}
},
parallel=1,
max=10)
print(query)
result = query[0]
print(result.section_run[0].section_single_configuration_calculation[-1].section_dos[0].dos_energies)
# type: ignore
'''
A simple example used in the NOMAD webinar API tutorial
'''
import requests
import json
base_url = 'http://nomad-lab.eu/prod/rae/api'
# response = requests.get(base_url + '/repo?datasets.name=NOMAD%20webinar')
response = requests.get(
base_url + '/repo',
params={'datasets.name': 'NOMAD webinar', 'per_page': 1})
data = response.json()
upload_id = data['results'][0]['upload_id']
calc_id = data['results'][0]['calc_id']
response = requests.get(
base_url + '/archive/%s/%s' % (upload_id, calc_id))
print(json.dumps(response.json(), indent=2))
print(
response.json()['section_run'][0]['section_single_configuration_calculation'][-1]['section_dos'][0]['dos_energies'])
{ {
"name": "nomad-fair-gui", "name": "nomad-fair-gui",
"version": "0.8.9", "version": "0.9.0",
"commit": "e98694e", "commit": "e98694e",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@lauri-codes/materia": "0.0.6",
"@material-ui/core": "^4.0.0", "@material-ui/core": "^4.0.0",
"@material-ui/icons": "^4.0.0", "@material-ui/icons": "^4.0.0",
"@material-ui/lab": "^4.0.0-alpha.49", "@material-ui/lab": "^4.0.0-alpha.49",
...@@ -23,10 +24,12 @@ ...@@ -23,10 +24,12 @@
"lodash": "^4.17.15", "lodash": "^4.17.15",
"material-ui-chip-input": "^1.0.0-beta.14", "material-ui-chip-input": "^1.0.0-beta.14",
"material-ui-flat-pagination": "^4.0.0", "material-ui-flat-pagination": "^4.0.0",
"mathjs": "^7.1.0",
"object-hash": "^2.0.3", "object-hash": "^2.0.3",
"pace": "^0.0.4", "pace": "^0.0.4",
"pace-js": "^1.0.2", "pace-js": "^1.0.2",
"piwik-react-router": "^0.12.1", "piwik-react-router": "^0.12.1",
"plotly.js-cartesian-dist-min": "^1.54.7",
"qs": "^6.8.0", "qs": "^6.8.0",
"react": "^16.13.1", "react": "^16.13.1",
"react-app-polyfill": "^1.0.1", "react-app-polyfill": "^1.0.1",
...@@ -51,7 +54,6 @@ ...@@ -51,7 +54,6 @@
"remark": "^12.0.1", "remark": "^12.0.1",
"remark-math": "^2.0.1", "remark-math": "^2.0.1",
"swagger-client": "^3.8.22", "swagger-client": "^3.8.22",
"three.js": "^0.77.1",
"url-parse": "^1.4.3", "url-parse": "^1.4.3",
"use-query-params": "^0.6.0" "use-query-params": "^0.6.0"
}, },
......
...@@ -2,15 +2,17 @@ window.nomadEnv = { ...@@ -2,15 +2,17 @@ window.nomadEnv = {
'keycloakBase': 'https://nomad-lab.eu/fairdi/keycloak/auth/', 'keycloakBase': 'https://nomad-lab.eu/fairdi/keycloak/auth/',
'keycloakRealm': 'fairdi_nomad_test', 'keycloakRealm': 'fairdi_nomad_test',
'keycloakClientId': 'nomad_gui_dev', 'keycloakClientId': 'nomad_gui_dev',
'appBase': 'http://nomad-lab.eu/prod/rae/beta',
'appBase': 'http://localhost:8000/fairdi/nomad/latest', 'appBase': 'http://localhost:8000/fairdi/nomad/latest',
'debug': false, 'debug': false,
'matomoEnabled': false, 'matomoEnabled': false,
'matomoUrl': 'https://nomad-lab.eu/fairdi/stat', 'matomoUrl': 'https://nomad-lab.eu/fairdi/stat',
'matomoSiteId': '2', 'matomoSiteId': '2',
'version': { 'version': {
'label': '0.8.8', 'label': '0.9.0',
'isBeta': false, 'isBeta': false,
'usesBetaData': false, 'isTest': true,
'usesBetaData': true,
'officialUrl': 'https://nomad-lab.eu/prod/rae/gui' 'officialUrl': 'https://nomad-lab.eu/prod/rae/gui'
}, },
'encyclopediaEnabled': true, 'encyclopediaEnabled': true,
......
gui/public/favicon.ico

1.12 KB | W: | H:

gui/public/favicon.ico

1.12 KB | W: | H:

gui/public/favicon.ico
gui/public/favicon.ico
gui/public/favicon.ico
gui/public/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
window.paceOptions = { window.paceOptions = {
restartOnPushState: true restartOnPushState: true
} }
// This needs to be defined for Plotly.js graphs, see
// https://github.com/plotly/plotly.js/blob/master/dist/README.md#partial-bundles
window.PlotlyConfig = {MathJaxConfig: 'local'}
</script> </script>
<script src="https://unpkg.com/pace-js@1.0.2/pace.min.js"></script> <script src="https://unpkg.com/pace-js@1.0.2/pace.min.js"></script>
<link href="%PUBLIC_URL%/pace.css" rel="stylesheet" /> <link href="%PUBLIC_URL%/pace.css" rel="stylesheet" />
...@@ -38,7 +41,6 @@ ...@@ -38,7 +41,6 @@
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script> <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<title>NOMAD</title> <title>NOMAD</title>
</head> </head>
<body> <body>
......
This diff is collapsed.
import React, { useContext, useLayoutEffect, useRef, useCallback, useEffect } from 'react' import React, { useContext, useLayoutEffect, useRef, useCallback, useEffect, useState } from 'react'
import {ReactComponent as AboutSvg} from './about.svg' import {ReactComponent as AboutSvg} from './about.svg'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Markdown from './Markdown' import Markdown from './Markdown'
import { appBase, optimadeBase, apiBase, debug, consent } from '../config' import { appBase, optimadeBase, apiBase, debug, consent, aitoolkitEnabled, encyclopediaEnabled } from '../config'
import { apiContext } from './api' import { apiContext } from './api'
import packageJson from '../../package.json' import packageJson from '../../package.json'
import { domains } from './domains' import { domains } from './domains'
import { Grid, Card, CardContent, Typography, makeStyles, Link } from '@material-ui/core' import { Grid, Card, CardContent, Typography, makeStyles, Link, Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@material-ui/core'
import { Link as RouterLink, useHistory } from 'react-router-dom' import { Link as RouterLink, useHistory } from 'react-router-dom'
import tutorials from '../toolkitMetadata'
import parserMetadata from '../parserMetadata'
export const CodeList = () => { function CodeInfo({code, ...props}) {
const {info} = useContext(apiContext) if (!code) {
return ''
}
if (!info) { const metadata = parserMetadata[code]
return '...'
let introduction = `
For [${metadata.codeLabel || code}](${metadata.codeUrl}) please provide
all files that were used as input, were output by the code, or were produced you.
`
if (metadata.tableOfFiles && metadata.tableOfFiles !== '') {
introduction = `
For [${metadata.codeLabel || code}](${metadata.codeUrl}) please provide at least
the files from the following table (if applicable). Ideally, you upload
all files that were used as input, were output by the code, or were produced you.
`
} }
return info.codes.reduce((result, code, index) => { return <Dialog open={true} {...props}>
if (index !== 0) { <DialogTitle>{metadata.codeLabel || code}</DialogTitle>
result.push(', ') <DialogContent>
<Markdown>{`
${introduction} NOMAD will present all files in the same directory for each
recognized calculation. This works best, if you put all files that belong to
individual code runs into individual directories or only combine files from a few
runs in the same directory.
${metadata.tableOfFiles}
${(metadata.parserSpecific && metadata.parserSpecific !== '' &&
`Please note specifically for ${metadata.codeLabel || code}: ${metadata.parserSpecific}`) || ''}
To create an upload with all calculations in a directory structure:
\`\`\`
zip -r <upload-file>.zip <directory>/*
\`\`\`
You can find further information on [the project page for NOMAD's ${metadata.codeLabel || code} parser](${metadata.parserGitUrl}).
`}</Markdown>
</DialogContent>
<DialogActions>
<Button onClick={() => props.onClose && props.onClose()} color="primary">
close
</Button>
</DialogActions>
</Dialog>
}
CodeInfo.propTypes = {
code: PropTypes.string,
onClose: PropTypes.func
}
export const CodeList = ({withUploadInstructions}) => {
const [selected, setSelected] = useState(null)
const codes = Object.keys(parserMetadata).map(code => {
const metadata = parserMetadata[code]
if (!metadata) {
return code
} }
if (code.code_homepage) {
result.push(<Link target="external" key={code.code_name} href={code.code_homepage}>{code.code_name}</Link>) if (withUploadInstructions) {
} else { return <Link
result.push(code.code_name) href="#" key={code} onClick={() => setSelected(code)}
>{code.codeLabel || code}</Link>
}
if (metadata.codeUrl) {
return <Link href={metadata.codeUrl} key={code} target="code">{code.codeLabel || code}</Link>
}
return code
})
const toRender = codes.reduce((list, value, index) => {
if (index !== 0) {
list.push(', ')
} }
return result list.push(value)
return list
}, []) }, [])
return <React.Fragment>
{toRender}
<CodeInfo code={selected} onClose={() => setSelected(null)} />
</React.Fragment>
}
CodeList.propTypes = {
withUploadInstructions: PropTypes.bool
} }
const useCardStyles = makeStyles(theme => ({ const useCardStyles = makeStyles(theme => ({
...@@ -97,10 +172,18 @@ export default function About() { ...@@ -97,10 +172,18 @@ export default function About() {
history.push('/upload') history.push('/upload')
}) })
makeClickable('encyclopedia', () => { makeClickable('encyclopedia', () => {
if (encyclopediaEnabled) {
window.location.href = `${appBase}/encyclopedia`
} else {
window.location.href = 'https://encyclopedia.nomad-coe.eu/gui/#/search' window.location.href = 'https://encyclopedia.nomad-coe.eu/gui/#/search'
}
}) })
makeClickable('analytics', () => { makeClickable('toolkit', () => {
window.location.href = 'https://nomad-lab.eu/AItutorials' if (aitoolkitEnabled) {
history.push('/aitoolkit')
} else {
window.location.href = 'https://nomad-lab.eu/tools/AItutorials'
}
}) })
makeClickable('search', () => { makeClickable('search', () => {
history.push('/search') history.push('/search')
...@@ -109,16 +192,19 @@ export default function About() { ...@@ -109,16 +192,19 @@ export default function About() {
useEffect(() => { useEffect(() => {
const statistics = (info && info.statistics) || {} const statistics = (info && info.statistics) || {}
statistics.n_tutorials = tutorials.tutorials.length
const value = (key, unit) => { const value = (key, unit) => {
const nominal = statistics[key] const nominal = statistics[key]
let stringValue = null let stringValue = null
if (nominal) { if (nominal) {
if (nominal >= 1.0e+9) { if (nominal >= 1.0e+9) {
stringValue = Math.floor(nominal / 1.0e+9) + ' bln.' stringValue = (nominal / 1.0e+9).toFixed(1) + ' billion'
} else if (nominal >= 1.0e+6) { } else if (nominal >= 1.0e+6) {
stringValue = Math.floor(nominal / 1.0e+6) + ' mln.' stringValue = (nominal / 1.0e+6).toFixed(1) + ' million'
} else if (nominal >= 1.0e+3) {
stringValue = (nominal / 1.0e+3).toFixed(1) + ' thousand'
} else { } else {
stringValue = Math.floor(nominal / 1.0e+3) + ' tsd.' stringValue = nominal.toString()
} }
return `${stringValue || '...'} ${unit}` return `${stringValue || '...'} ${unit}`
} else { } else {
...@@ -133,6 +219,12 @@ export default function About() { ...@@ -133,6 +219,12 @@ export default function About() {
value('n_calculations', 'results'), value('n_calculations', 'results'),
value('n_quantities', 'quantities') value('n_quantities', 'quantities')
]) ])
setText('encStats', [
value('n_materials', 'materials')
])
setText('toolkitStats', [
value('n_tutorials', 'notebooks')
])
}, [svg, info, setText]) }, [svg, info, setText])
return <div className={classes.root}> return <div className={classes.root}>
......
...@@ -58,6 +58,8 @@ export function ApiDialog({title, data, onClose, ...dialogProps}) { ...@@ -58,6 +58,8 @@ export function ApiDialog({title, data, onClose, ...dialogProps}) {
<DialogTitle>{title || 'API Code'}</DialogTitle> <DialogTitle>{title || 'API Code'}</DialogTitle>
<DialogContent classes={{root: classes.content}}> <DialogContent classes={{root: classes.content}}>
{ data.code && data.code.repo_url &&
renderCode(<span>URL to this query on the repository API:</span>, data.code.repo_url)}
{ data.code && data.code.curl && { data.code && data.code.curl &&
renderCode(<span>Access the archive as JSON via <i>curl</i>:</span>, data.code.curl)} renderCode(<span>Access the archive as JSON via <i>curl</i>:</span>, data.code.curl)}
{ data.code && data.code.python && { data.code && data.code.python &&
......
...@@ -5,13 +5,14 @@ import classNames from 'classnames' ...@@ -5,13 +5,14 @@ import classNames from 'classnames'
import { MuiThemeProvider, withStyles, makeStyles } from '@material-ui/core/styles' import { MuiThemeProvider, withStyles, makeStyles } from '@material-ui/core/styles'
import { LinearProgress, MenuList, Typography, import { LinearProgress, MenuList, Typography,
AppBar, Toolbar, Button, DialogContent, DialogTitle, DialogActions, Dialog, Tooltip, AppBar, Toolbar, Button, DialogContent, DialogTitle, DialogActions, Dialog, Tooltip,
Snackbar, SnackbarContent, FormGroup, FormControlLabel, Switch, IconButton } from '@material-ui/core' Snackbar, SnackbarContent, FormGroup, FormControlLabel, Switch, IconButton, Link as MuiLink } from '@material-ui/core'
import { Route, Link, withRouter, useLocation } from 'react-router-dom' import { Route, Link, withRouter, useLocation } from 'react-router-dom'
import BackupIcon from '@material-ui/icons/Backup' import BackupIcon from '@material-ui/icons/Backup'
import SearchIcon from '@material-ui/icons/Search' import SearchIcon from '@material-ui/icons/Search'
import UserDataIcon from '@material-ui/icons/AccountCircle' import UserDataIcon from '@material-ui/icons/AccountCircle'
import AboutIcon from '@material-ui/icons/Home' import AboutIcon from '@material-ui/icons/Home'
import FAQIcon from '@material-ui/icons/QuestionAnswer' import FAQIcon from '@material-ui/icons/QuestionAnswer'
import EncyclopediaIcon from '@material-ui/icons/Language'
import MetainfoIcon from '@material-ui/icons/Info' import MetainfoIcon from '@material-ui/icons/Info'
import DocIcon from '@material-ui/icons/Help' import DocIcon from '@material-ui/icons/Help'
import CodeIcon from '@material-ui/icons/Code' import CodeIcon from '@material-ui/icons/Code'
...@@ -25,7 +26,7 @@ import { ErrorSnacks, withErrors } from './errors' ...@@ -25,7 +26,7 @@ import { ErrorSnacks, withErrors } from './errors'
import { help as entryHelp, default as EntryPage } from './entry/EntryPage' import { help as entryHelp, default as EntryPage } from './entry/EntryPage'