def sanity_check_insufficient_bands(self, calculation): """Perform a sanity check on the band occupations of a successfully converged calculation. Verify that the occupation of the last band is below a certain threshold, unless `occupations` was explicitly set to `fixed` in the input parameters. If this is violated, the calculation used too few bands and cannot be trusted. The number of bands is increased and the calculation is restarted, starting from the last. """ from aiida_quantumespresso.utils.bands import get_highest_occupied_band # Only skip the check on the highest band occupation if `occupations` was explicitly set to `fixed`. if calculation.inputs.parameters.get_attribute('SYSTEM', {}).get('occupations', None) == 'fixed': return try: bands = calculation.outputs.output_band get_highest_occupied_band(bands) except ValueError as exception: args = [self._process_class.__name__, calculation.pk] self.report('{}<{}> run with smearing and highest band is occupied'.format(*args)) self.report('BandsData<{}> has invalid occupations: {}'.format(bands.pk, exception)) self.report('{}<{}> had insufficient bands'.format(calculation.process_label, calculation.pk)) nbnd_cur = calculation.outputs.output_parameters.get_dict()['number_of_bands'] nbnd_new = nbnd_cur + max(int(nbnd_cur * self.defaults.delta_factor_nbnd), self.defaults.delta_minimum_nbnd) self.ctx.inputs.parameters.setdefault('SYSTEM', {})['nbnd'] = nbnd_new self.report('Action taken: increased number of bands to {} and restarting from scratch'.format(nbnd_new)) return ProcessHandlerReport(True)
def sanity_check_insufficient_bands(self, calculation): """Perform a sanity check on the band occupations of a successfully converged calculation. Verify that the occupation of the last band is below a certain threshold, unless `occupations` was explicitly set to `fixed` in the input parameters. If this is violated, the calculation used too few bands and cannot be trusted. The number of bands is increased and the calculation is restarted, starting from the last. """ from aiida_quantumespresso.utils.bands import get_highest_occupied_band occupations = calculation.inputs.parameters.get_attribute( 'SYSTEM', {}).get('occupations', None) if occupations is None: self.report( '`SYSTEM.occupations` parameter is not defined: performing band occupation check. ' 'If you want to disable this, explicitly set `SYSTEM.occupations` to `fixed`.' ) # Only skip the check on the highest band occupation if `occupations` was explicitly set to `fixed`. if occupations == 'fixed': return try: bands = calculation.outputs.output_band except AttributeError: args = [self.ctx.process_name, calculation.pk] self.report( '{}<{}> does not have `output_band` output, skipping sanity check.' .format(*args)) return try: get_highest_occupied_band(bands) except ValueError as exception: args = [self.ctx.process_name, calculation.pk] self.report( '{}<{}> run with smearing and highest band is occupied'.format( *args)) self.report( f'BandsData<{bands.pk}> has invalid occupations: {exception}') self.report( f'{calculation.process_label}<{calculation.pk}> had insufficient bands' ) nbnd_cur = calculation.outputs.output_parameters.get_dict( )['number_of_bands'] nbnd_new = nbnd_cur + max( int(nbnd_cur * self.defaults.delta_factor_nbnd), self.defaults.delta_minimum_nbnd) self.ctx.inputs.parameters.setdefault('SYSTEM', {})['nbnd'] = nbnd_new self.report( f'Action taken: increased number of bands to {nbnd_new} and restarting from scratch' ) return ProcessHandlerReport(True)
def _handle_calculation_sanity_checks(self, calculation): """The current `calculation` has finished successfully according to the parser, but double-check. Verify that the occupation of the last band is below a certain threshold, unless `occupations` was explicitly set to `fixed` in the input parameters. If this is violated, the calculation used too few bands and cannot be trusted. The number of bands is increased and the calculation is restarted, starting from the last. """ from aiida_quantumespresso.utils.bands import get_highest_occupied_band # Only skip the check on the highest band occupation if `occupations` was explicitly set to `fixed`. if calculation.inputs.parameters.get_attribute('SYSTEM', {}).get('occupations', None) == 'fixed': return try: bands = calculation.outputs.output_band get_highest_occupied_band(bands) except ValueError as exception: self.report('calculation<{}> run with smearing and highest band is occupied'.format(calculation.pk)) self.report('BandsData<{}> has invalid occupations: {}'.format(bands.pk, exception)) return self._handle_insufficient_bands(calculation)
def test_threshold(self, fixture_database): """Test the `threshold` parameter.""" from aiida.orm import BandsData threshold = 0.002 bands = BandsData() bands.set_array('occupations', numpy.array([[2., 2., 2., 2., 0.001, 0.0015]])) bands.store() # All bands above the LUMO (occupation of 0.001) are below `2 * threshold` h**o = get_highest_occupied_band(bands, threshold=threshold) assert h**o == 4 bands = BandsData() bands.set_array('occupations', numpy.array([[2., 2., 2., 2., 0.001, 0.003]])) bands.store() # A band above the LUMO (occupation of 0.001) has an occupation above `2 * threshold` with pytest.raises(ValueError): get_highest_occupied_band(bands, threshold=threshold)
def test_spin_unpolarized(): """Test the function for a non spin-polarized calculation meaning there will be a single spin channel.""" from aiida.orm import BandsData occupations = numpy.array([ [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], ]) bands = BandsData() bands.set_array('occupations', occupations) bands.store() h**o = get_highest_occupied_band(bands) assert h**o == 4
def test_spin_polarized(self, fixture_database): """Test the function for a spin-polarized calculation meaning there will be two spin channels.""" from aiida.orm import BandsData occupations = numpy.array([ [ [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], ], [ [2., 2., 2., 2., 0.], [2., 2., 2., 2., 0.], ] ]) bands = BandsData() bands.set_array('occupations', occupations) bands.store() h**o = get_highest_occupied_band(bands) assert h**o == 4
def test_valid_node(): """Test that the correct exceptions are thrown for incompatible nodes.""" from aiida.orm import ArrayData, BandsData # Invalid node type node = ArrayData().store() with pytest.raises(ValueError): get_highest_occupied_band(node) # The `occupations` array is missing node = BandsData() node.set_array('not_occupations', numpy.array([])) node.store() with pytest.raises(ValueError): get_highest_occupied_band(node) # The `occupations` array has incorrect shape node = BandsData() node.set_array('occupations', numpy.array([1., 1.])) node.store() with pytest.raises(ValueError): get_highest_occupied_band(node)