Commit cc88b039 authored by Andrei Sobolev's avatar Andrei Sobolev
Browse files

Merge branch 'gims-output-fixes' into 'master'

FHI-aims output analyzer fixes and enhancements

See merge request !67
parents 30c06d24 a5ba7a6c
Loading
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -653,6 +653,7 @@ export const Fields_FHIaims = [
            noticeable broadening<br>
        `,
        units: ['eV', 'eV', ' ', 'eV'],
        defaultValue: () => [-20., 10., 3001, 0.1],
        workflow: 'default'
      },
      dielectric: {
@@ -671,6 +672,7 @@ export const Fields_FHIaims = [
           <b>Number of frequency points</b> for which the dielectric function is calculated should be at least 
           1000 or larger.`,
        units: ['eV', ''],
        defaultValue: () => [10., 1000],
        workflow: 'default'
      },
      hirshfeld: {
+60 −4
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@ export class InteractiveGraph{
    this.isDrawn = false
    this.lines = new Map()  // []
    this.lineStyles = new Map()  // this.linesProps = []
    this.modeBarButtons = []
    this.settingsPanel = null
    this.xTicksValues = []
    this.xTicksTexts = []
    this.data = []
@@ -138,6 +140,22 @@ export class InteractiveGraph{
  }


  /** Register a DOM element as a floating settings panel toggled by a gear button in the modebar.
   * Must be called before draw(). The element must have inline style="display:none" initially.
   * @param {HTMLElement} element
   */
  addSettingsPanel(element) {
    this.settingsPanel = element
  }

  /** Register a custom Plotly modebar button to be included when draw() is called.
   * @param {object} button - Plotly custom button descriptor ({name, icon, click})
   */
  addModeBarButton(button) {
    this.modeBarButtons.push(button)
  }


  /**
   * Adds a group of lines
   * @param {string} name Group name
@@ -216,7 +234,9 @@ export class InteractiveGraph{
    //  show the default trace
    const name = this.data[trace].meta
    this.data[trace].visible = true
    this.layout.yaxis.title = this.lineStyles.get(name).yTitle
    const defaultStyle = this.lineStyles.get(name)
    this.layout.yaxis.title = defaultStyle.yTitle
    this.layout.shapes = defaultStyle.shape ? [defaultStyle.shape] : []
    // make update menus
    let updateMenus = [{
      buttons: [],
@@ -229,12 +249,16 @@ export class InteractiveGraph{
    }]
    this.data.forEach((group, idx) => {
      const name = group.meta
      const style = this.lineStyles.get(name)
      let visible = groups.map(_ => false)
      visible[idx] = true
      updateMenus[0].buttons.push({
        args: [
          {visible: visible},
          {'yaxis.title': this.lineStyles.get(name).yTitle}
          {
            'yaxis.title': style.yTitle,
            'shapes': style.shape ? [style.shape] : []
          }
        ],
        method: 'update',
        label: name
@@ -302,10 +326,42 @@ export class InteractiveGraph{
    this.#dataToLines()
    const start = performance.now()
    if (withDropdown) this.#addDropdownControl(defaultGroup)
    Plotly.newPlot(this.parentElement, this.data, this.layout, CONFIG)
    const extraButtons = []
    if (this.settingsPanel) {
      const panel = this.settingsPanel
      extraButtons.push({
        name: 'Settings',
        icon: {
          width: 512, height: 512,
          path: 'M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z'
        },
        click: () => { panel.style.display = panel.style.display === 'none' ? 'block' : 'none' }
      })
    }
    const downloadButton = {
      name: 'Download data as TSV',
      icon: Plotly.Icons.disk,
      click: gd => {
        const sections = gd.data.map(trace => {
          const rows = trace.x
            .map((x, i) => x !== null ? `${x}\t${trace.y[i]}` : null)
            .filter(r => r !== null)
            .join('\n')
          return `# ${trace.meta || 'data'}\n# x\ty\n` + rows
        })
        const blob = new Blob([sections.join('\n\n')], {type: 'text/plain'})
        const a = Object.assign(document.createElement('a'), {
          href: URL.createObjectURL(blob),
          download: (gd.id || 'data') + '.tsv'
        })
        a.click()
        URL.revokeObjectURL(a.href)
      }
    }
    Plotly.newPlot(this.parentElement, this.data, this.layout,
      {...CONFIG, modeBarButtonsToAdd: [...extraButtons, downloadButton, ...this.modeBarButtons]})
    this.isDrawn = true
    console.log('time building graphs: ' + (performance.now() - start))
    
  }

  /**
+30 −0
Original line number Diff line number Diff line
@@ -352,6 +352,9 @@ export default class OutputAims extends Output {
    if (this.runTimeChoices.calculationType === 'relaxation') {
      this.getRelaxationSeries()
    }
    if (this.runTimeChoices.calculationType === 'md') {
      this.getMDSeries()
    }
  }

  _populateFromParsed(parsed, meta, parserErrors = [], parserWarnings = []) {
@@ -370,6 +373,13 @@ export default class OutputAims extends Output {
    if (meta.commit_hash) this.calculationInfo.commitNumber = { value: meta.commit_hash, info: 'Commit Number' }
    if (meta.num_tasks != null) this.calculationInfo.numberOfTasks = { value: String(meta.num_tasks), info: 'Number of Tasks' }

    // Numerical accuracy object
    this.accuracy = {}
    this.accuracy.chargeDensity = meta.sc_accuracy_rho ?? null
    this.accuracy.eigenvalues = meta.sc_accuracy_eev ?? null
    this.accuracy.totalEnergy = meta.sc_accuracy_etot ?? null
    this.accuracy.forces = meta.maxForce ?? null

    if (parsed.time?.total) {
      this.finalTimings = { totalTime: { value: String(parsed.time.total[0]), info: 'Total Time' } }
    }
@@ -509,6 +519,12 @@ export default class OutputAims extends Output {
        )
      }
    }
    if (step.md) {
      loop.md = {
        temperature: step.md.temperature ?? null,
        totalEnergy: step.md.total_energy ?? null,
      }
    }
    return loop
  }

@@ -612,6 +628,20 @@ export default class OutputAims extends Output {
  }


  getMDSeries() {
    const temps = [], energies = []
    this.scfLoops.forEach(loop => {
      temps.push(loop.md?.temperature ?? null)
      energies.push(loop.md?.totalEnergy ?? loop.finalScfEnergies?.totalEnergy ?? null)
    })
    const e0 = energies.find(v => v != null) ?? 0
    this.mdSeries = {
      temperature: { label: 'Temperature', color: 'rgb(200, 50, 50)', data: temps },
      totalEnergy: { label: 'Total Energy', color: 'rgb(42, 45, 52)', data: energies.map(v => v != null ? v - e0 : null) },
    }
  }


  /**
   * Returns the structure got from the geometry input file
   * @return {Structure}
+16 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ import downloadIcon from 'url:../../img/download-icon.png'
import {BSDOSDashboard} from "./bsdos_dashboard";
import {AbDiDashboard} from "./absorption_dashboard";
import {ConvergenceDashboard} from "./convergence_dashboard";
import {MDDashboard} from "./md_dashboard";
import {SpectroscopyDashboard} from "./spectroscopy_dashboard";


@@ -74,6 +75,11 @@ let init_html = `
		<div class="section-content" style="justify-content: space-evenly;"></div>
	</div>

	<div class="md-box page-section" style="display: none">
		<div class="page-section-title">Molecular Dynamics</div>
		<div class="section-content" style="justify-content: space-evenly;"></div>
	</div>

	<div class="output-file-content" style="display: none">
		<div class="page-section" >
			<div class="page-section-title">Calculation Summary</div>
@@ -145,6 +151,7 @@ export default class OutputAnalyzerMod extends UIComponent{
    // this.absorptionMsgBox = this.getElement('.absorption-error-msg')
    this.inputFilesBox = this.getElement('.input-files-section')
    this.spectroscopyBox = this.getElement('.spectroscopy-box')
    this.mdBox = this.getElement('.md-box')
    this.calculationSummary = undefined
  }

@@ -201,6 +208,7 @@ export default class OutputAnalyzerMod extends UIComponent{
          showElement(this.dosBsBox, false)
					showElement(this.absorptionBox, false)
					showElement(this.spectroscopyBox, false)
					showElement(this.mdBox, false)
          showElement(this.outputContentBoxes[1], false)
          showElement(this.inputFilesBox, false)
					showElement(this.bzViewerBox, false)
@@ -505,7 +513,14 @@ export default class OutputAnalyzerMod extends UIComponent{
    this.getElement('#summary-graphs').innerHTML = ''
    this.calculationSummary = new ConvergenceDashboard(this.getElement("#summary-graphs"),
      '.convergence-graphs', isRelaxation, isMD ? 'MD step' : 'Relaxation step')
    this.calculationSummary.plotData(output.dataSeries, output.relaxationSeries)
    this.calculationSummary.plotData(output.dataSeries, output.relaxationSeries, output.accuracy)

    this.mdBox.style.display = isMD ? 'block' : 'none'
    if (isMD && output.mdSeries) {
      this.mdBox.querySelector('.section-content').innerHTML = ''
      this.mdDashboard = new MDDashboard(this.mdBox.querySelector('.section-content'), '.md-graphs')
      this.mdDashboard.plotData(output.mdSeries)
    }

    // Calculation information
		this.getElement('.calculation-info-fields').innerHTML = getHtmlRows(output.getCalculationInfo(), false)
+27 −9
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ export class ConvergenceDashboard extends PlotlyDashboard {
   * @param parent {HTMLElement}
   * @param uiClass {string}
   * @param isRelaxation {boolean}
   * @param stepLabel {string}
\   */
  constructor(parent, uiClass, isRelaxation, stepLabel = 'Relaxation step') {
    super(parent, uiClass);
@@ -32,15 +33,16 @@ export class ConvergenceDashboard extends PlotlyDashboard {
      this.convergenceGraph = new ConvergenceGraph(this.getElement('.convergence-graph-ph'))
    this.stepSelector = new PaginationSelector(stepLabel, 1)
    this.e.insertBefore(this.stepSelector.e, this.convergenceGraph.parentElement)
    this.stepSelector.setPrevListener(s => this.plotIterData(s))
    this.stepSelector.setNextListener(s => this.plotIterData(s))
    this.stepSelector.setPrevListener(s => this.plotIterData(s, this.accuracy))
    this.stepSelector.setNextListener(s => this.plotIterData(s, this.accuracy))
  }

  /** Plots data on the dashboard
   * @param convergenceData
   * @param relaxationData
   * @param {Object|null} accuracy - accuracy object from AimsOutput
   */
  plotData(convergenceData, relaxationData=undefined) {
  plotData(convergenceData, relaxationData=undefined, accuracy=null) {
    // turn on the pagination selector
    // console.log(relaxationData)
    this.stepSelector.e.style.display = convergenceData.length > 1 ? 'block' : 'none'
@@ -49,20 +51,22 @@ export class ConvergenceDashboard extends PlotlyDashboard {

    this.convergenceData = convergenceData
    this.relaxationData = relaxationData
    this.accuracy = accuracy

    // clear the graphs
    if (this.isRelaxation && this.relaxationData !== undefined) {
      this.plotRelaxData()
      this.plotRelaxData(accuracy)
    }
    // draw convergence data
    this.plotIterData(1)
    this.plotIterData(1, accuracy)
  }

  /**
   * Sets and draws the data of an iteration
   * @param {number} iter
   * @param {Object|null} accuracy - accuracy object from AimsOutput
   */
  plotIterData(iter) {
  plotIterData(iter, accuracy) {
    // begin the number from 0
    iter -= 1
    let activeGroup = this.convergenceGraph.isDrawn ? this.convergenceGraph.getDropdownGroup() : 'Change of Charge Density'
@@ -76,14 +80,21 @@ export class ConvergenceDashboard extends PlotlyDashboard {
      if (!this.convergenceGraph.hasLineGroup(datum.label))
        this.convergenceGraph.addLineGroup(datum.label, {
          defaultColor: datum.color,
          yTitle: this.convergenceGraph.Y_AXIS_LABEL[name]})
          yTitle: this.convergenceGraph.Y_AXIS_LABEL[name],
          shape: accuracy[name] != null ? {
            type: 'line',
            xref: 'paper', x0: 0, x1: 1,
            yref: 'y', y0: parseFloat(accuracy[name]), y1: parseFloat(accuracy[name]),
            line: { color: '#888', width: 1.5, dash: 'dash' }
          } : null
        })
      this.convergenceGraph.setLineData([x, y], datum.label)
    }
    if (this.convergenceGraph.lines.size === 0) return
    this.convergenceGraph.draw(true, activeGroup)
  }

  plotRelaxData() {
  plotRelaxData(accuracy) {
    this.relaxationGraph.clear()
    delete this.relaxationData.labels
    for (const name in this.relaxationData) {
@@ -93,7 +104,14 @@ export class ConvergenceDashboard extends PlotlyDashboard {
      if (!this.relaxationGraph.hasLineGroup(datum.label))
        this.relaxationGraph.addLineGroup(datum.label, {
          defaultColor: datum.borderColor,
          yTitle: this.relaxationGraph.Y_AXIS_LABEL[name]})
          yTitle: this.relaxationGraph.Y_AXIS_LABEL[name],
          shape: accuracy?.[name] != null ? {
            type: 'line',
            xref: 'paper', x0: 0, x1: 1,
            yref: 'y', y0: parseFloat(accuracy[name]), y1: parseFloat(accuracy[name]),
            line: { color: '#888', width: 1.5, dash: 'dash' }
          } : null
        })
      this.relaxationGraph.setLineData([x, y], datum.label)
    }
    this.relaxationGraph.draw(true, 'Maximum Force Component')
Loading