Commit ddf3c2db authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Fixed issue with unit system information not propagating in the...

Fixed issue with unit system information not propagating in the ArchiveBrowser, added possibility to fetch converted unit labels together with values.
parent 70da69cd
Pipeline #107433 passed with stages
in 35 minutes and 13 seconds
......@@ -39,8 +39,7 @@ export const configState = atom({
default: {
'showMeta': false,
'showCodeSpecific': false,
'showAllDefined': false,
'energyUnit': 'joule'
'showAllDefined': false
}
})
......@@ -50,9 +49,15 @@ const visualizedSystem = {}
export default function ArchiveBrowser({data}) {
const searchOptions = useMemo(() => archiveSearchOptions(data), [data])
// For some reason, this hook does not work in all of the components used in
// the Browser (notably: Quantity, QuantityItemPreview). In order to pass the
// up-to-date unit information, we pass the hook value down the component
// hierarchy.
const units = useUnits()
return (
<Browser
adaptor={archiveAdaptorFactory(data)}
adaptor={archiveAdaptorFactory(data, undefined, units)}
form={<ArchiveConfigForm searchOptions={searchOptions} />}
/>
)
......@@ -137,8 +142,8 @@ ArchiveConfigForm.propTypes = ({
searchOptions: PropTypes.arrayOf(PropTypes.object).isRequired
})
function archiveAdaptorFactory(data, sectionDef) {
return new SectionAdaptor(data, sectionDef || rootSections.find(def => def.name === 'EntryArchive'), undefined, {archive: data})
function archiveAdaptorFactory(data, sectionDef, units) {
return new SectionAdaptor(data, sectionDef || rootSections.find(def => def.name === 'EntryArchive'), undefined, {archive: data}, units)
}
function archiveSearchOptions(data) {
......@@ -204,18 +209,19 @@ function archiveSearchOptions(data) {
}
class ArchiveAdaptor extends Adaptor {
constructor(obj, def, parent, context) {
constructor(obj, def, parent, context, units) {
super(obj)
this.def = def
this.parent = parent
this.context = context
this.units = units
}
adaptorFactory(obj, def, parent, context) {
if (def.m_def === 'Section') {
return new SectionAdaptor(obj, def, parent, context || this.context)
return new SectionAdaptor(obj, def, parent, context || this.context, this.units)
} else if (def.m_def === 'Quantity') {
return new QuantityAdaptor(obj, def, parent, context || this.context)
return new QuantityAdaptor(obj, def, parent, context || this.context, this.units)
}
}
......@@ -264,18 +270,17 @@ class SectionAdaptor extends ArchiveAdaptor {
}
}
render() {
return <Section section={this.e} def={this.def} parent={this.parent} />
return <Section section={this.e} def={this.def} parent={this.parent} units={this.units}/>
}
}
class QuantityAdaptor extends ArchiveAdaptor {
render() {
return <Quantity value={this.e} def={this.def} />
return <Quantity value={this.e} def={this.def} units={this.units}/>
}
}
function QuantityItemPreview({value, def}) {
const units = useUnits()
function QuantityItemPreview({value, def, units}) {
if (def.type.type_kind === 'reference') {
return <Box component="span" fontStyle="italic">
<Typography component="span">reference ...</Typography>
......@@ -310,28 +315,25 @@ function QuantityItemPreview({value, def}) {
</Typography>
</Box>
} else {
let finalValue = value
if (def.unit) {
finalValue = toUnitSystem(value, def.unit, units)
}
const [finalValue, finalUnit] = def.unit
? toUnitSystem(value, def.unit, units, true)
: [value, def.unit]
return <Box component="span" whiteSpace="nowarp">
<Number component="span" variant="body1" value={finalValue} exp={8} />
{def.unit && <Typography component="span">&nbsp;{def.unit}</Typography>}
{finalUnit && <Typography component="span">&nbsp;{finalUnit}</Typography>}
</Box>
}
}
QuantityItemPreview.propTypes = ({
value: PropTypes.any,
def: PropTypes.object.isRequired
def: PropTypes.object.isRequired,
units: PropTypes.object
})
function QuantityValue({value, def}) {
// Figure out the units
const units = useUnits()
let finalValue = value
if (def.unit) {
finalValue = toUnitSystem(value, def.unit, units)
}
function QuantityValue({value, def, units}) {
const [finalValue, finalUnit] = def.unit
? toUnitSystem(value, def.unit, units, true)
: [value, def.unit]
return <Box
marginTop={2} marginBottom={2} textAlign="center" fontWeight="bold"
......@@ -344,22 +346,22 @@ function QuantityValue({value, def}) {
</span>)}&nbsp;)
</Typography>
}
{def.unit && <Typography noWrap>{def.unit}</Typography>}
{finalUnit && <Typography noWrap>{finalUnit}</Typography>}
</Box>
}
QuantityValue.propTypes = ({
value: PropTypes.any,
def: PropTypes.object.isRequired
def: PropTypes.object.isRequired,
units: PropTypes.object
})
/**
* An optional overview for a section displayed directly underneath the section
* title.
*/
function Overview({section, def, parent}) {
function Overview({section, def, parent, units}) {
// States
const [mode, setMode] = useState('bs')
const units = useUnits()
// Styles
const useStyles = makeStyles(
......@@ -407,8 +409,8 @@ function Overview({section, def, parent}) {
const nAtoms = section.atom_species.length
let system = {
'species': section.atom_species,
'cell': section.lattice_vectors ? toUnitSystem(section.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
'positions': toUnitSystem(section.atom_positions, 'meter', {length: 'angstrom'}, false),
'cell': section.lattice_vectors ? toUnitSystem(section.lattice_vectors, 'meter', {length: 'angstrom'}) : undefined,
'positions': toUnitSystem(section.atom_positions, 'meter', {length: 'angstrom'}),
'pbc': section.configuration_periodic_dimensions
}
visualizedSystem.sectionPath = sectionPath
......@@ -428,7 +430,7 @@ function Overview({section, def, parent}) {
}
const system = {
species: section.atom_labels,
cell: section.lattice_vectors ? toUnitSystem(section.lattice_vectors, 'meter', {length: 'angstrom'}, false) : undefined,
cell: section.lattice_vectors ? toUnitSystem(section.lattice_vectors, 'meter', {length: 'angstrom'}) : undefined,
positions: section.atom_positions,
fractional: true,
pbc: section.periodicity
......@@ -451,7 +453,7 @@ function Overview({section, def, parent}) {
segments: section.section_k_band_segment,
reciprocal_cell: section.reciprocal_cell
}}
layout={{yaxis: {autorange: false, range: toUnitSystem(electronicRange, 'electron_volt', units, false)}}}
layout={{yaxis: {autorange: false, range: toUnitSystem(electronicRange, 'electron_volt', units)}}}
aspectRatio={1}
units={units}
></BandStructure>
......@@ -496,7 +498,7 @@ function Overview({section, def, parent}) {
const isVibrational = section.dos_kind === 'vibrational'
const layout = isVibrational
? undefined
: {yaxis: {autorange: false, range: toUnitSystem(electronicRange, 'electron_volt', units, false)}}
: {yaxis: {autorange: false, range: toUnitSystem(electronicRange, 'electron_volt', units)}}
return <DOS
className={style.dos}
layout={layout}
......@@ -515,10 +517,11 @@ function Overview({section, def, parent}) {
Overview.propTypes = ({
def: PropTypes.object,
section: PropTypes.object,
parent: PropTypes.object
parent: PropTypes.object,
units: PropTypes.object
})
function Section({section, def, parent}) {
function Section({section, def, parent, units}) {
const config = useRecoilValue(configState)
if (!section) {
......@@ -536,7 +539,7 @@ function Section({section, def, parent}) {
return <Content>
<Title def={def} data={section} kindLabel="section" />
<Overview def={def} section={section} parent={parent}></Overview>
<Overview def={def} section={section} parent={parent} units={units}></Overview>
<Compartment title="sub sections">
{sub_sections
.filter(subSectionDef => section[subSectionDef.name] || config.showAllDefined)
......@@ -575,7 +578,7 @@ function Section({section, def, parent}) {
<Box fontWeight="bold" component="span">
{quantityDef.name}
</Box>
</Typography>{!disabled && <span>&nbsp;=&nbsp;<QuantityItemPreview value={section[quantityDef.name]} def={quantityDef} /></span>}
</Typography>{!disabled && <span>&nbsp;=&nbsp;<QuantityItemPreview value={section[quantityDef.name]} def={quantityDef} units={units}/></span>}
</Box>
</Item>
})
......@@ -587,19 +590,21 @@ function Section({section, def, parent}) {
Section.propTypes = ({
section: PropTypes.object.isRequired,
def: PropTypes.object.isRequired,
parent: PropTypes.any
parent: PropTypes.any,
units: PropTypes.object
})
function Quantity({value, def}) {
function Quantity({value, def, units}) {
return <Content>
<Title def={def} data={value} kindLabel="value" />
<QuantityValue value={value} def={def} />
<QuantityValue value={value} def={def} units={units} />
<Meta def={def} />
</Content>
}
Quantity.propTypes = ({
value: PropTypes.any,
def: PropTypes.object.isRequired
def: PropTypes.object.isRequired,
units: PropTypes.object
})
const useMetaStyles = makeStyles(theme => ({
......
......@@ -125,11 +125,14 @@ export class Quantity {
this.value = value
this.unit = unit instanceof Unit ? unit : new Unit(unit)
}
toSI() {
return toSI(this.value, this.unit)
toSI(returnLabel = false) {
return toSI(this.value, this.unit, returnLabel)
}
toSystem(system) {
return toUnitSystem(this.value, this.unit, system)
toSystem(system, returnLabel = false) {
return toUnitSystem(this.value, this.unit, system, returnLabel)
}
label(system) {
return this.unit.label(system)
}
}
......@@ -138,12 +141,14 @@ export class Quantity {
* you can work with just the Unit and Quantity -classes, but sometimes it is
* cleaner to call this function directly instead.
*
* @param {*} value
* @param {*} unitDef
* @param {*} system
* @param {*} value The values to convert. Can be scalar or multi-dimensional.
* @param {*} unit Current unit as a string or an Unit object
* @param {object} system The target unit system
* @param {bool} returnlabel Whether also the converted unit label should be
* returned
* @returns
*/
export function toUnitSystem(value, unit, system) {
export function toUnitSystem(value, unit, system, returnLabel = false) {
// If value given as Quantity, extract unit and value from it
if (value instanceof Quantity) {
value = value.value
......@@ -156,73 +161,81 @@ export function toUnitSystem(value, unit, system) {
}
const unitDef = unit.unitDef
// Temperatures require special handling due to the fact that e.g. Celsius and
// Fahrenheit are not absolute units and are non-multiplicative. Two kinds of
// temperature conversions are supported: ones with a single temperature unit
// and ones where temperature is used as a part of an expression. If a single
// temperature unit is specified, they are converted normally taking the
// offset into account. If they are used as a part of an expression, they are
// interpreted as ranges and the offset is ignored.
if (unitDef instanceof SymbolNode) {
// Dimensionless quantities do not change in unit conversion
if (unitDef.name === 'dimensionless') {
return value
// Function for getting the converted values
function convert() {
// Temperatures require special handling due to the fact that e.g. Celsius and
// Fahrenheit are not absolute units and are non-multiplicative. Two kinds of
// temperature conversions are supported: ones with a single temperature unit
// and ones where temperature is used as a part of an expression. If a single
// temperature unit is specified, they are converted normally taking the
// offset into account. If they are used as a part of an expression, they are
// interpreted as ranges and the offset is ignored.
if (unitDef instanceof SymbolNode) {
// Dimensionless quantities do not change in unit conversion
if (unitDef.name === 'dimensionless') {
return value
}
const unitFrom = unitDef.name
if (unitMap[unitFrom].dimension === 'temperature') {
const unitTo = system['temperature']
const multiplier = conversionMap['temperature'].multipliers[unitFrom][unitTo]
const constant = conversionMap['temperature'].constants[unitFrom][unitTo]
let newValues = value
if (multiplier !== 1) {
newValues = scale(newValues, multiplier)
}
if (constant !== undefined) {
newValues = add(newValues, constant)
}
return newValues
}
}
const unitFrom = unitDef.name
if (unitMap[unitFrom].dimension === 'temperature') {
const unitTo = system['temperature']
const multiplier = conversionMap['temperature'].multipliers[unitFrom][unitTo]
const constant = conversionMap['temperature'].constants[unitFrom][unitTo]
let newValues = value
if (multiplier !== 1) {
newValues = scale(newValues, multiplier)
// Gather all units present
const variables = new Set()
unitDef.traverse((node, path, parent) => {
if (node.isSymbolNode) {
variables.add(node.name)
}
if (constant !== undefined) {
newValues = add(newValues, constant)
})
// Check if conversion is required.
let isConverted = true
for (const unit of variables) {
const dimension = unitMap[unit].dimension
const unitTo = system[dimension]
isConverted = unit === unitTo
if (!isConverted) {
break
}
return newValues
}
}
// Gather all units present
const variables = new Set()
unitDef.traverse((node, path, parent) => {
if (node.isSymbolNode) {
variables.add(node.name)
if (isConverted) {
return value
}
})
// Check if conversion is required.
let isConverted = true
for (const unit of variables) {
const dimension = unitMap[unit].dimension
const unitTo = system[dimension]
isConverted = unit === unitTo
if (!isConverted) {
break
// Gather conversion values for each present unit
const scope = {}
for (const unitFrom of variables) {
const dimension = unitMap[unitFrom].dimension
const unitTo = system[dimension]
scope[unitFrom] = conversionMap[dimension].multipliers[unitFrom][unitTo]
}
}
if (isConverted) {
return value
}
// Gather conversion values for each present unit
const scope = {}
for (const unitFrom of variables) {
const dimension = unitMap[unitFrom].dimension
const unitTo = system[dimension]
scope[unitFrom] = conversionMap[dimension].multipliers[unitFrom][unitTo]
}
// Compute the scaling factor by evaluating the unit definition with the
// SI units converted to target system
const code = unitDef.compile()
const factor = code.evaluate(scope)
// Compute the scaling factor by evaluating the unit definition with the
// SI units converted to target system
const code = unitDef.compile()
const factor = code.evaluate(scope)
// Scale values to new units
return scale(value, factor)
}
// Scale values to new units
let newValues = scale(value, factor)
return newValues
const converted = convert(value)
if (returnLabel) {
return [converted, unit.label(system)]
}
return converted
}
/**
......@@ -232,6 +245,6 @@ export function toUnitSystem(value, unit, system) {
* @param {*} system
* @returns
*/
export function toSI(value, unit) {
return toUnitSystem(value, unit, unitSystems['SI'].units)
export function toSI(value, unit, returnLabel = false) {
return toUnitSystem(value, unit, unitSystems['SI'].units, returnLabel)
}
......@@ -10331,8 +10331,9 @@ node-releases@^1.1.52, node-releases@^1.1.71:
integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==
 
"nomad-fair-gui@file:.":
version "0.10.4"
version "1.0.0"
dependencies:
"@date-io/date-fns" "1.x"
"@fontsource/material-icons" "^4.2.1"
"@fontsource/titillium-web" "^4.2.2"
"@material-ui/core" "^4.0.0"
......@@ -10357,7 +10358,7 @@ node-releases@^1.1.52, node-releases@^1.1.71:
material-ui-chip-input "^1.0.0-beta.14"
material-ui-flat-pagination "^4.0.0"
mathjs "^7.1.0"
nomad-fair-gui "file:../../../.cache/yarn/v6/npm-nomad-fair-gui-0.10.4-abb40f2a-ddea-4b73-a055-bfc2af15a7d3-1624907211196/node_modules/nomad-fair-gui"
nomad-fair-gui "file:../../../.cache/yarn/v6/npm-nomad-fair-gui-1.0.0-84c44d06-1e5b-41aa-b044-12f39d434c42-1628676964138/node_modules/nomad-fair-gui"
object-hash "^2.0.3"
pace "^0.0.4"
pace-js "^1.0.2"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment